mirror of
https://github.com/isledecomp/isle.git
synced 2026-01-20 14:51:15 +00:00
Simple integrated build process
* Introduce "configure.py" and "build.py" * Running "python configure.py && python build.py" will successfully build a clean clone of the project with no additional effort needed. * The repo now includes a copy of ninja for each platform, and the configure script will download MSVC420 for you as part of configuring the project. You can specify --modern-compiler when configuring to configure with a different toolchain if desired. * This also establishes a specific place (/original) for you to put your original game files. The build process will let you know that you need to put them there if you have not yet when you ask to compare to the original game. * The repo also now includes a .vscode folder by default, which contains a tasks.json file including the build / configure tasks. * This includes "build and compare function by cursor" task which allows a very effective workflow where you just hit Ctrl+Shift+B and get an updated diff of the function you were just editing.
This commit is contained in:
parent
904640e028
commit
8ef38caf7d
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,3 +4,5 @@ Release/
|
||||
ISLE.EXE
|
||||
LEGO1.DLL
|
||||
build/
|
||||
original/
|
||||
compiler/
|
||||
|
||||
31
.vscode/tasks.json
vendored
Normal file
31
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Configure decomp with standard setup",
|
||||
"type": "shell",
|
||||
"group": "build",
|
||||
"command": "configure.py"
|
||||
},
|
||||
{
|
||||
"label": "Build decomp",
|
||||
"type": "shell",
|
||||
"group": "build",
|
||||
"command": "build.py"
|
||||
},
|
||||
{
|
||||
"label": "Build decomp and print overall status",
|
||||
"type": "shell",
|
||||
"group": "build",
|
||||
"command": "build.py --status"
|
||||
},
|
||||
{
|
||||
"label": "Build decomp and inspect function by cursor",
|
||||
"type": "shell",
|
||||
"group": "build",
|
||||
"command": "build.py --inspect-shim ${file} ${lineNumber}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -173,6 +173,13 @@ add_library(lego1 SHARED
|
||||
# Link libraries
|
||||
target_link_libraries(lego1 PRIVATE ddraw dsound winmm)
|
||||
|
||||
# Includes
|
||||
target_include_directories(lego1 PRIVATE "${CMAKE_SOURCE_DIR}/LEGO1")
|
||||
|
||||
# Add VC420 directories
|
||||
target_include_directories(lego1 PRIVATE "$ENV{INCLUDE}")
|
||||
target_link_directories(lego1 PRIVATE "$ENV{LIB}")
|
||||
|
||||
# Make sure filenames are ALL CAPS
|
||||
set_property(TARGET lego1 PROPERTY OUTPUT_NAME LEGO1)
|
||||
set_property(TARGET lego1 PROPERTY SUFFIX ".DLL")
|
||||
@ -187,6 +194,10 @@ if (ISLE_BUILD_APP)
|
||||
# Include LEGO1 headers in ISLE
|
||||
target_include_directories(isle PRIVATE "${CMAKE_SOURCE_DIR}/LEGO1")
|
||||
|
||||
# Add VC420 directories
|
||||
target_include_directories(isle PRIVATE "$ENV{INCLUDE}")
|
||||
target_link_directories(isle PRIVATE "$ENV{LIB}")
|
||||
|
||||
# Link DSOUND, WINMM, and LEGO1
|
||||
target_link_libraries(isle PRIVATE dsound winmm lego1)
|
||||
|
||||
@ -220,4 +231,4 @@ if (MSVC)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "/incremental:no")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "/incremental:no /debug")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "/incremental:no")
|
||||
endif()
|
||||
endif()
|
||||
78
build.py
Normal file
78
build.py
Normal file
@ -0,0 +1,78 @@
|
||||
|
||||
import pathlib
|
||||
import subprocess
|
||||
import argparse
|
||||
import re
|
||||
import requests
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-i", "--inspect", metavar='<offset>',
|
||||
help="Get an assembly diff for the function at a given offset after building. "
|
||||
"When decomping you will typically run this command over and over again as "
|
||||
"you refine your code towards a 100% match.")
|
||||
parser.add_argument("-s", "--status", action="store_true",
|
||||
help="Get a general status report on the progress decompiling all functions.")
|
||||
parser.add_argument("--inspect-shim", nargs=2, metavar=('<file>', '<line>'),
|
||||
help="Inspect the assembly diff of the function spanning <line> in <file>. "
|
||||
"Intended to be invoked by IDE commands.")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Figure out what offset to pass to the reccmp tool if any
|
||||
def get_inspect_offset():
|
||||
if inspect_offset := args.inspect:
|
||||
if inspect_offset.startswith("0x"):
|
||||
return inspect_offset
|
||||
else:
|
||||
return "0x" + inspect_offset
|
||||
elif args.inspect_shim:
|
||||
print("Inspect: ", args.inspect_shim[0], ":", args.inspect_shim[1])
|
||||
with open(args.inspect_shim[0], 'r') as f:
|
||||
# get the lines in an array
|
||||
lines = f.readlines()
|
||||
OFFSET_PATTERN = re.compile(r'OFFSET:\s*LEGO1\s*(0x[0-9a-f]+)')
|
||||
for i in range(int(args.inspect_shim[1]), 0, -1):
|
||||
if i > 0 and i < len(lines):
|
||||
if match := OFFSET_PATTERN.search(lines[i]):
|
||||
inspect_offset = match.group(1)
|
||||
break
|
||||
if inspect_offset:
|
||||
return inspect_offset
|
||||
else:
|
||||
print("Could not find any function to inspect near cursor.")
|
||||
exit(1)
|
||||
return None
|
||||
|
||||
# If the build directory doesn't exist yet, run configure.py
|
||||
if not pathlib.Path("build").exists():
|
||||
print("Not configured yet, please run configure.py first.")
|
||||
exit(1)
|
||||
|
||||
# Run cmake, with no parallel build because that does not play nice the
|
||||
# MSVC420 compiler the original game was built with thanks to the different
|
||||
# threads contending over the pdb file.
|
||||
result = subprocess.run(["cmake", "--build", ".", "-j", "1"], cwd="build")
|
||||
|
||||
def require_original():
|
||||
if not pathlib.Path("original/LEGO1.DLL").exists():
|
||||
print("Could not find original/LEGO1.DLL to compare results against. "
|
||||
"Please obtain a copy the game and place its LEGO1.dll into the `original` folder.")
|
||||
exit(1)
|
||||
|
||||
# If the build succeeded, run the decompiler
|
||||
if result.returncode == 0:
|
||||
if inspect_offset := get_inspect_offset():
|
||||
require_original()
|
||||
subprocess.run(["python", "tools/reccmp/reccmp.py",
|
||||
"original/LEGO1.DLL", "build/LEGO1.DLL",
|
||||
"build/LEGO1.PDB", ".",
|
||||
"-v", inspect_offset])
|
||||
|
||||
elif args.status:
|
||||
require_original()
|
||||
subprocess.run(["python", "tools/reccmp/reccmp.py",
|
||||
"original/LEGO1.DLL", "build/LEGO1.DLL",
|
||||
"build/LEGO1.PDB", "."])
|
||||
|
||||
else:
|
||||
print("Build failed.")
|
||||
exit(1)
|
||||
75
configure.py
Normal file
75
configure.py
Normal file
@ -0,0 +1,75 @@
|
||||
|
||||
import pathlib
|
||||
import subprocess
|
||||
import argparse
|
||||
import shutil
|
||||
import platform
|
||||
import requests
|
||||
import zipfile
|
||||
import io
|
||||
import os
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-m", "--modern-compiler", action="store_true",
|
||||
help="Use a contemporary compiler instead of Microsoft Visual C++ 4.2 even though this will result in non-matching code")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Do we have a builtin ninja?
|
||||
specific_ninja = None
|
||||
if shutil.which("ninja") is None:
|
||||
sys = platform.system()
|
||||
# Use the bundled ninja
|
||||
if sys == "Windows":
|
||||
specific_ninja = "./tools/ninja/ninja-win.exe"
|
||||
elif sys == "Linux":
|
||||
specific_ninja = "./tools/ninja/ninja-linux"
|
||||
elif sys == "Darwin":
|
||||
specific_ninja = "./tools/ninja/ninja-mac"
|
||||
else:
|
||||
print("No bundled ninja for this platform, please install ninja and add it to your path.")
|
||||
exit(1)
|
||||
specific_ninja = str(pathlib.Path(specific_ninja).absolute())
|
||||
|
||||
# Create a build folder in the current directory if it does not exist
|
||||
pathlib.Path("build").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create a folder for the compiler
|
||||
pathlib.Path("compiler").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Check if MSVC420 is in the INCLUDE environment variable
|
||||
set_compiler_paths = False
|
||||
if not args.modern_compiler:
|
||||
if not pathlib.Path("compiler/MSVC420-master").exists():
|
||||
print("MSVC420 not found in compiler/...")
|
||||
url = "https://github.com/itsmattkc/MSVC420/archive/refs/heads/master.zip"
|
||||
print("Downloading MSVC420...")
|
||||
r = requests.get(url, stream=True)
|
||||
print("Unzipping to compiler/...")
|
||||
z = zipfile.ZipFile(io.BytesIO(r.content))
|
||||
z.extractall(path="compiler")
|
||||
set_compiler_paths = True
|
||||
|
||||
# Run cmake in the build folder
|
||||
cmake_args = []
|
||||
cmake_args += ["-G", "Ninja"]
|
||||
cmake_args += ["-DCMAKE_BUILD_TYPE=RelWithDebInfo"]
|
||||
if specific_ninja:
|
||||
cmake_args += ["-DCMAKE_MAKE_PROGRAM="+specific_ninja]
|
||||
print("Using specific ninja:", specific_ninja)
|
||||
if set_compiler_paths:
|
||||
full_compiler_path = pathlib.Path.cwd() / "compiler" / "MSVC420-master/"
|
||||
cmake_args += ["-DCMAKE_CXX_COMPILER=" + (full_compiler_path / "bin" / "CL.EXE").as_posix()]
|
||||
cmake_args += ["-DCMAKE_C_COMPILER=" + (full_compiler_path / "bin" / "CL.EXE").as_posix()]
|
||||
cmake_args += ["-DCMAKE_LINKER=" + (full_compiler_path / "bin" / "LINK.EXE").as_posix()]
|
||||
cmake_args += ["-DCMAKE_RC_COMPILER=" + (full_compiler_path / "bin" / "RC.EXE").as_posix()]
|
||||
os.environ["INCLUDE"] = (full_compiler_path / "include").as_posix()
|
||||
os.environ["LIB"] = (full_compiler_path / "lib").as_posix()
|
||||
cmake_args += [".."]
|
||||
subprocess.run(["cmake"] + cmake_args, cwd="build")
|
||||
|
||||
# Create a folder for the original binaries
|
||||
if not pathlib.Path("original/LEGO1.DLL").exists():
|
||||
pathlib.Path("original").mkdir(parents=True, exist_ok=True)
|
||||
print("Please obtain a copy of the original game and place its "
|
||||
"ISLE.EXE and LEGO1.DLL into the `original` folder if you plan "
|
||||
"on contributing so that the assembly diff tools can compare against them.")
|
||||
BIN
tools/ninja/ninja-linux
Normal file
BIN
tools/ninja/ninja-linux
Normal file
Binary file not shown.
BIN
tools/ninja/ninja-mac
Normal file
BIN
tools/ninja/ninja-mac
Normal file
Binary file not shown.
BIN
tools/ninja/ninja-win.exe
Normal file
BIN
tools/ninja/ninja-win.exe
Normal file
Binary file not shown.
@ -351,7 +351,8 @@ def parse_asm(file, addr, size):
|
||||
# If verbose, print the diff for that funciton to the output
|
||||
if verbose:
|
||||
if ratio == 1.0:
|
||||
print("%s: %s 100%% match.\n\nOK!" % (hex(addr), recinfo.name))
|
||||
ok_text = "OK!" if plain else (colorama.Fore.GREEN + "✨ OK! ✨" + colorama.Style.RESET_ALL)
|
||||
print("%s: %s 100%% match.\n\n%s\n\n" % (hex(addr), recinfo.name, ok_text))
|
||||
else:
|
||||
for line in udiff:
|
||||
if line.startswith("++") or line.startswith("@@") or line.startswith("--"):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user