DEVTOOLS: Refactor director-generate-xobj-stub to allow multiple formats

This commit is contained in:
Scott Percival 2024-01-11 19:50:45 +08:00 committed by Eugene Sandulenko
parent 94bd08a4dd
commit ed97e05a1d

View File

@ -6,7 +6,18 @@ import argparse
import os
import re
import struct
from typing import Any, BinaryIO
from typing import Any, BinaryIO, Literal
from typing_extensions import TypedDict
XCodeType = Literal["XFCN", "XCMD", "XObject", "Xtra"]
class XCode(TypedDict):
type: XCodeType
name: str
slug: str
method_table: list[str]
DIRECTOR_SRC_PATH = os.path.abspath(
os.path.join(__file__, "..", "..", "engines", "director")
@ -15,7 +26,7 @@ MAKEFILE_PATH = os.path.join(DIRECTOR_SRC_PATH, "module.mk")
LINGO_XLIBS_PATH = os.path.join(DIRECTOR_SRC_PATH, "lingo", "xlibs")
LINGO_OBJECT_PATH = os.path.join(DIRECTOR_SRC_PATH, "lingo", "lingo-object.cpp")
TEMPLATE_H = """/* ScummVM - Graphic Adventure Engine
LEGAL = """/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
@ -35,7 +46,11 @@ TEMPLATE_H = """/* ScummVM - Graphic Adventure Engine
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
"""
TEMPLATE_H = (
LEGAL
+ """
#ifndef DIRECTOR_LINGO_XLIBS_{slug_upper}_H
#define DIRECTOR_LINGO_XLIBS_{slug_upper}_H
@ -62,30 +77,13 @@ void close(int type);
#endif
"""
)
TEMPLATE_HEADER_METH = """void m_{methname}(int nargs);"""
TEMPLATE = """/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
TEMPLATE = (
LEGAL
+ """
#include "common/system.h"
#include "director/director.h"
@ -153,6 +151,7 @@ void {xobj_class}::m_new(int nargs) {{
}}
"""
)
XLIB_METHOD_TEMPLATE = """ {{ "{methname}", {xobj_class}::m_{methname}, {arg_count}, {arg_count}, {director_version} }},"""
XOBJ_STUB_TEMPLATE = """XOBJSTUB({xobj_class}::m_{methname}, {default})"""
@ -180,9 +179,9 @@ def read_uint32_be(data: bytes) -> int:
return struct.unpack(">L", data)[0]
def extract_xmethtable_macbinary(
file: BinaryIO, resource_offset: int, xobj_id: int | None = None
) -> tuple[str, str, list[str]]:
def extract_xcode_macbinary(
file: BinaryIO, resource_offset: int, xobj_id: str | None = None
) -> XCode:
file.seek(resource_offset)
resource_data_offset = read_uint32_be(file.read(4))
resource_map_offset = read_uint32_be(file.read(4))
@ -197,64 +196,70 @@ def extract_xmethtable_macbinary(
for _ in range(type_count + 1):
key = file.read(4)
types[key] = (read_uint16_be(file.read(2)) + 1, read_uint16_be(file.read(2)))
if b"XCOD" in types:
print("Found XCOD resources!")
file.seek(
resource_offset + resource_map_offset + type_list_offset + types[b"XCOD"][1]
)
resources = []
for _ in range(types[b"XCOD"][0]):
id = read_uint16_be(file.read(2))
name_offset = read_uint16_be(file.read(2))
file.read(1)
data_offset = (read_uint8(file.read(1)) << 16) + read_uint16_be(
file.read(2)
xobj: dict[str, dict[str, Any]] = {}
for chunk_type in [b"XCOD", b"XFCN", b"XCMD"]:
if chunk_type in types:
print(f"Found {chunk_type.decode('utf8')} resources!")
file.seek(
resource_offset
+ resource_map_offset
+ type_list_offset
+ types[chunk_type][1]
)
file.read(4)
resources.append((id, data_offset, name_offset))
xobj: dict[int, dict[str, Any]] = {}
for id, data_offset, name_offset in resources:
xobj[id] = {}
if name_offset != 0xFFFF:
file.seek(
resource_offset
+ resource_map_offset
+ name_list_offset
+ name_offset
resources: list[tuple[str, int, int]] = []
for _ in range(types[chunk_type][0]):
id = f"{chunk_type.decode('utf8')}_{read_uint16_be(file.read(2))}"
name_offset = read_uint16_be(file.read(2))
file.read(1)
data_offset = (read_uint8(file.read(1)) << 16) + read_uint16_be(
file.read(2)
)
name_size = read_uint8(file.read(1))
xobj[id]["name"] = file.read(name_size).decode("macroman")
else:
xobj[id]["name"] = "<unknown>"
file.seek(resource_offset + resource_data_offset + data_offset)
size = read_uint32_be(file.read(4)) - 12
file.read(12)
xobj[id]["xmethtable"] = []
while size > 0:
count = read_uint8(file.read(1))
if count == 0:
break
xobj[id]["xmethtable"].append(file.read(count).decode("macroman"))
size -= 1 + count
if xobj_id is None or xobj_id not in xobj:
print("Please re-run with one of the following XOBJ resource IDs:")
for id, data in xobj.items():
print(f"{id} - {data['name']}")
raise ValueError("Need to specify XOBJ resource ID")
for entry in xobj[xobj_id]["xmethtable"]:
print(entry)
return (
xobj[xobj_id]["name"],
xobj[xobj_id]["name"].lower(),
xobj[xobj_id]["xmethtable"],
)
file.read(4)
resources.append((id, data_offset, name_offset))
for id, data_offset, name_offset in resources:
xobj[id] = {}
if name_offset != 0xFFFF:
file.seek(
resource_offset
+ resource_map_offset
+ name_list_offset
+ name_offset
)
name_size = read_uint8(file.read(1))
xobj[id]["name"] = file.read(name_size).decode("macroman")
else:
xobj[id]["name"] = "<unknown>"
file.seek(resource_offset + resource_data_offset + data_offset)
xobj[id]["dump"] = file.read(read_uint32_be(file.read(4)) - 4)
file.seek(resource_offset + resource_data_offset + data_offset)
size = read_uint32_be(file.read(4)) - 12
file.read(12)
xobj[id]["xmethtable"] = []
while size > 0:
count = read_uint8(file.read(1))
if count == 0:
break
xobj[id]["xmethtable"].append(file.read(count).decode("macroman"))
size -= 1 + count
if not xobj:
raise ValueError("No extension resources found!")
raise ValueError("No XCOD resources found!")
if xobj_id is None or xobj_id not in xobj:
print("Please re-run with one of the following resource IDs:")
for id, data in xobj.items():
print(f"{id} - {data['name']}")
raise ValueError("Need to specify resource ID")
for entry in xobj[xobj_id]["xmethtable"]:
print(entry)
return {
"type": "XObject",
"name": xobj[xobj_id]["name"],
"slug": xobj[xobj_id]["name"].lower(),
"method_table": xobj[xobj_id]["xmethtable"],
}
def extract_xmethtable_win16(
file: BinaryIO, ne_offset: int
) -> tuple[str, str, list[str]]:
def extract_xcode_win16(file: BinaryIO, ne_offset: int) -> XCode:
# get resource table
file.seek(ne_offset + 0x24, os.SEEK_SET)
restable_offset = read_uint16_le(file.read(0x2))
@ -325,10 +330,15 @@ def extract_xmethtable_win16(
print(entry)
library_name = xmethtable[1]
xmethtable[1] = "--" + library_name
return library_name, file_name, xmethtable
return {
"type": "XObject",
"name": library_name,
"slug": file_name,
"method_table": xmethtable,
}
def extract_xmethtable_win32(file: BinaryIO, pe_offset: int) -> tuple[str, list[str]]:
def extract_xcode_win32(file: BinaryIO, pe_offset: int) -> XCode:
# get the .data section
# find a string b"msgTable\x00", get the offset
# get the .text section
@ -337,10 +347,10 @@ def extract_xmethtable_win32(file: BinaryIO, pe_offset: int) -> tuple[str, list[
# lookup addr2 in .data
# get c string, split by \x0a
return ("", [])
return {"type": "Xtra", "name": "", "slug": "", "method_table": []}
def extract_xmethtable(path: str, resid: int) -> tuple[str, str, list[str]]:
def extract_xcode(path: str, resid: str) -> XCode:
with open(path, "rb") as file:
magic = file.read(0x2)
if magic == b"MZ":
@ -350,7 +360,7 @@ def extract_xmethtable(path: str, resid: int) -> tuple[str, str, list[str]]:
magic = file.read(0x2)
if magic == b"NE":
print("Found Win16 NE DLL!")
return extract_xmethtable_win16(file, header_offset)
return extract_xcode_win16(file, header_offset)
elif magic == b"PE":
raise ValueError("No support yet for extracting from Win32 DLLs")
file.seek(0)
@ -373,17 +383,17 @@ def extract_xmethtable(path: str, resid: int) -> tuple[str, str, list[str]]:
+ ((128 - (data_size % 128)) if (data_size % 128) else 0)
)
print(f"resource offset: {resource_offset}")
return extract_xmethtable_macbinary(file, resource_offset, resid)
return extract_xcode_macbinary(file, resource_offset, resid)
raise ValueError("Unknown filetype")
def generate_stubs(
def generate_xobject_stubs(
xmethtable: list[str],
slug: str,
name: str,
director_version: int = 400,
dry_run=False,
dry_run: bool = False,
) -> None:
meths = []
for e in xmethtable:
@ -464,7 +474,7 @@ def main() -> None:
)
parser.add_argument("XOBJ_FILE", help="XObject/XLib file to test")
parser.add_argument(
"--resid", help="XOBJ resource ID (for MacBinary)", type=int, default=None
"--resid", help="Resource ID (for MacBinary)", type=str, default=None
)
parser.add_argument(
"--slug", help="Slug to use for files (e.g. {slug}.cpp, {slug}.h)"
@ -483,10 +493,13 @@ def main() -> None:
)
args = parser.parse_args()
library_name, file_name, xmethtable = extract_xmethtable(args.XOBJ_FILE, args.resid)
slug = args.slug or file_name
name = args.name or library_name
generate_stubs(xmethtable, slug, name, args.version, args.dry_run)
xcode = extract_xcode(args.XOBJ_FILE, args.resid)
slug = args.slug or xcode["slug"]
name = args.name or xcode["name"]
if xcode["type"] == "XObject":
generate_xobject_stubs(
xcode["method_table"], slug, name, args.version, args.dry_run
)
if __name__ == "__main__":