CommonLibVR
Loading...
Searching...
No Matches
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
8namespace REL
9{
10 namespace detail
11 {
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
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
55 {
56 private:
57 struct mapping_t
58 {
59 std::uint64_t id;
60 std::uint64_t offset;
61 };
62
63 public:
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
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
Offset2ID(ExecutionPolicy &&a_policy)
Definition ID.h:74
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
static IDDatabase & get()
Definition ID.h:127
std::size_t id2offset(std::uint64_t a_id) const
Definition ID.h:133
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
Version version() const noexcept
Definition Module.h:62
static Module & get()
Definition Module.h:54
Definition Version.h:6
Definition ID.h:13
~memory_map()
Definition ID.h:26
memory_map & operator=(memory_map &&a_rhs) noexcept
Definition ID.h:30
memory_map & operator=(const memory_map &)=delete
memory_map() noexcept=default
void * data() noexcept
Definition ID.h:42
bool create(stl::zwstring a_name, std::size_t a_size)
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
void report_and_fail(std::string_view a_msg, std::source_location a_loc=std::source_location::current())
Definition PCH.h:397
auto utf16_to_utf8(std::wstring_view a_in) noexcept -> std::optional< std::string >
Definition PCH.h:369
auto utf8_to_utf16(std::string_view a_in) noexcept -> std::optional< std::wstring >
Definition PCH.h:343
basic_zstring< wchar_t > zwstring
Definition PCH.h:83
static const ignore_column ignore_missing_column
Definition csv.h:759
Definition EffectArchetypes.h:65
Definition csv.h:843