Skip to main content

libretro_core/
memory.rs

1//! Memory regions, memory maps, software framebuffers, and savestate hints.
2//!
3//! The API keeps host slices, emulated addresses, masks, offsets, lengths, and
4//! access flags in distinct types so memory contracts remain reviewable.
5
6use enumflags2::{BitFlags, bitflags};
7use std::ffi::{CStr, c_char, c_void};
8use std::marker::PhantomData;
9use std::ptr::NonNull;
10use std::slice;
11
12use crate::raw::{
13    RETRO_MEMDESC_ALIGN_2, RETRO_MEMDESC_ALIGN_4, RETRO_MEMDESC_ALIGN_8, RETRO_MEMDESC_BIGENDIAN,
14    RETRO_MEMDESC_CONST, RETRO_MEMDESC_MINSIZE_2, RETRO_MEMDESC_MINSIZE_4, RETRO_MEMDESC_MINSIZE_8,
15    RETRO_MEMDESC_SAVE_RAM, RETRO_MEMDESC_SYSTEM_RAM, RETRO_MEMDESC_VIDEO_RAM,
16    RETRO_MEMORY_ACCESS_READ, RETRO_MEMORY_ACCESS_WRITE, RETRO_MEMORY_MASK, RETRO_MEMORY_ROM,
17    RETRO_MEMORY_RTC, RETRO_MEMORY_SAVE_RAM, RETRO_MEMORY_SYSTEM_RAM, RETRO_MEMORY_TYPE_CACHED,
18    RETRO_MEMORY_VIDEO_RAM, RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE,
19    RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT, RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE,
20    RETRO_SERIALIZATION_QUIRK_INCOMPLETE, RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE,
21    RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT, RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION,
22    retro_framebuffer, retro_game_info_ext, retro_pixel_format,
23};
24
25#[bitflags]
26#[repr(u32)]
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub enum FramebufferMemoryAccess {
29    Write = RETRO_MEMORY_ACCESS_WRITE,
30    Read = RETRO_MEMORY_ACCESS_READ,
31}
32
33pub type FramebufferMemoryAccessFlags = BitFlags<FramebufferMemoryAccess>;
34
35#[bitflags]
36#[repr(u32)]
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
38pub enum FramebufferMemoryType {
39    Cached = RETRO_MEMORY_TYPE_CACHED,
40}
41
42pub type FramebufferMemoryTypes = BitFlags<FramebufferMemoryType>;
43
44#[bitflags]
45#[repr(u64)]
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47pub enum MemoryDescriptorFlag {
48    Constant = RETRO_MEMDESC_CONST,
49    BigEndian = RETRO_MEMDESC_BIGENDIAN,
50    SystemRam = RETRO_MEMDESC_SYSTEM_RAM,
51    SaveRam = RETRO_MEMDESC_SAVE_RAM,
52    VideoRam = RETRO_MEMDESC_VIDEO_RAM,
53}
54
55pub type MemoryDescriptorFlags = BitFlags<MemoryDescriptorFlag>;
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58pub enum MemoryDescriptorAlignment {
59    TwoBytes,
60    FourBytes,
61    EightBytes,
62}
63
64impl MemoryDescriptorAlignment {
65    pub const fn as_raw_flag(self) -> u64 {
66        match self {
67            Self::TwoBytes => RETRO_MEMDESC_ALIGN_2,
68            Self::FourBytes => RETRO_MEMDESC_ALIGN_4,
69            Self::EightBytes => RETRO_MEMDESC_ALIGN_8,
70        }
71    }
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
75pub enum MemoryDescriptorMinAccessSize {
76    TwoBytes,
77    FourBytes,
78    EightBytes,
79}
80
81impl MemoryDescriptorMinAccessSize {
82    pub const fn as_raw_flag(self) -> u64 {
83        match self {
84            Self::TwoBytes => RETRO_MEMDESC_MINSIZE_2,
85            Self::FourBytes => RETRO_MEMDESC_MINSIZE_4,
86            Self::EightBytes => RETRO_MEMDESC_MINSIZE_8,
87        }
88    }
89}
90
91#[bitflags]
92#[repr(u64)]
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
94pub enum SerializationQuirk {
95    Incomplete = RETRO_SERIALIZATION_QUIRK_INCOMPLETE,
96    MustInitialize = RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE,
97    CoreVariableSize = RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE,
98    FrontendVariableSize = RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE,
99    SingleSession = RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION,
100    EndianDependent = RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT,
101    PlatformDependent = RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT,
102}
103
104pub type SerializationQuirks = BitFlags<SerializationQuirk>;
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107pub struct ExtendedGameInfo<'a> {
108    pub full_path: Option<&'a CStr>,
109    pub archive_path: Option<&'a CStr>,
110    pub archive_file: Option<&'a CStr>,
111    pub dir: Option<&'a CStr>,
112    pub name: Option<&'a CStr>,
113    pub extension: Option<&'a CStr>,
114    pub meta: Option<&'a CStr>,
115    pub data: Option<&'a [u8]>,
116    pub file_in_archive: bool,
117    pub persistent_data: bool,
118}
119
120impl<'a> ExtendedGameInfo<'a> {
121    pub(crate) unsafe fn from_raw(raw: &'a retro_game_info_ext) -> Self {
122        Self {
123            full_path: unsafe { cstr_from_ptr(raw.full_path) },
124            archive_path: unsafe { cstr_from_ptr(raw.archive_path) },
125            archive_file: unsafe { cstr_from_ptr(raw.archive_file) },
126            dir: unsafe { cstr_from_ptr(raw.dir) },
127            name: unsafe { cstr_from_ptr(raw.name) },
128            extension: unsafe { cstr_from_ptr(raw.ext) },
129            meta: unsafe { cstr_from_ptr(raw.meta) },
130            data: if raw.data.is_null() {
131                None
132            } else {
133                // SAFETY: The frontend provides a valid content buffer for the
134                // lifetime described by `persistent_data`.
135                Some(unsafe { slice::from_raw_parts(raw.data.cast::<u8>(), raw.size) })
136            },
137            file_in_archive: raw.file_in_archive,
138            persistent_data: raw.persistent_data,
139        }
140    }
141}
142
143unsafe fn cstr_from_ptr<'a>(ptr: *const c_char) -> Option<&'a CStr> {
144    if ptr.is_null() {
145        None
146    } else {
147        // SAFETY: libretro path/meta fields are NUL-terminated strings when non-null.
148        Some(unsafe { CStr::from_ptr(ptr) })
149    }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
153pub struct SoftwareFramebufferRequest {
154    pub width: u32,
155    pub height: u32,
156    pub access: FramebufferMemoryAccessFlags,
157}
158
159impl SoftwareFramebufferRequest {
160    pub fn new(width: u32, height: u32) -> Self {
161        Self {
162            width,
163            height,
164            access: FramebufferMemoryAccessFlags::from(FramebufferMemoryAccess::Write),
165        }
166    }
167
168    pub fn with_access(mut self, access: FramebufferMemoryAccessFlags) -> Self {
169        self.access = access;
170        self
171    }
172
173    pub(crate) fn into_raw(self) -> retro_framebuffer {
174        retro_framebuffer {
175            width: self.width,
176            height: self.height,
177            access_flags: self.access.bits(),
178            ..retro_framebuffer::default()
179        }
180    }
181}
182
183#[derive(Debug)]
184pub struct SoftwareFramebuffer {
185    data: NonNull<c_void>,
186    width: u32,
187    height: u32,
188    pitch: usize,
189    format: retro_pixel_format,
190    access: FramebufferMemoryAccessFlags,
191    memory: FramebufferMemoryTypes,
192}
193
194impl SoftwareFramebuffer {
195    pub(crate) fn from_raw(framebuffer: retro_framebuffer) -> Option<Self> {
196        Some(Self {
197            data: NonNull::new(framebuffer.data)?,
198            width: framebuffer.width,
199            height: framebuffer.height,
200            pitch: framebuffer.pitch,
201            format: framebuffer.format,
202            access: FramebufferMemoryAccessFlags::from_bits_truncate(framebuffer.access_flags),
203            memory: FramebufferMemoryTypes::from_bits_truncate(framebuffer.memory_flags),
204        })
205    }
206
207    pub const fn width(&self) -> u32 {
208        self.width
209    }
210
211    pub const fn height(&self) -> u32 {
212        self.height
213    }
214
215    pub const fn pitch(&self) -> usize {
216        self.pitch
217    }
218
219    pub const fn format(&self) -> retro_pixel_format {
220        self.format
221    }
222
223    pub const fn access(&self) -> FramebufferMemoryAccessFlags {
224        self.access
225    }
226
227    pub const fn memory(&self) -> FramebufferMemoryTypes {
228        self.memory
229    }
230
231    pub fn bytes(&self) -> Option<&[u8]> {
232        if !self.access.contains(FramebufferMemoryAccess::Read) {
233            return None;
234        }
235        let len = self.byte_len()?;
236        // SAFETY: libretro guarantees the returned pointer is valid during the
237        // current retro_run iteration for the access flags requested by the core.
238        Some(unsafe { slice::from_raw_parts(self.data.as_ptr().cast::<u8>(), len) })
239    }
240
241    pub fn bytes_mut(&mut self) -> Option<&mut [u8]> {
242        if !self.access.contains(FramebufferMemoryAccess::Write) {
243            return None;
244        }
245        let len = self.byte_len()?;
246        // SAFETY: libretro guarantees the returned pointer is valid during the
247        // current retro_run iteration for the access flags requested by the core.
248        Some(unsafe { slice::from_raw_parts_mut(self.data.as_ptr().cast::<u8>(), len) })
249    }
250
251    pub(crate) fn video_refresh_args(&self) -> (*const c_void, u32, u32, usize) {
252        (
253            self.data.as_ptr().cast_const(),
254            self.width,
255            self.height,
256            self.pitch,
257        )
258    }
259
260    fn byte_len(&self) -> Option<usize> {
261        self.pitch.checked_mul(self.height as usize)
262    }
263}
264
265#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
266pub struct MemoryMapOffset(usize);
267
268impl MemoryMapOffset {
269    pub const fn new(offset: usize) -> Self {
270        Self(offset)
271    }
272
273    pub const fn as_usize(self) -> usize {
274        self.0
275    }
276}
277
278impl From<usize> for MemoryMapOffset {
279    fn from(offset: usize) -> Self {
280        Self::new(offset)
281    }
282}
283
284#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
285pub struct EmulatedAddress(usize);
286
287impl EmulatedAddress {
288    pub const fn new(address: usize) -> Self {
289        Self(address)
290    }
291
292    pub const fn as_usize(self) -> usize {
293        self.0
294    }
295}
296
297impl From<usize> for EmulatedAddress {
298    fn from(address: usize) -> Self {
299        Self::new(address)
300    }
301}
302
303#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
304pub struct MemoryMapMask(usize);
305
306impl MemoryMapMask {
307    pub const fn new(mask: usize) -> Self {
308        Self(mask)
309    }
310
311    pub const fn zero() -> Self {
312        Self(0)
313    }
314
315    pub const fn as_usize(self) -> usize {
316        self.0
317    }
318}
319
320impl From<usize> for MemoryMapMask {
321    fn from(mask: usize) -> Self {
322        Self::new(mask)
323    }
324}
325
326#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
327pub struct MemoryMapLen(usize);
328
329impl MemoryMapLen {
330    pub const fn new(len: usize) -> Self {
331        Self(len)
332    }
333
334    pub const fn as_usize(self) -> usize {
335        self.0
336    }
337}
338
339impl From<usize> for MemoryMapLen {
340    fn from(len: usize) -> Self {
341        Self::new(len)
342    }
343}
344
345#[derive(Debug)]
346pub struct MemoryMapDescriptor<'a> {
347    pub flags: MemoryDescriptorFlags,
348    pub alignment: Option<MemoryDescriptorAlignment>,
349    pub min_access_size: Option<MemoryDescriptorMinAccessSize>,
350    pub ptr: Option<NonNull<u8>>,
351    pub offset: MemoryMapOffset,
352    pub start: EmulatedAddress,
353    pub select: MemoryMapMask,
354    pub disconnect: MemoryMapMask,
355    pub len: MemoryMapLen,
356    pub addrspace: Option<String>,
357    _lifetime: PhantomData<&'a mut [u8]>,
358}
359
360impl<'a> MemoryMapDescriptor<'a> {
361    pub fn new_inaccessible(
362        addrspace: impl Into<Option<String>>,
363        start: impl Into<EmulatedAddress>,
364        select: impl Into<MemoryMapMask>,
365    ) -> Self {
366        Self {
367            flags: MemoryDescriptorFlags::empty(),
368            alignment: None,
369            min_access_size: None,
370            ptr: None,
371            offset: MemoryMapOffset::new(0),
372            start: start.into(),
373            select: select.into(),
374            disconnect: MemoryMapMask::zero(),
375            len: MemoryMapLen::new(0),
376            addrspace: addrspace.into(),
377            _lifetime: PhantomData,
378        }
379    }
380
381    pub fn from_slice(
382        addrspace: impl Into<Option<String>>,
383        start: impl Into<EmulatedAddress>,
384        memory: &'a mut [u8],
385    ) -> Self {
386        Self {
387            flags: MemoryDescriptorFlags::empty(),
388            alignment: None,
389            min_access_size: None,
390            ptr: NonNull::new(memory.as_mut_ptr()),
391            offset: MemoryMapOffset::new(0),
392            start: start.into(),
393            select: MemoryMapMask::zero(),
394            disconnect: MemoryMapMask::zero(),
395            len: MemoryMapLen::new(memory.len()),
396            addrspace: addrspace.into(),
397            _lifetime: PhantomData,
398        }
399    }
400
401    pub fn with_flags(mut self, flags: MemoryDescriptorFlags) -> Self {
402        self.flags = flags;
403        self
404    }
405
406    pub fn with_alignment(mut self, alignment: MemoryDescriptorAlignment) -> Self {
407        self.alignment = Some(alignment);
408        self
409    }
410
411    pub fn with_min_access_size(mut self, size: MemoryDescriptorMinAccessSize) -> Self {
412        self.min_access_size = Some(size);
413        self
414    }
415
416    pub(crate) fn raw_flags(&self) -> u64 {
417        self.flags.bits()
418            | self
419                .alignment
420                .map(MemoryDescriptorAlignment::as_raw_flag)
421                .unwrap_or(0)
422            | self
423                .min_access_size
424                .map(MemoryDescriptorMinAccessSize::as_raw_flag)
425                .unwrap_or(0)
426    }
427
428    pub fn with_offset(mut self, offset: impl Into<MemoryMapOffset>) -> Self {
429        self.offset = offset.into();
430        self
431    }
432
433    pub fn with_select(mut self, select: impl Into<MemoryMapMask>) -> Self {
434        self.select = select.into();
435        self
436    }
437
438    pub fn with_disconnect(mut self, disconnect: impl Into<MemoryMapMask>) -> Self {
439        self.disconnect = disconnect.into();
440        self
441    }
442
443    pub fn with_len(mut self, len: impl Into<MemoryMapLen>) -> Self {
444        self.len = len.into();
445        self
446    }
447}
448
449#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
450pub enum SavestateContext {
451    #[default]
452    Normal,
453    RunaheadSameInstance,
454    RunaheadSameBinary,
455    RollbackNetplay,
456    Unknown(i32),
457}
458
459impl SavestateContext {
460    pub const fn from_raw(context: i32) -> Self {
461        match context {
462            0 => Self::Normal,
463            1 => Self::RunaheadSameInstance,
464            2 => Self::RunaheadSameBinary,
465            3 => Self::RollbackNetplay,
466            other => Self::Unknown(other),
467        }
468    }
469
470    pub const fn as_raw(self) -> i32 {
471        match self {
472            Self::Normal => 0,
473            Self::RunaheadSameInstance => 1,
474            Self::RunaheadSameBinary => 2,
475            Self::RollbackNetplay => 3,
476            Self::Unknown(context) => context,
477        }
478    }
479}
480
481#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
482pub enum MemoryRegion {
483    SaveRam,
484    Rtc,
485    SystemRam,
486    VideoRam,
487    Rom,
488    Unknown(u32),
489}
490
491impl MemoryRegion {
492    pub const fn from_raw(id: u32) -> Self {
493        match id & RETRO_MEMORY_MASK {
494            RETRO_MEMORY_SAVE_RAM => Self::SaveRam,
495            RETRO_MEMORY_RTC => Self::Rtc,
496            RETRO_MEMORY_SYSTEM_RAM => Self::SystemRam,
497            RETRO_MEMORY_VIDEO_RAM => Self::VideoRam,
498            RETRO_MEMORY_ROM => Self::Rom,
499            _ => Self::Unknown(id),
500        }
501    }
502
503    pub const fn as_raw(self) -> u32 {
504        match self {
505            Self::SaveRam => RETRO_MEMORY_SAVE_RAM,
506            Self::Rtc => RETRO_MEMORY_RTC,
507            Self::SystemRam => RETRO_MEMORY_SYSTEM_RAM,
508            Self::VideoRam => RETRO_MEMORY_VIDEO_RAM,
509            Self::Rom => RETRO_MEMORY_ROM,
510            Self::Unknown(id) => id,
511        }
512    }
513}
514
515#[derive(Debug)]
516pub enum CoreMemory<'a> {
517    ReadOnly(&'a [u8]),
518    ReadWrite(&'a mut [u8]),
519}
520
521impl<'a> CoreMemory<'a> {
522    pub fn read_only(data: &'a [u8]) -> Self {
523        Self::ReadOnly(data)
524    }
525
526    pub fn read_write(data: &'a mut [u8]) -> Self {
527        Self::ReadWrite(data)
528    }
529
530    pub fn len(&self) -> usize {
531        match self {
532            Self::ReadOnly(data) => data.len(),
533            Self::ReadWrite(data) => data.len(),
534        }
535    }
536
537    pub fn is_empty(&self) -> bool {
538        self.len() == 0
539    }
540
541    pub fn as_slice(&self) -> &[u8] {
542        match self {
543            Self::ReadOnly(data) => data,
544            Self::ReadWrite(data) => data,
545        }
546    }
547
548    pub(crate) fn as_mut_ptr(&mut self) -> *mut c_void {
549        if self.is_empty() {
550            return std::ptr::null_mut();
551        }
552        match self {
553            Self::ReadOnly(data) => data.as_ptr().cast_mut().cast::<c_void>(),
554            Self::ReadWrite(data) => data.as_mut_ptr().cast::<c_void>(),
555        }
556    }
557}
558
559#[cfg(test)]
560mod tests {
561    use super::{
562        CoreMemory, FramebufferMemoryAccess, FramebufferMemoryAccessFlags, FramebufferMemoryType,
563        FramebufferMemoryTypes, MemoryDescriptorAlignment, MemoryDescriptorFlag,
564        MemoryDescriptorFlags, MemoryDescriptorMinAccessSize, MemoryMapDescriptor, MemoryMapLen,
565        MemoryMapMask, MemoryMapOffset, MemoryRegion, SavestateContext, SerializationQuirk,
566        SerializationQuirks,
567    };
568
569    #[test]
570    fn known_memory_regions_round_trip_to_libretro_ids() {
571        let regions = [
572            MemoryRegion::SaveRam,
573            MemoryRegion::Rtc,
574            MemoryRegion::SystemRam,
575            MemoryRegion::VideoRam,
576            MemoryRegion::Rom,
577        ];
578
579        for region in regions {
580            assert_eq!(MemoryRegion::from_raw(region.as_raw()), region);
581        }
582    }
583
584    #[test]
585    fn unknown_memory_region_preserves_original_id() {
586        assert_eq!(MemoryRegion::from_raw(99), MemoryRegion::Unknown(99));
587        assert_eq!(MemoryRegion::Unknown(99).as_raw(), 99);
588    }
589
590    #[test]
591    fn memory_region_masks_libretro_memory_type_bits() {
592        let id_with_extra_bits = crate::raw::RETRO_MEMORY_SAVE_RAM | 0x100;
593
594        assert_eq!(
595            MemoryRegion::from_raw(id_with_extra_bits),
596            MemoryRegion::SaveRam
597        );
598    }
599
600    #[test]
601    fn core_memory_wraps_readonly_and_readwrite_slices() {
602        let readonly = [1, 2, 3];
603        let readonly_memory = CoreMemory::read_only(&readonly);
604        assert_eq!(readonly_memory.len(), 3);
605        assert_eq!(readonly_memory.as_slice(), &[1, 2, 3]);
606
607        let mut readwrite = [4, 5];
608        let mut readwrite_memory = CoreMemory::read_write(&mut readwrite);
609        assert_eq!(readwrite_memory.len(), 2);
610        assert_eq!(readwrite_memory.as_slice(), &[4, 5]);
611        assert!(!readwrite_memory.as_mut_ptr().is_null());
612
613        let mut empty = CoreMemory::read_only(&[]);
614        assert!(empty.as_mut_ptr().is_null());
615    }
616
617    #[test]
618    fn savestate_contexts_round_trip_to_libretro_ids() {
619        let contexts = [
620            SavestateContext::Normal,
621            SavestateContext::RunaheadSameInstance,
622            SavestateContext::RunaheadSameBinary,
623            SavestateContext::RollbackNetplay,
624        ];
625
626        for context in contexts {
627            assert_eq!(SavestateContext::from_raw(context.as_raw()), context);
628        }
629    }
630
631    #[test]
632    fn unknown_savestate_context_preserves_original_id() {
633        assert_eq!(
634            SavestateContext::from_raw(i32::MAX),
635            SavestateContext::Unknown(i32::MAX)
636        );
637        assert_eq!(SavestateContext::Unknown(99).as_raw(), 99);
638    }
639
640    #[test]
641    fn serialization_quirks_encode_libretro_flags() {
642        let quirks = SerializationQuirks::from(SerializationQuirk::MustInitialize)
643            | SerializationQuirk::PlatformDependent;
644
645        assert!(quirks.contains(SerializationQuirk::MustInitialize));
646        assert!(quirks.contains(SerializationQuirk::PlatformDependent));
647        assert_eq!(
648            quirks.bits(),
649            crate::raw::RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE
650                | crate::raw::RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT
651        );
652    }
653
654    #[test]
655    fn memory_descriptor_flags_encode_libretro_bits() {
656        let flags = MemoryDescriptorFlags::from(MemoryDescriptorFlag::Constant)
657            | MemoryDescriptorFlag::BigEndian
658            | MemoryDescriptorFlag::SaveRam;
659
660        assert_eq!(
661            flags.bits(),
662            crate::raw::RETRO_MEMDESC_CONST
663                | crate::raw::RETRO_MEMDESC_BIGENDIAN
664                | crate::raw::RETRO_MEMDESC_SAVE_RAM
665        );
666    }
667
668    #[test]
669    fn memory_descriptor_alignment_and_min_size_are_exclusive_values() {
670        assert_eq!(
671            MemoryDescriptorAlignment::TwoBytes.as_raw_flag(),
672            crate::raw::RETRO_MEMDESC_ALIGN_2
673        );
674        assert_eq!(
675            MemoryDescriptorAlignment::FourBytes.as_raw_flag(),
676            crate::raw::RETRO_MEMDESC_ALIGN_4
677        );
678        assert_eq!(
679            MemoryDescriptorAlignment::EightBytes.as_raw_flag(),
680            crate::raw::RETRO_MEMDESC_ALIGN_8
681        );
682        assert_eq!(
683            MemoryDescriptorMinAccessSize::TwoBytes.as_raw_flag(),
684            crate::raw::RETRO_MEMDESC_MINSIZE_2
685        );
686        assert_eq!(
687            MemoryDescriptorMinAccessSize::FourBytes.as_raw_flag(),
688            crate::raw::RETRO_MEMDESC_MINSIZE_4
689        );
690        assert_eq!(
691            MemoryDescriptorMinAccessSize::EightBytes.as_raw_flag(),
692            crate::raw::RETRO_MEMDESC_MINSIZE_8
693        );
694    }
695
696    #[test]
697    fn memory_map_descriptor_from_slice_tracks_typed_fields() {
698        let mut ram = [0u8; 8];
699        let descriptor =
700            MemoryMapDescriptor::from_slice(Some("WRAM".to_string()), 0x7e0000usize, &mut ram)
701                .with_flags(MemoryDescriptorFlags::from(MemoryDescriptorFlag::SystemRam))
702                .with_alignment(MemoryDescriptorAlignment::FourBytes)
703                .with_min_access_size(MemoryDescriptorMinAccessSize::TwoBytes)
704                .with_offset(MemoryMapOffset::new(2))
705                .with_select(MemoryMapMask::new(0xff0000))
706                .with_disconnect(MemoryMapMask::new(0x80))
707                .with_len(MemoryMapLen::new(4));
708
709        assert!(descriptor.ptr.is_some());
710        assert_eq!(descriptor.offset, MemoryMapOffset::new(2));
711        assert_eq!(descriptor.select, MemoryMapMask::new(0xff0000));
712        assert_eq!(descriptor.disconnect, MemoryMapMask::new(0x80));
713        assert_eq!(descriptor.len, MemoryMapLen::new(4));
714        assert_eq!(
715            descriptor.raw_flags(),
716            crate::raw::RETRO_MEMDESC_SYSTEM_RAM
717                | crate::raw::RETRO_MEMDESC_ALIGN_4
718                | crate::raw::RETRO_MEMDESC_MINSIZE_2
719        );
720    }
721
722    #[test]
723    fn framebuffer_memory_flags_encode_libretro_bits() {
724        let access = FramebufferMemoryAccessFlags::from(FramebufferMemoryAccess::Read)
725            | FramebufferMemoryAccess::Write;
726        let types = FramebufferMemoryTypes::from(FramebufferMemoryType::Cached);
727
728        assert_eq!(
729            access.bits(),
730            crate::raw::RETRO_MEMORY_ACCESS_READ | crate::raw::RETRO_MEMORY_ACCESS_WRITE
731        );
732        assert_eq!(types.bits(), crate::raw::RETRO_MEMORY_TYPE_CACHED);
733    }
734}