...

суббота, 9 октября 2021 г.

Как я портировал игру с VisualBasic 6 на С++, сделав её кросс-платформенной

Всем доброго времени суток! Это моя история о том, как я портировал исходный код одной фанатской Windows-игры о Марио с VisualBasic 6 на C++, и с какими трудностями я столкнулся в процессе создания.

Немного об оригинальной игре

Super Mario Bros. X (или коротко SMBX) - это фанатская игра по мотивам вселенной Марио, созданная в 2009 году американцем Эндрю Спинксом (который позже прославился как создатель игры Terraria). Эта фанатская игра была его первым опытом в разработке игр. В ней он познавал азы игростроя. Игра создавалась с использованием VisualBasic 6.

Главное меню игры Super Mario Bros. X версии 1.3
Главное меню игры Super Mario Bros. X версии 1.3

В игре имеется возможность играть за одного из пятерых персонажей: Марио, Луиджи, Пич, Тоад и Линк. Можно играть в одиночку, можно играть вдвоём в кооперативном режиме. Также имеется режим битвы, в котором игроки должны побить друг друга, пользуясь различными подручными средствами. Сама игра является неким подобием песочницы для сообщества, в которой игроки могут создавать свои уровни и целые эпизоды, используя предложенные элементы во встроенном редакторе. Также можно добавлять в игру собственные ресурсы (картинки и музыку). Даже не смотря на полное прекращение разработки игры в 2011 году, она пользовалась большим спросом и широко использовалась сообществом. Игра также привлекла внимание разработчиков-энтузиастов и хакеров, которые создавали для неё вспомогательные инструменты, а также делали попытки модифицировать и расширить игру. Самыми известными из них являются набор разработки из тулкита Moondust Project (изначально называвшимся PGE Project), а также, библиотека LunaLua (изначально известная как LunaDll), расширяющая функционал игры посредством dll-инъекции. Исходный код игры долгое время был закрытым. Однако, всё изменилось, когда 2 февраля 2020 года на форуме были опубликованы исходные коды игры.

Начало работ

Слухи о возможной публикации исходных кодов игры были ещё в 2016м году, однако, этого не произошло. Это дало мне идею переписать игру на C++, чтобы с ней удобно было возиться. Но, поскольку, исходников не было, идея слегла в долгий ящик. С выходом исходников в феврале 2020-го года, я буквально достал эту идею из глубокой ямы забвения. Первым делом, я запустил свою виртуальную машину с Windows XP, где я и развернул среду VB6, в которой затем я открыл проект игры, и, после нескольких исправлений ошибок компиляции, успешно запустил игру из исходного кода.

Работы начались с исследования устройства кода игры и его структуры, а также с замены кода воспроизведения аудио с древнего MCI на мою специальную сборку SDL Mixer X, созданную для работы в VB6-проектах. Таким образом, я решил проблему работы игры на Linux под Wine, а также значительно ускорил загрузку игры и уменьшил потребление ею памяти.

Инструментарий

Сначала я использовал самописный кусок кода на JavaScript, которым я через регулярные выражения парсил определения переменных, массивов и функций. Затем, я использовал бесплатную версию "VB to C++ Converter" от Tangible Software Solutions, чтобы относительно точно преобразовывать куски кода в C++ (Я пользовался режимом Snifit, поскольку конвертер был ориентирован на VB.NET, значительно отличавшийся от VB6. Также не обошлось без огромного числа дополнительных ручных манипуляций и макросов). Часть кода была переписана мною вручную, особенно на начальных этапах. Много модулей я писал с нуля (либо заимствовал из других моих проектов), чтобы обеспечить конечный проект всей необходимой функциональностью. Весь процесс разработки я вёл на Linux Mint, используя среду разработки Qt Creator, параллельно с CLion. Также, я использовал виртуальные машины с Windows XP и Windows 7 для запуска некоторых зависимых инструментов, а также VisualStudio Code для просмотра VB6-проекта игры из под Linux-системы.

Особенности VisualBasic и различия с C++

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

Видимость переменных и функций

Первый этап начался с создания файла globals.h, который описывал все глобальные переменные игры и все типы (массивы, структуры, константы, и т.п.). Всё дело в том, что по своей архитектуре, VisualBasic подразумевает, что все переменные и функции по умолчанию глобальны и видимы между всеми модулями проекта без предварительных включений или импортов модуля. Исключение лишь составляли элементы, отмеченные ключевым словом "Private". Потом, под каждый модуль, отдельно создавал списки прототипов функций и заголовочные файлы к ним. Затем, я включал эти заголовки по остальным модулям, чтобы обеспечить и полную видимость всех важных объектов, и навести некоторый порядок по файлам.

Структуры

Отдельной проблемой стало то, что VisualBasic прямо позволяет именовать структуры и переменные одинаково, буква-в-букву:

' Определение структуры
Public Type Controls
    Up As Boolean
    Down As Boolean
    Left As Boolean
    Right As Boolean
    Jump As Boolean
    AltJump As Boolean
    Run As Boolean
    AltRun As Boolean
    Drop As Boolean
    Start As Boolean
End Type

' Определение одноимённой переменной
Controls As Controls

Из-за этого мне пришлось добавлять всем именам структур окончание "_t", и проблема решилась:

struct Controls_t
{
    bool Up = false;
    bool Down = false;
    bool Left = false;
    bool Right = false;
    bool Jump = false;
    bool AltJump = false;
    bool Run = false;
    bool AltRun = false;
    bool Drop = false;
    bool Start = false;
};

extern Controls_t Controls;

Массивы с явным диапазоном

В VisualBasic предусмотрена возможность создавать массивы с различными диапазонами индексов, и не обязательно от 0 до N-1, а например, от 1 до 5, от -1000 до +1000, и т.п.:

Public BlockSwitch(1 To 4) As Boolean

В C++ такого нету. Однако, мне не помешало создать реализацию такой концепции, получился вот такой шаблон:

template <class T, long begin, long end>
class RangeArr
{
    static constexpr long range_diff = begin - end;
    static constexpr size_t size = (range_diff < 0 ? -range_diff : range_diff) + 1;
    static const long offset = -begin;
    T array[size];

public:
    RangeArr()
    {}

    ~RangeArr()
    {}

    RangeArr(const RangeArr &o)
    {
        for(size_t i = 0; i < size; i++)
            array[i] = o.array[i];
    }

    RangeArr& operator=(const RangeArr &o)
    {
        for(size_t i = 0; i < size; i++)
            array[i] = o.array[i];
        return *this;
    }

    void fill(const T &o)
    {
        for(size_t i = 0; i < size; i++)
            array[i] = o;
    }

    T& operator[](long index)
    {
        assert(index <= end);
        assert(index >= begin);
        assert(offset + index < static_cast<long>(size));
        assert(offset + index >= 0);
        return array[offset + index];
    }
};

А также вариант шаблона специально для целочисленных типов с предварительной инициализацией:

template <class T, long begin, long end, T defaultValue>
class RangeArrI
{
    static constexpr long range_diff = begin - end;
    static constexpr size_t size = (range_diff < 0 ? -range_diff : range_diff) + 1;
    static const long offset = -begin;
    T array[size];

public:
    RangeArrI()
    {
        for(size_t i = 0; i < size; i++)
            array[i] = defaultValue;
    }

    ~RangeArrI()
    {}

    RangeArrI(const RangeArrI &o)
    {
        for(size_t i = 0; i < size; i++)
            array[i] = o.array[i];
    }

    RangeArrI& operator=(const RangeArrI &o)
    {
        for(size_t i = 0; i < size; i++)
            array[i] = o.array[i];
        return *this;
    }

    void fill(const T &o)
    {
        for(size_t i = 0; i < size; i++)
            array[i] = o;
    }

    T& operator[](long index)
    {
        assert(index <= end);
        assert(index >= begin);
        assert(offset + index < static_cast<long>(size));
        assert(offset + index >= 0);
        return array[offset + index];
    }
};

Таким образом, выше представленный пример на C++ будет определён следующим образом:

extern RangeArrI<bool, 1, 4, false> BlockSwitch;

К счастью (или к сожалению), Эндрю не использовал в своём коде динамических массивов, хотя, с другой стороны, по сравнению с std::vector в С++, динамические массивы в VisualBasic 6 были не очень удобными в работе.

В моих шаблонах я также применил assert-ы, поскольку они позволяют отлавливать ошибки выхода за пределы диапазона массива в коде (в VisualBasic 6 типичная ошибка это "Runtime Error 9: Subscript out of range"). И тем самым, иметь возможность легко отлаживать их, не допуская последующей порчи памяти и возникновения SIGSEGV.

Опциональные аргументы-ссылки

В VisualBasic, как ни странно, в функциях и процедурах, аргументы передаются по принципу ссылок: их можно изменить непосредственно из кода функции:

Public Sub addValue(number As Integer)
  number = 42
End Sub

В C++ для создания подобной функции необходимо явно указывать, что аргумент является ссылкой:

void addValue(int &number)
{
  number = 42;
}

Однако, при портировании кода с VisualBasic 6 я столкнулся со следующим явлением:

Public Sub addValue(step As Integer, Optional number As Integer = 0)
  number = step
End Sub

То есть, самая настоящая опциональная ссылка. Её можно использовать, а можно не использовать, тогда записанное в неё значение просто потеряется.

В C++ из-за этого я словил баг, что значение, переданное по опциональному аргументу, не обновилось в соответствии с кодом.

void addValue(int step, int number = 0)
{
  number = step;
}

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

void addValue(int step, int &number)
{
  number = step;
}

