hellbound/gamectl.d/meta.php

679 lines
18 KiB
PHP
Raw Normal View History

2021-11-26 11:16:25 +03:00
<?php
include_once(dirname(__FILE__) . "/../utils/metagen/targets/php/php.inc.php");
include_once(dirname(__FILE__) . "/../utils/metagen/targets/cs/cs_generator.inc.php");
include_once(dirname(__FILE__) . "/../utils/metagen/targets/cs/cs.inc.php");
define('META_FLT_CLIENT', 1);
define('META_FLT_SERVER', 2);
define('META_FLT_BINDINGS', 4);
function get_meta($opts)
{
$m = parse_meta();
$m = _meta_filter($m, $opts);
return $m;
}
function parse_meta()
{
global $GAME_ROOT;
$meta_dirs = array("$GAME_ROOT/meta/");
$meta_files = scan_files_rec($meta_dirs);
$meta_cache = "$GAME_ROOT/build/tmp/meta.cache";
if(need_to_regen($meta_cache, $meta_files))
{
$meta = mtg_parse_meta($meta_dirs);
process_meta($meta);
validate_meta($meta);
ensure_write($meta_cache, serialize($meta));
return $meta;
}
else
return unserialize(ensure_read($meta_cache));
}
function process_meta(mtgMetaInfo $meta)
{
foreach($meta->getUnits() as $u)
{
$o = $u->object;
if($o instanceof mtgMetaStruct)
{
if($o->hasToken("table") && $o->hasToken("id") && $o->hasToken("owner"))
{
foreach($o->getFields() as $fld)
$fld->setToken("optional", true);
}
}
}
}
function validate_meta(mtgMetaInfo $meta)
{
foreach($meta->getUnits() as $u)
{
$o = $u->object;
if($o instanceof mtgMetaStruct)
{
$optional_spotted = false;
foreach($o->getFields() as $idx => $fld)
{
if(!$optional_spotted && $fld->hasToken("optional"))
$optional_spotted = true;
else if($optional_spotted && !$fld->hasToken("optional"))
throw new Exception("Field '{$fld->getName()}' in struct '{$o->getName()}' must be marked as @optional since there are preceding optional fields");
}
}
}
}
function _meta_filter(mtgMetaInfo $meta, $opts)
{
$filtered = new mtgMetaInfo();
foreach($meta->getUnits() as $u)
{
$o = $u->object;
if(($opts & META_FLT_CLIENT) != 0 &&
($o->hasToken('autogen') ||
strpos($u->file, 'bind.meta') === false)
)
{
$filtered->addUnit($u);
}
if(($opts & META_FLT_SERVER) != 0 &&
strpos($u->file, 'bind.meta') === false
)
{
$filtered->addUnit($u);
}
if(($opts & META_FLT_BINDINGS) != 0 && (
strpos($u->file, 'bind.meta') !== false ||
$o instanceof mtgMetaEnum ||
($o instanceof mtgUserType && $o->hasToken('bhl_bind')))
)
{
$filtered->addUnit($u);
}
}
return $filtered;
}
/**
* @deps cs_autogen
* @alias dlls
*/
function task_client_dlls()
{
global $GAME_ROOT;
build_mono_dll(
"$GAME_ROOT/dll_src/crc/",
"$GAME_ROOT/Assets/Plugins/game_crc.dll"
);
build_mono_dll(
"$GAME_ROOT/dll_src/msgpack/",
"$GAME_ROOT/Assets/Plugins/game_msgpack.dll"
);
build_mono_dll(
"$GAME_ROOT/dll_src/CodeStage/AntiCheatToolkit/Scripts/",
"$GAME_ROOT/Assets/Plugins/game_obscured.dll",
array(
"UnityEngine.dll",
"$GAME_ROOT/Assets/Plugins/game_crc.dll",
)
);
build_mono_dll(
"$GAME_ROOT/utils/metagen/targets/cs/metagen.cs",
"$GAME_ROOT/Assets/Plugins/game_metagen.dll",
array(
"$GAME_ROOT/Assets/Plugins/game_msgpack.dll",
"$GAME_ROOT/Assets/Plugins/game_obscured.dll",
),
is_dev() ? "-debug" : ""
);
build_mono_dll(
"$GAME_ROOT/dll_src/metagen/autogen",
"$GAME_ROOT/Assets/Plugins/game_autogen.dll",
array(
"UnityEngine.dll",
"$GAME_ROOT/Assets/Plugins/game_metagen.dll",
"$GAME_ROOT/Assets/Plugins/game_obscured.dll",
"$GAME_ROOT/Assets/Plugins/game_msgpack.dll",
),
is_dev() ? "-debug -define:DEBUG" : ""
);
//$bhl_dll = bhl_build_dll();
//build_mono_dll(
// "$GAME_ROOT/dll_src/bhl/autobind.cs",
// "$GAME_ROOT/Assets/Plugins/game_bhl_autobind.dll",
// array(
// "UnityEngine.dll",
// "$GAME_ROOT/Assets/Plugins/game_metagen.dll",
// "$GAME_ROOT/Assets/Plugins/game_autogen.dll",
// "$GAME_ROOT/Assets/Plugins/game_obscured.dll",
// $bhl_dll
// ),
// is_dev() ? "-debug" : ""
//);
}
/**
* @deps unity_defines,cs_autogen,php_autogen
*/
function task_autogen()
{}
/**
* @deps make_client_settings
*/
function task_cs_autogen()
{
global $GAME_ROOT;
include_once("$GAME_ROOT/utils/metagen/metagen.inc.php");
$meta = get_meta(META_FLT_CLIENT);
mtg_run(new CustomCsGenerator(), array(
"codegen" => new CustomCsCodegen(),
"meta" => $meta,
"out-dir" => "$GAME_ROOT/dll_src/metagen/autogen" ,
"bundle" => "$GAME_ROOT/dll_src/metagen/autogen/bundle.cs",
));
}
function task_php_autogen()
{
global $GAME_ROOT;
include_once("$GAME_ROOT/utils/metagen/metagen.inc.php");
include_once("$GAME_ROOT/utils/metagen/targets/php/php_generator.inc.php");
$meta = get_meta(META_FLT_SERVER);
mtg_run(new mtgPHPGenerator(),
array(
"codegen" => new CustomPHPCodegen(array(
'str2num' => 'flt_str2num',
'str2hash' => 'flt_str2hash',
'hex2num' => 'flt_hex2num',
'str2stamp' => 'flt_str2stamp',
'i18n' => 'flt_i18n',
)),
"meta" => $meta,
"out-dir" => "$GAME_ROOT/utils/autogen/" ,
"inc-dir" => "GAME_ROOT.'/utils/autogen/'",
"bundle" => "$GAME_ROOT/utils/autogen/bundle.inc.php"
));
init_php_autogen_bundle();
}
class CustomPHPCodegen extends mtgPHPCodegen
{
function preprocessField(mtgMetaStruct $struct, mtgMetaField $field)
{
if($field->hasToken("i18n"))
{
$new_fld = new mtgMetaField('__' . $field->getName(), new mtgTypeRef(new mtgArrType(new mtgTypeRef(new mtgBuiltinType('string')))));
$new_fld->setTokens(array('default' => '[]'));
$this->addTempField($struct, $new_fld);
}
else
parent::preprocessField($struct, $field);
}
}
class CustomCsGenerator extends mtgCsGenerator
{
function genBundle($OUT, array $DEPS, mtgMetaInfo $meta, mtgCsCodegen $codegen, array $units)
{
$str = parent::genBundle($OUT, $DEPS, $meta, $codegen, $units);
$str = "using CodeStage.AntiCheat.ObscuredTypes;\n" . $str;
return $str;
}
}
class CustomCsCodegen extends mtgCsCodegen
{
function fillStructRepls(mtgMetaStruct $struct, &$repl)
{
parent::fillStructRepls($struct, $repl);
if($struct->hasToken('diffable'))
{
$all_diff_classes = array();
$repl['%ext_methods%'] .= $this->genStructDiff($struct, $all_diff_classes);
foreach($all_diff_classes as $class)
$repl['%ext_methods%'] .= $this->genDiffHelpers($class);
$repl['%ext_methods%'] .= $this->genRemovedIdsHelpers();
}
if($struct->hasToken("id"))
{
$repl['%ext_methods%'] .=
"\n public ulong getPrimaryId() {".
"\n " . sprintf("return (ulong)%s;", $struct->getToken("id")) .
"\n }";
}
}
function genRemovedIdsHelpers()
{
$str = <<<EOD
static public List<ulong> GetClassRemovedIds(List<DataClassIds> removed_ids, uint class_id)
{
if(removed_ids == null)
return null;
DataClassIds res;
if(!FindClassRemovedIds(removed_ids, class_id, out res))
{
res = new DataClassIds();
res.class_id = class_id;
res.ids = new List<ulong>();
removed_ids.Add(res);
}
return res.ids;
}
static bool FindClassRemovedIds(List<DataClassIds> removed_ids, uint class_id, out DataClassIds res)
{
for(int i=0;i<removed_ids.Count;++i)
{
if(removed_ids[i].class_id == class_id)
{
res = removed_ids[i];
return true;
}
}
res = default(DataClassIds);
return false;
}
EOD;
return $str;
}
function preprocessField(mtgMetaStruct $struct, mtgMetaField $field)
{
if($field->hasToken('i18n'))
{
$new_fld = new mtgMetaField('__' . $field->getName(), new mtgTypeRef(new mtgArrType(new mtgTypeRef(new mtgBuiltinType('string')))));
$new_fld->setTokens(array('default' => '[]'));
$this->addTempField($struct, $new_fld);
//NOTE: renaming field, adding a prefix, real field access will be done via property
$this->tempRenameField($field, '_' . $field->getName());
}
else
parent::preprocessField($struct, $field);
}
function genI18NField($field, $prop_name)
{
global $pluralization_mark;
$type = $this->genFieldNativeType($field);
return "\n\n".
" public $type $prop_name {\n".
" get { return plural_$prop_name(double.NaN); }\n".
" set { _{$prop_name} = value; }\n".
" }\n".
" public {$type} plural_${prop_name}(double force_n) { return Meta.I18NPick(_{$prop_name}, __{$prop_name}, \"{$pluralization_mark}\", force_n); }\n";
}
function genFieldDecl(mtgMetaStruct $struct, mtgMetaField $field)
{
$str = parent::genFieldDecl($struct, $field);
$type = $field->getType();
if($field->hasToken('i18n'))
{
$prop_name = substr($field->getName(), 1);
$str .= $this->genI18NField($field, $prop_name);
}
else if($field->hasToken('obscured'))
{
if($type instanceof mtgBuiltinType && $type->isUint32())
$str = str_replace(" uint ", " ObscuredUInt ", $str);
else if($type instanceof mtgBuiltinType && $type->isUint64())
$str = str_replace(" ulong ", " ObscuredULong ", $str);
else if($type instanceof mtgBuiltinType && $type->isInt64())
$str = str_replace(" long ", " ObscuredLong ", $str);
else if($type instanceof mtgBuiltinType && $type->isFloat())
$str = str_replace(" float ", " ObscuredFloat ", $str);
else
throw new Exception("Not supported obscured type: " . $type);
}
return $str;
}
function genFieldDiff(mtgMetaField $field, $field_idx, $prefix, array &$all_diff_classes)
{
$str = '';
$type = $field->getType();
$name = $field->getName();
if($type instanceof mtgMetaStruct)
{
if(!$type->hasToken('POD'))
throw new Exception("Diffable struct '{$type->getName()}' must be POD");
$all_diff_classes[] = $type->getName();
$str .= "{\n";
$str .= "var __tmp = {$prefix}{$name};\n";
$str .= "if(DiffOne(ref __tmp, old.{$prefix}{$name})) { no_changes &= false; if(diff != null) { diff.{$prefix}{$name} = __tmp; Meta.SetFieldDirty(ref diff.fields_mask, {$field_idx}); } }\n";
$str .= "}\n";
}
else if($type instanceof mtgArrType)
{
$sub_type = $type->getValue();
if(!$sub_type->hasToken('POD'))
throw new Exception("Diffable struct '{$sub_type->getName()}' must be POD");
$all_diff_classes[] = $sub_type->getName();
$str .= "if(DiffCollection({$prefix}{$name}, old.{$prefix}{$name}, diff == null ? null : diff.{$prefix}{$name}, removed)) { no_changes &= false; if(diff != null) { Meta.SetFieldDirty(ref diff.fields_mask, {$field_idx}); } }\n";
}
else if($type instanceof mtgBuiltinType)
{
$str .= "if(diff != null) { diff.$name = {$prefix}{$name}; } if({$prefix}{$name} != old.{$prefix}{$name}) { no_changes &= false; if(diff != null) { Meta.SetFieldDirty(ref diff.fields_mask, {$field_idx}); } }\n";
}
else
throw new Exception("Diff for field '$name' is not supported");
return $str;
}
function genStructDiff(mtgMetaStruct $struct, array &$all_diff_classes)
{
$class = $struct->getName();
$str = "\n\n";
$str .= "public bool GetDiff($class old, $class diff = null, List<DataClassIds> removed = null)\n";
$str .= "{\n";
$str .= " if(diff != null) diff.reset();\n";
$str .= " bool no_changes = true;\n";
$fields = $struct->getFields();
$field_idx = -1;
foreach($fields as $field)
{
++$field_idx;
if($field->hasToken('nodiff'))
continue;
$str .= $this->genFieldDiff($field, $field_idx, '', $all_diff_classes);
}
$str .= " return !no_changes;\n";
$str .= "}\n";
return $str;
}
function genDiffHelpers($class)
{
$struct = $this->info->findUnit($class)->object;
$compare_fn_str = $this->genCompareFunc($struct);
$has_bitfields_token = $struct->hasToken("bitfields");
$compare_func_name = $has_bitfields_token ? "CompareAndMarkFields" : "IsEqual";
$str = <<<EOD
static public bool DiffOne(ref $class curr, $class old)
{
return !$compare_func_name(ref curr, old);
}
static public bool DiffCollection(List<$class> items, List<$class> old_items, List<$class> changed, List<DataClassIds> removed)
{
bool has_diff = false;
if(changed != null)
changed.Clear();
SortByPrimaryId(ref items);
SortByPrimaryId(ref old_items);
int i = 0;
int old_i = 0;
for(; i < items.Count && old_i < old_items.Count;)
{
var old = old_items[old_i];
var item = items[i];
if(old.getPrimaryId() < item.getPrimaryId())
{
var removed_ids = GetClassRemovedIds(removed, $class.STATIC_CLASS_ID);
if(removed_ids != null)
removed_ids.Add(old.getPrimaryId());
old_i++;
has_diff = true;
}
else if(old.getPrimaryId() > item.getPrimaryId())
{
item.SetDirtyMask();
if(changed != null)
changed.Add(item);
i++;
has_diff = true;
}
else
{
if(!$compare_func_name(ref item, old))
{
if(changed != null)
changed.Add(item);
has_diff = true;
}
i++;
old_i++;
}
}
//items leftovers are considered changed
for(; i < items.Count; i++)
{
var item = items[i];
item.SetDirtyMask();
if(changed != null)
changed.Add(item);
has_diff = true;
}
//old items leftovers are considered removed
for(; old_i < old_items.Count; old_i++)
{
var removed_ids = GetClassRemovedIds(removed, $class.STATIC_CLASS_ID);
if(removed_ids != null)
removed_ids.Add(old_items[old_i].getPrimaryId());
has_diff = true;
}
//if(changed.Count > 0)
// Meta.LogDebug("Changed in collection $class");
//if(removed_ids.Count > 0)
// Meta.LogDebug("Removed in collection $class");
return has_diff;
}
class {$class}Compare : IComparer<$class>
{
public int Compare($class o1, $class o2)
{
var id1 = o1.getPrimaryId();
var id2 = o2.getPrimaryId();
if(id1 == id2)
return 0;
return id1 > id2 ? 1 : -1;
}
}
static {$class}Compare {$class}_compare = new {$class}Compare();
static void SortByPrimaryId(ref List<$class> list)
{
list.Sort({$class}_compare);
}
public static bool $compare_func_name(ref $class a, $class b)
{
bool is_equal = true;
$compare_fn_str
return is_equal;
}
EOD;
return $str;
}
function genCompareFunc(mtgMetaStruct $struct)
{
$has_bitfields_token = $struct->hasToken("bitfields");
$str = '';
$index = 0;
if($has_bitfields_token)
$str .= "a.ResetFieldMask();\n";
foreach($struct->getFields() as $field)
{
$str .= $this->genFieldCompare($field, $index, $has_bitfields_token);
$index++;
}
if($has_bitfields_token)
{
$str .= $this->offset(3)."if(!is_equal)\n";
$str .= $this->offset(4)."a.SetPrimaryFieldsChanged();";
}
return $str;
}
function genFieldCompare(mtgMetaField $field, $index, $has_bitfields_token)
{
$name = $this->genFieldName($field);
$str = '';
$type = $field->getType();
if($type instanceof mtgArrType)
{
$str .= "if(a.{$name} == null || b.{$name} == null || a.{$name}.Count != b.{$name}.Count) return false;\n";
$str .= "for(int i=0;i<a.{$name}.Count;++i) {\n ";
$sub_type = $type->getValue();
if($sub_type instanceof mtgMetaStruct)
$str .= "if(!IsEqual(ref a.{$name}[i], b.{$name}[i])) return false;\n";
else
$str .= "if(a.{$name}[i] != b.{$name}[i]) return false;\n";
$str .= "}\n";
}
else if($type instanceof mtgMetaStruct)
$str .= "if(!IsEqual(ref a.{$name}, b.{$name})) return false;\n";
else if($type instanceof mtgBuiltinType && $type->isString())
{
$str .= $this->offset(3)."if((a.{$name} == null ? \"\" : a.{$name}) != (b.{$name} == null ? \"\" : b.{$name}))\n";
if($has_bitfields_token)
{
$str .= $this->offset(3)."{\n";
$str .= $this->offset(4)."Meta.SetFieldDirty(ref a.fields_mask, $index);\n";
$str .= $this->offset(4)."is_equal = false;\n";
$str .= $this->offset(3)."}\n";
}
else
$str .= "return false;\n";
}
else
{
$str .= $this->offset(3)."if(a.{$name} != b.{$name})\n";
if($has_bitfields_token)
{
$str .= $this->offset(3)."{\n";
$str .= $this->offset(4)."Meta.SetFieldDirty(ref a.fields_mask, $index);\n";
$str .= $this->offset(4)."is_equal = false;\n";
$str .= $this->offset(3)."}\n";
}
else
$str .= "return false;\n";
}
return $str;
}
}
function init_php_autogen_bundle()
{
global $GAME_ROOT;
include_once("$GAME_ROOT/utils/gme/utils.inc.php");
include_once("$GAME_ROOT/utils/autogen/bundle.inc.php");
}
function mtg_flt_i18n($val, $name, $struct, $args)
{
//NOTE: if value is an array we assume it's an expression
// and assign it to a special property
if(is_array($val))
{
$arr_name = '__' . $name;
foreach($val as $v)
array_push($struct->$arr_name, i18n_decode_string($v));
return '';
}
else
return i18n_decode_string($val);
}
function mtg_flt_str2num($val, $name, $struct, $args)
{
if(is_string($val))
{
if(strlen($val) === 0)
throw new Exception("Bad value, string empty, crc28 can't be generated");
//special case
if(strpos($val, '@') !== 0)
throw new Exception("@ expected");
$val = substr($val, 1) . '.conf.js';
$path = config_base_dir() . '/' . $val;
if(!file_exists($path))
throw new Exception("No such file '$path'");
if(config_get_header($path, $proto_id, $alias))
$val = $proto_id;
else
$val = mtg_crc28($val);
}
if(!is_numeric($val))
throw new Exception("Bad value, not a number(" . serialize($val) . ")");
return 1*$val;
}