Пользователь
0,0
рейтинг
6 мая 2014 в 00:10

Разработка → Back-инжиниринг Caesar III

Мне нравится играть в игры, особенно в экономические стратегии, хочу рассказать про градостроительный симулятор из детства — Caesar III, как принято говорить, тёплый и ламповый. Игра была выпущена в 1998 году, знатоками своего дела, Impressions Games. Это экономический симулятор управления древнеримским городом в реальном времени. Через много лет я решил вновь пройти её, а затем постараться продлить удовольствие от игры, посмотреть ресурсы и вникнуть в игровую логику с точки зрения программиста.

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


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

Графика

С графикой(текстурами) все намного сложнее, текстуры разбиты на несколько псевдоархивов с расширением .sg2 и .555.

Файл с расширением .sg2, назовем его “оглавлением“, содержит параметры текстур: размеры, смещение в атласе, имя и номер, идентификатор, различные флаги.

Файл с расширением .555, назовем его “атласом”, содержит сами изображения, в собственном формате описания, которые делятся на три типа:
— простые (bmp)
— изометрические
— с альфа-каналом
Для каждого типа текстур используется свой формат “сжатия”. “Оглавление” может ссылаться на несколько атласов, при этом имя “атласа”, должно соответствовать названию группы текстур, которые в нем содержатся. Простые текстуры читаются как массив цветов и их можно практически без обработки рисовать на экране, “обработка” состоит в преобразовании BGR555 цвета с глубиной 5 бит на канал, в более удобный для работы АRGB32. В игре Сaesar III текстуры с прозрачностью не используются, они будут задействованы позже в этой серии игр (Pharaoh, Cleopatra и др)

В файле С3.SG2 содержатся описания групп изображений.
Если открыть этот файл в hex-редакторе, то можно увидеть следующий блок данных,

который описывает группу из 44 (n_images: 0x0000002C) изображения с именем plateau, информация о которых начинается с индекса 201 (start_index: 0x000000C9). Всего в «оглавлении» есть место для 100 таких групп. После описания групп, идут описания конкретных изображений, перебирая которые можно восстановить сами картинки. Дело осталось за малым, прочитать оглавление, распаковать пожатые текстуры и собрать их в полноценные изображения. Вот что получилось при распаковке группы plateau


Вот еще несколько восстановленных текстур, в нативном формате, насколько это получилось, без фильтров.


А здесь обработанные текстуры с альфаканалом.


Если с атласом текстур и используемых в нем структурах данных еще можно разобраться, полагаясь на сообразительность, hex-редактор и долю везения, то с алгоритмами восстановления текстур такое не пройдет. И тут на помощь приходит Ильфак с незаменимым отладчиком IDA, и не менее полезным декомпилятором Hex-Rays. Открываем с3.exe в отладчике, видим картину отнюдь не радужную, я большую часть времени программирую на яве(java) или плюсах(c++) и для меня это, не то чтобы темный лес, но густой кустарник точно.


Тут нам поможет способность IDA восстанавливать asm в псевдокод plain-С. Нажимаем F5 и перед нами человеко-читабельный код, с которым уже можно работать.
.

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


А спустя некоторое время ( день, неделю, месяц и тд) он станет вот таким. Согласитесь, теперь намного удобнее искать алгоритмы
.