void addValue(int step)
{
  int dummy = 0;
  addValue(step, dummy);
}

Округление чисел

В VisualBasic принципиально отличается политика округления чисел, чем от C++:

  • Попытка присвоить число с плавающей точкой целочисленной переменной, всегда округляет своё значение. В C++ идёт приведение типа с отсечением дробной части.

  • Округление идёт нестандартным образом, через x86-инструкцию FRNDINT, которая округляет серединные значения по типу 0.5 к ближайшему чётному целому. То есть, было 15.5, округление будет в 16, если было 42.5, то округлится к 42м.

Почему я обратил на это внимание? Потому что физика в игре во многом зависит от частей кода, использующего округление. Если округление делать не правильно (не так, как оно делалось в VisualBasic), то в итоге, физика будет искажена (Например, будет искажено движение пресса-давилки на уровне "Dungeon of Pain" в эпизоде "The Invasion 2").

Чтобы полностью решить проблему округления, я реализовал соответствующий велосипед:

const double power10[] =
{
    1.0,
    10.0,
    100.0,
    1000.0,
    10000.0,

    100000.0,
    1000000.0,
    10000000.0,
    100000000.0,
    1000000000.0,

    10000000000.0,
    100000000000.0,
    1000000000000.0,
    10000000000000.0,
    100000000000000.0,

    1000000000000000.0,
    10000000000000000.0,
    100000000000000000.0,
    1000000000000000000.0,
    10000000000000000000.0,

    100000000000000000000.0,
    1000000000000000000000.0
};

double vb6round(double x, int deimals);

int vb6Round(double x)
{
    return static_cast<int>(vb6Round(x, 0));
}

static SDL_INLINE double toNearest(double x)
{
    int round_old = std::fegetround();
    if(round_old == FE_TONEAREST)
        return std::nearbyint(x);
    else
    {
        std::fesetround(FE_TONEAREST);
        x = std::nearbyint(x);
        std::fesetround(round_old);
        return x;
    }
}

double vb6Round(double x, int decimals)
{
    double res = x, decmul;

    if(decimals < 0 || decimals > 22)
        decimals = 0;

    if(SDL_fabs(x) < 1.0e16)
    {
        decmul = power10[decimals];
        res = toNearest(x * decmul) / decmul;
    }

    return res;
}

Таким образом, получилась весьма точная реализация округления, соответствующая поведению VisualBasic 6.

Таймеры

В игре использовались встроенные функции и глобальные переменные, отвечающие за время: глобальная функция Timer, возвращающее Single-значение (аналог float в C++) секунд, прошедших с полуночи, и WinAPI-функция Sleep для различных задержек.

Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

К счастью, SDL2 легко заменяет эти функции на собственные SDL_GetTicks() и SDL_Delay(). Однако, имеется вопрос более серьёзный, чем просто выжидание времени и задержка: игра старается жёстко выдерживать частоту обновления в 65 кадров в секунду. Если специально не стараться, игра будет работать с частотой 67 кадров в секунду без вертикальной синхронизации. Это большая проблема для спидраннеров, использующих внешние таймеры для честного измерения времени прохождения игры.

Чтобы исправить проблему, я нашёл код, который выдерживает определённую частоту, и доработал его. Получился целый модуль, реализующий логику жёсткой выдержки частоты в 65 кадров в секунду. Пришлось отдельно повозиться, поскольку в Linux и macOS уже были нужные функции, считающие нановремя, а в Windows с этим большая проблема. Однако, спустя время я нашёл универсальное решение, которое будет идеально работать и на миллисекундах.

Боль логических выражений

Ещё одна особенность, которая заключается в том, что VisualBasic 6 и C++ по разному обрабатывают логические выражения:

  • В С++ происходит постепенный расчёт значений и постепенное наложение. То есть, мы имеем выражение if(A < 5 && Array[A] == 1), и в случае, когда выражение A < 5 ложное, выражение Array[A] == 1 рассчитываться не будет.

  • В VisualVasic 6 члены выражения рассчитываются сразу, и только тогда идёт проверка логики. То есть, в выражении if A < 5 And Array(A) = 1 Then будут всегда рассчитываться оба члена. Здесь произойдёт ошибка из-за того, что индекс A выходит за пределы размера массива. Таким образом, при портировании на C++ я случайно исправил баг, рушивший игру. Из-за этой особенности языка в коде можно встретить больше число лазаний из if() { if {} if { .... } }, которая вместе с тем, что выглядит неуклюже, является необходимым костылём над особенностью обработки логических выражений в VisualBasic.

  • В VisualBasic 6, оператор And имеет приоритет над оператором Or, ровно как и умножение перед сложением.

  • В C++ оператор && также имеет приоритет над || , однако, при преобразовании сложных логических выражений может возникнуть путаница там, где не использовались скобки, а также всплывать предупреждения компилятора. Решается легко добавлением скобок вокруг ключевых логических групп.

Также было явно заметно, что Эндрю на тот момент абсолютно не имел никакого понятия о Select Case (аналога оператора switch()), и поэтому в его коде было чрезвычайное злоупотребление конструкциями if else if else if else... .

Кошмарный спагетти-полиморфизм

Фрагмент спагетти-кода, отвечающего за логику НИП разных типов
Фрагмент спагетти-кода, отвечающего за логику НИП разных типов

Ни для кого не секрет, что в VisualBasic полностью отсутствовало полноценное понятие классов, а реализация классов, представленная в VisualBasic 6, была сильно ограниченной. Эндрю решил не использовать её вовсе. Однако, Эндрю даже не стал создавать раздельные функции для разбиения логики разнотипных объектов. Вместо этого, он создал цепь громоздких божественных функций, каждая из которых содержала огромнейшие массивы кода, разбивающие логику разнотипных объектов через цепь if else if else if else. Ещё одна особенность кода игры, это очень и очень длинные однострочные логические и арифметические выражения, почти никакого переноса кода использовано не было. Также Эндрю не позаботился об использовании перечислений, чтобы наглядно именовать каждый тип элементов, а просто использовал сырые числовые значения, чтобы обозначить тот или иной элемент (а их там сотни разных!). Большую часть кода я преобразовал достаточно точно, сохранив исходную логику. Часть этого всего "спагетти" мне пришлось разбить на секторы, вынося в отдельные функции, чтобы тем самым сохранить наглядность.

Поддержка текста и преобразований строк в числа

VisualBasic 6 не умеет ничего, кроме локалезависимых ANSI-кодировок и очень ограниченной поддержки UTF16. Из-за этого возникают серьёзные проблемы при работе игры на компьютерах по всему миру. Поэтому, нельзя именовать игровые файлы на кириллице, иначе игра не заработает на компьютере в Китае. И наоборот. Я решил использовать в игре UTF8, поскольку эта кодировка является универсальной и повсеместной. Большинство операционных систем используют именно её в своих файловых системах. Отличается лишь Windows, которая предпочитает использовать локалезависимые ANSI-кодировки и UTF16. Из-за чего, в функциях взаимодействия с Windows я применил прямое преобразование между UTF8 и UTF16, чтобы продолжать использовать UTF8 внутри игры, и UTF16 при обращении к функциям самой Windows.

Что касается преобразований строк в числа, здесь они тоже локалезависимы: Эндрю, как положено по американским стандартам, использовал точку в качестве разделителя целой и десятичной частями чисел. Из-за этого, если в полях файлов присутствовали числа с плавающей точкой, игра падала, выдавая ошибку "Runtime Error 13" лишь потому, что по локальному стандарту (например, в России, в Германии и других странах) в качестве десятичного разделителя требуется запятая. Эта проблема требовала от игроков менять настройки стандартов, чтобы указывать точку, либо убирать числа с плавающей точкой из игровых файлов. В итоге, мне пришлось реализовать обёртку, которая позволяет преобразовать числа как с точкой, так и с запятой, чтобы обеспечить полную совместимость со всеми.

Графика

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

Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Public Declare Function StretchBlt Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, ByVal dwRop As Long) As Long
Public Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Public Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Public Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Public Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Public Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Public Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Public Declare Function GetWindowDC Lib "user32.dll" (ByVal hWnd As Long) As Long
Главная форма игры и код, рисующий на ней игровую сцену из текстуры.
Главная форма игры и код, рисующий на ней игровую сцену из текстуры.

Игра позволяла работать с форматами GIF, JPEG и BMP. К несчастью, отрисовать с нормальной прозрачностью через GDI была сложная задача, Эндрю решил её методом битовой маски. То есть, простыми словами, картинка разбивается на две части: основная и маска. Основная часть это обычная картинка, но с полностью чёрным фоном.

Фронтальная часть картинки - обязательный чёрный фон, который превратится в фон через ИЛИ
Фронтальная часть картинки - обязательный чёрный фон, который превратится в фон через ИЛИ

Маска - это чёрно-белая карта, отображающая пиксели с прозрачностью и без.

Битовая маска, там где пиксели белые - прозрачность, там где чёрные - будут закрашены
Битовая маска, там где пиксели белые - прозрачность, там где чёрные - будут закрашены

Сам процесс отрисовки состоит из двух этапов:

' Отрисовка маски, используя побитовое И 
' между каждым пикселем целевой поверхности и маски.
' Белые пиксели оставят фон нетронутым, чёрные закрасят его
BitBlt targetDCSurface, X, Y, W, H, mask.hdc, 0, 0, vbSrcAnd

' Отрисовка переднего плана поверх маски на ту же поверхность,
' используя побитовое ИЛИ между каждым пикселем поверхности и маски.
' Там где чёрные пиксели, цвет не изменится, а там где цветные, будут побитого
' смешаны с фоном. Если на фоне уже нарисован чёрный силует, то передний план
' отрисуется поверх него без цветовых искажений.
BitBlt targetDCSurface, X, Y, W, H, front.hdc, 0, 0, vbSrcPaint

