Skip to content

Commit

Permalink
Update template
Browse files Browse the repository at this point in the history
  • Loading branch information
ThirdEyeSqueegee committed May 10, 2024
1 parent 7a2b269 commit da258dd
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 77 deletions.
4 changes: 0 additions & 4 deletions .gitmodules

This file was deleted.

42 changes: 3 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,50 +23,14 @@

## Dependencies

- [vcpkg v2023.08.09+](https://github.com/microsoft/vcpkg/releases)
- [vcpkg](https://github.com/microsoft/vcpkg)
- Create a new Windows environment variable called `VCPKG_ROOT` which points to your vcpkg install directory
- [CMake v3.27+](https://cmake.org/)
- [LLVM v16.0.6+](https://github.com/llvm/llvm-project/releases)
- [CMake](https://cmake.org/)
- [LLVM](https://github.com/llvm/llvm-project/releases)
- Visual Studio 2022 with C++ workload

## Resources

- [Address Library Database](https://github.com/meh321/AddressLibraryDatabase)
- [Address Library Manager](https://github.com/meh321/AddressLibraryManager/releases)
- [Steamless](https://github.com/atom0s/Steamless/releases)

# Context

## What you need to know

- C++ and the tools mentioned in the Dependencies section
- Assembly (specifically, x86-64)
- x64 calling convention

## What is Skyrim reverse engineering?

We're all (hopefully) familiar with executables. The process of turning a set of human-readable source code files into machine-readable instructions consists of several layers of translation. Reverse engineering is the process of working back from machine-readable instructions to human-readable source code. Since we don't have the source code for Skyrim, we must use tools like the [Interactive Disassembler (IDA)](https://en.wikipedia.org/wiki/Interactive_Disassembler) to, you guessed it, reverse engineer the source code so that we may "hack into" the game's functionality and do all the interesting things that SKSE plugins do. Thankfully, a bunch of Really Smart People did most of the heavy lifting and went ahead and reverse engineered huge swathes of the game, resulting in [CommonLibSSE](https://github.com/Ryan-rsm-McKenzie/CommonLibSSE) and its various forks. Although these libraries provide a great deal of convenience and significantly speed up development, it is nonetheless often necessary to inspect the disassembled Skyrim executable in IDA or Ghidra.

## What is CommonLibSSE?

A library, written in C++, which consists of reverse-engineered classes, functions, etc. found in the disassembled Skyrim executable. Due to its age, there are several different runtime versions of Skyrim. The main versions of interest are v1.5.97, v1.6.640, and VR. Due to differences in struct layouts etc. amongst these runtimes and the resulting difficulty of building DLLs that target different runtimes, [CommonLibSSE-NG](https://github.com/CharmedBaryon/CommonLibSSE-NG) (the basis of this template) was created, which allows building a single DLL that can target every Skyrim runtime using clever tricks like [relocations](<https://en.wikipedia.org/wiki/Relocation_(computing)>).

## The Address Library:tm:

The Address Library is a mapping of integer IDs (e.g. 57463) to addresses in different Skyrim runtimes. It allows version-independent targeting of functions etc. using relocations. `REL::Relocation<decltype(&MyHook)> func{RELOCATION_ID(SE_ID, AE_ID), REL::Relocate(se_offset, ae_offset, vr_offset))}` returns a runtime-adjusted pointer to the function mapped by the given SE or AE address library IDs.

## Some terminology

- [Hooking](https://en.wikipedia.org/wiki/Hooking): Hooking involves intercepting function/subroutine calls and replacing or extending their logic. There are two types of hooks commonly seen in SKSE plugins:
- Call site hook: These hooks target individual `call` instructions at specific addresses, allowing you to intercept and replace/extend a specific function call.
- See `stl::write_call` in `CommonLibSSE-NG\include\SKSE\Trampoline.h`
- Virtual function hook: These hooks target _every_ invocation of a given function via the virtual function table of its parent class.
- See `stl::write_vfunc` in `CommonLibSSE-NG\include\REL\Relocation.h`
- More about function hooks: [CommonLibSSE NG sample plugin repo](https://gitlab.com/colorglass/commonlibsse-sample-plugin/-/blob/main/README.md#function-hooks)
- [Trampoline](<https://en.wikipedia.org/wiki/Trampoline_(computing)>): TODO
- [Thunk](https://en.wikipedia.org/wiki/Thunk): A thin wrapper around a hooked function, allowing the execution of arbitrary logic before or after the hooked code
- [Xbyak](https://github.com/herumi/xbyak): A just-in-time assembler that allows applying assembly patches anywhere in the disassembled executable

## Events

Those familiar with Papyrus-based mod development know that the game fires various events for things that occur in game (OnHit, OnCellAttach, etc.). CommonLibSSE-NG exposes various events, allowing you to listen for and execute logic during events relevant to your plugin. Note that events are passed from one loaded SKSE plugin to the next in succession at runtime in alphabetical order of loaded SKSE plugins. Since your plugin is only one of multiple plugins that may be overriding a given event's `ProcessEvent` method, it's important to return `RE::BSEventNotifyControl::kContinue` in all control paths of your `ProcessEvent` override.
16 changes: 8 additions & 8 deletions cmake/version.rc.in
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
#include <winres.h>
#include <winver.h>

1 VERSIONINFO
FILEVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0
PRODUCTVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
FILEFLAGS 0
#endif
FILEOS 0x4L
FILETYPE 0x1L
FILESUBTYPE 0x0L
FILEOS VOS_NT
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BLOCK "040904B0"
BEGIN
VALUE "FileDescription", "@PROJECT_DESCRIPTION@"
VALUE "FileVersion", "@PROJECT_VERSION@"
VALUE "InternalName", "@PROJECT_NAME@"
VALUE "LegalCopyright", "Apache License 2.0"
VALUE "LegalCopyright", "GNU General Public License v3.0"
VALUE "ProductName", "@PROJECT_FRIENDLY_NAME@"
VALUE "ProductVersion", "@PROJECT_VERSION@"
END
Expand Down
1 change: 0 additions & 1 deletion extern/CommonLibSSE-NG
Submodule CommonLibSSE-NG deleted from e83b23
64 changes: 41 additions & 23 deletions project_setup.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,26 @@
import os
import re
import shutil
import stat
import subprocess
import json

cwd = os.path.dirname(os.path.abspath(__file__))

vcpkg_repo = "https://github.com/microsoft/vcpkg"
stdout, stderr = subprocess.Popen(
["git", "ls-remote", vcpkg_repo], stdout=subprocess.PIPE
).communicate()
vcpkg_sha = re.split(r"\t+", stdout.decode("ascii"))[0]

subprocess.Popen(["git", "submodule", "update", "--init"], cwd=cwd).communicate()


def onexc(func, path, exc_info):
if not os.access(path, os.W_OK):
os.chmod(path, stat.S_IWUSR)
func(path)
else:
raise


if os.path.isdir(os.path.join(cwd, ".git")):
shutil.rmtree(os.path.join(cwd, ".git"), onexc=onexc)

os.remove(os.path.join(cwd, "README.md"))

project_name = input("Enter project name: ")
author = input("Enter author: ")
print()

from_path = input("Use CommonLibSSE-NG from path? (Y/n): ")
if from_path == "" or from_path.lower() == "y":
print(f"Using CommonLibSSE-NG from path {os.environ["CommonLibSSEPath"]}")
elif from_path.lower() == "n":
print("Using CommonLibSSE-NG as submodule")
else:
print("Invalid input")
exit()

pattern = re.compile(r"(?<!^)(?=[A-Z])")

with open(os.path.join(cwd, "vcpkg.json"), "r", encoding="utf-8") as vcpkg_json_file:
Expand All @@ -40,8 +30,6 @@ def onexc(func, path, exc_info):
vcpkg_json["name"] = name
vcpkg_json["version-semver"] = "1.0.0"

vcpkg_json["vcpkg-configuration"]["default-registry"]["baseline"] = vcpkg_sha

with open(os.path.join(cwd, "vcpkg.json"), "w", encoding="utf-8") as vcpkg_json_file:
json.dump(vcpkg_json, vcpkg_json_file, indent=2)

Expand All @@ -54,6 +42,9 @@ def onexc(func, path, exc_info):
cmakelists = cmakelists.replace("AuthorName", author)
cmakelists = cmakelists.replace("0.0.1", "1.0.0")

if from_path == "" or from_path.lower() == "y":
cmakelists = cmakelists.replace("extern/CommonLibSSE-NG", "$ENV{CommonLibSSEPath}")

with open(
os.path.join(cwd, "CMakeLists.txt"), "w", encoding="utf-8"
) as cmakelists_file:
Expand All @@ -75,3 +66,30 @@ def onexc(func, path, exc_info):
os.path.join(cwd, "src", "Settings.cpp"), "w", encoding="utf-8"
) as settings_cpp_file:
settings_cpp_file.write(settings_cpp)

print("Updating vcpkg.json...")
subprocess.Popen(
[f"{os.environ["VCPKG_ROOT"]}\\vcpkg.exe", "x-update-baseline"], cwd=cwd, shell=True
).communicate()
print()

subprocess.Popen(["git", "init"]).communicate()

if from_path.lower() == "n":
print("\nInitializing CommonLibSSE-NG submodule...")
subprocess.Popen(
[
"git",
"submodule",
"add",
"-b",
"ng",
"https://github.com/alandtse/CommonLibVR",
"extern/CommonLibSSE-NG",
],
cwd=cwd,
).communicate()

subprocess.Popen(
["git", "submodule", "update", "--init", "--recursive"], cwd=cwd
).communicate()
10 changes: 8 additions & 2 deletions vcpkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"name": "plugin-name",
"version-semver": "0.0.1",
"builtin-baseline": "28b1cf627c0570b3e094192df2fce31a3a2bc1d3",
"dependencies": ["fmt", "spdlog", "rapidcsv", "simpleini", "directxtk"],
"builtin-baseline": "cbf4a6641528cee6f172328984576f51698de726",
"dependencies": [
"fmt",
"spdlog",
"rapidcsv",
"simpleini",
"directxtk"
],
"license": "GPL-3.0-or-later"
}

0 comments on commit da258dd

Please sign in to comment.