...

суббота, 29 декабря 2018 г.

Авария в Дата-центре CenturyLink вызвала перебои в работе службы 911

Авария, устранение которой заняло более суток, вызвала перебои в работе многих служб по всей территории США. В некоторых регионах было невозможно дозвониться в службу экстренной помощи на номер 911 с сотовых операторов AT&T и Verizon. Также были проблемы со снятием наличных из банкоматов, была нарушена работа лотереи и один из госпиталей не мог получить доступ к медицинским записям пациентов.

Достоверно неизвестно что стало причиной сбоя, т.к. компания CenturyLink не предоставила никаких технических подробностей, ограничившись только твитом.


Инженеры и технические специалисты CenturyLink идентифицировали сетевой элемент, который повлиял на обслуживание клиентов и решают проблему с целью восстановления услуг. Мы предполагаем что работа всех сервисов будет восстановлена в течении четырех часов. Мы приносим свои извинения клиентам за вызванные неудобства.

Власти и департаменты на местах активно использовали соцсети и делились региональными номерами экстренных служб. Также советовали отправлять SMS сообщения на номер 911 в случае необходимости. При этом вызовы с городских номеров работали исправно.

Председатель федеральной комиссии по связи Аджид Пай (Ajit Pai) заявил, что подобные задержки неприемлемы и поручил комиссии начать расследование.

Краткое описание проблем, которые возникли из-за сбоя CenturyLink:


  • В штатах Аризона, Айдахо, Массачусетс, Миссури и Вашингтон временами был недоступен номер 911. Агентство по чрезвычайным ситуациям штата Массачусетс использовало систему экстренного оповещения EAS для передачи сообщения.

    Случайные отключения 911 продолжаются. В чрезвычайной ситуации, если 911 не работает, используйте местный 10-значный номер для вызова полиции/пожарной
  • Медицинский Центр Северного Колорадо не мог получить доступ к электронным записям пациентов. Пришлось на время отложить технологии и перейти на записи ручкой и бумагу.
  • Не работала телефонная связь в Департаменте Исправительных Учреждений и Министерстве Образования штата Айдахо, а также не работал Интернет на компьютерах в публичной библиотеке.
  • Лотерея штата Айдахо временно не могла продавать билеты, проверять и оплачивать заявки по выигрышным.
  • Сотовый оператор Verizon испытывал перебои в обслуживании в Альбукерке и некоторых районах Монтаны.
  • Не работали банкоматы в Айдахо и Монтане.

В сети активно выдвигаются теории заговора из-за взрыва трансформатора в Куинс в тот же день. Некоторые считают, что это спланированная Кибер-атака.

Let's block ads! (Why?)

Detox и Appium: автоматизированный тест интерфейса в React Native

Незнакомая мобильная среда


Я, возможно, также как и вы, пришел к React Native как разработчик JavaScript нежели как разработчик нативных мобильных приложений. Абсолютно новый мир со своими нюансами и хитростями.

Одной из самых важных тем для изучения станет тестирование. Когда все более или менее понятно с модульными тестами (unit), что делать с тестами интерфейса и сквозными тестами (end-to-end)? iOS. Android. На рынке смесь разных типов устройств.

Несмотря на то, что сама технология сравнительно новая, это все еще мобильная среда и многому приходится заимствовать и учиться у нативной стороны.

Я вкратце рассмотрю два фрэймворка, на которые стоит обратить внимания, чтобы облегчить себе жизнь как разработчику.

Appium


Использующая за кулисами Selenium WebDriver, Appium это мощный фреймворк с огромным сообществом разработчиков нативных мобильных приложений. Вышедший еще до React.js, это лидер и равных ему нет.

Начать работу с Appium довольно легко. С помощью npm устанавливаем пакеты “appium” и “appium-doctor”, можем глобально, можем как часть проекта. Команда “appium-doctor” расскажет нам, что еще нужно установить и настроить прежде чем приступать к работе, и, если возможно, поможет исправить недочеты. Когда все решено, пакеты установлены и конфигурация Jest на месте, можем запускать сервер Appium и тесты.

Не буду углубляться в подробности настройки, но вот как выглядит простой тест с конфигурацией (добавлены комментарии):

/* клиент selenium webdriver для node
*/
import wd from 'wd'

/* 60 секунд таймаут, после которых тест остановится, если застрянет
 */
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000

/* адрес сервера Appium. Запускаем с нашего компьютера, поэтому localhost
*/
const URL = 'localhost'
const PORT = 4723

/* создаем объект webdriver
*/
const driver = wd.promiseChainRemote(URL, PORT)

/* Вожможности сервера.
* инструкция для сервера Appium,
* как запускать тесты, другими словами настройки.
*/
const capabilities = {
 platformName: 'iOS', // или Android
 platformVersion: '12.1', // версия ОС
 deviceName: 'iPhone 8', // или “Android Emulator” или точное название устройства
 automationName: 'XCUITest', // фреймворк платформы (UIAutomator2 для Android)
 app: '/path/to/.app' // расположение файла .app (для Android это .apk)
}

beforeAll(async () => {
 try { // до того, как запустить тест
   await driver.init(capabilities) // запускаем драйвер
   await driver.sleep(4000) // да уж, вручную ставим таймер и ждем загрузку приложения, вот она хрупкость!
 } catch(err) {
   console.log(err) // если что, мы хотим знать, что не так
 }
})

afterAll(async () => {
 try {
   await driver.quit() // конец сессии
 } catch(err) {
   console.error(err)
 }
});

/* Jest, делаем что хотим, что позволяет Appium!
* в данном примере мы проверяем, соответствует ли текст
* 'topLabel' и 'subLabel' заданному
* Рекомендую ознакомиться с документацией на сайте Appium
*/
describe("Home Screen landing", () => {
 test("render search screen", async () => {
    let topLabel = await driver.elementById('topLabel')
    let subLabel = await driver.elementById('subLabel')
    expect(await topLabel.text()).toBe("OK")
    expect(await subLabel.text()).toBe("главный экран")
 })
})


Сам тест, это последние несколько строк, которые проверяют, если на экране текст “OK” и “главный экран”. Как видите, в тесте ничего особенного, тот же самый Jest. Документация на сайте Appium описывает все возможности фреймворка включая также примеры на JavaScript.

Неприязнь только к строке await driver.sleep(4000). К сожалению, тесты понятия не имеют, что происходит в приложении. Так называемый “черный ящик” или Blackbox. Представьте, если бы вы писали код на Node, и перед запросом http, вы бы ставили таймер вместо использования promise или callback. Вот она, хрупкость UI тестов.

В этом простом тесте мы ждем 4 секунды для запуска приложения. Со временем и с увеличением количества тестов, мы будем устанавливать таймеры чаще — запросы http, анимация, сам React Native — мост между нативным кодом и JavaScript только усложняет ситуацию.

Что нравится в Appium

  • 7+ лет в индустрии.
  • Широкие возможности API.
  • Легко найти помощь (это также минус, список ниже)
  • Поддержка разных языком, в том числе JavaScript.
  • Знакомая для разработчика JavaScript среда Jest.
  • Используется для сквозных тестов в MS AppCenter, BrowserStack и AWS DeviceFarm.
  • Возможность теста на настоящих устройствах.

Что не нравится в Appium
  • Поиск в сети выдает результаты для разных языков программирования, большинство из них это Java.
  • Тестирование “чёрного ящика” (тесты не знают о процессах внутри приложения).
  • Нет синхронности с приложением, хрупкость, еще больше проблем создает React Native.
  • testID по какой-то причине не работает на Android.

Заметьте три таба: логи сервера Appium, пакет metro bundler и сам тест.

Detox


Detox от компании Wix работает схоже с Appium. Главное отличие, это тестирование по стратегии «серого ящика». Одной из задач разработчиков Detox было решение проблем с хрупкостью — задача в приложении не будет начата, пока не закончилась предыдущая и пока приложение не будет свободно. Это стало возможно благодаря другому фреймворку созданному под названием EarlGrey.

Так же как и с Appium, устанавливаем настройки.

/* Дополнительная настройка в файле package.json, ниже пример
*/
const detox = require("detox");
const config = require("./package.json").detox;

/* адаптер Jest
*/
const adapter = require("detox/runners/jest/adapter");

/* Таймаут,
 * использование адаптера Jest
 */
jest.setTimeout(120000);
jasmine.getEnv().addReporter(adapter);

beforeAll(async () => {
 /* Запускаем сервер
 */
 await detox.init(config);
});

 /* beforeEach и afterEach для тестов Detox, 
  * используем от Jest
  * чистим после тестов
  */
beforeEach(async function() {
 await adapter.beforeEach();
});

afterAll(async () => {
 await adapter.afterAll();
 await detox.cleanup();
});


И настройка в package.json:
"detox": {
   "configurations": {
     "ios.detox": { // настройки для iOS (запускается командой detox test -c ios.detox)
       "binaryPath": "path/to/.app",
       "build": "xcodebuild -workspace ios/app.xcworkspace -scheme scheme -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", // файл workspace или project. В данном случае создаем пакет debug вместо production (release).
       "type": "ios.simulator",
       "name": "iPhone 8" // название симулятора
     },
     "android.detox": { // настройки для Android (запускается командой detox test -c android.detox)
       "binaryPath": "path/to/.apk",
       "build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..", // В данном случае создаем пакет debug вместо production (release).
       "type": "android.emulator",
       "name": "Pixel_2_API_28" // название симулятора. “adb devices” покажет список доступных устройств Android
     }
   },
   "test-runner": "jest",
   "runner-config": {
    "setupTestFrameworkScriptFile" : "./detox.init.js", // пример выше   
    "testEnvironment": "node",
    "testRegex": "(.*|\\.(ui))\\.(ts|tsx)$" // регулярное выражение, где искать тесты интерфейса
 }
   "specs": "./__tests__/" // расположение тестов интерфейса
 }



Тесты писать также легко, как и для Appium, но с использованием возможностей и ограничений Detox.

Что мне нравится в Detox

  • Создан Wix для React Native.
  • Сфокусирован на JavaScript.
  • Тест по стратегии «серого ящика».
  • Работает синхронно с приложением.

Что не нравится в Detox
  • Возможности не такие широкие как у Appium.
  • Маленькое сообщество.

Хрупкость