Такой метод отрисовки не смотря на свою простоту использования, имеет множество ограничений:

  • Отсутствует понятие полупрозрачности. Попытки изобразить полупрозрачность приводят к цветовым искажениям пикселей и артефактам.

  • Требуется предварительно подготавливать текстуру, сохраняя передний план и маску правильно.

  • В памяти находятся две части одного и того же изображения вместо одной.

Передо мной встала задача создать альтернативу для всего этого, и я применил библиотеку SDL2, которая мне полюбилась уже давно. Первым делом я создал класс-обёртку, названный "FrmMain", в честь главной формы игры. В этой "форме" я реализовал прямое взаимодействие с SDL2 по части графики, управления, событий и т.п.

Я у себя решил отказаться от концепции битовой маски в пользу альфа-канала, который полноценно поддерживался на стороне многих аппаратных графических интерфейсов, и на стороне SDL2, даже в режиме программной отрисовки. Чтобы сохранить совместимость старой графики, созданной сообществом для игры, я реализовал функцию, которая преобразует пару перед+маска в полноценную RGBA-картинку:

void bitmask_to_rgba(FIBITMAP *front, FIBITMAP *mask)
{
    unsigned int x, y, ym, img_w, img_h, mask_w, mask_h, img_pitch, mask_pitch;

    BYTE *img_bits, *mask_bits, *FPixP, *SPixP;
    RGBQUAD Npix = {0x00, 0x00, 0x00, 0xFF};   /* Цвет целевого пикселя */
    BYTE Bpix[] = {0x00, 0x0, 0x00, 0xFF};   /* Чёрный пиксель-заглушка */
    unsigned short newAlpha = 0xFF; /* Рассчитанное значение альфа-канала */

    BOOL endOfY = FALSE;

    if(!mask)
        return; /* Ничего не делать */

    img_w  = FreeImage_GetWidth(front);
    img_h  = FreeImage_GetHeight(front);
    img_pitch = FreeImage_GetPitch(front);
    mask_w = FreeImage_GetWidth(mask);
    mask_h = FreeImage_GetHeight(mask);
    mask_pitch = FreeImage_GetPitch(mask);

    img_bits  = FreeImage_GetBits(front);
    mask_bits = FreeImage_GetBits(mask);
    FPixP = img_bits;
    SPixP = mask_bits;

    ym = mask_h - 1;
    y = img_h - 1;

    while(1)
    {
        FPixP = img_bits + (img_pitch * y);
        if(!endOfY)
            SPixP = mask_bits + (mask_pitch * ym);

        for(x = 0; (x < img_w); x++)
        {
            Npix.rgbBlue = ((SPixP[FI_RGBA_BLUE] & 0x7F) | FPixP[FI_RGBA_BLUE]);
            Npix.rgbGreen = ((SPixP[FI_RGBA_GREEN] & 0x7F) | FPixP[FI_RGBA_GREEN]);
            Npix.rgbRed = ((SPixP[FI_RGBA_RED] & 0x7F) | FPixP[FI_RGBA_RED]);
            newAlpha = 255 - (((unsigned short)(SPixP[FI_RGBA_RED]) +
                               (unsigned short)(SPixP[FI_RGBA_GREEN]) +
                               (unsigned short)(SPixP[FI_RGBA_BLUE])) / 3);

            if((SPixP[FI_RGBA_RED] > 240u) // Почти белый
               && (SPixP[FI_RGBA_GREEN] > 240u)
               && (SPixP[FI_RGBA_BLUE] > 240u))
                newAlpha = 0;

            newAlpha += (((unsigned short)(FPixP[FI_RGBA_RED]) +
                          (unsigned short)(FPixP[FI_RGBA_GREEN]) +
                          (unsigned short)(FPixP[FI_RGBA_BLUE])) / 3);

            if(newAlpha > 255)
                newAlpha = 255;

            FPixP[FI_RGBA_BLUE]  = Npix.rgbBlue;
            FPixP[FI_RGBA_GREEN] = Npix.rgbGreen;
            FPixP[FI_RGBA_RED]   = Npix.rgbRed;
            FPixP[FI_RGBA_ALPHA] = (BYTE)(newAlpha);
            FPixP += 4;

            if(x >= mask_w - 1 || endOfY)
                SPixP = Bpix;
            else
                SPixP += 4;
        }

        if(ym == 0)
        {
            endOfY = TRUE;
            SPixP = Bpix;
        }
        else
            ym--;

        if(y == 0)
            break;
        y--;
    }
}

На заметку: Я использовал библиотеку FreeImage (а конкретно FreeImageLite, мой усечённый форк) для загрузки и предварительной обработки графики у себя.

Отдельная проблема у игры касалась её чрезвычайно долгой загрузки из-за обилия графических ресурсов. Чтобы решить эту проблему, я реализовал систему ленивой распаковки. То есть, я загружаю картинки с диска, но, я их не декодирую до тех пор, пока графический движок не запросит их отрисовку. Такая концепция позволила мне загружать игру почти мгновенно (в зависимости от мощности компьютера), а также значительно уменьшить потребление оперативной памяти с почти 600 мегабайт до 80~120 мегабайт. Однако, у этой концепции есть и недостаток: если диск или графический интерфейс недостаточно быстрые, игра будет слегка притормаживать при подгрузке каждой отдельной текстуры, впервые отрисовывающейся на экране, чего особенно хорошо заметно в главном меню, где воспроизводится демка с участием пятерых игровых персонажей, часто сменяющих свои состояния.

Саму отрисовку я делаю с использованием SDL_Renderer, сделав конечный результат проще и гибче. SDL2 поддерживает множество интерфейсов графических ускорителей: и OpenGL, и DirectX, и Metal. Также сохраняется поддержка программной отрисовки в случае, если невозможно задействовать один из аппаратных методов отрисовки.

Звук и музыка

Игра использовала для воспроизведения музыки и звука старинный интерфейс MCI, существующий ещё со времён Windows 3.1.

Public Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpstrCommand As String, ByVal lpstrReturnString As String, ByVal uReturnLength As Integer, ByVal hwndCallback As Integer) As Integer

Этот интерфейс позволял с помощью одной функции посылать текстовые команды системе для открытия аудио-файлов и управления ими: воспроизведение, приостановка, повтор, громкость, и т.п. Самый большой недостаток подобного интерфейса заключался в том, что требовалось доустанавливать в систему дополнительные пакеты кодеков. Из-за того, что игра открывает десятки аудиофайлов через MCI, во время работы игры, системный трей засорялся значками FFDshow и подобными (если не было отключено в настройках соответствующих кодеков). Также сам процесс подобной загрузки был чрезвычайно долгим. По факту, через интерфейс работали форматы MP3, WAV, WMA, а также криво-косо MIDI.

Системный лоток, засорённый значками кодека Lav
Системный лоток, засорённый значками кодека Lav

Я решил избавиться от такого недоразумения как MCI ещё 5 лет назад. Мною, на базе хакерского расширения LunaLua, было реализовано использование библиотеки SDL2_mixer (а затем форка SDL Mixer X), которая работала с музыкой и звуковыми эффектами намного лучше, а также совершенно не требовала установки каких либо внешних кодеков, поскольку все нужные библиотеки уже были включены в её состав. Вместе с этим была добавлена поддержка большого числа новых форматов: и OGG Vorbis, и FLAC, и огромного числа форматов трекерной музыки, и улучшенная поддерка MIDI. Также появилась возможность играть дублирующие звуковые эффекты параллельно.

Файловая система

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

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

Клавиатура, мышь, геймпады

Для работы с клавиатурой, в игре использовались функции WinAPI на базе Virtual Key. Из-за этого возникали проблемы при переключении раскладок, а также возникали трудности на клавиатурах QWERTZ и AZERTY, популярных в Германии и Франции. Чтобы решить все эти проблемы, я применил возможности библиотеки SDL2, переориентировав игровое управление на сканкоды. Они привязаны к физическим клавишам и абсолютно не зависимы от текущей раскладки.

Для поддержки геймпадов в игре использовались возможности библиотеки WinMM:

Public Declare Function joyGetPosEx Lib "winmm.dll" (ByVal uJoyID As Integer, pji As JOYINFOEX) As Integer
Public Declare Function joyGetDevCapsA Lib "winmm.dll" (ByVal uJoyID As Integer, pjc As JOYCAPS, ByVal cjc As Integer) As Integer
Public Type JOYCAPS
    wMid As Long
    wPid As Long
    szPname As String * 32
    wXmin As Long
    wXmax As Long
    wYmin As Long
    wYmax As Long
    wZmin As Long
    wZmax As Long
    wNumButtons As Long
    wPeriodMin As Long
    wPeriodMax As Long
    wRmin As Long
    wRmax As Long
    wUmin As Long
    wUmax As Long
    wVmin As Long
    wVmax As Long
    wCaps As Long
    wMaxAxes As Long
    wNumAxes As Long
    wMaxButtons As Long
    szRegKey As String * 32
    szOEMVxD As String * 260
End Type
Public Type JOYINFOEX
    dwSize As Long
    dwFlags As Long
    dwXpos As Long
    dwYpos As Long
    dwZpos As Long
    dwRpos As Long
    dwUpos As Long
    dwVpos As Long
    dwButtons As Long
    dwButtonNumber As Long
    dwPOV As Long
    dwReserved1 As Long
    dwReserved2 As Long
