mirror of
https://github.com/isledecomp/isle.git
synced 2024-11-22 15:48:09 -05:00
Match static function variables (#530)
* Match static function variables * IsleApp::Tick static variables
This commit is contained in:
parent
0497ef13fb
commit
264b9e815b
10 changed files with 167 additions and 17 deletions
|
@ -415,8 +415,8 @@ set_property(TARGET lego1 PROPERTY SUFFIX ".DLL")
|
||||||
if (ISLE_BUILD_APP)
|
if (ISLE_BUILD_APP)
|
||||||
add_executable(isle WIN32
|
add_executable(isle WIN32
|
||||||
ISLE/res/isle.rc
|
ISLE/res/isle.rc
|
||||||
ISLE/isleapp.cpp
|
|
||||||
ISLE/define.cpp
|
ISLE/define.cpp
|
||||||
|
ISLE/isleapp.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(isle PRIVATE ISLE_APP)
|
target_compile_definitions(isle PRIVATE ISLE_APP)
|
||||||
|
|
|
@ -32,9 +32,3 @@ int g_targetDepth = 16;
|
||||||
|
|
||||||
// GLOBAL: ISLE 0x410064
|
// GLOBAL: ISLE 0x410064
|
||||||
int g_reqEnableRMDevice = 0;
|
int g_reqEnableRMDevice = 0;
|
||||||
|
|
||||||
// GLOBAL: ISLE 0x4101bc
|
|
||||||
int g_startupDelay = 200;
|
|
||||||
|
|
||||||
// GLOBAL: ISLE 0x4101c0
|
|
||||||
MxLong g_lastFrameTime = 0;
|
|
||||||
|
|
|
@ -21,7 +21,5 @@ extern int g_targetWidth;
|
||||||
extern int g_targetHeight;
|
extern int g_targetHeight;
|
||||||
extern int g_targetDepth;
|
extern int g_targetDepth;
|
||||||
extern int g_reqEnableRMDevice;
|
extern int g_reqEnableRMDevice;
|
||||||
extern int g_startupDelay;
|
|
||||||
extern MxLong g_lastFrameTime;
|
|
||||||
|
|
||||||
#endif // DEFINE_H
|
#endif // DEFINE_H
|
||||||
|
|
|
@ -765,6 +765,12 @@ void IsleApp::LoadConfig()
|
||||||
// FUNCTION: ISLE 0x402c20
|
// FUNCTION: ISLE 0x402c20
|
||||||
inline void IsleApp::Tick(BOOL sleepIfNotNextFrame)
|
inline void IsleApp::Tick(BOOL sleepIfNotNextFrame)
|
||||||
{
|
{
|
||||||
|
// GLOBAL: ISLE 0x4101c0
|
||||||
|
static MxLong g_lastFrameTime = 0;
|
||||||
|
|
||||||
|
// GLOBAL: ISLE 0x4101bc
|
||||||
|
static int g_startupDelay = 200;
|
||||||
|
|
||||||
if (!this->m_windowActive) {
|
if (!this->m_windowActive) {
|
||||||
Sleep(0);
|
Sleep(0);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -134,7 +134,9 @@ def _load_cvdump(self):
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self._db.set_recomp_symbol(addr, sym.node_type, sym.name(), sym.size())
|
self._db.set_recomp_symbol(
|
||||||
|
addr, sym.node_type, sym.name(), sym.decorated_name, sym.size()
|
||||||
|
)
|
||||||
|
|
||||||
for lineref in cv.lines:
|
for lineref in cv.lines:
|
||||||
addr = self.recomp_bin.get_abs_addr(lineref.section, lineref.offset)
|
addr = self.recomp_bin.get_abs_addr(lineref.section, lineref.offset)
|
||||||
|
@ -168,7 +170,12 @@ def _load_markers(self):
|
||||||
self._db.skip_compare(fun.offset)
|
self._db.skip_compare(fun.offset)
|
||||||
|
|
||||||
for var in codebase.iter_variables():
|
for var in codebase.iter_variables():
|
||||||
self._db.match_variable(var.offset, var.name)
|
if var.is_static and var.parent_function is not None:
|
||||||
|
self._db.match_static_variable(
|
||||||
|
var.offset, var.name, var.parent_function
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._db.match_variable(var.offset, var.name)
|
||||||
|
|
||||||
for tbl in codebase.iter_vtables():
|
for tbl in codebase.iter_vtables():
|
||||||
self._db.match_vtable(tbl.offset, tbl.name)
|
self._db.match_vtable(tbl.offset, tbl.name)
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
orig_addr int,
|
orig_addr int,
|
||||||
recomp_addr int,
|
recomp_addr int,
|
||||||
name text,
|
name text,
|
||||||
|
decorated_name text,
|
||||||
size int,
|
size int,
|
||||||
should_skip int default(FALSE)
|
should_skip int default(FALSE)
|
||||||
);
|
);
|
||||||
|
@ -65,12 +66,13 @@ def set_recomp_symbol(
|
||||||
addr: int,
|
addr: int,
|
||||||
compare_type: Optional[SymbolType],
|
compare_type: Optional[SymbolType],
|
||||||
name: Optional[str],
|
name: Optional[str],
|
||||||
|
decorated_name: Optional[str],
|
||||||
size: Optional[int],
|
size: Optional[int],
|
||||||
):
|
):
|
||||||
compare_value = compare_type.value if compare_type is not None else None
|
compare_value = compare_type.value if compare_type is not None else None
|
||||||
self._db.execute(
|
self._db.execute(
|
||||||
"INSERT INTO `symbols` (recomp_addr, compare_type, name, size) VALUES (?,?,?,?)",
|
"INSERT INTO `symbols` (recomp_addr, compare_type, name, decorated_name, size) VALUES (?,?,?,?,?)",
|
||||||
(addr, compare_value, name, size),
|
(addr, compare_value, name, decorated_name, size),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_unmatched_strings(self) -> List[str]:
|
def get_unmatched_strings(self) -> List[str]:
|
||||||
|
@ -214,6 +216,56 @@ def match_vtable(self, addr: int, name: str) -> bool:
|
||||||
|
|
||||||
return did_match
|
return did_match
|
||||||
|
|
||||||
|
def match_static_variable(self, addr: int, name: str, function_addr: int) -> bool:
|
||||||
|
"""Matching a static function variable by combining the variable name
|
||||||
|
with the decorated (mangled) name of its parent function."""
|
||||||
|
|
||||||
|
cur = self._db.execute(
|
||||||
|
"""SELECT name, decorated_name
|
||||||
|
FROM `symbols`
|
||||||
|
WHERE orig_addr = ?""",
|
||||||
|
(function_addr,),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result := cur.fetchone()) is None:
|
||||||
|
logger.error("No function for static variable: %s", name)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get the friendly name for the "failed to match" error message
|
||||||
|
(function_name, decorated_name) = result
|
||||||
|
|
||||||
|
# Now we have to combine the variable name (read from the marker)
|
||||||
|
# and the decorated name of the enclosing function (the above variable)
|
||||||
|
# into a LIKE clause and try to match.
|
||||||
|
# For example, the variable "g_startupDelay" from function "IsleApp::Tick"
|
||||||
|
# has symbol: "?g_startupDelay@?1??Tick@IsleApp@@QAEXH@Z@4HA"
|
||||||
|
# The function's decorated name is: "?Tick@IsleApp@@QAEXH@Z"
|
||||||
|
cur = self._db.execute(
|
||||||
|
"""UPDATE `symbols`
|
||||||
|
SET orig_addr = ?
|
||||||
|
WHERE name LIKE '%' || ? || '%' || ? || '%'
|
||||||
|
AND orig_addr IS NULL
|
||||||
|
AND (compare_type = ? OR compare_type = ? OR compare_type IS NULL)""",
|
||||||
|
(
|
||||||
|
addr,
|
||||||
|
name,
|
||||||
|
decorated_name,
|
||||||
|
SymbolType.DATA.value,
|
||||||
|
SymbolType.POINTER.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
did_match = cur.rowcount > 0
|
||||||
|
|
||||||
|
if not did_match:
|
||||||
|
logger.error(
|
||||||
|
"Failed to match static variable %s from function %s",
|
||||||
|
name,
|
||||||
|
function_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
return did_match
|
||||||
|
|
||||||
def match_variable(self, addr: int, name: str) -> bool:
|
def match_variable(self, addr: int, name: str) -> bool:
|
||||||
did_match = self._match_on(SymbolType.DATA, addr, name) or self._match_on(
|
did_match = self._match_on(SymbolType.DATA, addr, name) or self._match_on(
|
||||||
SymbolType.POINTER, addr, name
|
SymbolType.POINTER, addr, name
|
||||||
|
|
|
@ -47,6 +47,10 @@ class ParserError(Enum):
|
||||||
# to ignore things like string literal that are not variables.
|
# to ignore things like string literal that are not variables.
|
||||||
GLOBAL_NOT_VARIABLE = 111
|
GLOBAL_NOT_VARIABLE = 111
|
||||||
|
|
||||||
|
# WARN: A marked static variable inside a function needs to have its
|
||||||
|
# function marked too, and in the same module.
|
||||||
|
ORPHANED_STATIC_VARIABLE = 112
|
||||||
|
|
||||||
# This code or higher is an error, not a warning
|
# This code or higher is an error, not a warning
|
||||||
DECOMP_ERROR_START = 200
|
DECOMP_ERROR_START = 200
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ def is_nameref(self) -> bool:
|
||||||
@dataclass
|
@dataclass
|
||||||
class ParserVariable(ParserSymbol):
|
class ParserVariable(ParserSymbol):
|
||||||
is_static: bool = False
|
is_static: bool = False
|
||||||
|
parent_function: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
)
|
)
|
||||||
from .marker import (
|
from .marker import (
|
||||||
DecompMarker,
|
DecompMarker,
|
||||||
|
MarkerCategory,
|
||||||
match_marker,
|
match_marker,
|
||||||
is_marker_exact,
|
is_marker_exact,
|
||||||
)
|
)
|
||||||
|
@ -53,6 +54,9 @@ def insert(self, marker: DecompMarker) -> bool:
|
||||||
self.markers[key] = marker
|
self.markers[key] = marker
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def query(self, category: MarkerCategory, module: str) -> Optional[DecompMarker]:
|
||||||
|
return self.markers.get((category, module))
|
||||||
|
|
||||||
def iter(self) -> Iterator[DecompMarker]:
|
def iter(self) -> Iterator[DecompMarker]:
|
||||||
for _, marker in self.markers.items():
|
for _, marker in self.markers.items():
|
||||||
yield marker
|
yield marker
|
||||||
|
@ -305,6 +309,23 @@ def _variable_done(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
parent_function = None
|
||||||
|
is_static = self.state == ReaderState.IN_FUNC_GLOBAL
|
||||||
|
|
||||||
|
# If this is a static variable, we need to get the function
|
||||||
|
# where it resides so that we can match it up later with the
|
||||||
|
# mangled names of both variable and function from cvdump.
|
||||||
|
if is_static:
|
||||||
|
fun_marker = self.fun_markers.query(
|
||||||
|
MarkerCategory.FUNCTION, marker.module
|
||||||
|
)
|
||||||
|
|
||||||
|
if fun_marker is None:
|
||||||
|
self._syntax_warning(ParserError.ORPHANED_STATIC_VARIABLE)
|
||||||
|
continue
|
||||||
|
|
||||||
|
parent_function = fun_marker.offset
|
||||||
|
|
||||||
self._symbols.append(
|
self._symbols.append(
|
||||||
ParserVariable(
|
ParserVariable(
|
||||||
type=marker.type,
|
type=marker.type,
|
||||||
|
@ -312,7 +333,8 @@ def _variable_done(
|
||||||
module=marker.module,
|
module=marker.module,
|
||||||
offset=marker.offset,
|
offset=marker.offset,
|
||||||
name=self.curly.get_prefix(variable_name),
|
name=self.curly.get_prefix(variable_name),
|
||||||
is_static=self.state == ReaderState.IN_FUNC_GLOBAL,
|
is_static=is_static,
|
||||||
|
parent_function=parent_function,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -419,8 +419,7 @@ def test_static_variable(parser):
|
||||||
"""We can detect whether a variable is a static function variable
|
"""We can detect whether a variable is a static function variable
|
||||||
based on the parser's state when we detect it.
|
based on the parser's state when we detect it.
|
||||||
Checking for the word `static` alone is not a good test.
|
Checking for the word `static` alone is not a good test.
|
||||||
Static class variables are filed as S_GDATA32, same as regular globals.
|
Static class variables are filed as S_GDATA32, same as regular globals."""
|
||||||
Only function statics are filed as S_LDATA32."""
|
|
||||||
|
|
||||||
parser.read_lines(
|
parser.read_lines(
|
||||||
[
|
[
|
||||||
|
@ -436,7 +435,7 @@ def test_static_variable(parser):
|
||||||
"// FUNCTION: TEST 0x5555",
|
"// FUNCTION: TEST 0x5555",
|
||||||
"void test_function() {",
|
"void test_function() {",
|
||||||
"// GLOBAL: TEST 0x8888",
|
"// GLOBAL: TEST 0x8888",
|
||||||
"int g_internal = 0;",
|
"static int g_internal = 0;",
|
||||||
"}",
|
"}",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -629,3 +628,70 @@ def test_match_qualified_variable(parser):
|
||||||
assert len(parser.variables) == 1
|
assert len(parser.variables) == 1
|
||||||
assert parser.variables[0].name == "MxTest::g_count"
|
assert parser.variables[0].name == "MxTest::g_count"
|
||||||
assert len(parser.alerts) == 0
|
assert len(parser.alerts) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_static_variable_parent(parser):
|
||||||
|
"""Report the address of the parent function that contains a static variable."""
|
||||||
|
|
||||||
|
parser.read_lines(
|
||||||
|
[
|
||||||
|
"// FUNCTION: TEST 0x1234",
|
||||||
|
"void test()",
|
||||||
|
"{",
|
||||||
|
" // GLOBAL: TEST 0x5555",
|
||||||
|
" static int g_count = 0;",
|
||||||
|
"}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(parser.variables) == 1
|
||||||
|
assert parser.variables[0].is_static is True
|
||||||
|
assert parser.variables[0].parent_function == 0x1234
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(
|
||||||
|
reason="""Without the FUNCTION marker we don't know that we are inside a function,
|
||||||
|
so we do not identify this variable as static."""
|
||||||
|
)
|
||||||
|
def test_static_variable_no_parent(parser):
|
||||||
|
"""If the function that contains a static variable is not marked, we
|
||||||
|
cannot match it with cvdump so we should skip it and report an error."""
|
||||||
|
|
||||||
|
parser.read_lines(
|
||||||
|
[
|
||||||
|
"void test()",
|
||||||
|
"{",
|
||||||
|
" // GLOBAL: TEST 0x5555",
|
||||||
|
" static int g_count = 0;",
|
||||||
|
"}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# No way to match this variable so don't report it
|
||||||
|
assert len(parser.variables) == 0
|
||||||
|
assert len(parser.alerts) == 1
|
||||||
|
assert parser.alerts[0].code == ParserError.ORPHANED_STATIC_VARIABLE
|
||||||
|
|
||||||
|
|
||||||
|
def test_static_variable_incomplete_coverage(parser):
|
||||||
|
"""If the function that contains a static variable is marked, but
|
||||||
|
not for each module used for the variable itself, this is an error."""
|
||||||
|
|
||||||
|
parser.read_lines(
|
||||||
|
[
|
||||||
|
"// FUNCTION: HELLO 0x1234",
|
||||||
|
"void test()",
|
||||||
|
"{",
|
||||||
|
" // GLOBAL: HELLO 0x5555",
|
||||||
|
" // GLOBAL: TEST 0x5555",
|
||||||
|
" static int g_count = 0;",
|
||||||
|
"}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Match for HELLO module
|
||||||
|
assert len(parser.variables) == 1
|
||||||
|
|
||||||
|
# Failed for TEST module
|
||||||
|
assert len(parser.alerts) == 1
|
||||||
|
assert parser.alerts[0].code == ParserError.ORPHANED_STATIC_VARIABLE
|
||||||
|
|
Loading…
Reference in a new issue