eldenring 0.14.0

Structures, bindings, and utilities for From Software's title Elden Ring
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
use std::{ffi::CStr, iter, ptr::NonNull, slice};

use crate::fd4::FD4ResCapHolder;
use crate::param::ParamDef;
use shared::{OwnedPtr, Subclass};

use super::{FD4ResCap, FD4ResRep};

// Under most circumstances, it would make the most sense to consider
// [FD4ParamRepository] to be the owner of the [FD4ParamResCap]s it contains.
// However, it's substantially less efficient to look up a given parameter
// through [FD4ParamRepository] than it is through more specific repositories
// like [SoloParamRepository]. Other repositories assemble a mapping from
// parameter "indexes" to the actual locations of those parameters in memory
// which is robust against the parameters being reordered on disk;
// [FD4ParamRepository] does not. Because of this, it's possible to do an O(1)
// lookup through individual repositories while any lookup here must be O(n).
//
// To mitigate this, we expose APIs that act like [FD4ParamRepository] is *not*
// the owner of its data, in the sense that all access to that data is `unsafe`
// on the condition that users have no other mutable references (or immutable
// references in the case of mutable access) to param data elsewhere in their
// program. This allows us to avoid the `unsafe` qualifier for access through
// individual parameter repositories, because they're guaranteed to have the
// only references on the Rust side. (We're insulated from references on the C++
// side by the safety requirements for accessing any of these globals in the
// first place.)
//
// It's important to note as well that this solution relies on all the
// individual parameter repositories having access to a disjoint set of
// parameters. If any of them overlap (in another patch or another game),
// they'll have the same soundness issues between one another and we may need to
// consider a different approach to solving that problem.
#[repr(C)]
#[shared::singleton("FD4ParamRepository")]
#[derive(Subclass)]
#[subclass(base = FD4ResRep, base = FD4ResCap)]
pub struct FD4ParamRepository {
    /// Resource repository holding the actual param data.
    pub res_rep: FD4ResRep,
    res_cap_holder: FD4ResCapHolder<FD4ParamResCap>,
    allocator: usize,
}

impl FD4ParamRepository {
    /// Returns this repository's collection of [FD4ParamResCap]s.
    ///
    /// ## Safety
    ///
    /// This accesses data that `fromsoftware-rs` considers to be owned by
    /// individual parameter repositories such as [`SoloParamRepository`]. The
    /// caller must guarantee that there are no mutable references to *any*
    /// parameter data anywhere in the program before calling this.
    ///
    /// [`SoloParamRepository`]: crate::cs::SoloParamRepository
    pub unsafe fn res_cap_holder(&self) -> &FD4ResCapHolder<FD4ParamResCap> {
        &self.res_cap_holder
    }

    /// Returns a mutable reference to this repository's collection of
    /// [FD4ParamResCap]s.
    ///
    /// ## Safety
    ///
    /// This accesses data that `fromsoftware-rs` considers to be owned by
    /// individual parameter repositories such as [`SoloParamRepository`]. The
    /// caller must guarantee that there are no other references to *any* parameter
    /// data anywhere in the program before calling this.
    ///
    /// [`SoloParamRepository`]: crate::cs::SoloParamRepository
    pub unsafe fn res_cap_holder_mut(&mut self) -> &mut FD4ResCapHolder<FD4ParamResCap> {
        &mut self.res_cap_holder
    }

    /// Returns the first parameter in the repository whose struct type is `P`,
    /// or `None` if there is no such parameter. This should never return `None`
    /// for a vanilla game, because the only parameters this library defines are
    /// ones that are found in the game.
    ///
    /// ## Safety
    ///
    /// This accesses data that `fromsoftware-rs` considers to be owned by
    /// individual parameter repositories such as [`SoloParamRepository`]. The
    /// caller must guarantee that there are no mutable references to *any*
    /// parameter data anywhere in the program before calling this.
    ///
    /// [`SoloParamRepository`]: crate::cs::SoloParamRepository
    pub unsafe fn get<P: ParamDef>(&self, id: u32) -> Option<&P> {
        unsafe { self.get_rescap::<P>() }.and_then(|entry| unsafe { entry.get::<P>(id) })
    }