End Type
Public JoyNum As Long
Public MYJOYEX As JOYINFOEX
Public MYJOYCAPS As JOYCAPS
Public CenterX(0 To 7) As Long
Public CenterY(0 To 7) As Long
Public JoyButtons(-15 To 15) As Boolean
Public CurrentJoyX As Long
Public CurrentJoyY As Long
Public CurrentJoyPOV As Long

К счастью, в библиотеке SDL2 есть значительно более мощные подсистемы SDL Joystick и SDL GameController, которые позволяют использовать тысячи различных моделей геймпадов через всевозможные интерфейсы, работает и на Linux, и на macOS, и на Android. Также поддерживается горячее подключение и отключение игровых контроллеров прямо во время игры.

Что касается мыши, использовались стандартные события главной формы Form_MouseDown, Form_MouseMove и Form_MouseUp с последующей записью состояния кнопок и координат указателя в глобальные переменные, используемые в коде самой игры. В обновлённой игре, я делаю аналогичное, но через SDL_PollEvent().

Итог

В результате проделанной работы, в течении первых двух-трёх недель после выпуска исходных кодов игры, получилась полноценная и кроссплатформенная реплика игры, крайне идентичная оригиналу (настолько, что люди до сих пор находят в ней баги 10-летней давности). Однако, вместе со старыми багами, местами появились и новые, в результате ошибок, допущенных при преобразовании кода или из-за упущенных различий в работе VB6 и C++, либо из-за опечаток. Большая часть подобных ошибок уже была исправлена мною в течении месяца, и после, в марте 2020го года я представил обновлённую и стабильную игру вместе со всеми исходными кодами. Далее, в течении последнего года, дорабатывал игру, исправляя баги и улучшая функционал.

Проект движка я назвал TheXTech по принципу: "The Super Mario Bros. X Tech".

Главное меню игры Super Mario Bros. X на базе TheXTech 1.3.5.2
Главное меню игры Super Mario Bros. X на базе TheXTech 1.3.5.2

В итоге, игра, изначально созданная жёстко под Windows на платформозависимом языке и на процессорах x86, превратилась в кроссплатформенную игру, которая работает не только на других операционных системах (Windows, Linux, macOS, Haiku, Android, Emscripten), но и на других процессорах (ARM, PowerPC, MIPS, и др.). Вместе с этим, игра наконец получила полноценную поддержку 64-битных процессоров и ARM-архитектуры. Энтузиасты также портируют игру на консоли (уже есть примеры, реализованные на 3DS и PS Vita).

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

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

Материалы

Исходный код оригинальной игры как есть

Мой полигон, мод оригинала

Репозиторий с кодом созданной игры

Adblock test (Why?)

Майкл Делл рассказал, что Джобс хотел предложить Dell лицензировать софт Apple

Основатель и руководитель компании Dell Майкл Делл рассказал, что Стив Джобс хотел предложить Dell лицензировать софт Apple, а именно macOS. Деллу предложение понравилось, но он с Джобсом не смог договориться по деталям.
Деллу было выгоднее, чтобы Dell платила роялти Apple за каждый проданный ПК с macOS. Джобса такая схема не устроила, так как она бы уменьшила прямые продажи самой Apple. Джобс пытался обосновать свою идею о том, чтобы Dell устанавливала его операционную систему на свои компьютеры в качестве альтернативы Windows, включая поставку системного блока или ноутбука пользователю сразу с двумя ОС на выбор (Windows и macOS). В этом случае Dell платила бы Apple за каждый проданный ПК без учета выбора пользователя.

В то время сделка не состоялась. Dell решила, что предложение Джобса выглядит не привлекательно, так как тогда macOS была не особо популярна. Dell опасалась, что плата за лицензию Apple не принесет никакого дохода. Компания была уверена, что пользователи просто будут выбирать варианты ПК с Windows. Также Джобс не смог гарантировать техподдержку для компьютеров Dell на macOS через несколько лет, что могло быть проблемой для пользователей.

Примечательно, что Джобс в 1993 году обращался в Dell с аналогичным предложением, но тогда он пытался продать лицензию на операционную систему NeXTSTEP на ПК с процессорами Intel. Dell не приняла доводы Джобса и отказалась от сделки.

Adblock test (Why?)

Анализ статей Хабрахабр

Решила повторить исследование, сделанное в 2017 году и посмотреть, что изменилось за 5 лет. Ссылка на предыдущую статью Анализ статей Хабрахабр и Geektimes. Дизайн сайта изменился, поэтому делала все в jupyter python, а не wolfram mathematica. Далеко не все графики удалось воспроизвести заново. Получился анализ более чем 260000 статей.

Результаты обработки данных

Анализ хабов

Распределение количества хабов, в которых размещена статья:

1 132354
2 47260
3 38671
5 25265
4 23349
NaN 54

Статей, в которых 5 хабов, стало больше, чем статей, в которых 4 хаба.

Самые большие хабы по количеству статей:

Чулан 29217.0
Программирование 14972.0
Информационная безопасность 14417.0
Разработка веб-сайтов 13796.0
IT-компании 11741.0
Научно-популярное 10972.0
JavaScript 7864.0
Гаджеты 7334.0
Системное администрирование 5706.0
Компьютерное железо 5685.0
Я пиарюсь 5637.0
Разработка игр 5465.0
Разработка мобильных приложений 5318.0
DIY или Сделай сам 5295.0
Open source 5223.0
Разработка под Android 5150.0
IT-инфраструктура 5076.0
Законодательство в IT 5073.0
Карьера в IT-индустрии 5015.0
Python 4885.0

В хабе "программирование" стало больше статей, чем в хабе Разработка веб-сайтов.

Если рассмотреть только уникальные статьи (относящиеся только к одному хабу):

Чулан 29125
IT-компании 5652
Я пиарюсь 5573 Информационная безопасность 4244
Разработка веб-сайтов 2753
Настройка Linux 2117
DIY или Сделай сам 1821
Разработка под Android 1820
Социальные сети и сообщества 1790 Компьютерное железо 1780
Гаджеты 1657
Habr 1647 Законодательство в IT 1521 Программирование 1131
PHP 1064
Java 1048
Разработка игр 1006
Научно-популярное 984
Управление проектами 981
JavaScript 979

Здесь все осталось примерно также.

Количество статей в зависимости от времени

Количество постов за месяц:

За год:

К 2018 количество статей уменьшилось, но затем снова возросло.

В хабе «Математика» за месяц:

В хабе «Математика» за год:

За исключением 2016 года количество статей росло.

Хаб «Космонавтика» за месяц и за год:

К 2021 году количество статей достигло уровня 2015 года.

Хаб «Хабрахабр» превратился в хаб «Habr»:

В 2019 году количество статей немного возросло, но по-прежнему уменьшается.

Облака ключевых слов и отдельных хабов

Здесь ничего не изменилось. Текст выглядит по-другому, так как использовала wordcloud python.

Хаб «Математика»:

Хаб «Программирование»:

Хаб «Java»:

Хаб «Open source»:

Хаб «Машинное обучение»:

Частота встречаемости слов

Здесь не хватило ресурсов для подсчета всех словосочетаний. Можно увидеть как появляются новые термины на примере слова "ковид". В 2013 году термин есть потому, что автор дополнил статью в 2020 году.

Средний и суммарный рейтинг по годам и месяцам

Средний рейтинг по годам:

Суммарный рейтинг по годам:

Средний рейтинг по месяцам:

Суммарный рейтинг по месяцам:

Посты с максимальным количеством

Комментариев: Судьба предателя, угнавшего новейший МиГ-25 в Японию

Рейтингом: Делаем приватный монитор из старого LCD монитора

Количеством плюсов: Делаем приватный монитор из старого LCD монитора

Количеством минусов: Первый пост

Добавлением в закладки: 300 потрясающих бесплатных сервисов

Adblock test (Why?)

Несколько технических вопросов к ДЭГ

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

Главный редактор эХО Москвы, Венедиктов, который упорно проталкивал ЭГ, и утверждает, что всё честно, и никто его ни в чём, обратном, не убедил.

Вопрос технический и интересный. И поэтому, я изучил Что же не так с ДЭГ в Москве? от Жижина. Зашёл на https://observer.mos.ru/all/ и скачал дампы базы данных, которые лежат там, PostgreSQL server и попробовал разобраться, что именно не так.

Техническая сторона, первое впечатление

Для изучения, я выбрал БД в которой информация о "Выборах депутатов Государственной Думы Федерального Собрания Российской Федерации восьмого созыва по одномандатному избирательному округу.", файл: observer-20210921_143000.sql

Всё очень плохо.

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

Первая и главная претензия всех, кто прикасался к этой БД в том, что нет связи с Зарегистрированным для голосования человеком и всеми последующими действиями с бюллетенями.

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

Как это происходит и почему?

Это происходит потому, что БД на https://observer.mos.ru/all/ создавалась не для работы, а именно для целей наблюдения. И была сделана абы как. Нельзя утверждать, что это было сделано специально, чтобы сделать невозможной проверку голосования. Потому, что если это просто "какая-то" база для наблюдателей, то с ней никто особо возиться не стал, и тем более проверять и тестировать, а потому и сделали криво и косо.