Несмотря на то, что Detox использует принцип «серого ящика», хрупкость все еще присутствует. Тест с вводом текста и свайпом не срабатывал как надо в 1 случае из 10. Нельзя быть уверенным на 100% в тестах интерфейса.

Скорость


Appium “тормозит” таймеры “.sleep” установленные в ручную, Detox в этом случае выигрывает, так как все синхронно. В целом я бы не делал еще каких-либо выводов со своей стороны, так как не писал большого кол-ва одинаковых тестов на обеих платформах. В 30-секундных тестах и простом тесте созданном для этой статьи, Detox справился на секунды быстрее. Если смотреть на две разные платформы, iOS и Android, тесты заняли +- одно и то же время. Главное, следует помнить, что тесты интерфейса занимают значительно больше времени модульных тестов.

Что выбрать


Я по прежнему изучаю оба фреймворка и понадобится какое-то время, чтобы понять все их преимущества, но на данный момент, как разработчик JavaScript, я выбираю Detox.

Испробуйте оба, к счастью, их только два. Все зависит от приложения, над которым вы работаете, и команды.

Тесты интерфейса в команде разработчиков — для разработчиков, пробуйте Detox. Более сложные сквозные тесты — возможно, лучше присмотреться к Appium с его богатыми возможностями API и поддержкой на платформах BrowserStack, MS AppCenter и AWS DeviceFarm.

Ссылки


Есть много полезных ресурсов и статей, но, к сожалению, на английском. Первым делом я рекомендую оф. сайты.

Appium
http://appium.io

Detox
https://github.com/wix/Detox

Let's block ads! (Why?)

[Из песочницы] «Камень я не дам» или как устроены ресурсы игры «Проклятые Земли»

Много ли вы вспомните российских игр? Качественных? Запоминающихся? Да, такие были. Если вам больше 35 или вы фанат российского игропрома, то с "Проклятыми Землями" вы наверняка знакомы.

История начиналась весьма прозаично: лето, жара. Делать особо нечего, а при ленивом просмотре содержимого жёсткого диска ноутбука взгляд зацепился за папку со знакомой иконкой-дракончиком, лежащую без дела уже пару лет.

Какому фанату игры не будет интересно узнать, что же там внутри?


Информация об игре

Проклятые Земли — или, как они назывались за пределами СНГ, Evil Islands: Curse of the Lost Soul, stealth-RPG игра, вышедшая в 2000 году. Разработкой игры занималась студия Nival Interactive, на тот момент уже зарекомендовавшая себя серией игр Аллоды (Rage of Mages за рубежом). Работали в ней, в основном, выпускники МГУ — им было вполне по силам реализовать одну из первых игр с полностью трёхмерным миром.
В 2010 году права на название перешли Mail.Ru (информация), однако игра продаётся в магазине GOG всё ещё от лица Nival.

Относительно недавно игре исполнилось 18 лет — днём рождения считается 26 октября, дата выхода в СНГ. Несмотря на возраст, официальный мастер-сервер ещё в строю: периодически кто-то решает поползать по лесам Гипата да стукнуть десяток-другой скелетов с отрядом товарищей.


Коротко о статье

Изначально, моей целью было лишь написать односторонний конвертер "для себя" на Python 3, причём с использованием исключительно стандартных библиотек. Однако в процессе плавно началось написание документации по форматам, попытки как-то стандартизировать вывод. Для части форматов была описана структура с помощью Kaitai Struct. В результате всё вылилось в написание данной статьи и wiki по форматам.

Сразу отмечу: большей частью, файлы игры уже исследованы, к ним были написаны фанатские редакторы. Однако информация крайне разрознена, а более-менее целостного описания форматов в открытом доступе нет, как и адекватного набора для создания модификаций.


… и о том, как её читать

Для всех форматов приведены схемы (.ksy файлы), которые можно в два клика сконвертировать в код на нескольких самых популярных языках.

К сожалению, уже на последних этапах написания этой статьи, я обнаружил, что многоуважаемый Хабр не умеет в подсветку YAML (и JSON), а все схемы использует именно его. Это не должно стать большой проблемой, но если читать схему неудобно, могу посоветовать скопировать в сторонний редактор, например, NPP.

Игра представляет собой портативное приложение, содержащее движок с библиотеками, лаунчер и, собственно, упакованные ресурсы.


Это интересно: настройки игры практически целиком хранятся в реестре. Баг камеры в GOG версии связан с тем, что установщик не прописывает корректные значения по-умолчанию.

При первом взгляде на содержимое папки game, мы сразу заметим пару новых расширений файлов: ASI и REG.
Первый — динамическая библиотека, которую рассматривать мы не будем (этим занимаются специалисты по реверс-инжинирингу), а вот второе — первый собственный формат файлов игры.


REG

Файлы этого типа — бинарная сериализация общеизвестных текстовых INI файлов.
Содержимое разбивается на секции, хранящие ключи и их значения. REG файл сохраняет эту иерархию, однако ускоряет чтение и разбор данных — в 2000 году это, видимо, было критично.

В общем виде, можно описать структуру данной диаграммой:


Описание структуры
meta:
  id: reg
  title: Evil Islands, REG file (packed INI)
  application: Evil Islands
  file-extension: reg
  license: MIT
  endian: le
doc: Packed INI file
seq:
  - id: magic
    contents: [0xFB, 0x3E, 0xAB, 0x45]
    doc: Magic bytes
  - id: sections_count
    type: u2
    doc: Number of sections
  - id: sections_offsets
    type: section_offset
    doc: Sections offset table
    repeat: expr
    repeat-expr: sections_count
types:
  section_offset:
    doc: Section position in file
    seq:
      - id: order
        type: s2
        doc: Section order number
      - id: offset
        type: u4
        doc: Global offset of section in file
    instances:
      section:
        pos: offset
        type: section
    types:
      section:
        doc: Section representation
        seq:
          - id: keys_count
            type: u2
            doc: Number of keys in section
          - id: name_len
            type: u2
            doc: Section name lenght
          - id: name
            type: str
            encoding: cp1251
            size: name_len
            doc: Section name
          - id: keys
            type: key
            doc: Section's keys
            repeat: expr
            repeat-expr: keys_count
        types:
          key:
            doc: Named key
            seq:
              - id: order
                type: s2
                doc: Key order in section
              - id: offset
                type: u4
                doc: Key offset in section
            instances:
              key_record:
                pos: _parent._parent.offset + offset
                type: key_data
          key_data:
            seq:
              - id: packed_type
                type: u1
                doc: Key value info
              - id: name_len
                type: u2
                doc: Key name lenght
              - id: name
                type: str
                encoding: cp1251
                size: name_len
                doc: Key name
              - id: value
                type: value
                doc: Key value
            instances:
              is_array:
                value: packed_type > 127
                doc: Is this key contain array
              value_type:
                value: packed_type & 0x7F
                doc: Key value type
            types:
              value:
                doc: Key value
                seq:
                  - id: array_size
                    type: u2
                    if: _parent.is_array
                    doc: Value array size
                  - id: data
                    type:
                      switch-on: _parent.value_type
                      cases:
                        0: s4
                        1: f4
                        2: string
                    repeat: expr
                    repeat-expr: '_parent.is_array ? array_size : 1'
                    doc: Key value data
              string:
                doc: Sized string
                seq:
                  - id: len
                    type: u2
                    doc: String lenght
                  - id: value
                    type: str
                    encoding: cp1251
                    size: len
                    doc: String

Это интересно: в 2002 году Nival поделился некоторыми инструментами с коммьюнити игры (снапшот сайта) — одним из них был сериализатор INI в REG. Как можно догадаться, почти сразу появился и десериализатор, пусть и не официальный.

Со стартовой папкой разобрались, перейдём к подкаталогам.
Первым взгляд падает на папку Cameras, содержащую CAM файлы.


CAM

Очень простой формат — просто упаковки положений камер во времени. Камера описывается позицией и вращением. Два остальных поля — предположительно, время и шаг в последовательности перемещений.


Описание структуры
meta:
  id: cam
  title: Evil Islands, CAM file (cameras)
  application: Evil Islands
  file-extension: cam
  license: MIT
  endian: le
doc: Camera representation
seq:
  - id: cams
    type: camera
    repeat: eos
types:
  vec3:
    doc: 3d vector
    seq:
      - id: x
        type: f4
        doc: x axis
      - id: y
        type: f4
        doc: y axis
      - id: z
        type: f4
        doc: z axis
  quat:
    doc: quaternion
    seq:
      - id: w
        type: f4
        doc: w component
      - id: x
        type: f4
        doc: x component
      - id: y
        type: f4
        doc: y component
      - id: z
        type: f4
        doc: z component
  camera:
    doc: Camera parameters
    seq:
      - id: unkn0
        type: u4
        doc: unknown
      - id: unkn1
        type: u4
        doc: unknown
      - id: position
        type: vec3
        doc: camera's position
      - id: rotation
        type: quat
        doc: camera's rotation

В соседней папке — Res, хранятся (неожиданно!) RES файлы, являющиеся архивами.


RES

Этот формат иногда прячется под другими расширениями, но оригинальное всё же именно RES.
Структура данных весьма типична для архива с произвольным доступом к файлам: есть таблицы для хранения информации о файлах внутри, таблица имён, само содержимое файлов.
Структура каталогов содержится прямо в именах.

Стоит отметить два крайне интересных факта:


  1. Архив оптимизирован под загрузку информации о файлах в связный список с закрытым хэшированием.
  2. Можно хранить содержимое файла один раз, но ссылаться на него под разными именами. Насколько мне известно, этот факт использовался в фанатском репаке, где за счёт этого был сильно уменьшен размер игры. В оригинальном дистрибутиве оптимизация архивов не использовалась.


Описание структуры
meta:
  id: res
  title: Evil Islands, RES file (resources archive)
  application: Evil Islands
  file-extension: res
  license: MIT
  endian: le
doc: Resources archive
seq:
  - id: magic
    contents: [0x3C, 0xE2, 0x9C, 0x01]
    doc: Magic bytes
  - id: files_count
    type: u4
    doc: Number of files in archive
  - id: filetable_offset
    type: u4
    doc: Filetable offset
  - id: nametable_size
    type: u4
    doc: Size of filenames
