'bhc'], function(array $args) { global $GAME_ROOT; if(count($args) < 1) { echo "Usage: ./gamectl bhl_callstack \"\""; return; } $items = explode("\n", $args[0]); foreach($items as $item) { $item = trim($item); if(!$item) continue; if(preg_match('~(\d+)\s+\(\)\s+\(at (\d+):(\d+)\)~', $item, $m)) { $func_hash = (int)$m[1]; $file_hash = (int)$m[2]; $line_num = (int)$m[3]; $func_name = key(bhl_find_name_hash($func_hash)); $file_name = bhl_find_module($file_hash); if(!$file_name) $file_name = "?"; else $file_name = normalize_path($file_name, true); echo ($func_name ? $func_name : $func_hash) . "() in $file_name@$line_num \n"; } } }); task('bhl_callstack_file', function(array $args) { global $GAME_ROOT; if(count($args) < 1) { echo "Usage: ./gamectl bhl_callstack_file \"\""; return; } $callstack = ensure_read($args[0]); run('bhl_callstack', [$callstack]); }); task('bhl_find_module', function(array $args) { if(count($args) < 1) { echo "Usage: ./gamectl bhl_find_module "; return; } $mod_id = (int)$args[0]; $bhl_file = bhl_find_module($mod_id); if($bhl_file) echo "Found '$bhl_file'\n"; }); function bhl_find_name_hash($hash) { global $GAME_ROOT; $result = array(); $files = scan_files_rec(array(config_base_dir()), array('bhl')); foreach($files as $file) { $cont = file_get_contents($file); if(preg_match_all('/(\w+)/', $cont, $ms)) { foreach($ms[1] as $name) { $name = trim($name, '"'); $var_hash = crc32($name) & 0xFFFFFFF; if($var_hash === $hash) { if(!isset($result[$name])) $result[$name] = array(); $result[$name][$file] = true; } } } } return $result; } function bhl_find_module($mod_id) { global $GAME_ROOT; $files = scan_files_rec(array(config_base_dir()), array('.bhl')); $conf_dir = normalize_path(config_base_dir(), true); foreach($files as $bhl_file) { $module_path = str_replace($conf_dir, '', $bhl_file); $module_path = ltrim(substr($module_path, 0, strlen($module_path)-4), '/'); if(crc32($module_path) === $mod_id) return $bhl_file; } } function bhl_proj_file() { return getor('BHL_PROJ_FILE', config_base_dir() . '/bhl.proj'); } function bhl_proj() { global $GAME_ROOT; static $proj; if(!$proj) { $proj_file = bhl_proj_file(); $proj = json_decode(ensure_read($proj_file)); if(!$proj) throw new Exception("Bad bhl project file: $proj_file"); foreach($proj->src_dirs as $k => $v) $proj->src_dirs[$k] = _bhl_make_abs_path($proj_file, $v); foreach($proj->bindings_sources as $k => $v) $proj->bindings_sources[$k] = _bhl_make_abs_path($proj_file, $v); $proj->bindings_dll= _bhl_make_abs_path($proj_file, $proj->bindings_dll); foreach($proj->postproc_sources as $k => $v) $proj->postproc_sources[$k] = _bhl_make_abs_path($proj_file, $v); $proj->postproc_dll= _bhl_make_abs_path($proj_file, $proj->postproc_dll); $proj->result_file = _bhl_make_abs_path($proj_file, $proj->result_file); $proj->error_file = _bhl_make_abs_path($proj_file, $proj->error_file); $proj->tmp_dir = _bhl_make_abs_path($proj_file, $proj->tmp_dir); } return $proj; } function _bhl_make_abs_path($proj_file, $path) { if($path && $path[0] == '.') return dirname($proj_file) . '/' . $path; else return $path; } function bhl_result_file() { global $GAME_ROOT; return bhl_proj()->result_file; } function bhl_dir() { global $GAME_ROOT; return "$GAME_ROOT/composer/vendor/bit/bhl"; } function bhl_shell($cmd, &$ret_var, &$ret_out) { $prev_path = mono_try_override_path(); try { shell_try(bhl_dir()."/bhl $cmd", $ret_var, $ret_out); } finally { mono_try_restore_path($prev_path); } } function bhl_shell_ensure($cmd) { bhl_shell($cmd, $ret_var, $ret_out); if($ret_var !== 0) throw new Exception("Error executing shell cmd: $ret_var"); } function bhl_run($debug = true, $force = false, $exit_on_err = true) { global $GAME_ROOT; $result_file = bhl_proj()->result_file; if($force || need_to_regen($result_file, scan_files_rec(bhl_proj()->src_dirs, array('.bhl')))) { bhl_shell("compile -p " . bhl_proj_file() . " " . ($debug ? " -d" : "") . " " . ($force || !getor("BHL_USE_CACHE", true) ? " -C" : ""), $ret_var, $ret_out ); if($ret_var != 0) { bhl_handle_error_result($ret_out, bhl_proj()->error_file, $exit_on_err); if(!$exit_on_err) return false; } else echo "BHL BUNDLE: total " . kb_len(filesize($result_file)) . "\n"; } @touch($result_file); } function bhl_handle_error_result(array $ret_out, $err_file, $exit = true) { if(!is_file($err_file)) { stderr("Something went wrong: " . (is_array($ret_out) ? implode("\n", $ret_out) : "????")); if($exit) exit(1); } $history = array(); $err_lines = file($err_file); $err_count = 0; foreach($err_lines as $err) { $jerr = json_decode($err); if(!$jerr) { stderr("Something went wrong, bhl error is in invalid format: $err\n"); continue; } $file = $jerr->file; //let's show only one error per file's line if(isset($history[$file.$jerr->line])) continue; $history[$file.$jerr->line] = true; ++$err_count; stderr("BHL ERROR (#$err_count): " . $jerr->error . " \nin '$file', line {$jerr->line}\n"); if(file_exists($file)) stderr(bhl_show_position($jerr->line, $jerr->column, file($file)) . "\n"); } if($err_lines && $exit) exit(1); } function bhl_clean() { global $GAME_ROOT; $files = scan_files_rec(array(config_base_dir()), array('js', 'gen.bhl')); foreach($files as $file) { $is_conf = strpos($file, '.gen.bhl') === false; if($is_conf) { //skipping .def.js files if(strpos($file, '.def.js') !== false) continue; @touch($file); } else if(!$is_conf) { ensure_rm($file); } } bhl_clean_cache(); bhl_shell_ensure("clean"); } function bhl_clean_cache() { global $GAME_ROOT; ensure_rm("$GAME_ROOT/build/bhl/"); } function bhl_show_position($line, $row, array $lines) { if($line > 0 && $line <= count($lines)) { //handling tabs $hint = str_replace("\t", " ", $lines[$line-1]); for($c=0;$c<$row;++$c) { if($lines[$line-1][$c] === "\t") $hint .= "----"; else $hint .= "-"; } $hint .= "^"; return $hint; } else return "??? @($line:$row)"; } function bhl_line_row_to_pos($file, $line, $row) { $pos = 0; $lines = file($file); //something went wrong if($line <= 0 || $line > count($lines)) return null; for($i=0;$i<($line-1);++$i) $pos += strlen($lines[$i]); $pos += $row; return $pos; }