CommonLibSSE NG
ID.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include "REL/Module.h"
4 
5 namespace REL
6 {
7  namespace detail
8  {
9  class memory_map
10  {
11  public:
12  memory_map() noexcept = default;
13  memory_map(const memory_map&) = delete;
14 
15  memory_map(memory_map&& a_rhs) noexcept:
16  _mapping(a_rhs._mapping),
17  _view(a_rhs._view)
18  {
19  a_rhs._mapping = nullptr;
20  a_rhs._view = nullptr;
21  }
22 
23  ~memory_map() { close(); }
24 
25  memory_map &operator=(const memory_map&) = delete;
26 
27  memory_map &operator=(memory_map&& a_rhs) noexcept
28  {
29  if (this != std::addressof(a_rhs)) {
30  _mapping = a_rhs._mapping;
31  a_rhs._mapping = nullptr;
32 
33  _view = a_rhs._view;
34  a_rhs._view = nullptr;
35  }
36 
37  return *this;
38  }
39 
40  [[nodiscard]] void* data() noexcept { return _view; }
41 
42  bool open(stl::zwstring a_name, std::size_t a_size);
43 
44  bool create(stl::zwstring a_name, std::size_t a_size);
45 
46  void close();
47 
48  private:
49  void* _mapping{nullptr};
50  void* _view{nullptr};
51  };
52  }
53 
54  class IDDatabase
55  {
56  private:
57  struct mapping_t
58  {
59  std::uint64_t id;
60  std::uint64_t offset;
61  };
62 
63  public:
64  enum class Format
65  {
66  SSEv1,
67  SSEv2,
68  VR
69  };
70 
71  class Offset2ID
72  {
73  public:
74  using value_type = mapping_t;
75  using container_type = std::vector<value_type>;
76  using size_type = typename container_type::size_type;
77  using const_iterator = typename container_type::const_iterator;
78  using const_reverse_iterator = typename container_type::const_reverse_iterator;
79 
80  template<class ExecutionPolicy>
81  explicit Offset2ID(ExecutionPolicy&& a_policy)
82  requires(std::is_execution_policy_v<std::decay_t<ExecutionPolicy>>)
83  {
84  const std::span<const mapping_t> id2offset = IDDatabase::get()._id2offset;
85  _offset2id.reserve(id2offset.size());
86  _offset2id.insert(_offset2id.begin(), id2offset.begin(), id2offset.end());
87  std::sort(a_policy, _offset2id.begin(), _offset2id.end(), [](auto &&a_lhs, auto &&a_rhs) {
88  return a_lhs.offset < a_rhs.offset;
89  });
90  }
91 
93  Offset2ID(std::execution::sequenced_policy{})
94  {}
95 
96  [[nodiscard]] std::uint64_t operator()(std::size_t a_offset) const
97  {
98  const mapping_t elem{0, a_offset};
99  const auto it = std::lower_bound(
100  _offset2id.begin(),
101  _offset2id.end(),
102  elem,
103  [](auto &&a_lhs, auto &&a_rhs) {
104  return a_lhs.offset < a_rhs.offset;
105  });
106  if (it == _offset2id.end()) {
108  std::format(
109  "Failed to find the offset within the database: 0x{:08X}"sv,
110  a_offset));
111  }
112 
113  return it->id;
114  }
115 
116  [[nodiscard]] const_iterator begin() const noexcept { return _offset2id.begin(); }
117  [[nodiscard]] const_iterator cbegin() const noexcept { return _offset2id.cbegin(); }
118  [[nodiscard]] const_iterator end() const noexcept { return _offset2id.end(); }
119  [[nodiscard]] const_iterator cend() const noexcept { return _offset2id.cend(); }
120  [[nodiscard]] const_reverse_iterator rbegin() const noexcept { return _offset2id.rbegin(); }
121  [[nodiscard]] const_reverse_iterator crbegin() const noexcept { return _offset2id.crbegin(); }
122  [[nodiscard]] const_reverse_iterator rend() const noexcept { return _offset2id.rend(); }
123  [[nodiscard]] const_reverse_iterator crend() const noexcept { return _offset2id.crend(); }
124  [[nodiscard]] size_type size() const noexcept { return _offset2id.size(); }
125 
126  private:
127  container_type _offset2id;
128  };
129 
130  [[nodiscard]] static IDDatabase& get()
131  {
132  if (_initialized.load(std::memory_order_relaxed)) {
133  return _instance;
134  }
135  [[maybe_unused]] std::unique_lock lock(_initLock);
136  _instance.load();
137  _initialized.store(true, std::memory_order_relaxed);
138  return _instance;
139  }
140 
141 #ifdef ENABLE_COMMONLIBSSE_TESTING
142  [[nodiscard]] static bool inject(std::wstring_view a_filePath, Format a_format)
143  {
144  return inject(a_filePath, a_format, Module::get().version());
145  }
146 
147  [[nodiscard]] static bool inject(std::wstring_view a_filePath, Format a_format, Version a_version)
148  {
149  _initialized = true;
150  _instance.clear();
151  switch (a_format) {
152  case Format::SSEv1:
153  return _instance.load_file(a_filePath.data(), a_version, 1, false);
154  case Format::SSEv2:
155  return _instance.load_file(a_filePath.data(), a_version, 2, false);
156 #ifdef ENABLE_SKYRIM_VR
157  case Format::VR:
158  return _instance.load_csv(a_filePath.data(), a_version, false);
159 #endif
160  default:
161  return false;
162  }
163  }
164 
165  static void reset()
166  {
167  _instance.clear();
168  _initialized = false;
169  }
170 #endif
171 
172  [[nodiscard]] inline std::size_t id2offset(std::uint64_t a_id) const
173  {
174  mapping_t elem{a_id, 0};
175  const auto it = std::lower_bound(
176  _id2offset.begin(),
177  _id2offset.end(),
178  elem,
179  [](auto &&a_lhs, auto &&a_rhs) {
180  return a_lhs.id < a_rhs.id;
181  });
182 
183  bool failed = false;
184  if (it == _id2offset.end()) {
185  failed = true;
186  } else if SKYRIM_REL_VR_CONSTEXPR (Module::IsVR()) {
187  if (it->id != a_id) {
188  failed = true;
189  }
190  }
191  if (failed) {
193  std::format(
194  "Failed to find the id within the address library: {}\n"
195  "This means this script extender plugin is incompatible with the address "
196  "library for this version of the game, and thus does not support it."sv,
197  a_id));
198  }
199 
200  return static_cast<std::size_t>(it->offset);
201  }
202 
203  private:
204  friend class Module;
205 
206  class istream_t
207  {
208  public:
209  using stream_type = std::ifstream;
210  using pointer = stream_type *;
211  using const_pointer = const stream_type *;
212  using reference = stream_type &;
213  using const_reference = const stream_type &;
214 
215  inline istream_t(stl::zwstring a_filename, std::ios_base::openmode a_mode) :
216  _stream(a_filename.data(), a_mode)
217  {
218  if (!_stream.is_open()) {
219  stl::report_and_fail("failed to open address library file");
220  }
221 
222  _stream.exceptions(std::ios::badbit | std::ios::failbit | std::ios::eofbit);
223  }
224 
225  inline void ignore(std::streamsize a_count) { _stream.ignore(a_count); }
226 
227  template<class T>
228  inline void readin(T &a_val)
229  {
230  _stream.read(reinterpret_cast<char *>(std::addressof(a_val)), sizeof(T));
231  }
232 
233  template<
234  class T,
235  std::enable_if_t<
236  std::is_arithmetic_v<T>,
237  int> = 0>
238  inline T readout()
239  {
240  T val{};
241  readin(val);
242  return val;
243  }
244 
245  private:
246  stream_type _stream;
247  };
248 
249  class header_t
250  {
251  public:
252  void read(istream_t &a_in, std::uint8_t a_formatVersion)
253  {
254  std::int32_t format{};
255  a_in.readin(format);
256  if (format != a_formatVersion) {
258  std::format(
259  "Unsupported address library format: {}\n"
260  "This means this script extender plugin is incompatible with the address "
261  "library available for this version of the game, and thus does not "
262  "support it."sv,
263  format));
264  }
265 
266  std::int32_t version[4]{};
267  std::int32_t nameLen{};
268  a_in.readin(version);
269  a_in.readin(nameLen);
270  a_in.ignore(nameLen);
271 
272  a_in.readin(_pointerSize);
273  a_in.readin(_addressCount);
274 
275  for (std::size_t i = 0; i < std::extent_v<decltype(version)>; ++i) {
276  _version[i] = static_cast<std::uint16_t>(version[i]);
277  }
278  }
279 
280  [[nodiscard]] std::size_t address_count() const noexcept { return static_cast<std::size_t>(_addressCount); }
281 
282  [[nodiscard]] std::uint64_t pointer_size() const noexcept { return static_cast<std::uint64_t>(_pointerSize); }
283 
284  [[nodiscard]] Version version() const noexcept { return _version; }
285 
286  private:
287  Version _version;
288  std::int32_t _pointerSize{0};
289  std::int32_t _addressCount{0};
290  };
291 
292  IDDatabase() = default;
293  IDDatabase(const IDDatabase&) = delete;
294  IDDatabase(IDDatabase&&) = delete;
295 
296  ~IDDatabase() = default;
297 
298  IDDatabase& operator=(const IDDatabase&) = delete;
299  IDDatabase& operator=(IDDatabase&&) = delete;
300 
301  void load()
302  {
303  const auto version = Module::get().version();
304 #ifdef ENABLE_SKYRIM_VR
306  const auto filename =
308  std::format("Data/SKSE/Plugins/version-{}.csv"sv, version.string()))
309  .value_or(L"<unknown filename>"s);
310  load_csv(filename, version, true);
311  } else {
312 #endif
313  const auto filename =
315  Module::IsAE() ?
316  std::format("Data/SKSE/Plugins/versionlib-{}.bin"sv, version.string()) :
317  std::format("Data/SKSE/Plugins/version-{}.bin"sv, version.string()))
318  .value_or(L"<unknown filename>"s);
319  load_file(filename, version, Module::IsAE() ? 2 : 1, true);
320 #ifdef ENABLE_SKYRIM_VR
321  }
322 #endif
323  }
324 
325  bool load_file(stl::zwstring a_filename, Version a_version, std::uint8_t a_formatVersion, bool a_failOnError);
326 
327 #ifdef ENABLE_SKYRIM_VR
328  bool load_csv(stl::zwstring a_filename, Version a_version, bool a_failOnError);
329 #endif
330 
331  bool unpack_file(istream_t &a_in, header_t a_header, bool a_failOnError)
332  {
333  std::uint8_t type = 0;
334  std::uint64_t id = 0;
335  std::uint64_t offset = 0;
336  std::uint64_t prevID = 0;
337  std::uint64_t prevOffset = 0;
338  for (auto& mapping: _id2offset) {
339  a_in.readin(type);
340  const auto lo = static_cast<std::uint8_t>(type & 0xF);
341  const auto hi = static_cast<std::uint8_t>(type >> 4);
342 
343  switch (lo) {
344  case 0:
345  a_in.readin(id);
346  break;
347  case 1:
348  id = prevID + 1;
349  break;
350  case 2:
351  id = prevID + a_in.readout<std::uint8_t>();
352  break;
353  case 3:
354  id = prevID - a_in.readout<std::uint8_t>();
355  break;
356  case 4:
357  id = prevID + a_in.readout<std::uint16_t>();
358  break;
359  case 5:
360  id = prevID - a_in.readout<std::uint16_t>();
361  break;
362  case 6:
363  id = a_in.readout<std::uint16_t>();
364  break;
365  case 7:
366  id = a_in.readout<std::uint32_t>();
367  break;
368  default:
369  return stl::report_and_error("unhandled type"sv, a_failOnError);
370  }
371 
372  const std::uint64_t tmp = (hi & 8) != 0 ? (prevOffset / a_header.pointer_size()) : prevOffset;
373 
374  switch (hi & 7) {
375  case 0:
376  a_in.readin(offset);
377  break;
378  case 1:
379  offset = tmp + 1;
380  break;
381  case 2:
382  offset = tmp + a_in.readout<std::uint8_t>();
383  break;
384  case 3:
385  offset = tmp - a_in.readout<std::uint8_t>();
386  break;
387  case 4:
388  offset = tmp + a_in.readout<std::uint16_t>();
389  break;
390  case 5:
391  offset = tmp - a_in.readout<std::uint16_t>();
392  break;
393  case 6:
394  offset = a_in.readout<std::uint16_t>();
395  break;
396  case 7:
397  offset = a_in.readout<std::uint32_t>();
398  break;
399  default:
400  return stl::report_and_error("unhandled type"sv, a_failOnError);
401  }
402 
403  if ((hi & 8) != 0) {
404  offset *= a_header.pointer_size();
405  }
406 
407  mapping = {id, offset};
408 
409  prevOffset = offset;
410  prevID = id;
411  }
412 
413  return true;
414  }
415 
416  void clear()
417  {
418  _mmap.close();
419  _id2offset = {};
420  }
421 
422  static IDDatabase _instance;
423  static inline std::atomic_bool _initialized{false};
424  static inline std::mutex _initLock;
425  detail::memory_map _mmap;
426  std::span<mapping_t> _id2offset;
427  };
428 
429  class ID
430  {
431  public:
432  constexpr ID() noexcept = default;
433 
434  explicit constexpr ID(std::uint64_t a_id) noexcept:
435  _id(a_id)
436  {}
437 
438  constexpr ID &operator=(std::uint64_t a_id) noexcept
439  {
440  _id = a_id;
441  return *this;
442  }
443 
444  [[nodiscard]] std::uintptr_t address() const { return base() + offset(); }
445 
446  [[nodiscard]] constexpr std::uint64_t id() const noexcept { return _id; }
447 
448  [[nodiscard]] std::size_t offset() const { return IDDatabase::get().id2offset(_id); }
449 
450  private:
451  [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); }
452 
453  std::uint64_t _id{0};
454  };
455 
457  {
458  public:
459  constexpr RelocationID() noexcept = default;
460 
461  explicit constexpr RelocationID(
462  [[maybe_unused]] std::uint64_t a_seID,
463  [[maybe_unused]] std::uint64_t a_aeID) noexcept
464  {
465 #ifdef ENABLE_SKYRIM_SE
466  _seID = a_seID;
467 #endif
468 #ifdef ENABLE_SKYRIM_AE
469  _aeID = a_aeID;
470 #endif
471 #ifdef ENABLE_SKYRIM_VR
472  _vrID = a_seID;
473 #endif
474  }
475 
476  explicit constexpr RelocationID(
477  [[maybe_unused]] std::uint64_t a_seID,
478  [[maybe_unused]] std::uint64_t a_aeID,
479  [[maybe_unused]] std::uint64_t a_vrID) noexcept
480  {
481 #ifdef ENABLE_SKYRIM_SE
482  _seID = a_seID;
483 #endif
484 #ifdef ENABLE_SKYRIM_AE
485  _aeID = a_aeID;
486 #endif
487 #ifdef ENABLE_SKYRIM_VR
488  _vrID = a_vrID;
489 #endif
490  }
491 
492  [[nodiscard]] std::uintptr_t address() const
493  {
494  auto thisOffset = offset();
495  return thisOffset ? base() + offset() : 0;
496  }
497 
498  [[nodiscard]] std::size_t offset() const
499  {
500  auto thisID = id();
501  return thisID ? IDDatabase::get().id2offset(thisID) : 0;
502  }
503 
504  [[nodiscard]] SKYRIM_REL std::uint64_t id() const noexcept
505  {
506  switch (Module::GetRuntime()) {
507 #ifdef ENABLE_SKYRIM_AE
508  case Module::Runtime::AE:
509  return _aeID;
510 #endif
511 #ifdef ENABLE_SKYRIM_SE
512  case Module::Runtime::SE:
513  return _seID;
514 #endif
515 #ifdef ENABLE_SKYRIM_VR
516  case Module::Runtime::VR:
517  return _vrID;
518 #endif
519  default:
520  return 0;
521  }
522  }
523 
524  [[nodiscard]] SKYRIM_REL explicit operator ID() const noexcept {
525  return ID(id());
526  }
527 
528  private:
529  [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); }
530 
531 #ifdef ENABLE_SKYRIM_SE
532  std::uint64_t _seID{0};
533 #endif
534 #ifdef ENABLE_SKYRIM_AE
535  std::uint64_t _aeID{0};
536 #endif
537 #ifdef ENABLE_SKYRIM_VR
538  std::uint64_t _vrID{0};
539 #endif
540  };
541 
542  class VariantID
543  {
544  public:
545  constexpr VariantID() noexcept = default;
546 
547  explicit constexpr VariantID(
548  [[maybe_unused]] std::uint64_t a_seID,
549  [[maybe_unused]] std::uint64_t a_aeID,
550  [[maybe_unused]] std::uint64_t a_vrOffset) noexcept
551  {
552 #ifdef ENABLE_SKYRIM_SE
553  _seID = a_seID;
554 #endif
555 #ifdef ENABLE_SKYRIM_AE
556  _aeID = a_aeID;
557 #endif
558 #ifdef ENABLE_SKYRIM_VR
559  _vrOffset = a_vrOffset;
560 #endif
561  }
562 
563  [[nodiscard]] std::uintptr_t address() const
564  {
565  auto thisOffset = offset();
566  return thisOffset ? base() + offset() : 0;
567  }
568 
569  [[nodiscard]] std::size_t offset() const
570  {
571  switch (Module::GetRuntime()) {
572 #ifdef ENABLE_SKYRIM_AE
573  case Module::Runtime::AE:
574  return _aeID ? IDDatabase::get().id2offset(_aeID) : 0;
575 #endif
576 #ifdef ENABLE_SKYRIM_SE
577  case Module::Runtime::SE:
578  return _seID ? IDDatabase::get().id2offset(_seID) : 0;
579 #endif
580 #ifdef ENABLE_SKYRIM_VR
581  case Module::Runtime::VR:
582  return _vrOffset;
583 #endif
584  default:
585  return 0;
586  }
587  }
588 
589  private:
590  [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); }
591 
592 #ifdef ENABLE_SKYRIM_SE
593  std::uint64_t _seID{0};
594 #endif
595 #ifdef ENABLE_SKYRIM_AE
596  std::uint64_t _aeID{0};
597 #endif
598 #ifdef ENABLE_SKYRIM_VR
599  std::uint64_t _vrOffset{0};
600 #endif
601  };
602 }
#define SKYRIM_REL_VR_CONSTEXPR
Definition: Common.h:85
#define SKYRIM_REL_CONSTEXPR
Definition: Common.h:47
#define SKYRIM_REL
Definition: Common.h:38
Definition: ID.h:72
Offset2ID()
Definition: ID.h:92
typename container_type::const_iterator const_iterator
Definition: ID.h:77
typename container_type::const_reverse_iterator const_reverse_iterator
Definition: ID.h:78
const_reverse_iterator crbegin() const noexcept
Definition: ID.h:121
Offset2ID(ExecutionPolicy &&a_policy) requires(std
Definition: ID.h:81
const_iterator begin() const noexcept
Definition: ID.h:116
std::vector< value_type > container_type
Definition: ID.h:75
size_type size() const noexcept
Definition: ID.h:124
const_reverse_iterator rbegin() const noexcept
Definition: ID.h:120
const_reverse_iterator crend() const noexcept
Definition: ID.h:123
const_iterator cbegin() const noexcept
Definition: ID.h:117
typename container_type::size_type size_type
Definition: ID.h:76
const_iterator cend() const noexcept
Definition: ID.h:119
std::uint64_t operator()(std::size_t a_offset) const
Definition: ID.h:96
mapping_t value_type
Definition: ID.h:74
const_reverse_iterator rend() const noexcept
Definition: ID.h:122
const_iterator end() const noexcept
Definition: ID.h:118
Definition: ID.h:55
std::size_t id2offset(std::uint64_t a_id) const
Definition: ID.h:172
Format
Definition: ID.h:65
static IDDatabase & get()
Definition: ID.h:130
Definition: ID.h:430
std::size_t offset() const
Definition: ID.h:448
constexpr ID & operator=(std::uint64_t a_id) noexcept
Definition: ID.h:438
constexpr std::uint64_t id() const noexcept
Definition: ID.h:446
constexpr ID() noexcept=default
std::uintptr_t address() const
Definition: ID.h:444
Definition: Module.h:57
static SKYRIM_REL_VR bool IsVR() noexcept
Definition: Module.h:254
static Module & get()
Definition: Module.h:82
Version version() const noexcept
Definition: Module.h:207
std::uintptr_t base() const noexcept
Definition: Module.h:201
static SKYRIM_REL Runtime GetRuntime() noexcept
Definition: Module.h:222
static SKYRIM_REL bool IsAE() noexcept
Definition: Module.h:238
Definition: ID.h:457
std::size_t offset() const
Definition: ID.h:498
constexpr RelocationID([[maybe_unused]] std::uint64_t a_seID, [[maybe_unused]] std::uint64_t a_aeID, [[maybe_unused]] std::uint64_t a_vrID) noexcept
Definition: ID.h:476
SKYRIM_REL std::uint64_t id() const noexcept
Definition: ID.h:504
std::uintptr_t address() const
Definition: ID.h:492
constexpr RelocationID() noexcept=default
Definition: ID.h:543
std::uintptr_t address() const
Definition: ID.h:563
constexpr VariantID() noexcept=default
std::size_t offset() const
Definition: ID.h:569
Definition: ID.h:10
~memory_map()
Definition: ID.h:23
void * data() noexcept
Definition: ID.h:40
memory_map & operator=(memory_map &&a_rhs) noexcept
Definition: ID.h:27
memory_map() noexcept=default
bool create(stl::zwstring a_name, std::size_t a_size)
memory_map & operator=(const memory_map &)=delete
bool open(stl::zwstring a_name, std::size_t a_size)
Definition: ID.h:6
requires(is_builtin_convertible_v< T >) struct GetRawType< T >
Definition: PackUnpack.h:30
void report_and_fail(std::string_view a_msg, std::source_location a_loc=std::source_location::current())
Definition: PCH.h:660
auto utf8_to_utf16(std::string_view a_in) noexcept -> std::optional< std::wstring >
Definition: PCH.h:544
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