Техническая сторона. Знакомство с БД

  1. таблица "blocks" - здесь эта таблица по существу бесполезна, в ней мог бы быть смысл, если бы предполагалась сверка с живым блокчейном. Но для любых проверок и изучений, от неё нет никакой пользы, поскольку она содержит только ссылки на транзакции, которые лежат в таблице "transactions", а в ней самой, уже содержатся данные о блоке. Единственное, что можно было бы сверить, действительно ли, все транзакции содержащиеся в блоке, есть в таблице транзакций.

  2. таблица "transactions" - главная таблица, где есть всё (что туда положили), а мы, из других материалов изучения этих данных уже знаем, что туда положили далеко не всё. Поле method_id, содержит код назначения этой транзакции, (описание взял у Жижина) важные для нас: 1 - регистрация избирателей, 4 - выдача бюллетеня, 5 - проверка доступа голосующего, 6 - приём бюллетеня (с зашифрованным голосом), 9 - расшифровка голоса

  3. таблица: "decrypted_ballots", содержит хэши транзакций с id 6 и id 9, и результат голосования в расшифрованном виде

Техническая сторона. Изучение данных

  • таблица "blocks", как оказалось, содержит множество пустых блоков. Создаётся впечатление, что система, зачем-то постоянно создаёт блоки, без особой зависимости от того, есть данные или нет. Как будто они создают некий time lapse, подтверждающий, что всё работает, просто операций нет. Теоретически, в этом нет ничего плохого, но и хорошего тоже нет. На графике, это выглядит это примерно так:

  • таблица "transactions" - главная таблица, где есть всё (что туда положили), а мы, из других материалов изучения этих данных уже знаем, что туда положили далеко не всё.

    • И тут возникает проблема, транзакции c method_id 1 и 4, содержат "voter_id" в виде "22443685531066461899103156899237857105006237160299631129744914447305194856993", а транзакции c method_id 5 и 6, содержат связанные поля voter_key и author в виде хеша "78cc1e391507d9ef8bcddfbf72d1cbde95c1f36f84940a5b8be4e8fa0542096a"Таким образом, связать избирателя с его бюллетенем невозможно. Есть вероятность, что voter_key это зашифрованный voter_id, но это не подтверждается, поскольку, если предполагать, что у нас в БД есть переголосования, то будут дубликаты voter_key в списке транзакций 5 и 6 типа.

    • Поиск двойных транзакций, дал результат для method_id = 4, но они не относятся к голосованию, а содержат записи об ошибках, которые произошли в одно и то же время. Всего, в базе есть 34294 избирателя у которого были записи об ошибках, и судя по времени этих ошибок (почти без интервала между ними), они носят чисто технический характер, запись в поле "status" для них выглядит так "{"type":"service_error","description":"Ballot cannot be issued","code":5,"runtime_id":0,"call_site":{"instance_id":1001,"call_type":"method","method_id":4}}". Бюллетень в результате выдаётся 1.

    • Итого, для method_id = 5 и 6, двойных записей нет, при проверке записей, где есть дубликаты voter_id, показали, что таких voter_id, в базе 34294, и всего записей, 78077. Итого, что получается?

      • method_id = 4 = 1987373 - 78077 + 34294 = 1943590 выдано бюллетеней, что соответствует данным на обсервере.

      • method_id = 5 = 2021969 записей о подтверждении

      • method_id = 6 = 2021969 записей о голосовании,

      Как такое может быть? Переголосования? но для него тоже выдаётся бюллетень (должен выдаваться, иначе как? О_о).
      Итого: 2021969 - 1943590 = 78379 лишних бюллетеней! это что вообще такое? Вбросы? но как проверить, связи с избирателями нет, просто бюллетени, которые лежат сверх тех, что были "напечатаны типографией". И этот вопрос ужа за гранью переголосований и т.п.

    • method_id 9 - расшифровка голоса, таких записей в таблице "transactions" - 1319943, что значительно меньше выданных бюллетеней, они содержат информацию о расшифровке голоса в бюллетене, и эти данные содержатся в следующей таблице: "decrypted_ballots". Почему их так мало, вопрос отдельный. Слышал такое объяснение, что данные о переголосованиях, лежат в отдельном секретном блокчейне, и вот с учётом сверки с ним, и были посчитаны голоса.

  • таблица: "decrypted_ballots", содержит 1318977 записей, что на 966 меньше чем в транзакциях о расшифровке!, Такого быть не должно в принципе! Записи попросту пропали, вдруг. Транзакции о расшифровке есть, а самой расшифровки нет. Конечно же, их можно расшифровать, но стоп. Зачем нам это делать? Есть программа, блокчейн, БД, которые должны работать. Но получается, что в их работе содержатся серьёзные проблемы, которые приводят к тому, что попросту пропадают данные.

  • на графике видно, что динамика, действительно похожа на переголосования, но возникает вопрос, как переголосование могло быть в районе 23 часов 2021.09.19? 236 голоса, сделанные в период с 19 до 20 часов, не были посчитаны. А мы помним, что переголосовать можно только через 3 часа после голосования. В то время, как последний блок был создан в 2021-09-19 21:19:07.902+03. Если это переголосования, а система должна блокировать возможность на 3 часа, тогда, это могло быть сделано только специальным ботом, который не учёл эту особенность.

  • Возникла идея, сравнить голоса расшифрованные и нерасшифрованные, (которые вероятно отменили по вероятному переголосованию):

  • Получается лютый треш. Последние нерасшифрованные 236 голоса, просто не были учтены, в этот час и после него, просто не было новых. Поэтому, необходим точный и ясный ответ. Являются ли голоса в таблице "decrypted_ballots" полными и корректными?

    • И если да, тогда что это за такое странное расхождение в переголосованиях? то 0%, то 100%, что с этими голосами не так? Куда пропадают часть голосов и по какой причине?

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

Почему я не стал делать статистику?

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

Уже тех фатов того, что:

  • Бюллетеней учтено больше чем было выдано - провал.

  • Пропали часть расшифрованных бюллетеней - провал.

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

  • Понять почему расшифрованы только 65,23% - невозможно - провал.

  • Если эти данные, которые нам дали не корректны, то дайте корректные, они не могут их предоставить - провал.

Достаточно, чтобы признать систему нерабочей и отменить результаты ЭГ полностью.

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

Вопрос чем поможет статистика, если организаторы не признают прямых и очевидных ошибок?

Некоторые выводы

  1. Как вообще интерпретировать такие соотношения, неизвестно. Факт в том, что у наблюдателей, по сути нет корректного материала для наблюдения и проверок.

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

  3. Если от нас скрывают некоторые данные, то возникает вопрос - почему? Блокчейны с деньгами релат в свободном доступе, а от нас скрывают блокчейн с голосованием. Если у этого есть цель, то какая? Варианты, которые приходят в голову:

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

    2. Скрывают реальные данные, поскольку они отличаются от тех итогов, которые нам предлагают как результаты голосования.

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

    4. Их секретный блокчейн содержит реальные данные избирателей, кто и как проголосовал. Увы, но система не может быть анонимной по определению. Регистрация на госсайтах не анонимная. СМС отправляются реальным людям. Поэтому, нам врут, что всё анонимно, а на самом деле нет.

    5. Любые комбинации на выбор.

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

Adblock test (Why?)

Google вернула приложение «Навальный» в российский магазин Google Play

image

9 октября 2021 года Google вернула приложение «Навальный» в российский магазин Google Play после нескольких петиций против его удаления от разработчиков. Ранее оно было заблокировано для пользователей из России по требования Роскомнадзора.
Google не прокомментировала факт разблокировки доступа к приложению.

Apple продолжает блокировать это приложение в App Store.

image
Приложения нет в App Store. При попытке обновить уже установленное приложение «Навальный» на iPhone появляется сообщение, что разработчик удалил его из AppStore.

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

Утром 17 сентября 2021 года Apple и Google удалили приложение «Навальный» из магазинов приложений App Store и Google Play.

Apple пояснила, что сделала то же самое в рамках соблюдения российских законов по запросу Роскомнадзора и российских правоохранительных органов. Компания не рассказала, почему отключила функцию «Частный узел» (iCloud+ Private Relay) в iOS 15 и macOS 12 для российских пользователей. Эта опция скрывает IP-адрес устройства и пользовательскую активность для обеспечения безопасности.

Google пояснила, то удалила из российского магазина приложений Google Play запрещенное в РФ приложение из-за угроз уголовного преследования работающих в России сотрудников компании.

16 сентября представитель Совфеда пригрозил уголовным преследованием работникам российских офисов Apple и Google за несоблюдение законов.

2 сентября СМИ сообщили, что Роскомнадзор потребовал от Apple и Google удалить из магазинов приложений App Store и Google Play мобильное приложение «Навальный». Надзорное ведомство предупредило, что отказ IT-компаний в сотрудничестве по этому запросу будет расцениваться регулятором как вмешательство в проходящие в Российской Федерации выборы. В этом случае просто миллионными штрафами компании могут не обойтись. За участие в деятельности экстремистских организаций предусмотрена уголовная ответственность, согласно ст. 282.2 УК РФ.

Adblock test (Why?)

Rockstar Games официально представила Grand Theft Auto: The Trilogy

В честь двадцатилетия Grand Theft Auto III американский издатель Rockstar Games анонсировал выход сборника обновленных версий игр серии. Помимо GTA III в трилогию войдут Grand Theft Auto: Vice City и Grand Theft Auto: San Andreas.

Три оригинальные игры вышли в первой половине двухтысячных годов. Они входят в пятерку самых продаваемых игр на PlayStation 2. Проекты были временными эксклюзивами для данной консоли, а уже потом их портировали на другие платформы.

Rockstar Games в своем анонсе рассказала, что GTA III вдохновила целое поколение игр с открытым миром, а последующие GTA: Vice City и GTA: San Andreas закрепили за серией статус культурного феномена.

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

Grand Theft Auto: The Trilogy — The Definitive Edition выйдет на PlayStation 4, PlayStation 5, Xbox Series X|S, Xbox One, Nintendo Switch, а также в Rockstar Star Launcher на ПК. Сборник поступит в продажу до конца года.