    /// Returns a mutable reference to the first parameter in the repository
    /// whose struct type is `P`, or `None` if there is no such parameter. This
    /// should never return `None` for a vanilla game, because the only
    /// parameters this library defines are ones that are found in the game.
    ///
    /// ## Safety
    ///
    /// This accesses data that `fromsoftware-rs` considers to be owned by
    /// individual parameter repositories such as [`SoloParamRepository`]. The
    /// caller must guarantee that there are no other references (mutable or
    /// immutable) to *any* parameter data anywhere in the program before
    /// calling this.
    ///
    /// [`SoloParamRepository`]: crate::cs::SoloParamRepository
    pub unsafe fn get_mut<P: ParamDef>(&mut self, id: u32) -> Option<&mut P> {
        unsafe { self.get_rescap_mut::<P>() }.and_then(|entry| unsafe { entry.get_mut::<P>(id) })
    }

    unsafe fn get_rescap<P: ParamDef>(&self) -> Option<&FD4ParamResCap> {
        self.res_cap_holder
            .entries()
            .find(|e| e.struct_name() == P::NAME)
    }

    unsafe fn get_rescap_mut<P: ParamDef>(&mut self) -> Option<&mut FD4ParamResCap> {
        self.res_cap_holder
            .entries_mut()
            .find(|e| e.struct_name() == P::NAME)
    }
}

#[repr(C)]
#[derive(Subclass)]
pub struct FD4ParamResCap {
    pub res_cap: FD4ResCap,

    /// The size in bytes of the [ParamFile] pointed at by [Self::data].
    pub size: u64,

    /// The raw row data for this param resource.
    pub data: OwnedPtr<ParamFile>,
}

impl FD4ParamResCap {
    /// Returns the name of the struct that this parameter uses.
    ///
    /// This corresponds to [`ParamDef::NAME`] (typically written in all-caps
    /// snake case) rather than the parameter name (typically written in camel
    /// case).
    ///
    /// [`ParamDef::NAME`]: crate::param::ParamDef::NAME
    pub fn struct_name(&self) -> &str {
        self.data.struct_name()
    }

    /// Returns the row in this parameter with the given `id`, interpreted as a
    /// `P`.
    ///
    /// # Safety
    ///
    /// Type `P` must match the actual row data structure for this param file.
    pub unsafe fn get<P: ParamDef>(&self, id: u32) -> Option<&P> {
        unsafe { self.data.get_row_by_id(id) }
    }

    /// Returns the mutable row in this parameter with the given `id`,
    /// interpreted as a `P`.
    ///
    /// # Safety
    ///
    /// Type `P` must match the actual row data structure for this param file.
    pub unsafe fn get_mut<P: ParamDef>(&mut self, id: u32) -> Option<&mut P> {
        unsafe { self.data.get_row_by_id_mut(id) }
    }
}

/// Runtime metadata prepended at offset -0x10 from the param file.
#[repr(C)]
struct ParamFileMetadata {
    /// The unaligned offset from the beginning of the parameter file to the end
    /// of its name string (or its [RowDescriptor] array if the name is stored
    /// inline).
    after_name_offset: u32,

    /// The number of rows in the parameter file.
    row_count: u32,

    _reserved: u64,
}

/// An entry in the runtime lookup table to look up rows by ID.
///
/// This table is sorted by ID, enabling O(log n) lookups.
#[repr(C)]
struct RowLookupEntry {
    param_id: u32,
    index: u32,
}

/// A row descriptor that makes Row descriptor for param files.
///
/// The actual size depends on the offset type:
/// - 32-bit offsets: 12 bytes (id + data_offset + name_offset)
/// - 64-bit offsets: 24 bytes (id + pad + data_offset + name_offset)
#[repr(C)]
struct RowDescriptor<T: Into<u64> + Copy> {
    /// The parameter ID of the row this describes.
    id: u32,

    data_offset: T,

    /// The offset between the beginning of the [ParamFile] and its struct name.
    /// This is the same for all descriptors.
    name_offset: T,
}

impl<T: Into<u64> + Copy> RowDescriptor<T> {
    /// The offset between the beginning of the [ParamFile] and the [ParamDef]
    /// data this descriptor refers to.
    pub fn data_offset(&self) -> usize {
        self.data_offset.into() as usize
    }
}

/// An in-memory representation of a file that contains all the data for a
/// single parameter type.
// Memory layout:
// ```text
// [ParamFileMetadata]            <- file_ptr-0x10
// [ParamFile]                    <- file_ptr ([FD4ParamResCap::file] points here)
// [RowDescriptor * row_count]
// [char...]                      <- struct name, if [ParamFile::has_offset_param_type] is true
// [aligned padding to 0x10]
// [ParamDef * row_count]         <- file_ptr + RowDescriptor::data_offset
// [RowLookupEntry * row_count]   <- lookup table for param indexes, sorted by param ID
// ```
#[repr(C)]
pub struct ParamFile {
    strings_offset: u32,
    short_data_offset: u16,
    _unk06: u16,
    paramdef_version: u16,
    row_count: u16,
    struct_name: ParamStructName,
    big_endian: u8,
    format_2d: u8,
    format_2e: u8,
}

