CommonLibSSE NG
Module.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include "REL/Version.h"
4 
5 #include "REX/W32/ADVAPI32.h"
6 #include "REX/W32/KERNEL32.h"
7 
8 namespace REL
9 {
10  class Segment
11  {
12  public:
13  enum Name : std::size_t
14  {
20  tls,
23  total
24  };
25 
26  Segment() noexcept = default;
27 
28  Segment(std::uintptr_t a_proxyBase, std::uintptr_t a_address, std::uintptr_t a_size) noexcept:
29  _proxyBase(a_proxyBase),
30  _address(a_address),
31  _size(a_size)
32  {}
33 
34  [[nodiscard]] std::uintptr_t address() const noexcept { return _address; }
35 
36  [[nodiscard]] std::size_t offset() const noexcept { return address() - _proxyBase; }
37 
38  [[nodiscard]] std::size_t size() const noexcept { return _size; }
39 
40  [[nodiscard]] void* pointer() const noexcept { return reinterpret_cast<void *>(address()); }
41 
42  template<class T>
43  [[nodiscard]] T* pointer() const noexcept
44  {
45  return static_cast<T*>(pointer());
46  }
47 
48  private:
49  friend class Module;
50 
51  std::uintptr_t _proxyBase{0};
52  std::uintptr_t _address{0};
53  std::size_t _size{0};
54  };
55 
56  class Module
57  {
58  public:
62  enum class Runtime : uint8_t
63  {
64  Unknown = 0,
65 
69  AE = 1 << 0,
70 
74  SE = 1 << 1,
75 
79  VR = 1 << 2
80  };
81 
82  [[nodiscard]] static Module &get()
83  {
84  if (_initialized.load(std::memory_order_relaxed)) {
85  return _instance;
86  }
87  [[maybe_unused]] std::unique_lock lock(_initLock);
88  _instance.init();
89  _initialized = true;
90  return _instance;
91  }
92 
93 #ifdef ENABLE_COMMONLIBSSE_TESTING
105  static bool inject(std::wstring_view a_filePath)
106  {
107  _instance.clear();
108  _initialized = true;
109  return _instance.init(a_filePath);
110  }
111 
129  static bool inject(Runtime a_runtime = Runtime::Unknown)
130  {
131  if (a_runtime == Runtime::Unknown) {
132  return inject(Runtime::SE) || inject(Runtime::VR);
133  }
134 
135  constexpr std::size_t bufferSize = 4096; // Max NTFS path length.
136  const wchar_t* subKey =
137  a_runtime == Runtime::VR ?
138  LR"(SOFTWARE\Bethesda Softworks\Skyrim VR)" :
139  LR"(SOFTWARE\Bethesda Softworks\Skyrim Special Edition)";
140  std::uint32_t length = bufferSize * sizeof(wchar_t);
141  std::uint8_t value[bufferSize];
142  if (REX::W32::RegGetValueW(REX::W32::HKEY_LOCAL_MACHINE, subKey, L"Installed Path", 0x20002u, nullptr, value, &length) != 0) {
143  return false;
144  }
145  std::filesystem::path installPath(reinterpret_cast<wchar_t *>(value));
146  installPath /= a_runtime == Runtime::VR ? L"SkyrimVR.exe" : L"SkyrimSE.exe";
147  return inject(installPath.c_str());
148  }
149 
150  static bool mock(
151  REL::Version a_version,
152  Runtime a_runtime = Runtime::Unknown,
153  std::wstring_view a_filename = L"SkyrimSE.exe"sv,
154  std::uintptr_t a_base = 0,
155  std::array<std::uintptr_t, Segment::total> a_segmentSizes = {0x1603000, 0, 0x8ee000, 0x1887000, 0x15c000, 0x3000, 0x2000, 0x1000})
156  {
157  _instance.clear();
158  _initialized = true;
159 
160  if (a_filename.empty() || !a_segmentSizes[0]) {
161  return false;
162  }
163 
164  _instance._filename = _instance._filePath = a_filename.data();
165  _instance._version = a_version;
166  if (a_runtime == Runtime::Unknown) {
167  switch (a_version[1]) {
168  case 4:
169  _instance._runtime = Runtime::VR;
170  break;
171  case 6:
172  _instance._runtime = Runtime::AE;
173  break;
174  default:
175  _instance._runtime = Runtime::SE;
176  }
177  } else {
178  _instance._runtime = a_runtime;
179  }
180  _instance._base = a_base;
181 
182  auto currentAddress = a_base + 0x1000;
183  for (std::size_t i = 0; i < a_segmentSizes.size(); ++i) {
184  auto &segment = _instance._segments[i];
185  segment._size = a_segmentSizes[i];
186  if (segment._size) {
187  segment._proxyBase = a_base;
188  segment._address = (currentAddress += segment._size);
189  }
190  }
191 
192  return true;
193  }
194 
195  static void reset() {
196  _initialized = false;
197  _instance.clear();
198  }
199 #endif
200 
201  [[nodiscard]] std::uintptr_t base() const noexcept { return _base; }
202 
203  [[nodiscard]] stl::zwstring filename() const noexcept { return _filename; }
204 
205  [[nodiscard]] stl::zwstring filePath() const noexcept { return _filePath; }
206 
207  [[nodiscard]] Version version() const noexcept { return _version; }
208 
209  [[nodiscard]] Segment segment(Segment::Name a_segment) const noexcept { return _segments[a_segment]; }
210 
211  [[nodiscard]] REX::W32::HMODULE pointer() const noexcept { return reinterpret_cast<REX::W32::HMODULE>(base()); }
212 
213  template<class T>
214  [[nodiscard]] T* pointer() const noexcept
215  {
216  return static_cast<T*>(pointer());
217  }
218 
222  [[nodiscard]] static SKYRIM_REL Runtime GetRuntime() noexcept
223  {
224 #if (!defined(ENABLE_SKYRIM_AE) && !defined(ENABLE_SKYRIM_VR))
225  return Runtime::SE;
226 #elif (!defined(ENABLE_SKYRIM_SE) && !defined(ENABLE_SKYRIM_VR))
227  return Runtime::AE;
228 #elif (!defined(ENABLE_SKYRIM_AE) && !defined(ENABLE_SKYRIM_SE))
229  return Runtime::VR;
230 #else
231  return get()._runtime;
232 #endif
233  }
234 
238  [[nodiscard]] static SKYRIM_REL bool IsAE() noexcept
239  {
240  return GetRuntime() == Runtime::AE;
241  }
242 
246  [[nodiscard]] static SKYRIM_REL bool IsSE() noexcept
247  {
248  return GetRuntime() == Runtime::SE;
249  }
250 
254  [[nodiscard]] static SKYRIM_REL_VR bool IsVR() noexcept
255  {
256 #ifndef ENABLE_SKYRIM_VR
257  return false;
258 #elif !defined(ENABLE_SKYRIM_AE) && !defined(ENABLE_SKYRIM_SE)
259  return true;
260 #else
261  return GetRuntime() == Runtime::VR;
262 #endif
263  }
264 
265  private:
266  Module() = default;
267  Module(const Module &) = delete;
268  Module(Module &&) = delete;
269 
270  ~Module() noexcept = default;
271 
272  Module &operator=(const Module &) = delete;
273  Module &operator=(Module &&) = delete;
274 
275  bool init()
276  {
277  const auto getFilename = [&]() {
279  ENVIRONMENT.data(),
280  _filename.data(),
281  static_cast<std::uint32_t>(_filename.size()));
282  };
283 
284  void *moduleHandle = nullptr;
285  _filename.resize(getFilename());
286  if (const auto result = getFilename();
287  result != _filename.size() - 1 ||
288  result == 0) {
289  for (auto runtime : RUNTIMES) {
290  _filename = runtime;
291  moduleHandle = REX::W32::GetModuleHandleW(_filename.c_str());
292  if (moduleHandle) {
293  break;
294  }
295  }
296  }
297  _filePath = _filename;
298  if (!moduleHandle) {
300  std::format(
301  "Failed to obtain module handle for: \"{0}\".\n"
302  "You have likely renamed the executable to something unexpected. "
303  "Renaming the executable back to \"{0}\" may resolve the issue."sv,
304  stl::utf16_to_utf8(_filename).value_or("<unicode conversion error>"s)));
305  }
306  return load(moduleHandle, true);
307  }
308 
309  bool init(std::wstring_view a_filePath)
310  {
311  std::filesystem::path exePath(a_filePath);
312  _filename = exePath.filename().wstring();
313  _filePath = exePath.wstring();
314  _injectedModule = REX::W32::LoadLibraryW(_filePath.c_str());
315  if (_injectedModule) {
316  return load(_injectedModule, false);
317  }
318  return false;
319  }
320 
321  [[nodiscard]] bool load(void *a_handle, bool a_failOnError)
322  {
323  _base = reinterpret_cast<std::uintptr_t>(a_handle);
324  if (!load_version(a_failOnError)) {
325  return false;
326  }
327  load_segments();
328  return true;
329  }
330 
331  void load_segments();
332 
333  bool load_version(bool a_failOnError)
334  {
335  const auto version = GetFileVersion(_filePath);
336  if (version) {
337  _version = *version;
338  switch (_version[1]) {
339  case 4:
340  _runtime = Runtime::VR;
341  break;
342  case 6:
343  _runtime = Runtime::AE;
344  break;
345  default:
346  _runtime = Runtime::SE;
347  }
348  return true;
349  }
350  return stl::report_and_error(
351  std::format(
352  "Failed to obtain file version info for: {}\n"
353  "Please contact the author of this script extender plugin for further assistance."sv,
354  stl::utf16_to_utf8(_filename).value_or("<unicode conversion error>"s)), a_failOnError);
355  }
356 
357  void clear();
358 
359  static constexpr std::array SEGMENTS{
361  std::make_pair(".idata"sv, 0u),
362  std::make_pair(".rdata"sv, 0u),
363  std::make_pair(".data"sv, 0u),
364  std::make_pair(".pdata"sv, 0u),
365  std::make_pair(".tls"sv, 0u),
367  std::make_pair(".gfids"sv, 0u)
368  };
369 
370  static constexpr auto ENVIRONMENT = L"SKSE_RUNTIME"sv;
371  static constexpr std::array<std::wstring_view, 2> RUNTIMES{
372  {L"SkyrimVR.exe", L"SkyrimSE.exe"}
373  };
374 
375  static Module _instance;
376  static inline std::atomic_bool _initialized{false};
377  static inline std::mutex _initLock;
378  REX::W32::HMODULE _injectedModule{nullptr};
379  std::wstring _filename;
380  std::wstring _filePath;
381  std::array<Segment, Segment::total> _segments;
382  Version _version;
383  std::uintptr_t _base{0};
384  Runtime _runtime{Runtime::AE};
385  };
386 }
#define SKYRIM_REL_VR
Definition: Common.h:76
#define SKYRIM_REL
Definition: Common.h:38
Definition: Module.h:57
static SKYRIM_REL_VR bool IsVR() noexcept
Definition: Module.h:254
static SKYRIM_REL bool IsSE() noexcept
Definition: Module.h:246
static Module & get()
Definition: Module.h:82
Runtime
Definition: Module.h:63
Version version() const noexcept
Definition: Module.h:207
std::uintptr_t base() const noexcept
Definition: Module.h:201
stl::zwstring filename() const noexcept
Definition: Module.h:203
REX::W32::HMODULE pointer() const noexcept
Definition: Module.h:211
T * pointer() const noexcept
Definition: Module.h:214
stl::zwstring filePath() const noexcept
Definition: Module.h:205
Segment segment(Segment::Name a_segment) const noexcept
Definition: Module.h:209
static SKYRIM_REL Runtime GetRuntime() noexcept
Definition: Module.h:222
static SKYRIM_REL bool IsAE() noexcept
Definition: Module.h:238
Definition: Module.h:11
std::size_t size() const noexcept
Definition: Module.h:38
void * pointer() const noexcept
Definition: Module.h:40
Name
Definition: Module.h:14
@ data
Definition: Module.h:18
@ tls
Definition: Module.h:20
@ textw
Definition: Module.h:21
@ gfids
Definition: Module.h:22
@ total
Definition: Module.h:23
@ idata
Definition: Module.h:16
@ textx
Definition: Module.h:15
@ pdata
Definition: Module.h:19
@ rdata
Definition: Module.h:17
T * pointer() const noexcept
Definition: Module.h:43
std::uintptr_t address() const noexcept
Definition: Module.h:34
Segment() noexcept=default
std::size_t offset() const noexcept
Definition: Module.h:36
Definition: Version.h:6
Definition: ID.h:6
std::optional< Version > GetFileVersion(stl::zwstring a_filename)
std::int32_t RegGetValueW(HKEY a_key, const wchar_t *a_subKey, const wchar_t *a_value, std::uint32_t a_flags, std::uint32_t *a_type, void *a_data, std::uint32_t *a_dataLen)
auto HKEY_LOCAL_MACHINE
Definition: ADVAPI32.h:9
HMODULE LoadLibraryW(const wchar_t *a_name) noexcept
constexpr auto IMAGE_SCN_MEM_EXECUTE
Definition: KERNEL32.h:96
constexpr auto IMAGE_SCN_MEM_WRITE
Definition: KERNEL32.h:98
HINSTANCE HMODULE
Definition: BASE.h:24
HMODULE GetModuleHandleW(const wchar_t *a_name) noexcept
std::uint32_t GetEnvironmentVariableW(const wchar_t *a_name, wchar_t *a_buf, std::uint32_t a_bufLen) noexcept
auto make_pair(T1 &&a_first, T2 &&a_second)
Definition: BSTTuple.h:177
void report_and_fail(std::string_view a_msg, std::source_location a_loc=std::source_location::current())
Definition: PCH.h:660
auto utf16_to_utf8(std::wstring_view a_in) noexcept -> std::optional< std::string >
Definition: PCH.h:570
bool report_and_error(std::string_view a_msg, bool a_fail=true, std::source_location a_loc=std::source_location::current())
Definition: PCH.h:598
basic_zstring< wchar_t > zwstring
Definition: PCH.h:78
Definition: ActorValueList.h:28