Компания уберет старые версии игр из цифровых магазинов на следующей неделе, а также она объявила, что Grand Theft Auto: The Trilogy — The Definitive Edition появится на Android и iOS в первой половине 2022 года. 

Издатель Take-Two Interactive, владеющий Rockstar Games, в сентябре подал в суд на нескольких разработчиков фанатского проекта реверс-инжиниринга GTA. Истцы считают, что пользовательские модификации содержат исходный цифровой контент, который принадлежит издателю. Компания требует компенсацию, не превышающую $ 150 тыс. за каждую игру. 

Ранее Take-Two потребовала удалить девять модификаций для GTA с российского фанатского сайта LibertyCity. Все эти дополнения, так или иначе, переносили место действия игр Rockstar Games в другие проекты компании. 

Игровой портал Kotaku рассказал, что компания работает над обновленной трилогией GTA в августе. По словам издания, проекты будут использовать игровой движок Unreal Engine. 

Adblock test (Why?)

Конвейер Volkswagen в Калуге встал из-за нехватки полупроводников

Дефицит микросхем привел к остановке работы завода Volkswagen в Калужской области, который производит Skoda Rapid, Volkswagen Polo и Tiguan. Аналитики прогнозируют, что прекращение работы предприятия будет недолгим, однако цены на автомобили все-равно вырастут из-за текущего простоя.

Предприятие, которое уже несколько дней не работает, не возобновит производство и на следующей неделе, рассказали источники издания Газета.Ru. Завод будет определять производственный план исходя из поставок необходимых деталей. В связи с этим Volkswagen Group Rus вынуждена уменьшить объем производства в ближайшие месяцы.

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

В сентябре текущего года Volkswagen продала на 28 % меньше автомобилей, чем за тот же период в прошлом году — 16 тыс. Российское производство компании сильно зависит от импортных компонентов, поскольку у производимых здесь машин низкий уровень локализации, говорит независимый консультант по автопрому Сергей Бургазлиев. Он пояснил, что приостановка работы завода логичный шаг, так как для компании нерентабельно производить изделия, которые не полностью закончены.

Бургазлиев прогнозирует перебои в работе других автоконцернов из-за дефицита комплектующих. Критической точкой эксперт называет конец года, когда у компаний закончатся запасы микросхем. В частности, он отмечает, что с данной проблемой столкнется Renault-Nissan, который уже несколько раз останавливал производство в 2021 году.

Однако дефицит полупроводников в меньшей степени коснется корейских и китайских автопроизводителей в России, поскольку им удалось наладить поставки важных деталей, объясняет Бургазлиев. По его мнению, приостановившие производство компании уступят долю рынка азиатским концернам.

Простой предприятия увеличит срок поставок Skoda и Volkswagen, который в настоящее время уже и так составляет полтора-два месяца, заверяет партнер аналитического агентства «Автостат» Игорь Моржаретто. Этой ситуацией пользуются автодилеры, которые предлагают потребителю машины с дополнительными опциями. В сумме эти приложения могут составлять почти треть от стоимости приобретаемого автомобиля, резюмирует аналитик.

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

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

Концерн Ford Motors был вынужден сократить выпуск автомобилей на шести заводах в Северной Америке из-за перебоев с поставками полупроводников в конце марта.

Ассоциация европейского бизнеса сообщила, что нехватка чипов сократила общие продажи машин в августе на 17 % по сравнению с тем же месяцем в 2020 году. Всего концернам удалось продать за этот период более 114 тыс. автомобилей, что на 23 тыс. меньше прошлогодних показателей.

Аналитики компании Counterpoint Research заявили, что в ближайшее время поставки полупроводников на рынок не вырастут. 

Adblock test (Why?)

Углубляем озеро в 100 раз

Автор: Даниил Ли

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

Итак, дано: небольшое пресноводное озеро, не озеро даже – скорее огромная лужа, глубиной три с мелочью метра. Вопрос: как без смс и регистрации утопить там две буровых установки, одиннадцать(!) барж и буксир? Причём без читерства, типа штормов, ураганов и прочих бедствий? Интересно? Сейчас я вам всё расскажу.

Осенью далёкого 1980 года на озере Пенёр, что в штате Луизиана, компания Wilson Brothers Corporation искала нефть по заказу компании Texaco. На дворе конец ноября, погода ясная, установка исправная, буровики опытные, ну что может пойти не так? Поэтому, когда утром двадцатого бур заклинило на глубине трёхсот семидесяти метров, никто не мог и предположить, во что это в итоге выльется.

примерный вид объекта SCP-1980-P "Пенёр". Различные пояснительные схемы и видео с объекта приложены в конце
примерный вид объекта SCP-1980-P "Пенёр". Различные пояснительные схемы и видео с объекта приложены в конце

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

Впрочем, изумление буровиков не шло ни в какое сравнение с эмоциями, которые испытали шахтёры компании Diamond Crystal Salt Company, добывавшие соль в шахте четырьмя сотнями метров ниже. Когда шахта начала ВНЕЗАПНО наполняться водой, на пятьдесят пять человек имелся один неспешный восьмиместный(!) лифт. Полагаю, отложенных ими кирпичей хватило бы полностью восстановить Новый Орлеан после Катрины, если бы, конечно, кто-нибудь заморочился поднимать эти стройматериалы со дна затопленной шахты. Последняя партия шахтёров уже успела проститься с жизнью, ожидая лифт по пояс в воде, но пронесло: на поверхность выбрались все.

Один из коридоров соляной шахты
Один из коридоров соляной шахты

Вернёмся на живописное озеро Пенёр. К этому моменту градус его живописности возрос до непередаваемых значений: озеро лихо косплеило американский унитаз (кто бывал в Штатах, тот в курсе, чем они отличаются от наших). На поверхности воды образовался огромный – метров пятьдесят – водоворот, засосавший две буровые установки, буксир, 11 барж, док, остров с искусственным садом, 26 гектаров берега, дома, машины... Из шахты вверх бил лихой стодвадцатиметровый водяной гейзер. За три часа из озера ушло больше 13 миллионов кубометров озёрной воды, на место которой прибывала морская вода из Мексиканского залива, поступавшая по каналу Делькамбре, и красиво обрушивавшаяся в озеро полусотметровым водопадом.

фото водопада имени AAPG (американской ассоциации геологов-нефтяников)
фото водопада имени AAPG (американской ассоциации геологов-нефтяников)

С тех пор Пенёр из унылой пресноводной лужи превратился в солёное озеро глубиной под четыре сотни метров. Спонсорами шоу выступили Wilson Brothers на пару с Texaco, которым пришлось выплатить 32 миллиона Diamond Crystal Salt за шахту и почти тринадцать миллионов местным за загубленное озеро.

Картинки почему-то под спойлер не убираются, поэтому допматериалы прикладываю так (видео последним):

Место действия. Залив внизу, озеро в центре. Канал между ними сменил направление движения воды)
Место действия. Залив внизу, озеро в центре. Канал между ними сменил направление движения воды)
Схема и расположение шахты
Схема и расположение шахты
Карта глубин нынешнего озера
Карта глубин нынешнего озера

Автор: Даниил Ли

Оригинал

Adblock test (Why?)

Как я прошел путь от игрушек на Objective-C к реальным проектам на Swift в большой компании

Привет, Хабр! Когда я был ещё совсем мал и только тянулся к разработке, я тоже почитывал Хабр и не раз видел статьи, где описывался путь с нуля до оффера. Иногда это были и вовсе удивительные истории о переходе в новую профессию, читая которые словно проживаешь жизнь другого человека.

Но теперь и мне есть о чём рассказать. Сразу скажу, что этот пост вряд ли чем-то удивит людей с опытом 3+ лет, но надеюсь, мой путь поможет другим людям, которые, как и я когда-то, только смотрят в сторону программирования. Ведь все мы, когда-то были начинающими. И так, меня зовут Александр Рубцов, я iOS-разработчик и это история моего пути в разработку…

Как я пришел к программированию

Для полной картины стоит начать с самого начала. В школьные годы мне попалась книжка-самоучитель от Герберта Шилдта по С++. По какой-то причине она увлекла меня до такой степени, что я пропал для своих друзей на год. Результатом такого увлечения, как и у многих, стала игра, написанная при помощи SFML библиотеки. Кому интересно, можно посмотреть на сей «шедевр» здесь.

Через некоторое время, уже в колледже, ко мне в руки попал макбук в виде подарка на день рождения. Первым же делом я решил изучить Objective-C (на дворе шёл 2014 год). Я пошёл по той же схеме - самоучитель и тотальная увлечённость. Немного разобравшись, я открыл для себя SpriteKit.

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

Естественно, ужасно хотелось выложить её в AppStore, ведь что может быть круче для начинающего разработчика, чем своя работа, которую любой может скачать? Однако, на тот момент мне не было 18, в связи с чем я не мог получить лицензию разработчика от Apple, по которой можно выкладывать приложения в стор. Пришлось оформлять свою маму как разработчика, чтобы получить эту возможность.

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

К сожалению, по окончании колледжа это беззаботное время кончилось, так как нужно было готовиться и поступать в вуз. Так как с будущей профессией я до конца не определился, поступил в 2 вуза. В один на прикладную информатику, на очное, и во второй на графический дизайн, на очно-заочное. Помимо этого, нужно было готовиться к поступлению в магистратуру, в зарубежный университет (NewCastle Британия), а именно, выучить английский до IELTS 6.5 с 0 уровня за полтора года. К огромному сожалению, возможности заниматься разработкой по работе или, уж тем более для души, у меня не было вообще. 