instances:
  nametable_offset:
    value: filetable_offset + 22 * files_count
    doc: Offset of filenames table
  filetable:
    pos: filetable_offset
    type: file_record
    repeat: expr
    repeat-expr: files_count
    doc: Files metadata table
types:
  file_record:
    doc: File metadata
    seq:
      - id: next_index
        type: s4
        doc: Next file index
      - id: file_size
        type: u4
        doc: Size of file in bytes
      - id: file_offset
        type: u4
        doc: File data offset
      - id: last_change
        type: u4
        doc: Unix timestamp of last change time
      - id: name_len
        type: u2
        doc: Lenght of filename
      - id: name_offset
        type: u4
        doc: Filename offset in name array
    instances:
      name:
        io: _root._io
        pos: name_offset + _parent.nametable_offset
        type: str
        encoding: cp1251
        size: name_len
        doc: File name
      data:
        io: _root._io
        pos: file_offset
        size: file_size
        doc: Content of file

Это интересно: в русской версии игры, архив Speech.res содержит два подкаталога s и t с полностью идентичным содержанием, из-за чего размер архива в два раза больше — именно поэтому игра не помещается на один CD.

Теперь можно распаковать все архивы (могут быть вложенными):


  • RES — просто архив,
  • MPR — ландшафт игровых уровней,
  • MQ — информация о заданиях мультиплеера,
  • ANM — набор анимаций,
  • MOD — 3d модель,
  • BON — расположение костей модели.

Если файлы внутри архива не имеют расширения, будем ставить расширение родителя — касается BON и ANM архивов.

Также можно разбить все полученные файлы на четыре группы:


  1. Текстуры;
  2. Базы данных;
  3. Модели;
  4. Файлы уровня.

Начнём с простого — с текстур.


MMP

Собственно, текстура. Имеет небольшой заголовок, указывающий на параметры изображения, число MIP уровней и использованное сжатие. После заголовка располагаются MIP уровни изображения по убыванию размера.


Описание структуры
meta:
  id: mmp
  title: Evil Islands, MMP file (texture)
  application: Evil Islands
  file-extension: mmp
  license: MIT
  endian: le
doc: MIP-mapping texture
seq:
  - id: magic
    contents: [0x4D, 0x4D, 0x50, 0x00]
    doc: Magic bytes
  - id: width
    type: u4
    doc: Texture width
  - id: height
    type: u4
    doc: Texture height
  - id: mip_levels_count
    type: u4
    doc: Number of MIP-mapping stored levels
  - id: fourcc
    type: u4
    enum: pixel_formats
    doc: FourCC label of pixel format
  - id: bits_per_pixel
    type: u4
    doc: Number of bits per pixel
  - id: alpha_format
    type: channel_format
    doc: Description of alpha bits
  - id: red_format
    type: channel_format
    doc: Description of red bits
  - id: green_format
    type: channel_format
    doc: Description of green bits
  - id: blue_format
    type: channel_format
    doc: Description of blue bits
  - id: unused
    size: 4
    doc: Empty space
  - id: base_texture
    type:
      switch-on: fourcc
      cases:
        'pixel_formats::argb4': block_custom
        'pixel_formats::dxt1': block_dxt1
        'pixel_formats::dxt3': block_dxt3
        'pixel_formats::pnt3': block_pnt3
        'pixel_formats::r5g6b5': block_custom
        'pixel_formats::a1r5g5b5': block_custom
        'pixel_formats::argb8': block_custom
        _: block_custom
types:
  block_pnt3:
    seq:
      - id: raw
        size: _root.bits_per_pixel
  block_dxt1:
    seq:
      - id: raw
        size: _root.width * _root.height >> 1
  block_dxt3:
    seq:
      - id: raw
        size: _root.width * _root.height
  block_custom:
    seq:
      - id: lines
        type: line_custom
        repeat: expr
        repeat-expr: _root.height
    types:
      line_custom:
        seq:
          - id: pixels
            type: pixel_custom
            repeat: expr
            repeat-expr: _root.width
        types:
          pixel_custom:
            seq:
              - id: raw
                type:
                  switch-on: _root.bits_per_pixel
                  cases:
                    8: u1
                    16: u2
                    32: u4
            instances:
              alpha:
                value: '_root.alpha_format.count == 0 ? 255 : 255 * ((raw & _root.alpha_format.mask) >> _root.alpha_format.shift) / (_root.alpha_format.mask >> _root.alpha_format.shift)'
              red:
                value: '255 * ((raw & _root.red_format.mask) >> _root.red_format.shift) / (_root.red_format.mask >> _root.red_format.shift)'
              green:
                value: '255 * ((raw & _root.green_format.mask) >> _root.green_format.shift) / (_root.green_format.mask >> _root.green_format.shift)'
              blue:
                value: '255 * ((raw & _root.blue_format.mask) >> _root.blue_format.shift) / (_root.blue_format.mask >> _root.blue_format.shift)'
  channel_format:
    doc: Description of bits for color channel
    seq:
      - id: mask
        type: u4
        doc: Binary mask for channel bits
      - id: shift
        type: u4
        doc: Binary shift for channel bits
      - id: count
        type: u4
        doc: Count of channel bits
enums:
  pixel_formats:
    0x00004444: argb4
    0x31545844: dxt1
    0x33545844: dxt3
    0x33544E50: pnt3
    0x00005650: r5g6b5
    0x00005551: a1r5g5b5
    0x00008888: argb8

Возможные форматы упаковки пикселей:


fourcc Описание
44 44 00 00 ARGB4
44 58 54 31 DXT1
44 58 54 33 DXT3
50 4E 54 33 PNT3 — RLE сжатый ARGB8
50 56 00 00 R5G5B5
51 55 00 00 A1R5G5B5
88 88 00 00 ARGB8

О PNT3

Если формат изображения PNT3, то структура пикселей после распаковки — ARGB8; bits_per_pixel — размер сжатого изображения в байтах.


Распаковка PNT3

n = 0
destination = b""

while src < size:
    v = int.from_bytes(source[src:src + 4], byteorder='little')
    src += 4

    if v > 1000000 or v == 0:
        n += 1
    else:
        destination += source[src - (1 + n) * 4:src - 4]
        destination += b"\x00" * v
        n = 0

Это интересно: часть текстур отражена по вертикали (или некоторые не отражены?).
А ещё игра весьма ревностно относится к прозрачности — если изображение с альфа каналом, цвет прозрачных пикселов должен быть точно чёрным. Или белым — тут как повезёт.

Простые форматы закончились, переходим к более жёстким — в своё время, ряды модмейкеров яростно хранили свои собственные инструменты редактирования следующих форматов, и не зря. Я вас предупредил.


Базы данных (*DB и иже с ними)

Этот формат крайне неудобно описывать — по существу, это сериализованное дерево нод (или таблиц записей). Файл состоит из нескольких таблиц с заданными типами полей. Общая структура: таблицы вложены в общую "корневую" ноду, записи — ноды внутри таблицы.

В каждой ноде задаётся её тип и размер:

unsigned char type_index;
unsigned char raw_size; // не используется вне этого блока
unsigned length; // не читается из файла

read(raw_size);

if (raw_size & 1)
{
    length = raw_size >> 1;
    for (int i = 0; i < 3; i++)
        length <<= 8;
        read(raw_size);
        length += raw_size;
}
else
    length = raw_size >> 1;

Тип поля таблицы берётся по индексу из форматной строки для таблицы, по полученному значению определяется реальный тип.


Типы полей
обозначение описание
S string
I 4b int
U 4b unsigned
F 4b float
X bits byte
f float array
i int array
B bool
b bool array
H unknown hex bytes
T time
0 not stated
1 0FII
2 SUFF
3 FFFF
4 0SISS
5 0SISS00000U

Описание баз

Предметы (.idb)


таблица структура
Материалы SSSIFFFIFIFfIX
Оружие SSISIIIFFFFIFIXB00000IHFFFfHHFF
Броня SSISIIIFFFFIFIXB00000ffBiHH
Быстрые предметы SSISIIIFFFFIFIXB00000IIFFSbH
Квестовые предметы SSISIIIFFFFIFIXB00000Is
Продаваемые предметы SSISIIIFFFFIFIXB00000IHI

Переключатели (.ldb)


таблица структура
Прототип переключателя SfIFTSSS

Умения и навыки (.pdb)


таблица структура
Умения SSI0000000s
Навыки SSI0000000SSIIIFFFIIIIBI

Следы (prints.db)


таблица структура
Следы крови 0S11
Следы пламени 0S110000001
Следы ног 0S11

Заклинания (.sdb)


таблица структура
Прототипы SSSFIFIFFFFIIIIUSSIIbIXFFFFF
Модификаторы SSFIFFISX
Шаблоны 0SssSX
Шаблоны для брони 0SssSX
Шаблоны для оружия 0SssSX

Существа (.udb)


таблица структура
Повреждаемые части SffUU
Расы SUFFUUFfFUUf222222000000000000SssFSsfUUfUUIUSBFUUUU
Прототипы монстров SSIUIFFFSFFFFFFFFFUFFFFFFff33sfssSFFFFFUFUSF
NPC SUFFFFbbssssFUB

Выкрики (acks.db)


таблица структура
Ответы 0S0000000044444444444444444444445444444444444
Крики 0S0000000044444
Прочее 0S0000000044

Задания (.qdb)


таблица структура
Задания SFIISIIs
Брифинги SFFsSsssssI

Это интересно: 16.01.2002 Nival выложил исходные базы для мультиплеера в csv формате, а также утилиту-конвертер в игровой формат (снапшот сайта). Естественно, обратный конвертер не замедлил появиться. Также есть минимум два документа с описанием полей и их типов от модмейкеров, но читать их весьма тяжело.

ADB

База данных анимации для конкретного типа юнитов. В отличии от упомянутых выше *DB, достаточно "человечна" — это одноуровневая таблица со статичными размерами полей.


Описание структуры
meta:
  id: adb
  title: Evil Islands, ADB file (animations database)
  application: Evil Islands
  file-extension: adb
  license: MIT
  endian: le
doc: Animations database
seq:
  - id: magic
    contents: [0x41, 0x44, 0x42, 0x00]
    doc: Magic bytes
  - id: animations_count
    type: u4
    doc: Number of animations in base
  - id: unit_name
    type: str
    encoding: cp1251
    size: 24
    doc: Name of unit
  - id: min_height
    type: f4
    doc: Minimal height of unit
  - id: mid_height
    type: f4
    doc: Middle height of unit
  - id: max_height
    type: f4
    doc: Maximal height of unit
  - id: animations
    type: animation
    doc: Array of animations
    repeat: expr
    repeat-expr: animations_count
