2021-11-03 19:50:32 +03:00
|
|
|
#!/usr/bin/env python3
|
2021-10-26 21:02:05 +03:00
|
|
|
"""Converts a statically-linked HAT package into a Dynamically-linked HAT package
|
|
|
|
|
2021-10-27 19:20:40 +03:00
|
|
|
HAT packages come in two varieties: statically-linked and dynamically-linked. A statically-linked
|
2022-01-25 11:05:29 +03:00
|
|
|
HAT package contains a binary file with the extension '.o', '.obj', '.a', or `.lib`. A dynamically-linked
|
2021-10-27 19:20:40 +03:00
|
|
|
HAT package contains a binary file with the extension '.dll' or '.so'. This tool converts a
|
|
|
|
statically-linked HAT package into a dynamically-linked HAT package.
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2022-01-29 11:01:38 +03:00
|
|
|
To use the tool, point it to the '.hat' file associated with the statically-linked package (the
|
|
|
|
'.hat' file knows where to find the associated binary file), and provide a filename for the new
|
2021-10-27 19:20:40 +03:00
|
|
|
'.hat' file.
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2022-01-12 06:59:50 +03:00
|
|
|
Dependencies on Windows:
|
2022-01-29 11:01:38 +03:00
|
|
|
* the cl.exe command-line compiler, available with Microsoft Visual Studio
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2022-01-20 17:17:39 +03:00
|
|
|
Dependencies on Linux / macOS:
|
|
|
|
* the gcc command-line compiler
|
2021-10-26 21:02:05 +03:00
|
|
|
"""
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import argparse
|
|
|
|
import shutil
|
2022-01-25 02:01:47 +03:00
|
|
|
from secrets import token_hex
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2022-03-30 23:50:00 +03:00
|
|
|
from .hat_file import HATFile, OperatingSystem
|
|
|
|
from .hat_package import HATPackage
|
|
|
|
from .platform_utilities import get_platform, ensure_compiler_in_path, run_command
|
2021-10-26 21:02:05 +03:00
|
|
|
|
|
|
|
|
2022-03-30 23:50:00 +03:00
|
|
|
def linux_create_dynamic_package(input_hat_path,
|
|
|
|
input_hat_binary_path,
|
|
|
|
output_hat_path,
|
|
|
|
hat_file,
|
|
|
|
quiet=True):
|
2022-01-25 11:05:29 +03:00
|
|
|
"""Creates a dynamic HAT (.so) from a static HAT (.o/.a) on a Linux/macOS platform"""
|
2021-10-27 16:28:25 +03:00
|
|
|
# Confirm that this is a static hat library
|
|
|
|
_, extension = os.path.splitext(input_hat_binary_path)
|
|
|
|
if extension not in [".o", ".a"]:
|
2022-01-29 11:01:38 +03:00
|
|
|
sys.exit(
|
2022-03-30 23:50:00 +03:00
|
|
|
f"ERROR: Expected input library to have extension .o or .a, but received {input_hat_binary_path} instead"
|
|
|
|
)
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2022-01-19 05:00:43 +03:00
|
|
|
# Create a C source file to resolve inline functions defined in the static HAT package
|
|
|
|
include_path = os.path.dirname(input_hat_binary_path)
|
|
|
|
inline_c_path = os.path.join(include_path, "inline.c")
|
2022-01-25 11:05:29 +03:00
|
|
|
inline_obj_path = os.path.join(include_path, "inline.o")
|
2022-01-19 05:00:43 +03:00
|
|
|
with open(inline_c_path, "w") as f:
|
|
|
|
f.write(f"#include <{os.path.basename(input_hat_path)}>")
|
|
|
|
# compile it separately so that we can suppress the warnings about the missing terminating ' character
|
2022-01-29 11:01:38 +03:00
|
|
|
run_command(
|
2022-03-30 23:50:00 +03:00
|
|
|
f'gcc -c -w -fPIC -o "{inline_obj_path}" -I"{include_path}" "{inline_c_path}"',
|
|
|
|
quiet=quiet)
|
2022-01-19 05:00:43 +03:00
|
|
|
|
2021-10-26 21:02:05 +03:00
|
|
|
# create new HAT binary
|
|
|
|
prefix, _ = os.path.splitext(output_hat_path)
|
2022-01-29 11:01:38 +03:00
|
|
|
# always create a new so file (avoids cases where so file is already loaded)
|
|
|
|
suffix = token_hex(4)
|
2022-01-25 02:01:47 +03:00
|
|
|
output_hat_binary_path = f"{prefix}_{suffix}.so"
|
2022-01-29 11:01:38 +03:00
|
|
|
libraries = " ".join(
|
|
|
|
[d.target_file for d in hat_file.dependencies.dynamic])
|
|
|
|
run_command(
|
2022-03-30 23:50:00 +03:00
|
|
|
f'gcc -shared -fPIC -o "{output_hat_binary_path}" "{inline_obj_path}" "{input_hat_binary_path}" {libraries}',
|
|
|
|
quiet=quiet)
|
2021-10-26 21:02:05 +03:00
|
|
|
|
|
|
|
# create new HAT file
|
2022-01-29 11:01:38 +03:00
|
|
|
# previous dependencies are now part of the binary
|
|
|
|
hat_file.dependencies.dynamic = []
|
|
|
|
hat_file.dependencies.link_target = os.path.basename(
|
|
|
|
output_hat_binary_path)
|
2022-01-20 17:17:39 +03:00
|
|
|
hat_file.Serialize(output_hat_path)
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2022-03-30 23:50:00 +03:00
|
|
|
return HATPackage(output_hat_path)
|
|
|
|
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2022-03-30 23:50:00 +03:00
|
|
|
def windows_create_dynamic_package(input_hat_path,
|
|
|
|
input_hat_binary_path,
|
|
|
|
output_hat_path,
|
|
|
|
hat_file,
|
|
|
|
quiet=True):
|
2021-10-27 16:28:25 +03:00
|
|
|
"""Creates a Windows dynamic HAT package (.dll) from a static HAT package (.obj/.lib)"""
|
2022-01-12 06:59:50 +03:00
|
|
|
|
2021-10-27 16:28:25 +03:00
|
|
|
# Confirm that this is a static hat library
|
|
|
|
_, extension = os.path.splitext(input_hat_binary_path)
|
|
|
|
if extension not in [".obj", ".lib"]:
|
2022-01-29 11:01:38 +03:00
|
|
|
sys.exit(
|
2022-03-30 23:50:00 +03:00
|
|
|
f"ERROR: Expected input library to have extension .obj or .lib, but received {input_hat_binary_path} instead"
|
|
|
|
)
|
2021-10-27 16:28:25 +03:00
|
|
|
|
2021-10-26 21:02:05 +03:00
|
|
|
# Create all file in a directory named build
|
|
|
|
if not os.path.exists("build"):
|
|
|
|
os.mkdir("build")
|
|
|
|
|
2022-01-05 10:46:04 +03:00
|
|
|
cwd = os.getcwd()
|
|
|
|
try:
|
|
|
|
os.chdir("build")
|
2022-01-29 11:01:38 +03:00
|
|
|
|
2022-01-05 10:46:04 +03:00
|
|
|
# Create a C source file for the DLL entry point and compile in into an obj
|
2022-01-19 05:00:43 +03:00
|
|
|
with open("dllmain.cpp", "w") as f:
|
|
|
|
f.write("#include <windows.h>\n")
|
|
|
|
# Resolve inline functions defined in the static HAT package
|
|
|
|
f.write("#include <{}>\n".format(os.path.basename(input_hat_path)))
|
2022-01-29 11:01:38 +03:00
|
|
|
f.write(
|
2022-03-30 23:50:00 +03:00
|
|
|
"BOOL APIENTRY DllMain(HMODULE, DWORD, LPVOID) { return TRUE; }\n"
|
|
|
|
)
|
2022-01-29 11:01:38 +03:00
|
|
|
run_command(
|
2022-03-30 23:50:00 +03:00
|
|
|
f'cl.exe /nologo /I"{os.path.dirname(input_hat_path)}" /Fodllmain.obj /c dllmain.cpp',
|
|
|
|
quiet=quiet)
|
2022-01-05 10:46:04 +03:00
|
|
|
|
|
|
|
# create the new HAT binary dll
|
2022-01-29 11:01:38 +03:00
|
|
|
# always create a new dll (avoids case where dll is already loaded)
|
|
|
|
suffix = token_hex(4)
|
2022-01-05 10:46:04 +03:00
|
|
|
prefix, _ = os.path.splitext(output_hat_path)
|
2022-01-25 02:01:47 +03:00
|
|
|
output_hat_binary_path = f"{prefix}_{suffix}.dll"
|
2022-01-05 10:46:04 +03:00
|
|
|
|
2022-01-20 17:17:39 +03:00
|
|
|
function_descriptions = hat_file.functions
|
|
|
|
function_names = [f.name for f in function_descriptions]
|
2022-01-25 11:05:29 +03:00
|
|
|
exports = " /EXPORT:".join(function_names)
|
2022-01-20 17:17:39 +03:00
|
|
|
|
2022-01-29 11:01:38 +03:00
|
|
|
libraries = " ".join(
|
|
|
|
[d.target_file for d in hat_file.dependencies.dynamic])
|
2022-01-25 11:05:29 +03:00
|
|
|
linker_command_line = f'link.exe /NOLOGO /DLL /FORCE:MULTIPLE /EXPORT:{exports} /OUT:out.dll dllmain.obj "{input_hat_binary_path}" {libraries}'
|
2022-01-29 11:01:38 +03:00
|
|
|
run_command(linker_command_line, quiet=quiet)
|
2022-01-05 10:46:04 +03:00
|
|
|
shutil.copyfile("out.dll", output_hat_binary_path)
|
|
|
|
|
|
|
|
# create new HAT file
|
2022-01-29 11:01:38 +03:00
|
|
|
# previous dependencies are now part of the binary
|
|
|
|
hat_file.dependencies.dynamic = []
|
|
|
|
hat_file.dependencies.link_target = os.path.basename(
|
|
|
|
output_hat_binary_path)
|
2022-01-20 17:17:39 +03:00
|
|
|
hat_file.Serialize("out.hat")
|
2022-01-05 10:46:04 +03:00
|
|
|
shutil.copyfile("out.hat", output_hat_path)
|
|
|
|
finally:
|
2022-01-29 11:01:38 +03:00
|
|
|
os.chdir(cwd) # restore the current working directory
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2022-03-30 23:50:00 +03:00
|
|
|
return HATPackage(output_hat_path)
|
|
|
|
|
2022-01-25 11:05:29 +03:00
|
|
|
|
2021-10-26 21:02:05 +03:00
|
|
|
def parse_args():
|
|
|
|
"""Parses and checks the command line arguments"""
|
2022-03-30 23:50:00 +03:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
description=
|
|
|
|
"Creates a dynamically-linked HAT package from a statically-linked HAT package.\n"
|
|
|
|
"Example:\n"
|
|
|
|
" hatlib.hat_to_dynamic input.hat output.hat\n")
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
"input_hat_path",
|
|
|
|
type=str,
|
|
|
|
help=
|
|
|
|
"Path to the existing HAT file, which represents a statically-linked HAT package"
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"output_hat_path",
|
|
|
|
type=str,
|
|
|
|
help=
|
|
|
|
"Path to the new HAT file, which will represent a dynamically-linked HAT package"
|
|
|
|
)
|
|
|
|
parser.add_argument('-v',
|
|
|
|
"--verbose",
|
|
|
|
action='store_true',
|
|
|
|
help="Enable verbose output")
|
2021-10-26 21:02:05 +03:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
# check args
|
|
|
|
if not os.path.exists(args.input_hat_path):
|
2021-10-27 19:17:52 +03:00
|
|
|
sys.exit(f"ERROR: File {args.input_hat_path} not found")
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2022-03-30 23:50:00 +03:00
|
|
|
if os.path.abspath(args.input_hat_path) == os.path.abspath(
|
|
|
|
args.output_hat_path):
|
2021-10-26 21:02:05 +03:00
|
|
|
sys.exit("ERROR: Output file must be different from input file")
|
|
|
|
|
|
|
|
_, extension = os.path.splitext(args.input_hat_path)
|
|
|
|
if extension != ".hat":
|
2022-01-29 11:01:38 +03:00
|
|
|
sys.exit(
|
2022-03-30 23:50:00 +03:00
|
|
|
f"ERROR: Expected input file to have extension .hat, but received {extension} instead"
|
|
|
|
)
|
2021-10-26 21:02:05 +03:00
|
|
|
|
|
|
|
_, extension = os.path.splitext(args.output_hat_path)
|
|
|
|
if extension != ".hat":
|
2022-01-29 11:01:38 +03:00
|
|
|
sys.exit(
|
2022-03-30 23:50:00 +03:00
|
|
|
f"ERROR: Expected output file to have extension .hat, but received {extension} instead"
|
|
|
|
)
|
2021-10-26 21:02:05 +03:00
|
|
|
|
|
|
|
return args
|
|
|
|
|
|
|
|
|
2022-01-29 11:01:38 +03:00
|
|
|
def create_dynamic_package(input_hat_path, output_hat_path, quiet=True):
|
2021-10-26 21:02:05 +03:00
|
|
|
platform = get_platform()
|
2022-01-25 11:05:29 +03:00
|
|
|
ensure_compiler_in_path()
|
2021-10-26 21:02:05 +03:00
|
|
|
|
|
|
|
# load the function decscriptions and the library path from the hat file
|
2022-01-05 10:46:04 +03:00
|
|
|
input_hat_path = os.path.abspath(input_hat_path)
|
2022-01-20 17:17:39 +03:00
|
|
|
hat_file = HATFile.Deserialize(input_hat_path)
|
2021-10-26 21:02:05 +03:00
|
|
|
|
2021-10-27 16:28:25 +03:00
|
|
|
# get the absolute path to the input binary
|
2022-01-20 17:17:39 +03:00
|
|
|
input_hat_binary_filename = hat_file.dependencies.link_target
|
2022-03-30 23:50:00 +03:00
|
|
|
input_hat_binary_path = os.path.join(os.path.dirname(input_hat_path),
|
|
|
|
input_hat_binary_filename)
|
2021-10-26 21:02:05 +03:00
|
|
|
|
|
|
|
# create the dynamic package
|
2022-01-05 10:46:04 +03:00
|
|
|
output_hat_path = os.path.abspath(output_hat_path)
|
2022-01-25 11:05:29 +03:00
|
|
|
if platform == OperatingSystem.Windows:
|
2022-03-30 23:50:00 +03:00
|
|
|
windows_create_dynamic_package(input_hat_path,
|
|
|
|
input_hat_binary_path,
|
|
|
|
output_hat_path,
|
|
|
|
hat_file,
|
|
|
|
quiet=quiet)
|
2022-01-25 11:05:29 +03:00
|
|
|
elif platform in [OperatingSystem.Linux, OperatingSystem.MacOS]:
|
2022-03-30 23:50:00 +03:00
|
|
|
linux_create_dynamic_package(input_hat_path,
|
|
|
|
input_hat_binary_path,
|
|
|
|
output_hat_path,
|
|
|
|
hat_file,
|
|
|
|
quiet=quiet)
|
2021-10-26 21:02:05 +03:00
|
|
|
|
|
|
|
|
2022-01-05 10:46:04 +03:00
|
|
|
def main():
|
|
|
|
args = parse_args()
|
2022-03-30 23:50:00 +03:00
|
|
|
create_dynamic_package(args.input_hat_path,
|
|
|
|
args.output_hat_path,
|
|
|
|
quiet=not args.verbose)
|
2022-01-05 10:46:04 +03:00
|
|
|
|
|
|
|
|
2021-10-26 21:02:05 +03:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|