Исполняемый файл игры Caesar III был собран с отладочной информацией компилятором Visual C++ 5.0, что также позволяет восстанавливать логику приложения более продуктивно. Используя отладчик, декомпилятор и собственные серые клетки можно добраться до функции чтения изображений из архива
Много кода
int __cdecl fun_drawGraphic(signed int graphicId, int xOffset, int yOffset)
{
  int result; // eax@2
  LONG v4; // [sp+50h] [bp-8h]@43

  drawGraphic_graphicId = graphicId;
  drawGraphic_xOffset = xOffset;
  drawGraphic_yOffset = yOffset;
  if ( graphicId <= 0 )
    return 0;
  if ( graphicId >= 10000 )
    return 0;
  drawGraphic_fileOffset = c3_sg2[graphicId].offset;
  if ( drawGraphic_fileOffset <= 0 )
    return 0;
  LOWORD(drawGraphic_width) = c3_sg2[graphicId].width;
  LOWORD(drawGraphic_height) = c3_sg2[graphicId].height;
  drawGraphic_type = c3_sg2[graphicId].type;
  graphic_xOffset = xOffset;
  graphic_yOffset = yOffset;
  drawGraphic_visiblePixelsClipX = (signed __int16)drawGraphic_width;
  if ( c3_sg2[graphicId].extern_flag && (signed __int16)drawGraphic_width <= ddraw_width )
  {
    strcpy(drawGraphic_555file, &c3sg2_bitmaps[200 * c3_sg2[graphicId].bitmap_id]);
    j_fun_changeFileExtensionTo(drawGraphic_555file, &extension_555[4 * graphics_format_id]);
    if ( !j_fun_readDataFromFilename(
            drawGraphic_555file,
            screen_buffer,
            c3_sg2[graphicId].data_length,
            c3_sg2[graphicId].offset - 1) )
    {
      j_fun_changeFileExtensionTo(drawGraphic_555file, "555");
      if ( !j_fun_readDataFromFilename(
              drawGraphic_555file,
              screen_buffer,
              c3_sg2[graphicId].data_length,
              c3_sg2[graphicId].offset - 1) )
        return 0;
      if ( c3_sg2[graphicId].compr_flag )
        j_fun_convertCompressedGraphicToSurfaceFormat(screen_buffer, c3_sg2[graphicId].data_length);
      else
        j_fun_convertGraphicToSurfaceFormat(screen_buffer, c3_sg2[graphicId].data_length);
    }
    j_fun_setGraphicXClipCode();
    j_fun_setGraphicYClipCode();
    if ( drawGraphic_clipYCode == 5 )
      return 0;
    if ( drawGraphic_type )
    {
      if ( drawGraphic_clipYCode == 5 )
        return 0;
      drawGraphic_fileOffset = 2 * (signed __int16)drawGraphic_width * drawGraphic_invisibleHeightClipTop;
      drawGraphic_fileOffset += 2 * drawGraphic_invisibleWidthClipLeft;
      if ( drawGraphic_clipXCode == 1 )
      {
        j_fun_drawGraphicUncompressedClipLeft((char *)screen_buffer + drawGraphic_fileOffset);
      }
      else
      {
        if ( drawGraphic_clipXCode == 2 )
          j_fun_drawGraphicUncompressedClipRight((char *)screen_buffer + drawGraphic_fileOffset);
        else
          j_fun_drawGraphicUncompressedClipY((char *)screen_buffer + drawGraphic_fileOffset);
      }
    }
    else
    {
      if ( c3_sg2[graphicId].compr_flag )
      {
        if ( drawGraphic_clipXCode == 1 )
        {
          j_fun_drawGraphicCompressedClipLeft((char *)screen_buffer);
        }
        else
        {
          if ( drawGraphic_clipXCode == 2 )
            j_fun_drawGraphicCompressedClipRight((char *)screen_buffer);
          else
            j_fun_drawGraphicCompressedFull((char *)screen_buffer);
        }
      }
      else
      {
        drawGraphic_fileOffset = 2 * (signed __int16)drawGraphic_width * drawGraphic_invisibleHeightClipTop;
        drawGraphic_fileOffset += 2 * drawGraphic_invisibleWidthClipLeft;
        if ( drawGraphic_clipXCode == 1 )
        {
          j_fun_drawGraphicUncompressedClipLeft((char *)screen_buffer + drawGraphic_fileOffset);
        }
        else
        {
          if ( drawGraphic_clipXCode == 2 )
            j_fun_drawGraphicUncompressedClipRight((char *)screen_buffer + drawGraphic_fileOffset);
          else
            j_fun_drawGraphicUncompressedClipY((char *)screen_buffer + drawGraphic_fileOffset);
        }
      }
    }
    result = (signed __int16)drawGraphic_width;
  }
  else
  {
    if ( c3_sg2[graphicId].extern_flag )
    {
      if ( window_id == 21 || window_id == 20 )
      {
        drawGraphic_visiblePixelsClipX = fullscreenImage_width;
        drawGraphic_visiblePixelsClipY = fullscreenImage_height;
        drawGraphic_copyBytesInBufferForClipX = 2 * ((signed __int16)drawGraphic_width - drawGraphic_visiblePixelsClipX);
        drawGraphic_skipBytesInBufferForClipX = 2 * (ddraw_width - drawGraphic_visiblePixelsClipX);
        j_fun_drawGraphicUncompressedFull(&c3_555[2 * fullscreenImage_xOffset + 13000000] + 2
                                                                                          * (signed __int16)drawGraphic_width
                                                                                          * fullscreenImage_yOffset);
        return drawGraphic_visiblePixelsClipX;
      }
      v4 = 2 * (signed __int16)drawGraphic_width * fullscreenImage_yOffset + 2 * fullscreenImage_xOffset;
      drawGraphic_visiblePixelsClipX = fullscreenImage_width;
      drawGraphic_visiblePixelsClipY = fullscreenImage_height;
      strcpy(drawGraphic_555file, &c3sg2_bitmaps[200 * c3_sg2[graphicId].bitmap_id]);
      j_fun_changeFileExtensionTo(drawGraphic_555file, &extension_555[4 * graphics_format_id]);
      if ( !j_fun_readUncompressedImageData(
              drawGraphic_555file,
              screen_buffer,
              2 * drawGraphic_visiblePixelsClipX,
              drawGraphic_visiblePixelsClipY,
              v4) )
      {
        j_fun_changeFileExtensionTo(drawGraphic_555file, "555");
        if ( !j_fun_readUncompressedImageData(
                drawGraphic_555file,
                screen_buffer,
                2 * drawGraphic_visiblePixelsClipX,
                drawGraphic_visiblePixelsClipY,
                v4) )
          return 0;
        j_fun_convertGraphicToSurfaceFormat(
          screen_buffer,
          drawGraphic_visiblePixelsClipY * 2 * drawGraphic_visiblePixelsClipX);
      }
      drawGraphic_copyBytesInBufferForClipX = 0;
      drawGraphic_skipBytesInBufferForClipX = 0;
      j_fun_drawGraphicUncompressedFull((char *)screen_buffer);
      result = drawGraphic_visiblePixelsClipX;
    }
    else                                        // internal
    {
      if ( (unsigned __int8)drawGraphic_type == 30 )// isometric
      {
        switch ( (signed __int16)drawGraphic_width )
        {
          case 58:
            LOWORD(drawGraphic_height) = 30;
            break;
          case 26:
            LOWORD(drawGraphic_height) = 14;
            break;
          case 10:
            LOWORD(drawGraphic_height) = 6;
            break;
          default:
            if ( (signed __int16)drawGraphic_width == 118 )
              return j_fun_drawBuildingFootprintSize2();
            if ( (signed __int16)drawGraphic_width == 178 )
              return j_fun_drawBuildingFootprintSize3();
            if ( (signed __int16)drawGraphic_width == 238 )
              return j_fun_drawBuildingFootprintSize4();
            if ( (signed __int16)drawGraphic_width == 298 )
              return j_fun_drawBuildingFootprintSize5();
            break;
        }
      }
      j_fun_setGraphicXClipCode();
      j_fun_setGraphicYClipCode();
      if ( drawGraphic_clipYCode == 5 )
      {
        result = 0;
      }
      else
      {
        if ( drawGraphic_type )
        {
          if ( (unsigned __int8)drawGraphic_type == 30 )
          {
            if ( drawGraphic_clipXCode == 1 )
            {
              switch ( (signed __int16)drawGraphic_width )
              {
                case 58:
                  j_fun_drawBuildingFootprint_xClipRight(&c3_555[drawGraphic_fileOffset], drawGraphic_clipYCode);
                  break;
                case 26:
                  j_fun_drawBuildingFootprint_26px_xClipRight();
                  break;
                case 10:
                  j_fun_drawBuildingFootprint_10px_xClipRight();
                  break;
                default:
                  j_fun_drawGraphicUncompressedClipLeft(&c3_555[drawGraphic_fileOffset]);
                  break;
              }
            }
            else
            {
              if ( drawGraphic_clipXCode == 2 )
              {
                switch ( (signed __int16)drawGraphic_width )
                {
                  case 58:
                    j_fun_drawBuildingFootprint_xClipLeft(&c3_555[drawGraphic_fileOffset], drawGraphic_clipYCode);
                    break;
                  case 26:
                    j_fun_drawBuildingFootprint_26px_xClipLeft();
                    break;
                  case 10:
                    j_fun_drawBuildingFootprint_10px_xClipLeft();
                    break;
                  default:
                    j_fun_drawGraphicUncompressedClipRight(&c3_555[drawGraphic_fileOffset]);
                    break;
                }
              }
              else
              {
                switch ( (signed __int16)drawGraphic_width )
                {
                  case 58:
                    j_fun_drawBuildingFootprint_xFull(&c3_555[drawGraphic_fileOffset], drawGraphic_clipYCode);
                    break;
                  case 26:
                    j_fun_drawBuildingFootprint_26px_xFull();
                    break;
                  case 10:
                    j_fun_drawBuildingFootprint_10px_xFull();
                    break;
                  default:
                    j_fun_drawGraphicUncompressedClipY(&c3_555[drawGraphic_fileOffset]);
                    break;
                }
              }
            }
          }
          else
          {
            if ( (unsigned __int8)drawGraphic_type == 13 && drawGraphic_clipXCode )
            {
              j_fun_drawImage_32x32((int *)&c3_555[drawGraphic_fileOffset]);
            }
            else
            {
              if ( (unsigned __int8)drawGraphic_type == 12 && drawGraphic_clipXCode )
              {
                j_fun_drawImage_24x24((int *)&c3_555[drawGraphic_fileOffset]);
              }
              else
              {
                if ( (unsigned __int8)drawGraphic_type == 10 && drawGraphic_clipXCode )
                {
                  j_fun_drawImage_16x16((int *)&c3_555[drawGraphic_fileOffset]);
                }
                else
                {
                  if ( (unsigned __int8)drawGraphic_type == 2 && drawGraphic_clipXCode )
                  {
                    j_fun_drawGraphicType2(&c3_555[drawGraphic_fileOffset]);
                  }
                  else
                  {
                    if ( (unsigned __int8)drawGraphic_type == 20 )
                    {
                      if ( drawGraphic_clipXCode == 1 )
                      {
                        j_fun_drawGraphicLetterColoredClipLeft(&c3_555[drawGraphic_fileOffset]);
                      }
                      else
                      {
                        if ( drawGraphic_clipXCode == 2 )
                          j_fun_drawGraphicLetterColoredClipRight(&c3_555[drawGraphic_fileOffset]);
                        else
                          j_fun_drawGraphicLetterColoredFull(&c3_555[drawGraphic_fileOffset]);
                      }
                    }
                    else
                    {
                      drawGraphic_fileOffset += 2
                                              * (signed __int16)drawGraphic_width
                                              * drawGraphic_invisibleHeightClipTop;
                      drawGraphic_fileOffset += 2 * drawGraphic_invisibleWidthClipLeft;
                      if ( drawGraphic_clipXCode == 1 )
                      {
                        j_fun_drawGraphicUncompressedClipLeft(&c3_555[drawGraphic_fileOffset]);
                      }
                      else
                      {
                        if ( drawGraphic_clipXCode == 2 )
                        {
                          j_fun_drawGraphicUncompressedClipRight(&c3_555[drawGraphic_fileOffset]);
                        }
                        else
                        {
                          if ( drawGraphic_clipYCode )
                            j_fun_drawGraphicUncompressedClipY(&c3_555[drawGraphic_fileOffset]);
                          else
                            j_fun_drawGraphicUncompressedFull(&c3_555[drawGraphic_fileOffset]);
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
        else                                    // type == 0
        {
          if ( c3_sg2[graphicId].compr_flag )
          {
            if ( drawGraphic_clipXCode == 1 )
            {
              j_fun_drawGraphicCompressedClipLeft(&c3_555[drawGraphic_fileOffset]);
            }
            else
            {
              if ( drawGraphic_clipXCode == 2 )
                j_fun_drawGraphicCompressedClipRight(&c3_555[drawGraphic_fileOffset]);
              else
                j_fun_drawGraphicCompressedFull(&c3_555[drawGraphic_fileOffset]);
            }
            if ( drawGraphic_colorMask )
            {
              if ( drawGraphic_clipXCode == 1 )
              {
                j_fun_drawGraphicCompressedColorMaskClipLeft(&c3_555[drawGraphic_fileOffset], drawGraphic_colorMask);
              }
              else
              {
                if ( drawGraphic_clipXCode == 2 )
                  j_fun_drawGraphicCompressedColorMaskClipRight(&c3_555[drawGraphic_fileOffset], drawGraphic_colorMask);
                else
                  j_fun_drawGraphicCompressedColorMaskFull(&c3_555[drawGraphic_fileOffset], drawGraphic_colorMask);
              }
            }
          }
          else                                  // not compressed
          {
            drawGraphic_fileOffset += 2 * (signed __int16)drawGraphic_width * drawGraphic_invisibleHeightClipTop;
            drawGraphic_fileOffset += 2 * drawGraphic_invisibleWidthClipLeft;
            if ( drawGraphic_clipXCode == 1 )
            {
              j_fun_drawGraphicUncompressedClipLeft(&c3_555[drawGraphic_fileOffset]);
            }
            else
            {
              if ( drawGraphic_clipXCode == 2 )
                j_fun_drawGraphicUncompressedClipRight(&c3_555[drawGraphic_fileOffset]);
              else
                j_fun_drawGraphicUncompressedClipY(&c3_555[drawGraphic_fileOffset]);
            }
          }
        }
        result = drawGraphic_visiblePixelsClipX;
      }
    }
  }
  return result;
}


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

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

Каковы цели у ремейка
+ Дать возможность другим людям поиграть в забытую игру и не только под Windows.
+ Играть в Caesar III без эмуляторов, танцев с бубном, возни с запуском игры под Wine, дикого на текущий момент разрешения 800х600.
+ Повысить качество текстур, шрифтов и скорости игры.
+ Получить удовольствие от разработки — я люблю играть в игры, особенно экономические, и мне очень не нравится когда игра глючит, вылетает или работает неправильно. Мне проще сделать ремейк, чем писать свою игру, ведь к свои программам я отношусь очень критично, стараясь убрать глюки и по максимуму настроить баланс. Но результат всегда чуть хуже, чем ожидаешь, наверное поэтому на создание своего проекта уходит времени в разы больше.
+ Добавить наконец сетевую игру, которой мне так не хватало в детстве.
+ На планшете побить варваров, стоя в пробке — согласитесь намного интереснее, чем донатить в ферму.
+ Сделать хороший перевод, не только для русскоговорящих, а например для французов, до них игра дошла на английском.

Что делать с авторскими правами
Вариантов немного:
1. Забить и делать то, что хочешь — не наш путь, мы ведь цивилизованные люди, не хочется тратить громадное количество времени на ремейк, чтобы авторы оригинала запретили его на финише.
2. Писать на почту правообладателем и просить разрешения (устное, разрешение на использование ресурсов или бренда, «на бумаге» и пр.). Тут еще хуже, цивилизованные авторы, или держатели прав( на данный момент это Activision), как правило держатся за них до последнего, даже если игра не приносит прибыли. Права есть — значит ремейка не будет. Точка.
3. Позиционировать игру как мод, которому для работы нужна оригинальная игра, скачанная с торрента честно купленная на GOG.com, так поступили например Corsix TH, выпустив ремейк Theme Hospital. Самый оправданный и безопасный путь, хотя…

Старые игры не значит плохие. Многие старые игры, если с них сдуть пыль, подчистить, подмазать и подклеить… Эти игрушки заткнут за пояс многие современные поделки.
Вадим Балашов

Благодарю, что дочитали до конца!

P.S.

Отдельное спасибо людям, которые помогают в развитии ремейка.
Bianca van Schaik (http://pecunia.nerdcamp.net/), back-инжиниринг оригинальной игры
Gregoire Athanase (http://sourceforge.net/projects/opencaesar3/), автор рендера и многих алгоритмов
George Gaal (https://github.com/gecube/opencaesar3) back-инжиниринг сейвов
и многие другие коммитеры


UPD1. Eсли Вы заинтересовались результатами бэк-инжиринга этой игры (exe + idb), лучше наверно связаться через почту или ПМ, тема, что называется «gray legal area». Для ознакомления с игрой использовался IDA 5.5 + Hex-Rays 1.01. Файлы и материалы выложены с разрешения Bianca van Schaik (http://caesar.biancavanschaik.nl/).

UPD2. Почему это пост попал в хаб linux. OllyDbg u IDA запущены на виртуалке Win7, для разработки используется QtCreator 3.0.1 + cmake + gcc 4.8, игра нативно пишется для linux. Для сборки под Windows используется кросскомпилятор mingw-w64, для MacOSX и Haiku подняты виртуальные машины. Для сборки под андроид используется окружение из libsdl-android.
@dalerank
карма
155,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (98)

  • +13
    Есть ещё потрясающая игра из этой серии «Зевс». Особенно хороша с шедевральной озвучкой Фаргуса: «Я только надеюсь, что дерево, которое я срубил — это просто дерево. А не убежище нимф, дафний или дуб-прорицатель».

    Скриншот
    image
    • 0
      Да, Зевс конечно ВЕЩЬ, но мне сложноватым показался… и овец постоянно ктото тырил, так миссию с руном и не прошел
      • 0
        Овец, наверное, жрецы «тырили» для жертвоприношений во святилищах.
        Но он действительно сложный очень. Одна из игр, которая заставляет оттачивать стратегию развития.
        • 0
          По мне цезарь намного сложнее был
        • +4
          Зевс показался очень простой игрой после Фараона.
    • +7
      А как же Фараона забыли? Столько времени уложено было в нее, столько пирамид построено…
      • +1
        Вообще, эта целая серия игр на одном движке (Цезарь, Фараон, Зевс, Император) + вариации (Клеопатра, Посейдон и т.п.).
        Изредка бывает провожу вечер играя в Зевса, как самую привычную мне версию, просчитывая идеальную структуру квартала :)
    • 0
      А мне больше всех (кроме цезаря 3) понравился Emperor: rise of the Middle kingdom.
      • 0
        Да, Император, достойный продолжатель серии, только боевку наоборот почти выпилили из игры
  • +6
    Много времени на римейк ушло?
    А то я тут Зевса на досуге ковырял примерно с такой же целью (iOS 7, SpriteKit):
    image
    • +1
      Я занимаюсь игрой с весны прошлого года, с переменным успехом, выходит около часа каждый день )))
      • +2
        Блин, ребята, не останавливайтесь. Это мои самые любимые игры детства, сколько часов я на них потратил, не счесть… очень хочу ремейки :)
  • 0
    О! Только вчера запускал оригинал. Кстати, на семерке работает без проблем.
    • 0
      Угу, работает у меня тоже, но вылеты присутствуют, а еще у меня патч был для широких экранов, для английской версии, вот он работать перестал
  • +4
    мне из этой серии особенно про китай понравилось. В Цезаре была большая проблема для меня — нельзя было перекрывать дорогу, что бы людишки ходили так как мне нужно и правильно обслуживали дома…
    • 0
      Можно было перекрывать городскими воротами, насколько я помню. Правда, расставленные посреди города ворота смотрелись несколько дико.
  • 0
    Сам недавно решил понастальгировать в Цезаря, предполагая, что пройду пару миссий и снова забуду, однако этот градостроительный сим и правда заткнет многие более современные релизы.
    А вам спасибо за ремейк, буду следить за его судьбой.
  • +8
    Автор поскромничал, и не выложил ссылку на steam greenlight: тык.
    Надеюсь не будет проблем с правообладателями, и заявленные «Plans for the future» получится воплотить в жизнь.
    • +2
      И зря поскромничал. Нужно добавить в статью.
  • 0
    В век планшетов

    Я правильно понял, что планируется андроид-версия? Тряхнул бы стариной 8)
    • +3
      Так она есть, только управление несколько неудобное, поначалу… Посмотрите в релизах, не буду ссылку выкладывать, чтобы хабр не причем был)))
      • 0
        Ох, круть, попробую! Спасибо!
  • 0
    Третий вариант (позиционирование игры как мода) очень любопытен: нужны ли какие-то чисто технические допилки для того, чтобы обьявить результат модом? Например, обязательное наличие конвейера между оригинальным контентом и «модом»?
    • 0
      Есть два варианта: opensource-версия и мод.

      По опыту OpenXCOM первый вариант реализуется так: версии игры нужны только ресурсы (рисунки, звуки) для запуска, а код полностью свой. Юридических претензий вроде до сих пор не было.

      Мод в данном случае выглядит, например, так: взять движок и добавить сколько-то новых построек или немного модифицировать механику игры. Иногда бывает, что издатели сначала запрещают моды (пока рассчитывают заработать на DLC), а потом относятся к ним лояльно (даже иногда помогают, публикуя часть исходных кодов или инструменты создания дополнений).
      • 0
        Т.е. для «opensource» версии нужно вместе с кодом поставлять средства извлечения контента? Или извлечённый контент вместе с исходниками и инструкцией «как собрать»?
        • +2
          Про OpenXCOM знаю, что он устанавливается поверх обычной версии XCOM, хотя код там весь свой. Подробнее здесь:

          Q: Is using the original X-COM resources legal?
          A: It’s kind of a grey area. We’ve contacted the copyright holders just to be sure but they never replied back, so we just play it safe like every other clone. The code is completely new and none of the copyrighted files are actually included with the project, players still need their own copy of X-COM to play, so it should be fine.

          В. Законно ли использование ресурсов оригинального XCOM?
          О. Это что-то вроде «серой зоны». На всякий случай, мы писали владельцам авторских прав, но они ни разу ни ответили. Так что мы просто ведем себя корректно, как и авторы любого другого клона. Код полностью новый и защищенных авторским правом файлов в проекте нет, а игрокам все равно нужна копия оригинального XCOM для запуска, так что все должно быть в порядке.

          У VCMI (клон Heroes 3), по-моему, похожая история.

        • 0
          В последних версиях, благодаря коммитеру Hellium, который добавил полноценную поддержку .sg2 архивов, можно использовать файлы оригинальной игры, во время работы, так что проблема «извлечения» контента решилась.
  • +10
    Это лучший день в моей жизни!!! Любимейшая стратегия, я даже через эмулятор на андроиде запускал, но там баг какой-то с «мышью», в угол карты камера уходила, боже, чувак, я помогу тебе всем чем только смогу. Пишу на Delphi, разбираюсь в фотошопе, чем тебе помочь?!
    • +1
      Спасибо, помощь всегда приветствуется. Посмотрите, например задачи, может чего придется по душе. Еще помощь может выражаться в поиске багов, например, запускаете игру, выбираете миссию или просто фриплей и играете, а найденные неточности и баги заносите в задачи. Времени на разработку уходит достаточно, иногда не успеваю полностью отыграть реализованные вещи.
  • 0
    Снимают шляпу за проделанную работу. Спасибо.
  • +1
    Реквестирую сборочку под мак.
    • 0
      Сборка под 10.8 лежит тут, перед запуском надо поставить фреймворки SDL, SDL_mixer и SDL_ttf. Запускать желательно в полноэкранном режиме, а то бывает, что мышь не работает
      • 0
        А можно пошаговую инструкцию как запустить под максосью? распаковал архив, а там много всего :)
        • 0
          Я пробовал сделать бандл, но чтото не получилось, поэтому поступаем как в обычном линуксе.

          1. В файле app_game/resources/settings.model (текстовый файл) поправьте параметр fullscreen, чтобы было fullscreen: true
          2. теперь в терминале перейдите в папку с игрой и скажите ./caesaria.macos
          если нет проблем с библиотеками, должно запуститься,
          про фреймворки SDL я написал выше
          • 0
            Сохранение/загрузка не работает везде? Или только под Mac?
            • 0
              Разобрался:
              Cannot create directory ./saves error=-1
              Some error on create directory ./saves
              После создания директории — все заработало.
              • 0
                возможно под маком есть какие-то особенности??? вообще игра проверяет каталог для сейвов на старте, и создает его в случае отсутствия, добавлю на всякий случай в баги, может еще у кого проявится.
                • 0
                  К слову, на линуксе я напоролся на то, что vfs не регистро-независимая абстракция, поэтому бинарник при запуске не нашел C3.SG2, который в ресурсах игры был C3.sg2, пришлось плодить симлинки :)
                  • 0
                    еще один баг найден, спасибо )))
          • +2
            Набросал на коленке небольшой скрипт для запуска под маком, пользуйтесь.
            gist.github.com/Anakros/95b55df12fc0fee0535a

            Код тут
            #!/bin/sh
             
            LIBDIR=~/Library/Frameworks/
             
            mkdir caesaria
            pushd caesaria
             
            curl -C - -OL http://downloads.sourceforge.net/project/opencaesar3/bin/caesaria-mac-b1362.zip
            unzip caesaria-mac-b1362.zip
             
            curl -O https://www.libsdl.org/release/SDL-1.2.15.dmg
            curl -O http://www.libsdl.org/projects/SDL_mixer/release/SDL_mixer-1.2.12.dmg
            curl -O http://www.libsdl.org/projects/SDL_ttf/release/SDL_ttf-2.0.11.dmg
             
            hdiutil mount SDL_ttf-2.0.11.dmg
            hdiutil mount SDL_mixer-1.2.12.dmg
            hdiutil mount SDL-1.2.15.dmg
             
            mkdir -p $LIBDIR
            cp -r /Volumes/SDL/SDL.framework $LIBDIR
            cp -r /Volumes/SDL_mixer/SDL_mixer.framework $LIBDIR
            cp -r /Volumes/SDL_ttf/SDL_ttf.framework $LIBDIR
             
            sed -i '' s/false/true/ resources/settings.model
            chmod +x caesaria.macos && ./caesaria.macos
            

            • 0
              оу! Т.е. фреймворки ставятся простым переносом в папку???
              Спасибо
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Можно скачать, это open-source игра. Система сборки cmake, настроена так. чтобы собиралось из коробки. Под винду я собираю кросскомпилятором на линуксе, непосредственно под виндой давно не собирал. Думаю проблем не будет.
    • 0
      Еще удачный пример: kamremake.com — ремейк Knights & Merchants. Для запуска требует оригинальную версию, купленную на GOG.
      • 0
        да, я связывался с одним из разработчиков, как раз по поводу использования оригинальных ресурсов, после этого и был выбран механизм распространения игры: open-source + оригинальная игра. Но у них попроще, правообладатели поддержали их начинание, исходников правда не дают, все больше консультациями.
  • +1
    Eсли Вы заинтересовались результатами бэк-инжиринга этой игры (exe + idb), лучше наверно связаться через почту или ПМ, тема, что называется «gray legal area». Для ознакомления с игрой использовался IDA 5.5 + Hex-Rays 1.01. Файлы и материалы выложены с разрешения Bianca van Schaik (http://caesar.biancavanschaik.nl/).
  • +1
    Удивительно, но в последнее время я не наблюдаю годных стратегий, в которые заигрывался в детстве.
    Вернее как, они есть, но в сравнении с другими жанрами игр их количество просто ничтожно.

    То ли дело 2000-е: Stronghold, Cossacks, Caesar (и аналоги, доставляла древнекитайская, не помню названия), Praetorians…
    • 0
      Точно такая же грусть.
      Из новых экономических стратегий только АННО серия выпускается и симсити.
      А просто стратегии — Старкравт2 и похоже что все(.

      Скоро должны выйти Age of wonders 3. Если кто сможет дополнить список годный стратегий — буду признателен.
      • 0
        Попробуйте Banished.
        • 0
          Ух ты. По виду именно то что надо, спасибо большое.
          Попробую теперь это запустить на маке.
        • 0
          Попробуйте Banished.

          Вау. Круть, спасибо.
          Скажите, в таком стиле (но ещё, по моему, не 3д) когда-то была прикольная стратежка. Не помните?
          Вообще, очень близкой тематики вопрос я задавал на Тостере. Ребята, кто читает этот топик — не могли бы зайти и ответить? Очень интересно, ибо ответов получил довольно мало(
          • +1
            Игр похожих на по стилю на Banished, к сожалению, не знаю.
            Судя по всему вам интересны стратегии немецкой школы.
            Могу посоветовать Knights & Merchants (в этой игре дороги обязательные). Есть активно разрабатываемая open-source версия kamremake с нормальным мультиплеером, все фичи из оригинальной игры реализованы.
            Из похожих на K&M — Cultures и Затерянный мир (Alien nations).
            Судя по скриншотам может быть вам еще понравится серия Tropico, но в нее я не играл.
            • 0
              Вау. Оказывается, это имеет называние «стратегии немецкой школы». Огромное спасибо. А есть ещё какие-то, менее известные игры подобные? Тропико, кстати, шикарный) Особенно второй.
              • 0
                Вот есть список, правда он не полный.
                Glory of The Roman Empire, например, но это ближе к Цезарю.
                Могу еще посоветовать Dungeon Keeper, правда это не совсем экономическая стратегия, ближе к dwarf fortress.
                • 0
                  Dungeon Keeper был пройден, он шикарен.
                  За список спасибо. Кажется, игра моей мечты мне приснилась((
              • 0
                Эх, первые сеттлеры… мультиплеер на одном компе с двум воткнутыми мышами, эх ностальгия…
                • 0
                  • 0
                    Нет, вот эта
                    Мышки тогда ещё в ком-порты втыкались…
                • 0
                  У нас вот как-то со вторыми заладилось.
                  И сплитскрин был и по сети.
                  Они даже на Нинтендо ДС были выпущены. Там дискетка заменена на СД карту в картинках:
                  image
          • 0
            Есть онлайн вот такая, например.
    • +3
      Согласен. добавлю кщё HoMM III и Civilization III :-)

      Лично мне кажется, что стратегии очень сильно убивает триде. Но не потому, что оно не тёплое и ламповое, а потому, что так намного сложнее ориентироваться в пространстве, чем на ортогональной сетке. Например, во вчерашней моей партии в Цивилизации у меня было свыше 200 городов. Я бы сошел с ума, если бы довелось повернуть карту хотя бы на пять градусов. Ну и нельзя не упомянуть крутую спрайтовую графику — а ведь стратегии практически единственный жанр, где она уместна и выигрывает у полигональной. Вроде как Цезар, Зевс, Стронгхолд, Козаки, Цивилизация и Герои это доказали.
      • 0
        del
  • +3
    Caesar III, CivCity: Rome и Stronghold писались одним и тем же программером, Саймоном Бредбери(Simon Bradbury)
  • +1
    Читал с немалым интересом, но углядев фразу про ремейк Hospital… О господи, неужели?

    Огромное спасибо за статью, и главное — за эту фразу, теперь я знаю, чем займусь после сессии. Если будет возможность, постараюсь помочь с проектом, хотя я боле шарпист, нежели плюсоасмщик. Но, за такую информацию чего только не сделаешь, да и ради общего дела.
  • 0
    Я буду рад помочь вам с локализацией, если в этом есть необходимость. Я тоже очень любил эту игру и с удовольствием внесу свой посильный вклад.
  • +1
    Благодарю, есть локализации для русского, немецкого, шведского и испанского полностью, румынского и французского частично. Подробно о том, как устроена локализация написано здесь
    • 0
      Не совсем понял. Достаточно просто править файл с переводами? Или как?
      • 0
        Нет, они генерятся автоматически, я вам в личку скинул ссылку на гугл таблицу, собственно изменять надо её, при построении релиза скрипт парсит эту таблицу и формирует локализации, вот так немного, до этого я использовал libgettext, но на винде и маке наблюдались баги с определением системной кодировки, в общем решил отказаться и от самой либы, и от автоопределения
        • 0
          Are you kidding me?)

          Попробуй использовать www.transifex.com/ для переводов, гораздо удобнее чем google docs, + есть средства для автоматического upload/download'а файликов с переводами из/в исходники.
          • 0
            Нет, не разыгрываю )))
            Просто первый раз использую локализацию вне qt приложений, поэтому есть промахи ))) в qt это все намного проще было, спасибо за подсказку
            • 0
              Могу помочь разобраться, если интересно — пиши в личку :)
        • 0
          Хорошо, спасибо! Это не самый удобный вариант локализации, но пуркуа бы и не па? :)
  • 0
    Отличная идея! Спасибо автору.

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

    Кстати забавно что можно поставить скорость игры 0 и даже отрицательной. Я уж было подумал что действительно всё пойдёт обратно, но нет, после применения перепрыгивает на 10%.
  • 0
    Да, после перехода на разрешения больше 1024х768, места стало действительно больше, и даже пустые места образовались, спасибо за новые идеи, попробую их встроить для удобства. Кнопки ± на клавиатуре меняют скорость игры. F10 скриншот, f5/f9 быстрая сохранение/загрузка. Перемещeние стрелками и WASD. Хм, набралось достаточно, надо бы добавить хелп в игре, чтоли )))
    Честно говоря, я не ожидал такого количества откликов, игра довольно старая и, судя по коментариям в стиме, многие о ней не знают.
    • 0
      Думаю, что многие фанаты игры не знают о Стиме :-)
  • 0
    Я во второй мисси не могу построить ферму на чистом месте, говорит надо строить на очищенной територии.
    • 0
      A, понял, строить надо на спец. поле.
      • 0
        Спасибо за баг, там должна выводиться другая подсказка
  • 0
    Крутой проект, есть пара вопросов:

    1. Я вижу есть roadmap aka backlog, а есть список людей, которые сейчас работают над данной фичей? Было бы очень удобно, вот я бы хотел законтрибьютить, но не хотелось бы начать работать и узнать что кто-то уже все сделал :)

    2. А как вы относитесь к рефакторингу (к примеру, в контексте имплементации чего-то нового или багфикса)? Мне вот сразу бросилось в глаза обилие magic numbers, ну и название филдов типа _hb по которым вообще не понятно для чего они.
  • 0
    0. Спасибо на добром слове. Просто нашлись люди (Gregoire Athanase), который написал рендер и базовые алгоритмы. Pecunia и Gecube восстанавливают исходники оригинала, другие тоже свою лепту вносили, список можно в кредитсах в игре посмотреть. Както так сложилось, что все это взлетело
    1. Шкодить остался пока я один, двое реверсят оригинал, еще двое правили перевод. Можно забрать таск, тогда рядом с ним будет отображаться имя того, кто делает.
    2. Отношусь очень положительно, но без фанатизма, лучше я ченить новое запилю. А пример можно??? стараюсь писать чтобы понятно было. Кудаж без них, например с текстурами зданий нашли алгоритм который автоматически считает смещение относительно начала тайла, с текстурами людей тоже работает, но не со всеми, приходится ручками офсеты править, с мостами вообще беда.
    • 0
      Не подумайте, что придираюсь — ну вот, к примеру:
      CitizenGroup::CitizenGroup()
      {
        _hb.resize( longliver+1 );
        _hb.reserve( longliver+2 );
      }
      


      Мне вот вообще непонятно, что значит _hb (до сих пор). И кстати, какие бонусы дает то, что список из целых чисел (vector) определен как тип Peoples? Почему список не может остатся списком? ИМХО
      vector<int> _peoples 
      
      гораздо более читаемо.

      Magic numbers:
        f.seekg(1276, std::ios::cur);
      


          for (int j = 0; j < 1000; j++)
          {
            pk->skip(10);
            pk->readShort();
            pk->skip(8);
            pk->readByte();
            pk->readByte();
            pk->skip(106);
          }
      


      Ну и тд. В некоторых файлах их нет (типа game.cpp), кое-где (loader_sav.cpp) — навалом.
      • 0
        А вот так понятнее будет int y_humant[100]??? так он описывается в оригинальной игре.
        Выскажите предположения, а я потом расскажу, что это оказался за массив )))
        Учтите что игра писалась на plain-C, без классов и других плюшек.
        • 0
          Все-же обижаетесь, я смотрю =)

          А вот так понятнее будет int y_humant[100]??? так он описывается в оригинальной игре.


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

          Учтите что игра писалась на plain-C, без классов и других плюшек.


          Вы про оригинал, как понимаю. Ну ок, и что теперь в С++ классы не использовать?

          Вообщем предлагаю прекратить бессмысленный спор. Есть вещи о которых можно спорить — типа AbstractFactory нужен или нет или ставить ли пробелы в списке параметров, а есть общепринятые антипаттерны. Восстановление кода из той каши в IDA тяжелый процесс, я понимаю. Не надо воспринимать это лично.
          • 0
            Нет, что вы, никаких упреков и обид, я предложил рассмотреть физический смысл этой переменной с учетом технических возможностей того времени ))) Не критикуют только мертых, проект открытый я буду только рад, если вы поможете сделать его лучше
          • 0
            Я бы вам показал код Вангеров да не могу… там все переменные названы но толку от этого 0 (как и смысла в названиях, часто только путает).
  • 0
    К сожалению, по другому не получилось реализовать класс загрузки сейвов оригинала)))

    Класс GameLoaderC3Sav, который описан в этом файле(loader_sav.cpp) загружает данные из оригинального сохранения Caesar III, часть формата я разобрал, часть не получилось( там где не получилось понять что значит блок данных, я его пропускал pk->skip(106) ),
    Про формат, в котором оригинальная игра сохраняет данные известно вот что — это снимок участка памяти, который описывает игровую ситуцию в момент сохранения, как есть ))) но, часть этого участка жмется с помощью библиотеки PKWare Compression Library. Когда понадобилась необходимость грузить оригинальные сейвы, я этот класс написал, а потом перешел к реализации других вещей и подзабросил.

    при разборе сейва число 1276 это смещение до координат точки входа кораблей в город
  • +1
    В детстве только и оставалось мечтать, чтобы вышла новая часть или хоть какое-то дополнение. Спасибо огромное за проделанный труд. Игра просто шедевр!
  • 0
    Большое спасибо! Очень любил эту игру.
    Отчасти вижу те же проблемы с которыми столкнулся при портировании Вангеров, мне хоть и были доступны исходники но иногда казалось, что легче с 0 написать.
    Очень завидую, что в стратегии разрешение поднять легче… :)

    ЗЫ можете и с activision поговорить, иногда компании идут на встречу. ;)
  • 0
    Интересно подлежит ли реверс-разработке третий Х-СОМ? Ремейков первого горы, второй близок к первому, а третий почему-то всеми забыт :(
    • 0
      насчет такой работы не уверен, но порт есть github.com/pmprog/OpenApoc
      • 0
        О, спасибо. Даже в голову не приходило искать подобное. Может даже своими кривыми ручонками чем-нибудь помогу проекту! :D
      • 0
        Странные они какие-то. 20 дней у них висит файл, из-за которого оно падает молча с сегфолтом при старте. На пулл-реквесты не реагируют :(
        • 0
          Это опенсорс ) седня я могу посвятить проекту два часа, а завтра 0, послезавтра 0
  • 0
    А вы закончили уже? Можно играть?
    • 0
      Со старыми ресами почти можно играть
      • 0
        Я тут столкнулся с неожиданной проблемой. Даже хз с чего начать. В общем у меня на ноуте Win 10 и разрешение 3200x1800. И оно всё очень мелкое, смена разрешения не помогает. И если не включить галку про совместимость масштабирования то вообще разлазится за пределы экрана.
        • 0
          Мда мелковато будет, пока ничего не могу предложить кроме оконного режима. Может позже, сейчас как раз движком занимаюсь, учту ваше пожелание.
          • 0
            Попробовал в окне. Оно такое мелкое капец.

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