types:
  animation:
    doc: Animation's parameters
    seq:
      - id: name
        type: str
        encoding: cp1251
        size: 16
        doc: Animation's name
      - id: number
        type: u4
        doc: Index in animations array
      - id: additionals
        type: additional
        doc: Packed structure with animation parameters
      - id: action_probability
        type: u4
        doc: Percents of action probability
      - id: animation_length
        type: u4
        doc: Lenght of animation in game ticks
      - id: movement_speed
        type: f4
        doc: Movement speed
      - id: start_show_hide1
        type: u4
      - id: start_show_hide2
        type: u4
      - id: start_step_sound1
        type: u4
      - id: start_step_sound2
        type: u4
      - id: start_step_sound3
        type: u4
      - id: start_step_sound4
        type: u4
      - id: start_hit_frame
        type: u4
      - id: start_special_sound
        type: u4
      - id: spec_sound_id1
        type: u4
      - id: spec_sound_id2
        type: u4
      - id: spec_sound_id3
        type: u4
      - id: spec_sound_id4
        type: u4
    types:
      additional:
        seq:
          - id: packed
            type: u8
        instances:
          weapons:
            value: 'packed & 127'
          allowed_states:
            value: '(packed >> 15) & 7'
          action_type:
            value: '(packed >> 18) & 15'
          action_modifyer:
            value: '(packed >> 22) & 255'
          animation_stage:
            value: '(packed >> 30) & 3'
          action_forms:
            value: '(packed >> 36) & 63'

Это интересно: для нескольких юнитов используется частично урезанный формат базы, практически не исследованный.

Разобравшись с базами данных, объявляем рекламную паузу. Но рекламировать ничего не будем — не наш метод. Лучше обозначим то, что пригодится дальше — как именуются файлы существ.


Формат имён моделей

Имя собирается из групп по два символа — сокращений логического "уровня".
Например, персонаж-женщина будет unhufeUnit > Human > Female, а initwespInventory > Item > Weapon > Spear, то есть, копьё в инвентаре (не спине, и то хорошо).


Полное дерево элементов имени:
un: # unit
  an: # animal
    wi: # wild
      ti # tiger
      ba # bat
      bo # boar
      hy # hyen
      de # deer
      gi # rat
      ra # rat
      cr # crawler
      wo # wolf
    ho: # home
      co # cow
      pi # pig
      do # dog
      ho # horse
      ha # hare
  or: # orc
    fe # female
    ma # male
  mo: # monster
    co # column (menu)
    un # unicorn
    cu # Curse
    be # beholder
    tr # troll
    el # elemental
    su # succub (harpie)
    ba # banshee
    dr # driad
    sh # shadow
    li # lizard
    sk # skeleton
    sp # spider
    go # golem, goblin
    ri # Rick
    og # ogre
    zo # zombie
    bi # Rik's dragon
    cy # cyclope
    dg # dragon
    wi # willwisp
    mi # octopus
    to # toad
  hu: # human
    fe # female
    ma # male
in: # inventory
  it: # item
    qu # quest
    qi # interactive
    ar: # armor
      pl # plate
      gl # gloves
      lg # leggins
      bt # boots
      sh # shirt
      hl # helm
      pt # pants
    li: # loot
      mt # material
      tr # trade
    we: # weapon
      hm # hammer
      dg # dagger
      sp # spear
      cb # crossbow
      sw # sword
      ax # axe
      bw # bow
  gm # game menu
  fa: # faces
    un: # unit
      an: # animal
        wi: # wild
          ti: # tiger
            face # face
          ba: # bat
            face # face
          bo: # boar
            face # face
          de: # deer
            face # face
          ra: # rat
            face # face
          cr: # crawler
            face # face
          wo: # wolf
            face # face
        ho: # home
          co: # cow
            face # face
          pi: # pig
            face # face
          do: # dog
            face # face
          ho: # horse
            face # face
          ha: # hare
            face # face
      hu: # human
        fe: # female
          fa # 
          me # 
          th # 
        ma: # male
          fa # 
          me # 
          th # 
      mo: # monster
        to: # toad
          face # face
        tr: # troll
          face # face
        or: # orc
          face # face
        sp: # spider
          face # face
        li: # lizard
          face # face
na: # nature
  fl: # flora
    bu # bush
    te # termitary
    tr # tree
    li # waterplant
  wa # waterfall
  sk # sky
  st # stone
ef: # effects
  cu # 
  ar # 
co # components
st: # static
  si # switch
  bu: # building
    to # tower
    ho # house
  tr # trap
  br # bridge
  ga # gate
  we # well (waterhole)
  wa: # wall
    me # medium
    li # light
  to # torch
  st # static

Это интересно: по данной классификации, грибы — деревья, големы с гоблинами — братья, а Тка-Рик — монстр. Также тут можно заметить "рабочие" имена монстров, подозрительно похожие на таковые из D&D — beholder (злобоглаз), succub (гарпия), ogre (людоед), driad (лесовики).

Морально отдохнув, окунёмся с головой в модели. Они представлены несколькими форматами, которые компонуются между собой.


LNK

Логически — основа модели. Описывает иерархию частей модели, в терминах современного 3d моделирования — иерархию костей.


Описание структуры
meta:
  id: lnk
  title: Evil Islands, LNK file (bones hierarchy)
  application: Evil Islands
  file-extension: lnk
  license: MIT
  endian: le
doc: Bones hierarchy
seq:
  - id: bones_count
    type: u4
    doc: Number of bones
  - id: bones_array
    type: bone
    repeat: expr
    repeat-expr: bones_count
    doc: Array of bones
types:
  bone:
    doc: Bone node
    seq:
      - id: bone_name_len
        type: u4
        doc: Length of bone's name
      - id: bone_name
        type: str
        encoding: cp1251
        size: bone_name_len
        doc: Bone's name
      - id: parent_name_len
        type: u4
        doc: Length of bone's parent name
      - id: parent_name
        type: str
        encoding: cp1251
        size: parent_name_len
        doc: Bone's parent name

Имя родителя основной кости — пустая строка (длины 0).

Кости есть, однако недостаточно назвать их и сложить кучкой — нужно собрать их в скелет.


BON

Ранее упоминавшийся, этот формат (если он не архив) задаёт положение частей (костей) модели относительно части-родителя. Хранится лишь смещение, без вращения — одно из отличий от современных форматов.


Описание структуры
meta:
  id: bon
  title: Evil Islands, BON file (bone position)
  application: Evil Islands
  file-extension: bon
  license: MIT
  endian: le
doc: Bone position
seq:
  - id: position
    type: vec3
    doc: Bone translation
    repeat: eos
types:
  vec3:
    doc: 3d vector
    seq:
      - id: x
        type: f4
        doc: x axis
      - id: y
        type: f4
        doc: y axis
      - id: z
        type: f4
        doc: z axis

Как можно заметить, чисел здесь слишком много для одного смещения — дело в том, что здесь мы впервые наткнулись на одну из ключевых фишек движка игры — трилинейную интерполяцию моделей.

Как это работает: у модели есть три параметра интерполяции — условно, сила, ловкость, рост. Также есть 8 крайних состояний модели. Используя параметры, можем получить итоговую модель трилинейной интерполяцией.


Непосредственно, алгоритм
def trilinear(val, coefs=[0, 0, 0]):
    # Linear interpolation by str
    t1 = val[0] + (val[1] - val[0]) * coefs[1]
    t2 = val[2] + (val[3] - val[2]) * coefs[1]
    # Bilinear interpolation by dex
    v1 = t1 + (t2 - t1) * coefs[0]

    # Linear interpolation by str
    t1 = val[4] + (val[5] - val[4]) * coefs[1]
    t2 = val[6] + (val[7] - val[6]) * coefs[1]
    # Bilinear interpolation by dex
    v2 = t1 + (t2 - t1) * coefs[0]

    # Trilinear interpolation by height
    return v1 + (v2 - v1) * coefs[2]

Это интересно: трилинейная интерполяция модели используется для анимации некоторых объектов, например, открытия каменной двери и сундуков.

Теперь самое время посмотреть на сами части модели.


FIG

Пожалуй, этот формат понять слёту невозможно. В сети можно найти его описание и плагин для блендера, но даже с ними осознание приходит не сразу. Взгляните:


Описание структуры
meta:
  id: fig
  title: Evil Islands, FIG file (figure)
  application: Evil Islands
  file-extension: fig
  license: MIT
  endian: le
doc: 3d mesh
seq:
  - id: magic
    contents: [0x46, 0x49, 0x47, 0x38]
    doc: Magic bytes
  - id: vertex_count
    type: u4
    doc: Number of vertices blocks
  - id: normal_count
    type: u4
    doc: Number of normals blocks
  - id: texcoord_count
    type: u4
    doc: Number of UV pairs
  - id: index_count
    type: u4
    doc: Number of indeces
  - id: vertex_components_count
    type: u4
    doc: Number of vertex components
  - id: morph_components_count
    type: u4
    doc: Number of morphing components
  - id: unknown
    contents: [0, 0, 0, 0]
    doc: Unknown (aligment)
  - id: group
    type: u4
    doc: Render group
  - id: texture_index
    type: u4
    doc: Texture offset
  - id: center
    type: vec3
    doc: Center of mesh
    repeat: expr
    repeat-expr: 8
  - id: aabb_min
    type: vec3
    doc: AABB point of mesh
    repeat: expr
    repeat-expr: 8
  - id: aabb_max
    type: vec3
    doc: AABB point of mesh
    repeat: expr
    repeat-expr: 8
  - id: radius
    type: f4
    doc: Radius of boundings
    repeat: expr
    repeat-expr: 8
  - id: vertex_array
    type: vertex_block
    doc: Blocks of raw vertex data
    repeat: expr
    repeat-expr: 8
  - id: normal_array
    type: vec4x4
    doc: Packed normal data
    repeat: expr
    repeat-expr: normal_count
  - id: texcoord_array
    type: vec2
    doc: Texture coordinates data
    repeat: expr
    repeat-expr: texcoord_count
  - id: index_array
    type: u2
    doc: Triangles indeces
    repeat: expr
    repeat-expr: index_count
  - id: vertex_components_array
    type: vertex_component
    doc: Vertex components array
    repeat: expr
    repeat-expr: vertex_components_count
  - id: morph_components_array
    type: morph_component
    doc: Morphing components array
    repeat: expr
    repeat-expr: morph_components_count
