feat: add native Tomie adapter and strict import compatibility checks
This commit is contained in:
1 parent
c8d83b3fe7
commit
57a4edcbfd
45 files changed
+2985
-71
No files matched your search
@@ -0,0 +1,91 @@
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
import os
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from app.converter.nani_expr import assignment, parse
|
||||
from app.converter.tomie_profile import ASSEMBLY_HASH, FUNCTIONS, constant_value, extract_tables, identify
|
||||
|
||||
|
||||
def instruction(op, operand=None):
|
||||
return SimpleNamespace(mnemonic=op, operand=operand)
|
||||
|
||||
|
||||
def test_il_table_reader_accepts_constants_and_refuses_code():
|
||||
code = [instruction("ldc.i4.2"), instruction("newarr"), instruction("dup"), instruction("ldc.i4.0"),
|
||||
instruction("ldstr", SimpleNamespace(rid=1)), instruction("stelem.ref"),
|
||||
instruction("dup"), instruction("ldc.i4.1"), instruction("ldstr", SimpleNamespace(rid=2)), instruction("stelem.ref")]
|
||||
assert constant_value(code, lambda rid: {1: "A", 2: "B"}[rid]) == ["A", "B"]
|
||||
with pytest.raises(ValueError, match="инициализация"):
|
||||
constant_value([instruction("call")], lambda _: "")
|
||||
with pytest.raises(ValueError, match="Неполная"):
|
||||
constant_value([instruction("ldc.i4.1"), instruction("newarr")], lambda _: "")
|
||||
|
||||
|
||||
def test_tomie_profile_requires_exact_assembly(tmp_path):
|
||||
path = tmp_path / "TomieWGM_Data/Managed/Assembly-CSharp.dll"
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_bytes(b"a renamed unrelated game")
|
||||
assert identify(tmp_path) is None
|
||||
with pytest.raises(ValueError, match="не соответствует"):
|
||||
extract_tables(tmp_path)
|
||||
|
||||
|
||||
def test_profile_expression_functions_are_opt_in():
|
||||
with pytest.raises(ValueError, match="WillpowerDamage"):
|
||||
parse("WillpowerDamage(100, 50, 20, 0, 100)")
|
||||
assert parse("WillpowerDamage(100, 50, 20, 0, 100)", FUNCTIONS)[0] == "call"
|
||||
assert parse('\\"quoted\\"', FUNCTIONS) == ["const", "quoted"]
|
||||
assert parse('If(false, 1/0, 5)', FUNCTIONS)[0] == "if"
|
||||
assert assignment("points+=3", FUNCTIONS)["v"]["ast"] == ["bin", "+", ["name", "points"], ["const", 3]]
|
||||
|
||||
|
||||
def test_full_vm_validation_rejects_unimplemented_and_broken_commands():
|
||||
from app.converter.tomie_validation import validate_runtime
|
||||
data = {"scripts": {"Main": {"labels": {"Start": 0}, "commands": [
|
||||
{"op": "AddChoice", "args": {"ChoiceSummary": "OK", "OnSelected": ['@set result=If(true, 1, 2)']}, "line": 1},
|
||||
{"op": "Goto", "args": {"Path": ".Missing"}, "line": 2},
|
||||
{"op": "PlayBgm", "args": {"BgmPath": "song", "CustomPlugin": True}, "line": 3},
|
||||
{"op": "NativeCode", "args": {}, "line": 4},
|
||||
{"op": "BeginIf", "args": {"Expression": "true"}, "line": 5},
|
||||
{"op": "AddChoice", "args": {"OnSelected": ['@set points+1.5']}, "line": 6},
|
||||
]}}}
|
||||
issues = validate_runtime(data)
|
||||
assert {item.code for item in issues} == {"missing_target", "unsupported_argument", "unsupported_command", "invalid_branch", "unsupported_choice"}
|
||||
assert not any(item.location == "Main:1" for item in issues)
|
||||
|
||||
|
||||
def test_choice_templates_and_quoted_commas_are_validated_without_evaluating_them():
|
||||
from app.converter.tomie_validation import inline_commands, validate_runtime
|
||||
assert inline_commands('@set x=If(true, "a,b", "c"), @return') == ['@set x=If(true, "a,b", "c")', '@return']
|
||||
data = {"scripts": {"Main": {"labels": {}, "commands": [
|
||||
{"op": "AddChoice", "args": {"OnSelected": {"template": '@set result="{answer}"'}}},
|
||||
{"op": "AddChoice", "args": {"OnSelected": ['@goto Other.Start']}},
|
||||
]}, "Other": {"labels": {"Start": 0}, "commands": []}}}
|
||||
assert validate_runtime(data) == []
|
||||
with pytest.raises(ValueError, match="Незакрытая"):
|
||||
inline_commands('@set x=If(true, 1')
|
||||
|
||||
|
||||
def test_native_effect_and_ui_extensions_are_rejected_before_media_export():
|
||||
from app.converter.tomie_validation import validate_runtime
|
||||
data = {"scripts": {"Main": {"labels": {}, "commands": [
|
||||
{"op": "Spawn", "args": {"Path": "NativePlugin"}},
|
||||
{"op": "ShowUI", "args": {"UINames": ["CustomScreen"]}},
|
||||
{"op": "EndIf", "args": {}},
|
||||
]}}}
|
||||
assert {issue.code for issue in validate_runtime(data)} == {"unsupported_effect", "unsupported_ui"}
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os.getenv("PGAMES_TOMIE_ORIGINAL"), reason="Requires the user's original Tomie build")
|
||||
def test_original_il_tables_match_the_recovered_port():
|
||||
from hashlib import sha256
|
||||
root = Path(os.environ["PGAMES_TOMIE_ORIGINAL"])
|
||||
assert sha256((root / "TomieWGM_Data/Managed/Assembly-CSharp.dll").read_bytes()).hexdigest() == ASSEMBLY_HASH
|
||||
tables = extract_tables(root)
|
||||
assert {key: len(value) for key, value in tables.items()} == {"names": 2738, "normalComment": 55, "lewdComment": 181, "videos": 191}
|
||||
if os.getenv("PGAMES_TOMIE_PORT"):
|
||||
reference = json.loads((Path(os.environ["PGAMES_TOMIE_PORT"]) / "story/function_data.json").read_text(encoding="utf-8"))
|
||||
assert tables == reference
|
||||
Reference in new issue
Block a user