CommonLibSSE (powerof3)
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  return *this;
37  }
38 
39  [[nodiscard]] void* data() noexcept { return _view; }
40 
41  bool open(stl::zwstring a_name, std::size_t a_size);
42  bool create(stl::zwstring a_name, std::size_t a_size);
43  void close();
44 
45  private:
46  void* _mapping{ nullptr };
47  void* _view{ nullptr };
48  };
49  }
50 
51  class IDDatabase
52  {
53  private:
54  struct mapping_t
55  {
56  std::uint64_t id;
57  std::uint64_t offset;
58  };
59 
60  public:
61  class Offset2ID
62  {
63  public:
64  using value_type = mapping_t;
65  using container_type = std::vector<value_type>;
66  using size_type = typename container_type::size_type;
67  using const_iterator = typename container_type::const_iterator;
68  using const_reverse_iterator = typename container_type::const_reverse_iterator;
69 
70  template <class ExecutionPolicy>
71  explicit Offset2ID(ExecutionPolicy&& a_policy) //
72  requires(std::is_execution_policy_v<std::decay_t<ExecutionPolicy>>)
73  {
74  const std::span<const mapping_t> id2offset = IDDatabase::get()._id2offset;
75  _offset2id.reserve(id2offset.size());
76  _offset2id.insert(_offset2id.begin(), id2offset.begin(), id2offset.end());
77  std::sort(a_policy, _offset2id.begin(), _offset2id.end(), [](auto&& a_lhs, auto&& a_rhs) {
78  return a_lhs.offset < a_rhs.offset;
79  });
80  }
81 
83  Offset2ID(std::execution::sequenced_policy{})
84  {}
85 
86  [[nodiscard]] std::uint64_t operator()(std::size_t a_offset) const
87  {
88  const mapping_t elem{ 0, a_offset };
89  const auto it = std::lower_bound(
90  _offset2id.begin(),
91  _offset2id.end(),
92  elem,
93  [](auto&& a_lhs, auto&& a_rhs) {
94  return a_lhs.offset < a_rhs.offset;
95  });
96  if (it == _offset2id.end()) {
98  std::format(
99  "Failed to find the offset within the database: 0x{:08X}"sv,
100  a_offset));
101  }
102 
103  return it->id;
104  }
105 
106  [[nodiscard]] const_iterator begin() const noexcept { return _offset2id.begin(); }
107  [[nodiscard]] const_iterator cbegin() const noexcept { return _offset2id.cbegin(); }
108 
109  [[nodiscard]] const_iterator end() const noexcept { return _offset2id.end(); }
110  [[nodiscard]] const_iterator cend() const noexcept { return _offset2id.cend(); }
111 
112  [[nodiscard]] const_reverse_iterator rbegin() const noexcept { return _offset2id.rbegin(); }
113  [[nodiscard]] const_reverse_iterator crbegin() const noexcept { return _offset2id.crbegin(); }
114 
115  [[nodiscard]] const_reverse_iterator rend() const noexcept { return _offset2id.rend(); }
116  [[nodiscard]] const_reverse_iterator crend() const noexcept { return _offset2id.crend(); }
117 
118  [[nodiscard]] size_type size() const noexcept { return _offset2id.size(); }
119 
120  private:
121  container_type _offset2id;
122  };
123 
124  [[nodiscard]] static IDDatabase& get()
125  {
126  static IDDatabase singleton;
127  return singleton;
128  }
129 
130  [[nodiscard]] inline std::size_t id2offset(std::uint64_t a_id) const
131  {
132  mapping_t elem{ a_id, 0 };
133  const auto it = std::lower_bound(
134  _id2offset.begin(),
135  _id2offset.end(),
136  elem,
137  [](auto&& a_lhs, auto&& a_rhs) {
138  return a_lhs.id < a_rhs.id;
139  });
140  if (it == _id2offset.end()) {
142  std::format(
143  "Failed to find the id within the address library: {}\n"
144  "This means this script extender plugin is incompatible with the address "
145  "library for this version of the game, and thus does not support it."sv,
146  a_id));
147  }
148 
149  return static_cast<std::size_t>(it->offset);
150  }
151 
152  private:
153  friend Offset2ID;
154 
155  class header_t
156  {
157  public:
158  void read(binary_io::file_istream& a_in)
159  {
160  const auto [format] = a_in.read<std::int32_t>();
161 #ifdef SKYRIM_SUPPORT_AE
162  if (format != 2) {
163 #else
164  if (format != 1) {
165 #endif
167  std::format(
168  "Unsupported address library format: {}\n"
169  "This means this script extender plugin is incompatible with the address "
170  "library available for this version of the game, and thus does not "
171  "support it."sv,
172  format));
173  }
174 
175  const auto [major, minor, patch, revision] =
176  a_in.read<std::int32_t, std::int32_t, std::int32_t, std::int32_t>();
177  _version[0] = static_cast<std::uint16_t>(major);
178  _version[1] = static_cast<std::uint16_t>(minor);
179  _version[2] = static_cast<std::uint16_t>(patch);
180  _version[3] = static_cast<std::uint16_t>(revision);
181 
182  const auto [nameLen] = a_in.read<std::int32_t>();
183  a_in.seek_relative(nameLen);
184 
185  a_in.read(_pointerSize, _addressCount);
186  }
187 
188  [[nodiscard]] std::size_t address_count() const noexcept { return static_cast<std::size_t>(_addressCount); }
189  [[nodiscard]] std::uint64_t pointer_size() const noexcept { return static_cast<std::uint64_t>(_pointerSize); }
190  [[nodiscard]] Version version() const noexcept { return _version; }
191 
192  private:
193  Version _version;
194  std::int32_t _pointerSize{ 0 };
195  std::int32_t _addressCount{ 0 };
196  };
197 
198  IDDatabase() { load(); }
199 
200  IDDatabase(const IDDatabase&) = delete;
201  IDDatabase(IDDatabase&&) = delete;
202 
203  ~IDDatabase() = default;
204 
205  IDDatabase& operator=(const IDDatabase&) = delete;
206  IDDatabase& operator=(IDDatabase&&) = delete;
207 
208  void load()
209  {
210  const auto version = Module::get().version();
211  const auto filename =
213  std::format(
214 #ifdef SKYRIM_SUPPORT_AE
215  "Data/SKSE/Plugins/versionlib-{}.bin"sv,
216 #else
217  "Data/SKSE/Plugins/version-{}.bin"sv,
218 #endif
219  version.string()))
220  .value_or(L"<unknown filename>"s);
221  load_file(filename, version);
222  }
223 
224  void load_file(stl::zwstring a_filename, Version a_version)
225  {
226  try {
227  binary_io::file_istream in(a_filename);
228  header_t header;
229  header.read(in);
230  if (header.version() != a_version) {
231  stl::report_and_fail("version mismatch"sv);
232  }
233 
234  auto mapname = L"CommonLibSSEOffsets-v2-"s;
235  mapname += a_version.wstring();
236  const auto byteSize = static_cast<std::size_t>(header.address_count()) * sizeof(mapping_t);
237  if (_mmap.open(mapname, byteSize)) {
238  _id2offset = { static_cast<mapping_t*>(_mmap.data()), header.address_count() };
239  } else if (_mmap.create(mapname, byteSize)) {
240  _id2offset = { static_cast<mapping_t*>(_mmap.data()), header.address_count() };
241  unpack_file(in, header);
242  std::sort(
243  _id2offset.begin(),
244  _id2offset.end(),
245  [](auto&& a_lhs, auto&& a_rhs) {
246  return a_lhs.id < a_rhs.id;
247  });
248  } else {
249  stl::report_and_fail("failed to create shared mapping"sv);
250  }
251  } catch (const std::system_error&) {
253  std::format(
254  "Failed to locate an appropriate address library with the path: {}\n"
255  "This means you are missing the address library for this specific version of "
256  "the game. Please continue to the mod page for address library to download "
257  "an appropriate version. If one is not available, then it is likely that "
258  "address library has not yet added support for this version of the game."sv,
259  stl::utf16_to_utf8(a_filename).value_or("<unknown filename>"s)));
260  }
261  }
262 
263  void unpack_file(binary_io::file_istream& a_in, header_t a_header)
264  {
265  std::uint8_t type = 0;
266  std::uint64_t id = 0;
267  std::uint64_t offset = 0;
268  std::uint64_t prevID = 0;
269  std::uint64_t prevOffset = 0;
270  for (auto& mapping : _id2offset) {
271  a_in.read(type);
272  const auto lo = static_cast<std::uint8_t>(type & 0xF);
273  const auto hi = static_cast<std::uint8_t>(type >> 4);
274 
275  switch (lo) {
276  case 0:
277  a_in.read(id);
278  break;
279  case 1:
280  id = prevID + 1;
281  break;
282  case 2:
283  id = prevID + std::get<0>(a_in.read<std::uint8_t>());
284  break;
285  case 3:
286  id = prevID - std::get<0>(a_in.read<std::uint8_t>());
287  break;
288  case 4:
289  id = prevID + std::get<0>(a_in.read<std::uint16_t>());
290  break;
291  case 5:
292  id = prevID - std::get<0>(a_in.read<std::uint16_t>());
293  break;
294  case 6:
295  std::tie(id) = a_in.read<std::uint16_t>();
296  break;
297  case 7:
298  std::tie(id) = a_in.read<std::uint32_t>();
299  break;
300  default:
301  stl::report_and_fail("unhandled type"sv);
302  }
303 
304  const std::uint64_t tmp = (hi & 8) != 0 ? (prevOffset / a_header.pointer_size()) : prevOffset;
305 
306  switch (hi & 7) {
307  case 0:
308  a_in.read(offset);
309  break;
310  case 1:
311  offset = tmp + 1;
312  break;
313  case 2:
314  offset = tmp + std::get<0>(a_in.read<std::uint8_t>());
315  break;
316  case 3:
317  offset = tmp - std::get<0>(a_in.read<std::uint8_t>());
318  break;
319  case 4:
320  offset = tmp + std::get<0>(a_in.read<std::uint16_t>());
321  break;
322  case 5:
323  offset = tmp - std::get<0>(a_in.read<std::uint16_t>());
324  break;
325  case 6:
326  std::tie(offset) = a_in.read<std::uint16_t>();
327  break;
328  case 7:
329  std::tie(offset) = a_in.read<std::uint32_t>();
330  break;
331  default:
332  stl::report_and_fail("unhandled type"sv);
333  }
334 
335  if ((hi & 8) != 0) {
336  offset *= a_header.pointer_size();
337  }
338 
339  mapping = { id, offset };
340 
341  prevOffset = offset;
342  prevID = id;
343  }
344  }
345 
346  detail::memory_map _mmap;
347  std::span<mapping_t> _id2offset;
348  };
349 
350  class ID
351  {
352  public:
353  constexpr ID() noexcept = default;
354 
355  explicit constexpr ID(std::uint64_t a_id) noexcept :
356  _id(a_id)
357  {}
358 
359  constexpr ID& operator=(std::uint64_t a_id) noexcept
360  {
361  _id = a_id;
362  return *this;
363  }
364 
365  [[nodiscard]] std::uintptr_t address() const { return base() + offset(); }
366  [[nodiscard]] constexpr std::uint64_t id() const noexcept { return _id; }
367  [[nodiscard]] std::size_t offset() const { return IDDatabase::get().id2offset(_id); }
368 
369  private:
370  [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); }
371 
372  std::uint64_t _id{ 0 };
373  };
374 }
Definition: ID.h:62
Offset2ID()
Definition: ID.h:82
typename container_type::const_iterator const_iterator
Definition: ID.h:67
typename container_type::const_reverse_iterator const_reverse_iterator
Definition: ID.h:68
const_reverse_iterator crbegin() const noexcept
Definition: ID.h:113
Offset2ID(ExecutionPolicy &&a_policy) requires(std
Definition: ID.h:71
const_iterator begin() const noexcept
Definition: ID.h:106
std::vector< value_type > container_type
Definition: ID.h:65
size_type size() const noexcept
Definition: ID.h:118
const_reverse_iterator rbegin() const noexcept
Definition: ID.h:112
const_reverse_iterator crend() const noexcept
Definition: ID.h:116
const_iterator cbegin() const noexcept
Definition: ID.h:107
typename container_type::size_type size_type
Definition: ID.h:66
const_iterator cend() const noexcept
Definition: ID.h:110
std::uint64_t operator()(std::size_t a_offset) const
Definition: ID.h:86
mapping_t value_type
Definition: ID.h:64
const_reverse_iterator rend() const noexcept
Definition: ID.h:115
const_iterator end() const noexcept
Definition: ID.h:109
Definition: ID.h:52
std::size_t id2offset(std::uint64_t a_id) const
Definition: ID.h:130
static IDDatabase & get()
Definition: ID.h:124
Definition: ID.h:351
std::size_t offset() const
Definition: ID.h:367
constexpr ID & operator=(std::uint64_t a_id) noexcept
Definition: ID.h:359
constexpr std::uint64_t id() const noexcept
Definition: ID.h:366
constexpr ID() noexcept=default
std::uintptr_t address() const
Definition: ID.h:365
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: ID.h:10
~memory_map()
Definition: ID.h:23
void * data() noexcept
Definition: ID.h:39
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
void report_and_fail(std::string_view a_msg, std::source_location a_loc=std::source_location::current())
Definition: PCH.h:396
auto utf16_to_utf8(std::wstring_view a_in) noexcept -> std::optional< std::string >
Definition: PCH.h:368
auto utf8_to_utf16(std::string_view a_in) noexcept -> std::optional< std::wstring >
Definition: PCH.h:342
requires(std::invocable< std::remove_reference_t< EF >>) class scope_exit
Definition: PCH.h:153
basic_zstring< wchar_t > zwstring
Definition: PCH.h:82
Definition: EffectArchetypes.h:65