types:
  morph_component:
    doc: Morphing components indeces
    seq:
      - id: morph_index
        type: u2
        doc: Index of morphing data
      - id: vertex_index
        type: u2
        doc: Index of vertex
  vertex_component:
    doc: Vertex components indeces
    seq:
      - id: position_index
        type: u2
        doc: Index of position data
      - id: normal_index
        type: u2
        doc: Index of normal data
      - id: texture_index
        type: u2
        doc: Index of texcoord data
  vec2:
    doc: 2d vector
    seq:
      - id: u
        type: f4
        doc: u axis
      - id: v
        type: f4
        doc: v axis
  vec3:
    doc: 3d vector
    seq:
      - id: x
        type: f4
        doc: x axis
      - id: y
        type: f4
        doc: y axis
      - id: z
        type: f4
        doc: z axis
  vec3x4:
    doc: 3d vector with 4 values per axis
    seq:
      - id: x
        type: f4
        doc: x axis
        repeat: expr
        repeat-expr: 4
      - id: y
        type: f4
        doc: y axis
        repeat: expr
        repeat-expr: 4
      - id: z
        type: f4
        doc: z axis
        repeat: expr
        repeat-expr: 4
  vertex_block:
    doc: Vertex raw block
    seq:
      - id: block
        type: vec3x4
        doc: Vertex data
        repeat: expr
        repeat-expr: _root.vertex_count
  vec4x4:
    doc: 4d vector with 4 values per axis
    seq:
      - id: x
        type: f4
        doc: x axis
        repeat: expr
        repeat-expr: 4
      - id: y
        type: f4
        doc: y axis
        repeat: expr
        repeat-expr: 4
      - id: z
        type: f4
        doc: z axis
        repeat: expr
        repeat-expr: 4
      - id: w
        type: f4
        doc: w axis
        repeat: expr
        repeat-expr: 4

В чём сложность? Так ведь данные нормалей и вершин хранятся в блоках по 4, а вершины ещё и скомпонованы в 8 блоков для интерполяции.


Это интересно: предположительно, такая группировка сделана для ускорения обработки с помощью SSE инструкций, появившихся в процессорах Intel с 1999.

Что ж, модель мы прочли и составили, однако чего-то не хватает. Точно — анимации!


ANM

Анимация хранится в виде ключевых состояний покомпонентно. Интересен тот факт, что реализована поддержка не только скелетной анимации, но и повершинного морфинга.


Описание структуры
meta:
  id: anm
  title: Evil Islands, ANM file (bone animation)
  application: Evil Islands
  file-extension: anm
  license: MIT
  endian: le
doc: Bone animation
seq:
  - id: rotation_frames_count
    type: u4
    doc: Number of rotation frames
  - id: rotation_frames
    type: quat
    repeat: expr
    repeat-expr: rotation_frames_count
    doc: Bone rotations
  - id: translation_frames_count
    type: u4
    doc: Number of translation frames
  - id: translation_frames
    type: vec3
    repeat: expr
    repeat-expr: translation_frames_count
    doc: Bone translation
  - id: morphing_frames_count
    type: u4
    doc: Number of morphing frames
  - id: morphing_vertex_count
    type: u4
    doc: Number of vertices with morphing
  - id: morphing_frames
    type: morphing_frame
    repeat: expr
    repeat-expr: morphing_frames_count
    doc: Array of morphing frames
types:
  vec3:
    doc: 3d vector
    seq:
      - id: x
        type: f4
        doc: x axis
      - id: y
        type: f4
        doc: y axis
      - id: z
        type: f4
        doc: z axis
  quat:
    doc: quaternion
    seq:
      - id: w
        type: f4
        doc: w component
      - id: x
        type: f4
        doc: x component
      - id: y
        type: f4
        doc: y component
      - id: z
        type: f4
        doc: z component
  morphing_frame:
    doc: Array of verteces morphing
    seq:
      - id: vertex_shift
        type: vec3
        repeat: expr
        repeat-expr: _parent.morphing_vertex_count
        doc: Morphing shift per vertex

Всё — теперь у нас есть полноценная модель, можно полюбоваться на свежеотрендеренного ящера-отшельника:


Момент ностальгии

Узнать, что нужно Ящеру

Разговор с ящером в его жилище

Ящер-Отшельник: Ты пришел, человек. Это хорошо.

Зак: Это все, что ты хотел мне сказать?

Ящер-Отшельник: Ты опять торопишься. Я помню твои вопросы и буду на них отвечать. Я пришел к людям в железе, чтобы заключить сделку. Но я увидел, как они поступили с тобой. Они не держат слова, я перестал им верить. Ты сдержал слово. Сделка будет предложена тебе.

Ящер-Отшельник: Люди любят золото. Ящерам золото неинтересно. Ты выполнишь мое задание, и я дам тебе золото, которое есть у меня. Этого золота много.

Зак (задумчиво и без особой заинтересованности): Хм… Золото… Оно, конечно, не помешает…

Зак: Было бы лучше, если бы ты помог мне узнать, где живет старый маг, которого я так долго ищу. Ведь ящеры — древний народ, и вы можете это знать!

Ящер-Отшельник: Ты прав. Ящеры — древний народ. Я могу собрать все, что нам известно про старика. Ты согласен выполнить мое задание?

Зак: О чем разговор! Считай, что все уже сделано.

Ящер-Отшельник (серьезно): Уже сделано? Ты хочешь меня обмануть?

Зак: Вообще-то я хотел пошутить, а то ты уж больно серьезен.

Ящер-Отшельник: Понимаю. Это шутка. Наверное, я тоже смогу пошутить. Потом. А сейчас мне надо, чтобы ты вернул воду в Канал. Воду украли у нас орки.

Ящер-Отшельник: Иди на юг вдоль воды. Увидишь плотину и Канал. Плотину надо поднять. Рычагом. Я его дам. Канал нужно завалить. Камнем. Камень я не дам. Он уже лежит на краю Канала. Вверх по течению от плотины. Камень тяжелый. Когда орки копали, они его поднимали долго. Если ты его толкнешь, обратно он будет падать быстро.

Ящер-Отшельник: После этого возвращайся. Я расскажу тебе все, что узнаю про старого Мага.

Зак: По рукам! Но, кстати, если ты добавишь к рассказу немножко монет, я вовсе не обижусь.

Ящер-Отшельник: За монетами отправляйся к моим сородичам, которые живут на отмелях дальше, на юге. Пройди на самый дальний песчаный остров, третий по счету. Сокровища будут твоими!

Ящер-Отшельник (сам себе): Странно. Этот человек любит юмор. Я пошутил. Человек не засмеялся. Очень странно.

Теперь — самое интересное: как хранится карта.


MP

Это — заголовочный файл карты. По несчастливому стечению обстоятельств, расширение совпадает с таковым у файлов сохранения мультиплеера, которые мы рассматривать не будем.

Сначала нужно дать общую характеристику ландшафту:


  • число "чанков" — кусков карты 32х32 метра;
  • максимальную высоту (так как высота вершин хранится в целочисленной шкале);
  • число тайловых атласов.

Дополнительно идёт описание материалов карты, а также анимированных тайлов — например, воды или лавы.


Описание структуры
meta:
  id: mp
  title: Evil Islands, MP file (map header)
  application: Evil Islands
  file-extension: mp
  license: MIT
  endian: le
doc: Map header
seq:
  - id: magic
    contents: [0x72, 0xF6, 0x4A, 0xCE]
    doc: Magic bytes
  - id: max_altitude
    type: f4
    doc: Maximal height of terrain
  - id: x_chunks_count
    type: u4
    doc: Number of sectors by x
  - id: y_chunks_count
    type: u4
    doc: Number of sectors by y
  - id: textures_count
    type: u4
    doc: Number of texture files
  - id: texture_size
    type: u4
    doc: Size of texture in pixels by side
  - id: tiles_count
    type: u4
    doc: Number of tiles
  - id: tile_size
    type: u4
    doc: Size of tile in pixels by side
  - id: materials_count
    type: u2
    doc: Number of materials
  - id: animated_tiles_count
    type: u4
    doc: Number of animated tiles
  - id: materials
    type: material
    doc: Map materials
    repeat: expr
    repeat-expr: materials_count
  - id: id_array
    type: u4
    doc: Tile type
    repeat: expr
    repeat-expr: tiles_count
    enum: tile_type
  - id: animated_tiles
    type: animated_tile
    doc: Animated tiles
    repeat: expr
    repeat-expr: animated_tiles_count
types:
  material:
    doc: Material parameters
    seq:
      - id: type
        type: u4
        doc: Material type by
        enum: terrain_type
      - id: color
        type: rgba
        doc: RGBA diffuse color
      - id: self_illumination
        type: f4
        doc: Self illumination
      - id: wave_multiplier
        type: f4
        doc: Wave speed multiplier
      - id: warp_speed
        type: f4
        doc: Warp speed multiplier
      - id: unknown
        size: 12
    types:
      rgba:
        doc: RGBA color
        seq:
          - id: r
            type: f4
            doc: Red channel
          - id: g
            type: f4
            doc: Green channel
          - id: b
            type: f4
            doc: Blue channel
          - id: a
            type: f4
            doc: Alpha channel
    enums:
      terrain_type:
        0: base
        1: water_notexture
        2: grass
        3: water
  animated_tile:
    doc: Animated tile parameters
    seq:
      - id: start_index
        type: u2
        doc: First tile of animation
      - id: length
        type: u2
        doc: Animation frames count
enums:
  tile_type:
    0: grass
    1: ground
    2: stone
    3: sand
    4: rock
    5: field
    6: water
    7: road
    8: empty
    9: snow
    10: ice
    11: drygrass
    12: snowballs
    13: lava
    14: swamp
    15: highrock

Список типов ландшафта
terrain type Тип
0 Базовый ландшафт
1 Вода без текстуры
2 Текстурированная трава
3 Текстурированная вода