impl ParamFile {
    /// The alignment used for the beginning of the [RowLookupEntry] table.
    const LOOKUP_TABLE_ALIGNMENT: u32 = 0x10;

    /// Returns the name of the struct that this parameter uses.
    ///
    /// This corresponds to [`ParamDef::NAME`] (typically written in all-caps
    /// snake case) rather than the parameter name (typically written in camel
    /// case).
    ///
    /// [`ParamDef::NAME`]: crate::param::ParamDef::NAME
    pub fn struct_name(&self) -> &str {
        if self.has_offset_param_type() {
            self.read_offset_struct_name()
        } else {
            self.read_inline_struct_name()
        }
    }

    /// The revision of this paramdef struct type.
    pub const fn paramdef_version(&self) -> u16 {
        self.paramdef_version
    }

    /// The number of rows this file contains.
    pub const fn row_count(&self) -> usize {
        self.row_count as usize
    }

    /// Returns the row in this file with the given `id`, if one exists.
    ///
    /// # Safety
    ///
    /// Type `P` must match the actual row data structure for this param file.
    pub unsafe fn get_row_by_id<P: ParamDef>(&self, id: u32) -> Option<&P> {
        let index = self.find_index(id)?;
        let (actual_id, data_offset) = self.row_data_offset(index)?;
        debug_assert_eq!(id, actual_id, "Unexpected row ID for {}", P::NAME);
        Some(unsafe { self.offset::<P>(data_offset).as_ref() })
    }

    /// Returns the mutable row in this file with the given `id`, if one exists.
    ///
    /// # Safety
    ///
    /// Type `P` must match the actual row data structure for this param file.
    pub unsafe fn get_row_by_id_mut<P: ParamDef>(&mut self, id: u32) -> Option<&mut P> {
        let index = self.find_index(id)?;
        let (actual_id, data_offset) = self.row_data_offset(index)?;
        debug_assert_eq!(id, actual_id, "Unexpected row ID for {}", P::NAME);
        Some(unsafe { self.offset::<P>(data_offset).as_mut() })
    }

    /// Returns the row in this file at the given `index`, if one exists.
    ///
    /// # Safety
    ///
    /// Type `P` must match the actual row data structure for this param file.
    pub unsafe fn get_row_by_index<P: ParamDef>(&self, row_index: usize) -> Option<&P> {
        let data_offset = self.row_data_offset(row_index)?.1;
        Some(unsafe { self.offset::<P>(data_offset).as_ref() })
    }

    /// Returns the mutable row in this file at the given `index`, if one exists.
    ///
    /// # Safety
    ///
    /// Type `P` must match the actual row data structure for this param file.
    pub unsafe fn get_row_by_index_mut<P: ParamDef>(&mut self, row_index: usize) -> Option<&mut P> {
        let data_offset = self.row_data_offset(row_index)?.1;
        Some(unsafe { self.offset::<P>(data_offset).as_mut() })
    }