Ключевые этапы подготовки. По каким материалам я готовился

Но вот наступает 2021 год – последний курс, пора задуматься, чем я буду заниматься после вуза. Вспоминаю про те славные времена, когда я писал на Objective-C, и решаю, что нужно продолжить, так как это область, в которой я больше всего разбирался (как я, наивный, тогда думал).

Пока я учился, в Apple уже перешли на новый язык и я решаю заняться разработкой под iOS платформу всерьёз. И тут мне помогла пандемия (как бы странно это ни звучало). Ходить в 2 вуза, на 2 дипломные практики и бешено учить английский практически не выходя из дома оказалось гораздо легче, чем в оффлайне, и даже остаётся свободное время.

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

Такая куча задач может сильно выбить из колеи, но при должном планировании времени всё возможно. Для каждого дела я завожу либо список, либо kanban-доску, а ключевые задачи и время их выполнения заношу в ежедневник.

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

Моя kanban-доска по дипломам.
Моя kanban-доска по дипломам.

Для подготовки к работе iOS-разработчиком я сделал целую дорожную карту, на которой по ходу изучения необходимых тем отмечал прогресс.

Мой роадмап без отметок.
Мой роадмап без отметок.

По ссылке доступна большая картинка. Жёлтым выделены темы, которые крайне желательно знать iOS-разработчику. Большинство тем взято из этого старого проекта, а другие я нашёл сам. Помимо этого, я завёл репозиторий, в который закидывал (и до сих пор закидываю) ссылки на материалы для изучения каждой из этих тем.

Такой штукой я хотел убить сразу 3 зайцев. 

  • Во-первых, сделать полезный ресурс для таких же, как я. 

  • Во-вторых, чтобы был козырь на собеседованиях.

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

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

Промежуточный результат изучения тем.
Промежуточный результат изучения тем.

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

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

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

Так вышло, что изучение свифта выпало как раз на тот период, когда мне надо было особенно усердно готовиться к сдаче экзамена IELTS. На тот момент я стабильно набирал 5.5 балла, что для меня было недопустимо.

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

Остаться или уехать

Оба диплома с потом и кровью (в основном) написаны, в это же время я получаю сертификат IELTS 6.5 с необходимым баллом для поступления, и уже получаю приглашение от университета.

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

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

И вот, неожиданно по ходу прохождения одного из собеседований мне написал HR от MTS Digital. На самом деле я даже и не думал подавать резюме в крупные компании, потому что, видимо, не был уверен в своих силах. В итоге я прошёл собеседование и меня взяли!

Что самое важное в работе

Мне очень важно, чтобы время, которое я провёл на работе, не было потрачено для меня лично впустую. Есть что-то важнее, чем просто зарплата, и это навыки, которые можно применить вне работы.

Получение опыта

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

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

Атмосфера

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

Тайминг

Вся моя работа происходит удалённо, что, на мой взгляд, очень круто. Благодаря этому, я экономлю около 3 часов на дорогу. Из-за того, что рабочий день с учётом дороги составляет не 11 часов, а реальные 8, порой складывается впечатление, что он слишком короткий, от чего можно с большей охотой остаться после окончания рабочего дня и уделить больше внимания на текущую задачу.

Разнообразие задач

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

Если вы подумываете войти в эту профессию, то стоит помнить, что она для тех, кто способен с 8 утра и до часу ночи сидеть над неуловимым багом, лишь бы лечь спать с чувством выполненного долга. Не у каждого есть такое терпение и усидчивость. В связи с таким образом жизни, нужно следить за своим здоровьем и заниматься физкультурой, чтобы не было проблем со спиной. Берегите себя и спасибо, что прочитали мой пост!

Adblock test (Why?)

Как это сделано: Оптика для EUV/BEUV литографии

Прохождение излучения в EUV степпере
Прохождение излучения в EUV степпере

Часто в обсуждениях, посвященных внедрению все более мелких техпроцессов изготовленя СБИС, всплывает тема рентгеновской литографии. Тема довольно сложная, и запутанная, особенно если обсуждать вопрос "кто кого родил - Cymer или ASML. Но этот пост совершенно не про историю.

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

Что за рентгеновская оптика?

Рентгеновская оптика – это одна из современных ключевых технологий, жизненно важная для многих инженерных и научных областей, таких как рентгено-флуорисцентный анализ, космическая астрономия, лазеры на свободных электронах, и т.п.  Наиболее важной областью применения рентгеновской оптики стало новое поколение литографии, использующее длину волны 13.5 нм. Формально, такое излучение относится к мягкому рентгену, но больше известно под «коммерческим именем» Extreme Ultraviolet (EUV).

Уменьшение норм техпроцесса с развитием литографии
Уменьшение норм техпроцесса с развитием литографии

С развитием микроэлектроники, нормы техпроцесса, используемого для ее производства, продолжают уменьшаться. В настоящее время наиболее широко используется глубокий ультрафиолет (Deep Ultraviolet (DUV)) с длиной волны 193 нм. Это оборудование относится к предыдущему поколению.  Новое поколение уже использует EUV. Поскольку природа взаимодействие мягкого рентгеновского излучения с веществом сильно отличается от таковой для ультрафиолета, переход на новую длину волны требует буквально «все переделать». В частности, особенности взаимодействия мягких рентгеновских лучей с веществом требуют использования отражающей оптики.

Схема EUV степпера
Схема EUV степпера

Рисунок выше иллюстрирует оптическую систему типичного EUV степпера, используемого в литографии.  Как видно, он содержит не менее 5 зеркал в подсветке оптики и 6 зеркал в разделах проекционной оптики. Общая пропускная способность такой оптической системы примерно в сто раз ниже, чем отражательная способность одноразового зеркала из-за множественного отражения. Увеличение коэффициента отражения (КО) каждого зеркала с 53 до 65% увеличит интегральную пропускную способность системы примерно в 7 раз. Таким образом, даже небольшое улучшение отражающей способности дает немедленное повышение эффективности литографических систем. Это также снижает общую стоимость производства, уменьшая время экспозиции. Поэтому, отражательная способность рентгеновской оптики имеет решающее значение для эффективности EUV степпера. «Так давайте же увеличим КО, отполируем зеркала, к примеру…» подумает любознательный читатель. Но не все так просто…

Хьюстон, у нас проблема…

Слабое отражение мягкого рентгеновского излучения твердыми веществами требует использования специальных отражающих оптических устройств. Сразу после открытия рентгеновских лучей было обнаружено, что показатель преломления всех материалов очень маленький (~ 10-5). Позже, в результате открытия рентгеновской дифракцию на кристаллах, был обнаружен и способ отклонения рентгеновских лучей. «Новая версия» рентгеновской дифракции была предложена 30 лет назад, когда было предложено использовать искусственные дифракционные структуры вместо природных кристаллов для манипулирования рентгеновскими лучами. Обычно такие искусственные структуры состоят из периодически чередуемых нанослоев двух материалов, как показано на рисунке 3. Таким образом, отражательная способность рентгеновских лучей усиливается из-за умножения интерфейсов. Такие структуры называются многослойными периодическими рентгеновскими зеркалами (англ. periodical multilayer X-ray mirrors  - PMMS).

Схема многослойного рентгеновского зеркала
Схема многослойного рентгеновского зеркала

Многослойки, по сути, являются искусственными Брегговскими кристаллами, состоящими из чередующихся слоев «легкого» и «тяжелого» материала; они также называются «spacer» и «absorber». Периодичность такой структуры составляет примерно половину рабочей длины волны для угла падения, близкого к нормали. Многослойные зеркала достигают высокой отражательной способности в результате интерференции излучения, отраженного на границах между легкими и тяжелыми слоями.

Показатель преломления в рентгеновском диапазоне является комплексным и равен  1 - Δ + iβ. На любой заданной длине волны материалы с высоким δ и низким β действуют в качестве отражающих слоев. Материалы с низким δ и низким β служат спейсерами. Последовательность слоев с высокими и низкими δ (чередования спейсера и абсорбера) обеспечивают оптический контраст. Как абсорберы, так и спейсеры требуют низкого β, чтобы минимизировать поглощение излучения. Например, на длине волны выше 12,8 нм свойства молибдена позволяют использовать его в качестве абсорбера. На той же длине волны кремний действует как спейсер.

 Сначала посчитаем

Проектирование высокоэффективных зеркал для конкретной длины волны включает в себя несколько основных шагов. Первый шаг - выбор пары материала. Выбор определяется оптическими свойствами материалов в данном диапазоне длин волн. Например, пара Mo/Si в основном используются для длины волны 13,5 нм (EUV), а многослойки Mo/B оптимальны для 6,7 нм, поскольку бор имеет более низкое поглощение в этом диапазоне по сравнению с кремнием; Co/C являются наиболее эффективными для более короткого диапазона длин волн 4,4 - 4,5 нм.

Следующим шагом выбирает толщину отдельных слоев и их количество. Этот шаг обычно основан на компьютерном моделировании KO на заданной длине волны. Для расчета КО используются различные инструменты. Мы предпочитаем наш собственный X-RayCalc.

Например, для 13.5 нм, оптимальные толщины Mo и Si составляют 2.8 и 4.6 нм, общее число пар (периодов) - 50-60:

Электронно-микроскопическое изображение рентгеновского зеркала Mo/Si (поперечное сечение) и зависимость КО от числа преодов.
Электронно-микроскопическое изображение рентгеновского зеркала Mo/Si (поперечное сечение) и зависимость КО от числа преодов.

Так как же это сделано?