Список типов материалов
material type Тип
0 grass
1 ground
2 stone
3 sand
4 rock
5 field
6 water
7 road
8 (empty)
9 snow
10 ice
11 drygrass
12 snowballs
13 lava
14 swamp
15 highrock

Тип материала должен влиять на проходимость, судя по информации в файле Res/aiinfo.res/tileDesc.reg.


Это интересно: во всех общедоступных описаниях формата, допущена ошибка — поля земли и воды перепутаны по типам.
И опять же: можно спутать эти файлы с сохранениями мультиплеера.

Теперь мы готовы обработать сами части карты. За дело!


SEC

Файл хранит единичный сектор карты — кусок 32х32 метра. Положение на карте хранится в имени файла, которое имеет вид ZonenameXXXYYY.


Описание структуры
meta:
  id: sec
  title: Evil Islands, SEC file (map sector)
  application: Evil Islands
  file-extension: sec
  license: MIT
  endian: le
doc: Map sector
seq:
  - id: magic
    contents: [0x74, 0xF7, 0x4B, 0xCF]
    doc: Magic bytes
  - id: liquids
    type: u1
    doc: Liquids layer indicator
  - id: vertexes
    type: vertex
    doc: Vertex array 33x33
    repeat: expr
    repeat-expr: 1089
  - id: liquid_vertexes
    type: vertex
    doc: Vertex array 33x33
    if: liquids != 0
    repeat: expr
    repeat-expr: 'liquids != 0 ? 1089 : 0'
  - id: tiles
    type: tile
    doc: Tile array 16x16
    repeat: expr
    repeat-expr: 256
  - id: liquid_tiles
    type: tile
    doc: Tile array 16x16
    if: liquids != 0
    repeat: expr
    repeat-expr: 'liquids != 0 ? 256 : 0'
  - id: liquid_material
    type: u2
    doc: Index of material
    if: liquids != 0
    repeat: expr
    repeat-expr: 'liquids != 0 ? 256 : 0'
types:
  vertex:
    doc: Vertex data
    seq:
      - id: x_shift
        type: s1
        doc: Shift by x axis
      - id: y_shift
        type: s1
        doc: Shift by y axis
      - id: altitude
        type: u2
        doc: Height (z position)
      - id: packed_normal
        type: normal
        doc: Packed normal
  normal:
    doc: Normal (3d vector)
    seq:
      - id: packed
        type: u4
        doc: Normal packed in 4b
    instances:
      x:
        doc: Unpacked x component
        value: packed >> 11 & 0x7FF
      y:
        doc: Unpacked y component
        value: packed & 0x7FF
      z:
        doc: Unpacked z component
        value: packed >> 22
  tile:
    doc: Tile parameters
    seq:
      - id: packed
        type: u2
        doc: Tile information packed in 2b
    instances:
      index:
        doc: Tile index in texture
        value: packed & 63
      texture:
        doc: Texture index
        value: packed >> 6 & 255
      rotation:
        doc: Tile rotation (*90 degrees)
        value: packed >> 14 & 3

Тут разработчики размахнулись на славу — практически все данные хранятся в запакованном виде.


Набор алгоритмов распаковки

Распаковка нормали

10 бит на ось z, по 11 на x и y

unsigned packed_normal;

float x = ((float)((packed_normal >> 11) & 0x7FF) - 1000.0f) / 1000.0f;
float y = ((float)(packed_normal & 0x7FF) - 1000.0f) / 1000.0f;
float z = (float)(packed_normal >> 22) / 1000.0f;

Информация о текстуре

6 бит на индекс в атласе, 8 на номер текстуры, 2 на вращение

unsigned short texture;

unsigned char tile_index = f & 63;
unsigned char texture_index = (f >> 6) & 255;
unsigned char rotation = (f >> 14) & 3;

Генерация 3d модели

Получение ландшафта

Вершины идут по 33 элемента в 33 строки, то есть, образуя 32х32 клетки. Длина клетки по стороне — 1 условная единица.

Позиция вершины:
x = индекс по x + x_offset / 254
y = индекс по y + y_offset / 254
z = altitude / 65535 * max_altitude (из .mp файла)

Вершины объединяются в полигоны "гребёнкой", при этом четыре вершины образуют два полигона:

 0   1 2
   *-*-*
   |/|/| ~
33 *-*-*
   |/|/| ~
66 *-*-*
    ~ ~  ~

Текстура накладывается на сразу четыре таких клетки, то есть, 16х16 тайлов. Длина тайла — 2 условные единицы. Тайл может быть повёрнут на угол, кратный 90 градусам.

Сектор может содержать информацию о жидкостях на уровне. В таком случае, помимо вершин и текстурной информации, в конце файла указывается ID материала воды, являющийся индексом в таблице материалов из MP файла.


Это интересно: как и для MP, в описаниях формата допущена ошибка, но здесь она уже гораздо более весомая: указание ID материала считали указанием видимости тайла, из-за чего меш строился бы некорректно.
Также ID разбивает жидкости уровня на несколько групп — подъём воды после применения рычага как раз использует это.

Отлично — теперь у нас есть готовый ландшафт:

Осталось совсем чуть-чуть — добавить на него объекты, а заодно и рассмотреть последний в данной статье формат.


MOB

Если вам понравился (или наоборот) формат баз данных, то здесь используется несколько иной, но чисто теоретически схожий принцип: сериализованное дерево записей. Причём отличие от формата баз огромно и в хорошую сторону — нет чёткой структуры "по шаблону", зато есть правило составления полей.
Поле может быть либо записью и содержать другие поля, либо быть ключом (хранить значение в конкретном формате).

Очень краткое представление:

typedef structure
{
    unsigned type_id;
    unsigned size;

    byte data[size - 8];
} node;

Графическое представление (неполное, слабонервным не смотреть!)

Описание структуры (опять же, неполное)

meta:
  id: mob
  title: Evil Islands, MOB file (map entities)
  application: Evil Islands
  file-extension: mob
  license: MIT
  endian: le
doc: Map entities tree
seq:
  - id: root_node
    type: node
    doc: Root node
types:
  node:
    doc: Entity node
    seq:
      - id: type_id
        type: u4
        doc: Node children type ID
      - id: size
        type: u4
        doc: Node full size
      - id: data
        type: node_data
        size: size - 8
        doc: Node stored data
  node_data:
    doc: Node data
    seq:
      - id: value
        type:
          switch-on: _parent.type_id
          cases:
            0xA000: node
            0x00001E00: node
            0x00001E01: node
            0x00001E02: node
            0x00001E03: node
            0x00001E0B: node
            0x00001E0E: node
            0x0000A000: node
            0x0000AA01: node
            0x0000ABD0: node
            0x0000B000: node
            0x0000B001: node
            0x0000CC01: node
            0x0000DD01: node
            0x0000E000: node
            0x0000E001: node
            0x0000F000: node
            0x0000FF00: node
            0x0000FF01: node
            0x0000FF02: node
            0xBBAB0000: node
            0xBBAC0000: node
            0xBBBB0000: node
            0xBBBC0000: node
            0xBBBD0000: node
            0xBBBE0000: node
            0xBBBF0000: node
            0xDDDDDDD1: node
            _: u1
        doc: Node elements
        repeat: eos

Полный список типов данных
тип данных размер (обычно) описание
AiGraph граф проходимости
AreaArray
Byte 1 1б беззнаковое целое
Diplomacy 4096 32x32 матрица из 2б целых
Dword 4 4б беззнаковое целое
Float 4 4б вещественное
LeverStats 12 параметры рычага
Null 0 пустая нода
Plot 12 3 floats (vec3)
Plot2DArray
Quaternion 16 4 floats (vec4)
Record >8 контейнер нод
Rectangle
String строка
StringArray >4 массив строк
StringEncrypted >4 зашифрованный скрипт уровня
UnitStats 180 параметры существа
Unknown

