CommonLibVR
ID.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include "REL/Module.h"
4 #ifdef SKYRIMVR
5 # include <csv.h>
6 #endif
7 
8 namespace REL
9 {
10  namespace detail
11  {
12  class memory_map
13  {
14  public:
15  memory_map() noexcept = default;
16  memory_map(const memory_map&) = delete;
17 
18  memory_map(memory_map&& a_rhs) noexcept :
19  _mapping(a_rhs._mapping),
20  _view(a_rhs._view)
21  {
22  a_rhs._mapping = nullptr;
23  a_rhs._view = nullptr;
24  }
25 
26  ~memory_map() { close(); }
27 
28  memory_map& operator=(const memory_map&) = delete;
29 
30  memory_map& operator=(memory_map&& a_rhs) noexcept
31  {
32  if (this != std::addressof(a_rhs)) {
33  _mapping = a_rhs._mapping;
34  a_rhs._mapping = nullptr;
35 
36  _view = a_rhs._view;
37  a_rhs._view = nullptr;
38  }
39  return *this;
40  }
41 
42  [[nodiscard]] void* data() noexcept { return _view; }
43 
44  bool open(stl::zwstring a_name, std::size_t a_size);
45  bool create(stl::zwstring a_name, std::size_t a_size);
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  class Offset2ID
65  {
66  public:
67  using value_type = mapping_t;
68  using container_type = std::vector<value_type>;
69  using size_type = typename container_type::size_type;
70  using const_iterator = typename container_type::const_iterator;
71  using const_reverse_iterator = typename container_type::const_reverse_iterator;
72 
73  template <class ExecutionPolicy>
74  explicit Offset2ID(ExecutionPolicy&& a_policy) //
75  requires(std::is_execution_policy_v<std::decay_t<ExecutionPolicy>>)
76  {
77  const std::span<const mapping_t> id2offset = IDDatabase::get()._id2offset;
78  _offset2id.reserve(id2offset.size());
79  _offset2id.insert(_offset2id.begin(), id2offset.begin(), id2offset.end());
80  std::sort(a_policy, _offset2id.begin(), _offset2id.end(), [](auto&& a_lhs, auto&& a_rhs) {
81  return a_lhs.offset < a_rhs.offset;
82  });
83  }
84 
86  Offset2ID(std::execution::sequenced_policy{})
87  {}
88 
89  [[nodiscard]] std::uint64_t operator()(std::size_t a_offset) const
90  {
91  const mapping_t elem{ 0, a_offset };
92  const auto it = std::lower_bound(
93  _offset2id.begin(),
94  _offset2id.end(),
95  elem,
96  [](auto&& a_lhs, auto&& a_rhs) {
97  return a_lhs.offset < a_rhs.offset;
98  });
99  if (it == _offset2id.end()) {
101  std::format(
102  "Failed to find the offset within the database: 0x{:08X}"sv,
103  a_offset));
104  }
105 
106  return it->id;
107  }
108 
109  [[nodiscard]] const_iterator begin() const noexcept { return _offset2id.begin(); }
110  [[nodiscard]] const_iterator cbegin() const noexcept { return _offset2id.cbegin(); }
111 
112  [[nodiscard]] const_iterator end() const noexcept { return _offset2id.end(); }
113  [[nodiscard]] const_iterator cend() const noexcept { return _offset2id.cend(); }
114 
115  [[nodiscard]] const_reverse_iterator rbegin() const noexcept { return _offset2id.rbegin(); }
116  [[nodiscard]] const_reverse_iterator crbegin() const noexcept { return _offset2id.crbegin(); }
117 
118  [[nodiscard]] const_reverse_iterator rend() const noexcept { return _offset2id.rend(); }
119  [[nodiscard]] const_reverse_iterator crend() const noexcept { return _offset2id.crend(); }
120 
121  [[nodiscard]] size_type size() const noexcept { return _offset2id.size(); }
122 
123  private:
124  container_type _offset2id;
125  };
126 
127  [[nodiscard]] static IDDatabase& get()
128  {
129  static IDDatabase singleton;
130  return singleton;
131  }
132 
133  [[nodiscard]] inline std::size_t id2offset(std::uint64_t a_id) const
134  {
135  mapping_t elem{ a_id, 0 };
136  const auto it = std::lower_bound(
137  _id2offset.begin(),
138  _id2offset.end(),
139  elem,
140  [](auto&& a_lhs, auto&& a_rhs) {
141  return a_lhs.id < a_rhs.id;
142  });
143  if (it->id != a_id) {
145  std::format(
146  "Failed to find the id within the address library: {}\n"
147  "This means that this script extender plugin needs a newer version of the "
148  "library than you currently have."sv,
149  a_id));
150  }
151 
152  return static_cast<std::size_t>(it->offset);
153  }
154 
155 #ifdef SKYRIMVR
156  bool IsVRAddressLibraryAtLeastVersion(const char* pluginName, const char* minimalVRAddressLibVersion, bool showWindowsMessage = false) const
157  {
158  const auto minimalVersion = REL::Version(minimalVRAddressLibVersion);
159 
160  bool validVersion = minimalVersion <= _vrAddressLibraryVersion;
161 
162  if (!validVersion && showWindowsMessage) {
163  REX::W32::MessageBoxA(NULL, std::format("You need version: {} of VR Address Library for SKSEVR, you have version: {}", minimalVersion.string(), _vrAddressLibraryVersion.string()).c_str(), pluginName, 0x00000000L | 0x00000030L);
165  return false;
166  }
167 
168  return validVersion;
169  }
170 #endif // SKYRIMVR
171 
172  private:
173  friend Offset2ID;
174 
175  class header_t
176  {
177  public:
178  void read(binary_io::file_istream& a_in)
179  {
180  const auto [format] = a_in.read<std::int32_t>();
181 #ifdef SKYRIM_SUPPORT_AE
182  if (format != 2) {
183 #else
184  if (format != 1) {
185 #endif
187  std::format(
188  "Unsupported address library format: {}\n"
189  "This means this script extender plugin is incompatible with the address "
190  "library available for this version of the game, and thus does not "
191  "support it."sv,
192  format));
193  }
194 
195  const auto [major, minor, patch, revision] =
196  a_in.read<std::int32_t, std::int32_t, std::int32_t, std::int32_t>();
197  _version[0] = static_cast<std::uint16_t>(major);
198  _version[1] = static_cast<std::uint16_t>(minor);
199  _version[2] = static_cast<std::uint16_t>(patch);
200  _version[3] = static_cast<std::uint16_t>(revision);
201 
202  const auto [nameLen] = a_in.read<std::int32_t>();
203  a_in.seek_relative(nameLen);
204 
205  a_in.read(_pointerSize, _addressCount);
206  }
207 
208  [[nodiscard]] std::size_t address_count() const noexcept { return static_cast<std::size_t>(_addressCount); }
209  [[nodiscard]] std::uint64_t pointer_size() const noexcept { return static_cast<std::uint64_t>(_pointerSize); }
210  [[nodiscard]] Version version() const noexcept { return _version; }
211 
212  private:
213  Version _version;
214  std::int32_t _pointerSize{ 0 };
215  std::int32_t _addressCount{ 0 };
216  };
217 
218  IDDatabase() { load(); }
219 
220  IDDatabase(const IDDatabase&) = delete;
221  IDDatabase(IDDatabase&&) = delete;
222 
223  ~IDDatabase() = default;
224 
225  IDDatabase& operator=(const IDDatabase&) = delete;
226  IDDatabase& operator=(IDDatabase&&) = delete;
227 
228  void load()
229  {
230  const auto version = Module::get().version();
231  const auto filename =
233  std::format(
234 #ifdef SKYRIM_SUPPORT_AE
235  "Data/SKSE/Plugins/versionlib-{}.bin"sv,
236 #elif SKYRIMVR
237  "Data/SKSE/Plugins/version-{}.csv"sv,
238 #else
239  "Data/SKSE/Plugins/version-{}.bin"sv,
240 #endif //SKYRIM_SUPPORT_AE
241  version.string()))
242  .value_or(L"<unknown filename>"s);
243 #ifdef SKYRIMVR
244  load_csv(filename, version);
245 #else
246  load_file(filename, version);
247 #endif // SKYRIMVR
248  }
249 
250 #ifdef SKYRIMVR
251  bool load_csv(stl::zwstring a_filename, Version a_version)
252  {
253  // conversion code from https://docs.microsoft.com/en-us/cpp/text/how-to-convert-between-various-string-types?view=msvc-170
254  const wchar_t* orig = a_filename.data();
255  std::size_t origsize = wcslen(orig) + 1;
256  std::size_t convertedChars = 0;
257  const std::size_t newsize = origsize * 2;
258  char* nstring = new char[newsize];
259  wcstombs_s(&convertedChars, nstring, newsize, orig, _TRUNCATE);
260  if (!std::filesystem::exists(nstring))
261  stl::report_and_fail(std::format("Required VR Address Library file {} does not exist"sv, nstring));
263  in.read_header(io::ignore_missing_column, "id", "offset");
264  std::size_t id, address_count;
265  std::string version, offset;
266  auto mapname = L"CommonLibSSEOffsets-v2-"s;
267  mapname += a_version.wstring();
268  in.read_row(address_count, version);
269  _vrAddressLibraryVersion = Version(version);
270  const auto byteSize = static_cast<std::size_t>(address_count * sizeof(mapping_t));
271  if (!_mmap.open(mapname, byteSize) &&
272  !_mmap.create(mapname, byteSize)) {
273  stl::report_and_fail("failed to create shared mapping"sv);
274  }
275  _id2offset = { static_cast<mapping_t*>(_mmap.data()), static_cast<std::size_t>(address_count) };
276  int index = 0;
277  while (in.read_row(id, offset)) {
278  if (index >= address_count)
279  stl::report_and_fail(std::format("VR Address Library {} tried to exceed {} allocated entries."sv, version, address_count));
280  _id2offset[index++] = { static_cast<std::uint64_t>(id),
281  static_cast<std::uint64_t>(std::stoul(offset, 0, 16)) };
282  }
283  if (index != address_count)
284  stl::report_and_fail(std::format("VR Address Library {} loaded only {} entries but expected {}. Please redownload."sv, version, index, address_count));
285  std::sort(
286  _id2offset.begin(),
287  _id2offset.end(),
288  [](auto&& a_lhs, auto&& a_rhs) {
289  return a_lhs.id < a_rhs.id;
290  });
291  // _natvis = _id2offset.data();
292  return true;
293  }
294 #endif
295 
296  void load_file(stl::zwstring a_filename, Version a_version)
297  {
298  try {
299  binary_io::file_istream in(a_filename);
300  header_t header;
301  header.read(in);
302  if (header.version() != a_version) {
303  stl::report_and_fail("version mismatch"sv);
304  }
305 
306  auto mapname = L"CommonLibSSEOffsets-v2-"s;
307  mapname += a_version.wstring();
308  const auto byteSize = static_cast<std::size_t>(header.address_count()) * sizeof(mapping_t);
309  if (_mmap.open(mapname, byteSize)) {
310  _id2offset = { static_cast<mapping_t*>(_mmap.data()), header.address_count() };
311  } else if (_mmap.create(mapname, byteSize)) {
312  _id2offset = { static_cast<mapping_t*>(_mmap.data()), header.address_count() };
313  unpack_file(in, header);
314  std::sort(
315  _id2offset.begin(),
316  _id2offset.end(),
317  [](auto&& a_lhs, auto&& a_rhs) {
318  return a_lhs.id < a_rhs.id;
319  });
320  } else {
321  stl::report_and_fail("failed to create shared mapping"sv);
322  }
323  } catch (const std::system_error&) {
325  std::format(
326  "Failed to locate an appropriate address library with the path: {}\n"
327  "This means you are missing the address library for this specific version of "
328  "the game. Please continue to the mod page for address library to download "
329  "an appropriate version. If one is not available, then it is likely that "
330  "address library has not yet added support for this version of the game."sv,
331  stl::utf16_to_utf8(a_filename).value_or("<unknown filename>"s)));
332  }
333  }
334 
335  void unpack_file(binary_io::file_istream& a_in, header_t a_header)
336  {
337  std::uint8_t type = 0;
338  std::uint64_t id = 0;
339  std::uint64_t offset = 0;
340  std::uint64_t prevID = 0;
341  std::uint64_t prevOffset = 0;
342  for (auto& mapping : _id2offset) {
343  a_in.read(type);
344  const auto lo = static_cast<std::uint8_t>(type & 0xF);
345  const auto hi = static_cast<std::uint8_t>(type >> 4);
346 
347  switch (lo) {
348  case 0:
349  a_in.read(id);
350  break;
351  case 1:
352  id = prevID + 1;
353  break;
354  case 2:
355  id = prevID + std::get<0>(a_in.read<std::uint8_t>());
356  break;
357  case 3:
358  id = prevID - std::get<0>(a_in.read<std::uint8_t>());
359  break;
360  case 4:
361  id = prevID + std::get<0>(a_in.read<std::uint16_t>());
362  break;
363  case 5:
364  id = prevID - std::get<0>(a_in.read<std::uint16_t>());
365  break;
366  case 6:
367  std::tie(id) = a_in.read<std::uint16_t>();
368  break;
369  case 7:
370  std::tie(id) = a_in.read<std::uint32_t>();
371  break;
372  default:
373  stl::report_and_fail("unhandled type"sv);
374  }
375 
376  const std::uint64_t tmp = (hi & 8) != 0 ? (prevOffset / a_header.pointer_size()) : prevOffset;
377 
378  switch (hi & 7) {
379  case 0:
380  a_in.read(offset);
381  break;
382  case 1:
383  offset = tmp + 1;
384  break;
385  case 2:
386  offset = tmp + std::get<0>(a_in.read<std::uint8_t>());
387  break;
388  case 3:
389  offset = tmp - std::get<0>(a_in.read<std::uint8_t>());
390  break;
391  case 4:
392  offset = tmp + std::get<0>(a_in.read<std::uint16_t>());
393  break;
394  case 5:
395  offset = tmp - std::get<0>(a_in.read<std::uint16_t>());
396  break;
397  case 6:
398  std::tie(offset) = a_in.read<std::uint16_t>();
399  break;
400  case 7:
401  std::tie(offset) = a_in.read<std::uint32_t>();
402  break;
403  default:
404  stl::report_and_fail("unhandled type"sv);
405  }
406 
407  if ((hi & 8) != 0) {
408  offset *= a_header.pointer_size();
409  }
410 
411  mapping = { id, offset };
412 
413  prevOffset = offset;
414  prevID = id;
415  }
416  }
417 
418  detail::memory_map _mmap;
419  std::span<mapping_t> _id2offset;
420 
421 #ifdef SKYRIMVR
422  Version _vrAddressLibraryVersion;
423 #endif // SKYRIMVR
424  };
425 
426  class ID
427  {
428  public:
429  constexpr ID() noexcept = default;
430 
431  explicit constexpr ID(std::uint64_t a_id) noexcept :
432  _id(a_id)
433  {}
434 
435  constexpr ID& operator=(std::uint64_t a_id) noexcept
436  {
437  _id = a_id;
438  return *this;
439  }
440 
441  [[nodiscard]] std::uintptr_t address() const { return base() + offset(); }
442  [[nodiscard]] constexpr std::uint64_t id() const noexcept { return _id; }
443  [[nodiscard]] std::size_t offset() const { return IDDatabase::get().id2offset(_id); }
444 
445  private:
446  [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); }
447 
448  std::uint64_t _id{ 0 };
449  };
450 }
Definition: ID.h:65
Offset2ID()
Definition: ID.h:85
typename container_type::const_iterator const_iterator
Definition: ID.h:70
typename container_type::const_reverse_iterator const_reverse_iterator
Definition: ID.h:71
const_reverse_iterator crbegin() const noexcept
Definition: ID.h:116
Offset2ID(ExecutionPolicy &&a_policy) requires(std
Definition: ID.h:74
const_iterator begin() const noexcept
Definition: ID.h:109
std::vector< value_type > container_type
Definition: ID.h:68
size_type size() const noexcept
Definition: ID.h:121
const_reverse_iterator rbegin() const noexcept
Definition: ID.h:115
const_reverse_iterator crend() const noexcept
Definition: ID.h:119
const_iterator cbegin() const noexcept
Definition: ID.h:110
typename container_type::size_type size_type
Definition: ID.h:69
const_iterator cend() const noexcept
Definition: ID.h:113
std::uint64_t operator()(std::size_t a_offset) const
Definition: ID.h:89
mapping_t value_type
Definition: ID.h:67
const_reverse_iterator rend() const noexcept
Definition: ID.h:118
const_iterator end() const noexcept
Definition: ID.h:112
Definition: ID.h:55
std::size_t id2offset(std::uint64_t a_id) const
Definition: ID.h:133
static IDDatabase & get()
Definition: ID.h:127
Definition: ID.h:427
std::size_t offset() const
Definition: ID.h:443
constexpr ID & operator=(std::uint64_t a_id) noexcept
Definition: ID.h:435
constexpr std::uint64_t id() const noexcept
Definition: ID.h:442
constexpr ID() noexcept=default
std::uintptr_t address() const
Definition: ID.h:441
static Module & get()
Definition: Module.h:54
Version version() const noexcept
Definition: Module.h:62
std::uintptr_t base() const noexcept
Definition: Module.h:60
Definition: Version.h:6
Definition: ID.h:13
~memory_map()
Definition: ID.h:26
void * data() noexcept
Definition: ID.h:42
memory_map & operator=(memory_map &&a_rhs) noexcept
Definition: ID.h:30
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: csv.h:1261
Definition: ID.h:9
bool TerminateProcess(HANDLE a_process, std::uint32_t a_exitCode) noexcept
std::int32_t MessageBoxA(HWND a_wnd, const char *a_text, const char *a_caption, std::uint32_t a_type) noexcept
HANDLE GetCurrentProcess() noexcept
string(const CharT(&)[N]) -> string< CharT, N - 1 >
void report_and_fail(std::string_view a_msg, std::source_location a_loc=std::source_location::current())
Definition: PCH.h:588
auto utf16_to_utf8(std::wstring_view a_in) noexcept -> std::optional< std::string >
Definition: PCH.h:560
auto utf8_to_utf16(std::string_view a_in) noexcept -> std::optional< std::wstring >
Definition: PCH.h:534
requires(std::invocable< std::remove_reference_t< EF >>) class scope_exit
Definition: PCH.h:151
basic_zstring< wchar_t > zwstring
Definition: PCH.h:80
static const ignore_column ignore_missing_column
Definition: csv.h:759
Definition: EffectArchetypes.h:65
Definition: csv.h:843