    /// Returns an iterator over each row in this file along with their parameter IDs,
    /// in ID order.
    ///
    /// # Safety
    ///
    /// Type `P` must match the actual row data structure for this param file.
    pub unsafe fn rows<'a, P: ParamDef + 'a>(&'a self) -> impl Iterator<Item = (u32, &'a P)> + 'a {
        self.lookup_table()
            .iter()
            .map(|l| unsafe { (l.param_id, self.get_row_by_index(l.index as usize).unwrap()) })
    }

    /// Returns an iterator over each mutable row in this file along with their
    /// parameter IDs, in ID order.
    ///
    /// # Safety
    ///
    /// Type `P` must match the actual row data structure for this param file.
    pub unsafe fn rows_mut<'a, P: ParamDef + 'a>(
        &'a mut self,
    ) -> impl Iterator<Item = (u32, &'a mut P)> + 'a {
        // We have to do this more manually to avoid having a reference to the
        // `lookup_table` slice coexisting with the mutable reference returned
        // by the iterator.
        let mut file = NonNull::from_ref(self);
        let range = self.lookup_table().as_ptr_range();
        let mut ptr = range.start;
        let end = range.end;

        iter::from_fn(move || {
            if ptr == end {
                return None;
            }

            // Safety: We know `ptr` is valid because the iterator holds a
            // reference to `self` and thus to its lookup table, and `ptr` can't
            // be `end` at this point. We know `file` is valid because of that
            // same reference.
            unsafe {
                let lookup = ptr.as_ref().unwrap();
                let result = file
                    .as_mut()
                    .get_row_by_index_mut(lookup.index as usize)
                    .unwrap();
                ptr = ptr.add(1);
                Some((lookup.param_id, result))
            }
        })
    }

    /// Returns the index of the parameter row with the given `id`.
    pub fn find_index(&self, id: u32) -> Option<usize> {
        let table = self.lookup_table();
        let target_index = self
            .lookup_table()
            .binary_search_by(|entry| entry.param_id.cmp(&id))
            .ok()?;
        Some(table[target_index].index as usize)
    }

    /// Returns a reference to the lookup table used to efficiently map
    /// parameter IDs to indices.
    fn lookup_table(&self) -> &[RowLookupEntry] {
        let aligned_file_size =
            self.metadata()
                .after_name_offset
                .next_multiple_of(Self::LOOKUP_TABLE_ALIGNMENT) as usize;

        unsafe {
            slice::from_raw_parts(
                self.offset::<RowLookupEntry>(aligned_file_size).as_ptr(),
                self.row_count as usize,
            )
        }
    }

    /// Returns the metadata that's stored in memory before this file.
    const fn metadata(&self) -> &ParamFileMetadata {
        unsafe {
            let metadata_ptr = (self as *const Self).byte_sub(size_of::<ParamFileMetadata>())
                as *const ParamFileMetadata;
            &*metadata_ptr
        }
    }

    /// Returns a pointer to [offset] bytes after the beginning of this struct.
    ///
    /// ## Safety
    ///
    /// The `offset` must be in range of [isize] and the resulting addition must
    /// not overflow the address space.
    const unsafe fn offset<T>(&self, offset: usize) -> NonNull<T> {
        unsafe { NonNull::from_ref(self).cast::<u8>().add(offset).cast::<T>() }
    }

    fn read_inline_struct_name(&self) -> &str {
        unsafe {
            CStr::from_ptr(self.struct_name.inline.as_ptr() as *const i8)
                .to_str()
                .unwrap_or("")
        }
    }

    fn read_offset_struct_name(&self) -> &str {
        unsafe {
            let offset = self.struct_name.offset.value;
            if offset == 0 {
                return "";
            }
            CStr::from_ptr(self.offset::<i8>(offset as usize).as_ptr())
                .to_str()
                .unwrap_or("")
        }
    }

    /// Returns this param file's row descriptor list. This uses the same
    /// indices as the paramter data.
    const fn row_descriptors<T: Into<u64> + Copy>(&self) -> &[RowDescriptor<T>] {
        let offset = size_of::<ParamFile>() + if self.has_extended_header() { 0x10 } else { 0 };
        unsafe {
            slice::from_raw_parts(
                self.offset::<RowDescriptor<T>>(offset).as_ptr(),
                self.row_count as usize,
            )
        }
    }

    /// Returns the parameter ID and offset from the beginning of this paramter
    /// file to the data of the row at `row_index`.
    ///
    /// Returns `None` if `row_index` is out-of-range for this file.
    fn row_data_offset(&self, row_index: usize) -> Option<(u32, usize)> {
        if row_index >= self.row_count() {
            return None;
        }

        if self.is_64_bit() {
            self.row_descriptors::<u64>()
                .get(row_index)
                .map(|d| (d.id, d.data_offset()))
        } else {
            self.row_descriptors::<u32>()
                .get(row_index)
                .map(|d| (d.id, d.data_offset()))
        }
    }

    /// Whether param type is stored as an offset (bit 7 of format_2d).
    const fn has_offset_param_type(&self) -> bool {
        (self.format_2d & 0x80) != 0
    }

    /// Whether the file uses 64-bit offsets (bit 2 of format_2d).
    const fn is_64_bit(&self) -> bool {
        (self.format_2d & 0x04) != 0
    }

    /// Whether the header has the extended 16-byte section.
    const fn has_extended_header(&self) -> bool {
        self.is_64_bit() || ((self.format_2d & 0x01) != 0 && (self.format_2d & 0x02) != 0)
    }
}

#[repr(C)]
union ParamStructName {
    inline: [u8; 0x20],
    offset: ParamStructNameOffset,
}

#[repr(C)]
#[derive(Clone, Copy)]
struct ParamStructNameOffset {
    _pad: u32,
    value: u32,
    _reserved: [u32; 6],
}