Список возможных type_id
type_id Тип данных Имя поля
0x00000000 Record ROOT
0x00001E00 Record VSS_SECTION
0x00001E01 Record VSS_TRIGER
0x00001E02 Record VSS_CHECK
0x00001E03 Record VSS_PATH
0x00001E04 Dword VSS_ID
0x00001E05 Rectangle VSS_RECT
0x00001E06 Dword VSS_SRC_ID
0x00001E07 Dword VSS_DST_ID
0x00001E08 String VSS_TITLE
0x00001E09 String VSS_COMMANDS
0x00001E0A Byte VSS_ISSTART
0x00001E0B Record VSS_LINK
0x00001E0C String VSS_GROUP
0x00001E0D Byte VSS_IS_USE_GROUP
0x00001E0E Record VSS_VARIABLE
0x00001E0F StringArray VSS_BS_CHECK
0x00001E10 StringArray VSS_BS_COMMANDS
0x00001E11 String VSS_CUSTOM_SRIPT
0x0000A000 Record OBJECTDBFILE
0x0000AA00 Null LIGHT_SECTION
0x0000AA01 Record LIGHT
0x0000AA02 Float LIGHT_RANGE
0x0000AA03 String LIGHT_NAME
0x0000AA04 Plot LIGHT_POSITION
0x0000AA05 Dword LIGHT_ID
0x0000AA06 Byte LIGHT_SHADOW
0x0000AA07 Plot LIGHT_COLOR
0x0000AA08 String LIGHT_COMMENTS
0x0000ABD0 Record WORLD_SET
0x0000ABD1 Plot WS_WIND_DIR
0x0000ABD2 Float WS_WIND_STR
0x0000ABD3 Float WS_TIME
0x0000ABD4 Float WS_AMBIENT
0x0000ABD5 Float WS_SUN_LIGHT
0x0000B000 Record OBJECTSECTION
0x0000B001 Record OBJECT
0x0000B002 Dword NID
0x0000B003 Dword OBJTYPE
0x0000B004 String OBJNAME
0x0000B005 Null OBJINDEX
0x0000B006 String OBJTEMPLATE
0x0000B007 String OBJPRIMTXTR
0x0000B008 String OBJSECTXTR
0x0000B009 Plot OBJPOSITION
0x0000B00A Quaternion OBJROTATION
0x0000B00B Null OBJTEXTURE
0x0000B00C Plot OBJCOMPLECTION
0x0000B00D StringArray OBJBODYPARTS
0x0000B00E String PARENTTEMPLATE
0x0000B00F String OBJCOMMENTS
0x0000B010 Null OBJ_DEF_LOGIC
0x0000B011 Byte OBJ_PLAYER
0x0000B012 Dword OBJ_PARENT_ID
0x0000B013 Byte OBJ_USE_IN_SCRIPT
0x0000B014 Byte OBJ_IS_SHADOW
0x0000B015 Null OBJ_R
0x0000B016 String OBJ_QUEST_INFO
0x0000C000 Null SC_OBJECTDBFILE
0x0000CC00 Null SOUND_SECTION
0x0000CC01 Record SOUND
0x0000CC02 Dword SOUND_ID
0x0000CC03 Plot SOUND_POSITION
0x0000CC04 Dword SOUND_RANGE
0x0000CC05 String SOUND_NAME
0x0000CC06 Dword SOUND_MIN
0x0000CC07 Dword SOUND_MAX
0x0000CC08 String SOUND_COMMENTS
0x0000CC09 Null SOUND_VOLUME
0x0000CC0A StringArray SOUND_RESNAME
0x0000CC0B Dword SOUND_RANGE2
0x0000CC0D Byte SOUND_AMBIENT
0x0000CC0E Byte SOUND_IS_MUSIC
0x0000D000 Null PR_OBJECTDBFILE
0x0000DD00 Null PARTICL_SECTION
0x0000DD01 Record PARTICL
0x0000DD02 Dword PARTICL_ID
0x0000DD03 Plot PARTICL_POSITION
0x0000DD04 String PARTICL_COMMENTS
0x0000DD05 String PARTICL_NAME
0x0000DD06 Dword PARTICL_TYPE
0x0000DD07 Float PARTICL_SCALE
0x0000E000 Record DIRICTORY
0x0000E001 Record FOLDER
0x0000E002 String DIR_NAME
0x0000E003 Dword DIR_NINST
0x0000E004 Dword DIR_PARENT_FOLDER
0x0000E005 Byte DIR_TYPE
0x0000F000 Record DIRICTORY_ELEMENTS
0x0000FF00 Record SEC_RANGE
0x0000FF01 Record MAIN_RANGE
0x0000FF02 Record RANGE
0x0000FF05 Dword MIN_ID
0x0000FF06 Dword MAX_ID
0x31415926 AiGraph AIGRAPH
0xACCEECCA String SS_TEXT_OLD
0xACCEECCB StringEncrypted SS_TEXT
0xBBAB0000 Record MAGIC_TRAP
0xBBAB0001 Dword MT_DIPLOMACY
0xBBAB0002 String MT_SPELL
0xBBAB0003 AreaArray MT_AREAS
0xBBAB0004 Plot2DArray MT_TARGETS
0xBBAB0005 Dword MT_CAST_INTERVAL
0xBBAC0000 Record LEVER
0xBBAC0001 Null LEVER_SCIENCE_STATS
0xBBAC0002 Byte LEVER_CUR_STATE
0xBBAC0003 Byte LEVER_TOTAL_STATE
0xBBAC0004 Byte LEVER_IS_CYCLED
0xBBAC0005 Byte LEVER_CAST_ONCE
0xBBAC0006 LeverStats LEVER_SCIENCE_STATS_NEW
0xBBAC0007 Byte LEVER_IS_DOOR
0xBBAC0008 Byte LEVER_RECALC_GRAPH
0xBBBB0000 Record UNIT
0xBBBB0001 Null UNIT_R
0xBBBB0002 String UNIT_PROTOTYPE
0xBBBB0003 Null UNIT_ITEMS
0xBBBB0004 UnitStats UNIT_STATS
0xBBBB0005 StringArray UNIT_QUEST_ITEMS
0xBBBB0006 StringArray UNIT_QUICK_ITEMS
0xBBBB0007 StringArray UNIT_SPELLS
0xBBBB0008 StringArray UNIT_WEAPONS
0xBBBB0009 StringArray UNIT_ARMORS
0xBBBB000A Byte UNIT_NEED_IMPORT
0xBBBC0000 Record UNIT_LOGIC
0xBBBC0001 Null UNIT_LOGIC_AGRESSIV
0xBBBC0002 Byte UNIT_LOGIC_CYCLIC
0xBBBC0003 Dword UNIT_LOGIC_MODEL
0xBBBC0004 Float UNIT_LOGIC_GUARD_R
0xBBBC0005 Plot UNIT_LOGIC_GUARD_PT
0xBBBC0006 Byte UNIT_LOGIC_NALARM
0xBBBC0007 Byte UNIT_LOGIC_USE
0xBBBC0008 Null UNIT_LOGIC_REVENGE
0xBBBC0009 Null UNIT_LOGIC_FEAR
0xBBBC000A Float UNIT_LOGIC_WAIT
0xBBBC000B Byte UNIT_LOGIC_ALARM_CONDITION
0xBBBC000C Float UNIT_LOGIC_HELP
0xBBBC000D Byte UNIT_LOGIC_ALWAYS_ACTIVE
0xBBBC000E Byte UNIT_LOGIC_AGRESSION_MODE
0xBBBD0000 Record GUARD_PT
0xBBBD0001 Plot GUARD_PT_POSITION
0xBBBD0002 Null GUARD_PT_ACTION
0xBBBE0000 Record ACTION_PT
0xBBBE0001 Plot ACTION_PT_LOOK_PT
0xBBBE0002 Dword ACTION_PT_WAIT_SEG
0xBBBE0003 Dword ACTION_PT_TURN_SPEED
0xBBBE0004 Byte ACTION_PT_FLAGS
0xBBBF0000 Record TORCH
0xBBBF0001 Float TORCH_STRENGHT
0xBBBF0002 Plot TORCH_PTLINK
0xBBBF0003 String TORCH_SOUND
0xDDDDDDD1 Record DIPLOMATION
0xDDDDDDD2 Diplomacy DIPLOMATION_FOF
0xDDDDDDD3 StringArray DIPLOMATION_PL_NAMES
0xFFFFFFFF Unknown UNKNOWN

Этот файл содержит всю информацию об уровне — дипломатию, расположение и параметры юнитов, информацию для легендарного редактора Nival, а самое интересное — скрипт уровня, причём в зашифрованном виде (ключ лежит рядом, не беспокойтесь).


Расшифровка строк
unsigned key;

for (size_t i = 0; i < size; i++) 
{
    key += (((((key * 13) << 4) + key) << 8) - key) * 4 + 2531011;
    data[i] ^= key >> 16;
}

Это интересно: этот формат очень важен для модмейкеров, однако отреверсить файлы и расшифровать скрипт (а потом зашифровать обратно) было очень непростой задачей. В давние времена, одна из команд написала свою утилиту, в которой шифрование производилось одним постоянным ключом, с целью выявления факта использования конкурирующей командой.

Тот самый легендарный редактор уровней (взято с форумов, точная дата неизвестна, однако на скриншоте — Windows 98):


Это интересно: скриншот редактора несколько раз появлялся на форумах, где его страстно желали заполучить. Естественно, что его так никому и не дали (кроме как, возможно, разработчикам "Проклятые Земли: Затерянные в Астрале", но информации у меня нет).

Вот теперь, получив всю необходимую нам информацию, мы наконец-то можем сконвертировать всё в более-менее известный формат файлов, например, Collada и сделать финальный рендер на память:

Наша краткая экскурсия по файлам Проклятых Земель подошла к концу. Мы рассмотрели устройство большей части форматов, а особо заинтересованные наверняка уже воспользовались схемами и написали свой собственный конвертер или просмотровщик.

Я надеюсь, что эта статья пригодится фанатам игры или привлечёт новых людей в коммьюнити. Теперь же шансы сделать что-то новое хоть немного, но возросли — кто-нибудь захочет написать редактор карт, это привлечёт ещё больше заинтересованных людей. Эх, мечты-мечты...

На этом я прощаюсь — до встреч на просторах Кании!

Let's block ads! (Why?)

Финтех-стартап Robinhood не смог запустить аналог банковских счетов

Изображение: Robinhood

13 декабря 2018 года финтех-стартап Robinhood, который разрабатывает приложение для торговли на американских биржах без комиссии, объявил о запуске собственных сберегательных счетов. В заявлении компании указывалось, что пользователи смогут получить доход по депозиту около 3% – при том что в США максимальный доход по депозитам не превышает 2%. Также депозиты на сумму до $250 тыс. должны были быть застрахованы.

Однако анонс новой услуги вызвал серьезный скандал, и ее пришлось свернуть уже на следующий день.

Что случилось


Пользователи Robinhood должны были получить возможность использования счетов, которые полностью аналогичны банковским. С возможностью получения процентов, а также плаcтиковых карт, с которых можно снимать деньги в любом банкомате без комиссий.

Помимо этого, согласно заявлению Robinhood, депозиты пользователей до $250 тыс. должны были быть застрахованы некоммерческой организацией SIPC (Securities Investor Protection Corporation). Однако выяснилось, что организация не планирует сотрудничество со стартапом. Об этом руководитель SIPC Стивен Харбек (Stephen Harbeck) заявил в интервью изданию Barron's:

«SIPC защищает деньги, которые размещены на брокерских счетах с единственной целью – покупки акций. Средства, которые не предназначены для этой цели не будут защищены».

Более того, он отправил запрос о законности предоставления стартапом такой услуги в соответствующее подразделение SEC. Согласно правилам открывать сберегательные (savings account) и чековые счета (checking account) могут только лицензированные банки, но предложение Robinhood называлось «checking and saving account» – теоретически, это могло помочь обойти ограничения. Но этого не произошло.

Последствия


Анонс о новой услуге был опубликован в четверг, а уже в пятницу информация о ней была удалена. В блоге Robinhood появился пост, в котором основатели компании Байджу Бхатт (Baiju Bhatt) и Влад Тенев (Vlad Tenev) заявили, что предыдущие публикации «могли вызвать определенное недопонимание».

Бхатт и Тенев написали, что планируют серьезно переработать маркетинговые материалы услуги, а также нацеливаются на «плотную работу с регулирующими органами». Вместо checking and savings счетов Robinhood теперь пишет о будущем запуске функции управления финансами (cash management).

Оцениваемый в $5.6 млрд стартап Robinhood в последнее время столкнулся с целым рядом скандалов. Помимо шумихи, вызванной анонсом аналога банковских счетов, компанию обвиняли в продаже данных о биржевых заявках пользователей различным брокерам, что могло негативно влиять на скорость исполнения приказов.