Традиционный метод изготовления многослойных рентгеновских зеркал – магнетронное распыление (никакого отношения в ВЧ магнетронам не имеет). Весь процесс происходит в сильно разряженной атмосфере инертного газа (аргона). Если сильно упростить – магнетронный источник имеет два магнитных кольца, расположенных под мишенью (диск из материала, который нужно распылять). Магниты создают замкнутое магнитное поле, улавливающее электроны. Электроны эмитируются из мишени под действием отрицательно электрического потенциала, прикладываемого к мишени (обычно, несколько сотен вольт). Электроны двигаются в магнитном поле, сталкиваются с атомами газа, ионизируя его. Ионы бомбардируют подложку, распыляя ее. Поток атомов направляется на подложку, где и осаждается, формируя тонкую пленку. Недавно на хабре вышла статья о постройке магнетронного источника в домашних условиях.

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

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

Еще одно лирическое отступление

За свою карьеру я построил три установки для напыления зеркал. Первая была в Харькове, во времена аспирантуры, с очень ограниченным бюджетом, и «построил» следует читать буквально, поскольку процесс включал заливку бетонного фундамента для форвакуумного насоса. Установка работает до сих пор, хотя в этом году будет отмечать двадцатилетие. Посмотрите, какой стимпанк (ось карусели расположена горизонтально):

Вакуумная установка для изготовления рентгеновских зеркал, первое поколение. Харьков, 2002 г.
Вакуумная установка для изготовления рентгеновских зеркал, первое поколение. Харьков, 2002 г.

Дальше я уже ничего не строил сам, а только проектировал. Вторая установка была в Корее, и на ней удалось сделать очень неплохие зеркала Mo/B для длины волны 6.7 нм. Тут ось вертикальная, магнетроны сверху, подложка под ними.

Второе поколение. Корея, 2017-2019 г.
Второе поколение. Корея, 2017-2019 г.

На схеме: 1 - вакуумная камера, 2 и 3 - магнетроны. 4-5 - подложкодержатель.

Установка третьего поколения была запущена несколько месяцев назад. Накопленный опыт + (почти) неограниченный бюджет произвели вот эту красоту:

Третье поколение. Китай, 2021г.
Третье поколение. Китай, 2021г.

Четыре 150 мм магнетрона, ионное травление, водо-охлаждаемый держатель подложки, шлюз,мощные насосы, максимальная автоматизация. Базовое давление - 1e-7 Торр. Наконец-то можно развернуться по полной программе.

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

Система управления приводами подложкодержателя и заслонки.
Система управления приводами подложкодержателя и заслонки.

Ну и красивый самописный интерфейс на Delphi, куда же без него. На данном этапе это выглядит так:

Интерфейс системы управления напылением.
Интерфейс системы управления напылением.

Ключевое здесь – свой скриптовый язык, позволяющий полностью автоматизировать весь процесс, включая управление блоками питания. Когда изготовление одного зеркала занимает 3-5 часов, не считая подготовки, без автоматизации никак.

 И где мы?

Напылить рентгеновское зеркало – это только полдела. Его еще нужно аттестовать, и убедится в том, что напылили именно то, что спроектировали (обычно – нет). Прямолинейный метод – поехать на ближайший синхротрон, и померять КО на нужной длине волны. К сожалению, долго, дорого, и если что-то пошло не так, мы это увидим, но вряд ли поймем почему.

Тут на помощь приходит малоугловая рентгеновская дифракция + описанное выше компьютерное моделирование. Идея следующая – берем обычный лабораторный рентгеновский дифрактометр, использующий жесткий рентген (обычно 0.154 нм), и снимаем дифракционную кривую. Поскольку есть периодическая структура, то все работает почти так же, как с обычными кристаллами. Но из-за большего параметра периодичности, дифракционные максимумы будут находиться вблизи первичного пучка, на очень малых углах. Потом берем нашу физическую модель, считаем с помощью X-Ray Calc, и совмещаем обе дифракционные кривые. Не совпало? Подгоняем параметры модели до тех пор, пока не совпадет. Примерно так:

Не попали …

Ключевой параметр многослойного зеркала -  это его период, равный сумме толщин одного легкого и одного тяжелого слоя. Все рентгеновская оптика завязана на одно простое уравнение Брэгга, которое определят условие дифракции:

2d sin θ = nλ

Где d – период, тета – угол дифракции, лямбда – длина волны, n – дифракционный порядок.

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

В итоге можно промахнуться на пару ангстрем (при периоде в 6,5 нм) и получится вот так:

Всего-то на один ангстрем промазали ...
Всего-то на один ангстрем промазали ...

Тут вроде и пиковый КО на месте, только вот положение пика уехало почти на семь градусов. Такой промах юстировкой оптики уже не исправить ...

Другая распространенная проблема – дрифт периода вызванный непостоянством скорости напыления во время изготовления зеркала. Опять же, в процессе меняется атмосфера, растет температура, и т.п. Это лечится длительной подготовкой, обычно за загрузкой подложек следует прогрев самой вакуумной камеры, потом несколько часов магнетроны просто работают вхолостую. «У нас поплыл период» на малоугловой дифракции выглядит как-то так:

Дрифт периода проявляется в уширении и расщеплении дифракционных пиков (серая кривая)
Дрифт периода проявляется в уширении и расщеплении дифракционных пиков (серая кривая)

Ясное дело, ждать высокого КО на целевой длине волны тут не приходится.

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

Но позвольте,

Где же наш КО?

В смысле, почему такая разница с теорий, и не в нашу пользу? Проблема в том, что наноструктура многослойных зеркал имеет критическое влияние на оптические свойства. В принципе, ее (наноструктуру) можно контролировать в определённых пределах. Разумеется, это возможно, когда есть полное понимание взаимосвязей между структурой и условиями изготовления. Структурные дефекты, такие как шероховатость интерфейса и перемешивание между слоями, уменьшают резкость интерфейсов и ухудшают отражательную способность. Таким образом, без знания реальной структуры невозможно правильно предсказать отражающую способность зеркал. По этой причине интенсивное исследование структуры и механизмов формирования зеркал для EUV диапазона предшествовало их коммерческому использованию.

Межслоевая шероховатость уменьшает отражательную способность каждой границы внутри зеркала из-за диффузного рассеяния. Влияние шероховатости может быть оценено с использованием различных теоретических моделей, например, с использованием коэффициента т.н. Дебаевского фактора.  Если распределение шероховатости описывается функцией Гаусса, влияние шероховатости на КО можно описать следующей функцией:

 где r0 - это отражательная способность идеально гладкой и границы между двумя слоями. Расчет показывает, что введение 0,3 нм шероховатости на границах раздела может уронить КО на десятки процентов по сравнению с идеальной моделью. Уменьшение шероховатости от 0,3 до 0,2 нм увеличивает отражательную способность на 5-6%.

 Дальше хуже …

Помимо шероховатости, для многих пар материалов характерно протекание твердотельных химических реакций, приводящее к т.н. межслоевому перемешиванию. Например, осаждение молибдена на кремний Si приводит к образованию тонкого слоя молибден дисилицида молибдена MoSi2. В случае Mo и B появление диборида молибден MoB2:

Микроскопический снимок границы между молибденом и бором.
Микроскопический снимок границы между молибденом и бором.

Перемешивание считается структурным несовершенством, поскольку смежные слои уменьшают оптическую контрастность границы и опять роняют КО, несмотря на их низкую толщину (около 1 нм). Если произошло перемешивание, интерфейс (граница раздела)  Mo/B, замещается двумя куда менее контрастными интерфейсами B/MoB2 и MoB2/Mo. Кроме того, общий объем интерфейсов в покрытии увеличился. Поскольку каждый интерфейс также имеет собственную шероховатость , происходит кумулятивное ухудшение отражательной способности. Таким образом, подавление или даже полное устранение смешивания имеют решающее значение для улучшения отражательной способности зеркал:

Эффект различных структурных носовершенств на коэффициент отражения. Фиолетовая линия - результат выбора неправильного материала для "спейсера".
Эффект различных структурных носовершенств на коэффициент отражения. Фиолетовая линия - результат выбора неправильного материала для "спейсера".

 И что с этим делать?

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

А что после EUV

Технология движется в строну дальнейшего уменьшения длины волны, до 6.7 нм (т.н. BEUV). Там проблем еще больше. Во-первых, перид зеркал нужно уменьшить почти вдое (до 3.4 нм). Во-вторых, число периодов увеличить хотя бы до 300. В третьих, заменить кремнй на бор (та еще зараза). Позитивный момент - больше энергия фотонов, немного проще засвечивать фоторезист, что несколько компенсирует меньший KO.

На этом все. Если вас заинтересовала эта тема, то множество подробностей об EUV литографии (включая историю) можно найти в этой замечатльной книге:

Vivek Bakshi, EUV Lithography, SPIE 2018

Еще больше деталей по самим зеркалам и сопутствующим технологиям можно найти здесь:

Spiller Eberhard, Soft X-Ray Optic, SPIE 1994

Виноградов А., Брытов И., Грудский А., Зеркальная рентгеновская оптика, Л. Машиностроение, 1989

На тему проблем  BEUV оптики можно почитать наш свежий обзор: Multilayer Reflective Coatings for BEUV Lithography: A Review.

Немножечко информации для тех, кому это интересно

В моем лице исследовательский центр инновационных наноматериалов при Международном кампусе Джедзянского университета (Zhejiang University, 53 место в мировом рейтинге THE), ищет магистров и аспирантов, желающих двигать науку в области материаловедения, включая многослойную рентгеновскую оптику. 

Adblock test (Why?)