'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]); }); 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; } 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_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_result_file() { global $GAME_ROOT; return "$GAME_ROOT/build/ext_config/bhl.bytes"; } 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, $check_deps = true, $exit_on_err = true) { global $GAME_ROOT; $bhl_inc_dir = config_base_dir(); $all_files = scan_files_rec(array(config_base_dir()), array('.bhl')); $res_file = bhl_result_file(); _bhl_shuffle_files($all_files); $res_file = bhl_result_file(); $res = bhl_run_ex($bhl_inc_dir, $all_files, $res_file, $debug, $force, $check_deps, $exit_on_err); return $res; } function _bhl_shuffle_files(&$files) { shuffle($files); } function bhl_run_ex($bhl_inc_dir, array $bhl_files, $result_file, $debug = true, $force = false, $check_deps = true, $exit_on_err = true) { global $GAME_ROOT; ensure_mkdir("$GAME_ROOT/build"); $post_proc_file = "$GAME_ROOT/build/bhl.post.".crc32($result_file); $err_file = "$GAME_ROOT/build/bhl.error"; ensure_rm($err_file); $user_sources = array( get("UNITY_ASSETS_DIR")."/Scripts/bhl/register.cs", get("UNITY_ASSETS_DIR")."/Scripts/bhl/autobind.cs" ); $postproc_sources = array( get("UNITY_ASSETS_DIR")."/Scripts/bhl/postproc.cs", ); $bhl_deps = $bhl_files; $bhl_deps = array_merge($bhl_deps, $user_sources); $bhl_deps = array_merge($bhl_deps, $postproc_sources); $bhl_deps = array_merge($bhl_deps, scan_files_rec(array(bhl_dir()), array(".cs"))); if($force || need_to_regen($result_file, $bhl_deps) || need_to_regen($post_proc_file, $bhl_deps)) { $bhl_file_list = "$GAME_ROOT/build/bhl/file_list.tmp"; ensure_write($bhl_file_list, implode("\n", $bhl_files)); putenv("POSTPROC_PROJ_ROOT=$GAME_ROOT"); putenv("POSTPROC_RESULT=$post_proc_file"); if($force) putenv("POSTPROC_FORCE=1"); $bhl_dir = bhl_dir(); bhl_shell("compile " . "--user-sources=".implode(",",$user_sources)." --postproc-sources=".implode(",",$postproc_sources)." " . "--dir=$bhl_inc_dir --files=$bhl_file_list --result=$result_file " . "--tmp-dir=$GAME_ROOT/build/bhl/ " . "--error=$GAME_ROOT/build/bhl.error " . "--deterministic " . "--threads=" . (is_win() ? 1 : 4) . " " . ($debug ? " -d" : "") . " " . ($force ? " -C" : "") . " ". (!$check_deps ? " -N" : "") . " ", $ret_var, $ret_out ); if($ret_var != 0) { bhl_handle_error_result($ret_out, $err_file, $exit_on_err); if(!$exit_on_err) return false; } else echo "BHL BUNDLE: total " . kb_len(filesize($result_file)) . "\n"; } @touch($result_file); @touch($post_proc_file); $res = bhl_ast_post_proc($result_file, $post_proc_file); return $res; } 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); } $err = file_get_contents($err_file); $jerr = json_decode($err); if(!$jerr) { stderr("Something went wrong, bhl error is in invalid format: $err"); if($exit) exit(1); } $file = $jerr->file; stderr("BHL ERROR: \n" . $jerr->error . " \nin '$file', line {$jerr->line}\n"); stderr(bhl_show_position($jerr->line, $jerr->column, file($file)) . "\n"); if($exit) exit(1); } class BhlAstProcResult { var $res_file; var $func2assets_deep = array(); var $func2refs_deep = array(); } function bhl_ast_post_proc($res_file, $post_proc_file) { $res = new BhlAstProcResult(); $res->res_file = $res_file; $data = config_msgpack_unpack(ensure_read($post_proc_file)); if(!$data) return $res; $func2assets = $data[0]; foreach($func2assets as $item) { list($func1, $func2, $assets) = $item; $func = ($func2 << 31) | $func1; $res->func2assets_deep[$func] = array_flip($assets); } $func2crefs = $data[1]; foreach($func2crefs as $item) { list($func1, $func2, $crefs) = $item; $func = ($func2 << 31) | $func1; $res->func2refs_deep[$func] = array_flip($crefs); } return $res; } 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 < count($lines)) { $txt = $lines[$line-1]; $c = str_repeat('-', $row); return "{$txt}{$c}^"; } //weird special case else if($line-1 == count($lines)) { $txt = $lines[count($lines)-1]; $c = str_repeat('-', $row); return "{$txt}{$c}^"; } else return "???"; } function bhl_get_past_input($c, $in, $context_chars) { $past = substr($in, 0, $c+1); return (strlen($past) > $context_chars ? '...' : '') . substr($past, -$context_chars); } function bhl_get_upcoming_input($c, $in, $context_chars) { $next = substr($in, $c+1); return substr($next, 0, $context_chars) . (strlen($next) > $context_chars ? '...' : ''); } function bhl_line_row_to_pos($file, $line, $row) { $pos = 0; $lines = file($file); //something went wrong if(count($lines) < $line) return null; for($i=0;$i<($line-1);++$i) $pos += strlen($lines[$i]); $pos += $row; return $pos; }