Другие материалы по теме финансов и фондового рынка от ITI Capital:


Let's block ads! (Why?)

[Из песочницы] (5-2) Способа перенести большую SQL таблицу

Введение


Всем привет! Это моя первая статья и пишу я ее от лица младшего инженера-разработчика на языке C#. Так что здесь не будет каких-то подробных сведений о SQL, лишь практические сведения и размышления по решению довольно не очевидной задачи, с которой мне пришлось столкнуться, для таких же новичков, как и я сам.

Сначала я опишу формулировку своей задачи в качестве примера, в котором возникает реальная необходимость переноса большой таблицы.

Итак, представим, что у вас есть web-сервис и SQL (MS-SQL) база данных с таблицей html-писем, которые ваш сервис рассылает пользователям. Письма хранятся за некоторое количество лет и удалить их нельзя, так как они нужны для сбора статистики и аналитики. Однако, с каждым годом количество писем растет, база разрастается, а места на SQL-сервере все меньше (в нашем случае еще одним фактором было восстановление базы на тестовую площадку, т.к. его время пропорционально росло) и с этим нужно что-то делать. Благо, в нашем случае есть свободный сервер с кучей свободного места (в реальности его может не быть и конечно это временное решение, но это выходит за рамки статьи). Так возникла задача по переносу большой таблицы (и говоря «большой», я имею в виду реально большую таблицу, все что я видел, пока искал похожие решения, было в районе 60-100Гб, в нашем случае таблица весила более 300 Гб).

Мы рассмотрим несколько способов решения этой задачи, но не все они будут относится к переносу вида сервер – сервер. Иногда может возникнуть необходимость переноса таблицы между базами в рамках одного сервера. Также, некоторые способы чисто теоретические, я не проверял их все на практике, однако они наверняка должны сработать.

Способ -1. Data


Как бы очевидно не звучало, но вы должны знать, какие данные собираетесь переносить. Чаще всего, данные хранятся не самым оптимальным способом, также может храниться лишняя информация. Вполне вероятно, что в вашем конкретном случае можно обойтись без переноса всех данных.

Во-первых, вероятно вам может помочь удаление какого-либо столбца, однако это блокирующая операция, а не всегда есть возможность остановить web-сервис. И на Хабре есть статья, в которой рассказывается, как это можно осуществить.

Во-вторых, не забывайте про нормализацию. Возможно какие-то данные можно перенести в словарь (в случае с письмами, можно было хранить не тела писем, а шаблоны с теми данными, которые туда вставляются), а в большой таблице хранить лишь id эти элементов, это может освободить вам кучу места.

Способ 0. SELECT INTO


Шутка =) Так вы разве что положите себе базу. Однако если речь идет о небольших размерах таблицы (тогда что вы тут делаете) можно попытаться перенести базу с помощью этой инструкции. Также, если вы располагаете тестовой базой, можно провести эксперимент, чтобы оценить общее время переноса данным методом «в лоб».

Способ 1. Backup


Самый «каноничный» способ, именно это стало решением моей задачи. Делаем бэкап базы, содержащей нашу таблицу, и восстанавливаем его на другом сервере и очищаем от всего лишнего. Далее, если есть возможность остановить web-сервис, можно его передеплоить, настроив запись в перенесенную таблицу, а старую удаляем* (тут скорее всего может возникнуть момент того, что необходимо будет писать к ней запросы с джойнами, для этого гуглите как линковать sql-серверы). Если нет такой возможности, фиксируем id последнего письма (для синхронизации), затем надо будет удалить* все перенесенные письма (писать продолжим в старую таблицу).

* Удаление отдельная тема для разговора, может показаться, что оно выполняется гораздо быстрее, чем перенос, но это не так и в общем случае советую удалять порционно.

Способ 2. MS-SQL Management Studio


Если у вас есть данная студия, можете попробовать воспользоваться встроенным инструментом для экспорта и импорта данных. Лично я прочитал на stack overflow, что это штука зависла на таблице в 60 гигов и рисковать не стал.

Способ 3. Partition


Улучшенный метод «в лоб». Идея в том, чтобы переносить данные обычным способом с таймером между итерациями. Вы разбиваете все строки на порции (например, по 100к) переносите порцию (и тут же ее можно удалять, однако не уверен, насколько это безопасно), затем засыпаете и так до победного конца. Переносить лучше с конца, чтобы не пришлось синхронизировать данные по окончанию. Способ, очевидно, очень медленный, однако таким образом вы перенесете все без остановки web-сервиса. Скорее всего это будет удобнее реализовать не SQL-скриптом, а с помощью какого-нибудь ORM.

Итог


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

Еще в конце хотелось бы добавить 2 важных замечания.

Любой процесс переноса-удаления строк в SQL логируется в transaction log для возможности все откатить в случае ошибки (я ранее предполагал, что это осуществляется только в рамках транзакции). Причем размер лога получается даже чуть больше объема данных. Убедитесь, что располагаете необходимым количеством места либо отключите логирование, однако это небезопасно.

Перед переносом нужно удостовериться, что файл данных и файл лога имеют необходимый размер, т.к. операции по их расширению тратят значительное количество времени, и сконфигурировать их соответствующим образом Таким образом вы оптимизируете перенос.
Спасибо всем прочитавшим! Буду рад любой критике, замечаниям и уточнениям. Делитесь своими способами и техниками работы с большими данными, т.к. зачастую это очень важная и нужная информация, которую не так-то просто найти.

Let's block ads! (Why?)

Роботизация может вести к диктатуре

Предыдущая статья на тему замены человека роботом получила большое количество комментариев. Получается, тема живая не только в наших головах.

Поскольку мы сами вносим вклад в роботизацию как в контексте обучения в нашей Школе, так и в контексте проектов, которые мы делаем, то невольно нам приходится задумываться на предмет того, куда в пределе этот процесс может вести и как избежать сопутствующих ему угроз.

В этой публикации мы решили отчасти ответить на комментарии из предыдущей статьи, отчасти немного дальше развить тему. Если кто-то не читал изначальную публикацию — предлагаем это сделать, а также комментарии к ней.

Итак, давайте временно не будем спорить о том, случится так, что роботы смогут заменить человека или нет. Не случится — ок. Но, вот если случится, то дальнейшее нам видится так:

Роботы вытесняют все больше человеческих специальностей и людей с них. Люди в попытке выжить и нормально жить движутся в четырех направлениях:

  • одни развиваются в сторону все более интеллектуальных и креативных видов деятельности. Причем, возможно возникнут интеллектуальные и креативные продукты, которых мы себе сейчас даже не можем представить;
  • другие идут в сферу обслуживания и торговли, в ту их часть, где все еще важен личный человеческий контакт. Однако, надо понимать, что поскольку потребителям этих услуг тоже нужны доходы, то массовость самого рынка также сокращается;
  • третьи бегут в те страны и регионы, где уровень роботизации еще не слишком высокий и сталкиваются там с высокой конкуренцией со стороны других таких же людей;
  • a четвертые делают на самом деле самое правильное (для них) и очевидное — идут во власть. Это может быть как власть экономическая (то есть, владение орудиями производства — соответственно, роботами), так и власть политическая.

И, соответственно, эти четвертые, находясь у власти и деля ее с очень немногими другими понимают, что от 90% людей на самом деле ничего не зависит совсем, они не представляют не только физической силы, но и экономической. Соответственно, с ними уже нет необходимости считаться.

Можно, конечно, поспорить на тему того, что есть (по крайней мере в ряде стран) система “сдержек и противовесов” в структуре управления государством, которая не позволит этому случится. Однако, давайте вспомним, что все эти принципы по факту результат французской революции и революционных движений в целом.

Соответственно, исходят они из парадигмы, что если большинство народа недовольно, то они восстанут и сделают так, как им надо (это если упрощенно, конечно). А чтобы этого не случилось, нужно встроить “голос народа” в саму систему управления государством (или сделать видимость этого встраивания).

Однако, что, если уже будет не важно, восстанет народ или нет. Так как само его наличие уже не является обязательным. Что сохранит все эти постулаты управления?

Мораль? То есть, те, у кого будут все ресурсы и орудия производства (по факту все, что необходимо, чтобы жить, развиваться итд) должны будут быть настолько морально-ответственными, чтобы вечно кормить всех остальных? Мне кажется, это неравновесная система, и в какой-то момент она уравновесится и явно не в пользу всех этих остальных.

Кто-то в комментариях предлагал разделить результат производства между всеми. Но так уже пытались в истории. Ни у кого это нормально и долговечно не получилось, и такие попытки всегда были связаны с возникновением диктатуры.

Можно, в теории, поступить по другому: разделить права на орудия производства, то есть, на роботов. Это значит, например, что у каждого будет свой робот-аватар, которого он будет отправлять работать на завод, пока сам будет лежать на солнышке на Карибах. Красивая идея. Проблема, с моей точки зрения, в отчуждаемости собственности и концентрации власти. Соответственно, такая система также не является равновесной и в какой-то момент права на роботов будут перераспределены.

Нужна идея равновесной системы, то есть, такой, где ресурсы будут зарабатываться, а не отдаваться по доброй воле. То есть, нужно придумать ценность, которую будут генерить оставшиеся люди за пределами того, что можно сделать, вырастить или построить.

Вам приходит что-то в голову? Мне пока нет. Не в массовом масштабе.

Еще способ это сильно возвысить ценность самого человека. То есть, не того, что он создает или производит, а его самого и сделать это до такого уровня, чтобы эта ценность перекрывала все материальные интересы. Так можно? Может быть, и можно. Но, думаю, вы согласитесь, что сейчас общество к такому не готово и на то, чтобы оно стало готово, потребуются годы, десятилетия, столетия.

Такие мысли пока у нас. А вы что думаете?

И, как обычно, хотите больше узнать про интеллектуальных роботов и как их делают — приходите к нам на курс для аналитиков. Мы сильно углубили программу в сторону искусственного интеллекта.

Основная часть по машинному обучению (без основ никуда) сопровождается большими блоками по сверточным и рекуррентным нейросетям, компьютерному зрению, генерации изображений, natural language understanding и диалоговым системам.

Let's block ads! (Why?)