asar_snes/
lib.rs

1//! # Asar Rust Bindings
2//! This crate provides a safe wrapper around the [Asar](<https://github.com/RPGHacker/asar>) library version 1.91.
3//!
4//! By default this crate is not thread-safe.
5//!
6//! In case it is needed to use this crate in a multithreaded environment, the `thread-safe` feature should be enabled, doing so will make all the functions use a global lock to ensure that the Asar API is called in a thread-safe manner.
7pub(crate) mod bindings {
8    #![allow(non_upper_case_globals)]
9    #![allow(non_camel_case_types)]
10    #![allow(non_snake_case)]
11    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
12}
13
14#[cfg(test)]
15mod test;
16
17extern crate asar_snes_proc_macros;
18pub use asar_snes_proc_macros::use_asar_global_lock;
19
20use core::fmt;
21#[cfg(feature = "thread-safe")]
22use parking_lot::ReentrantMutex;
23#[cfg(feature = "thread-safe")]
24use std::sync::OnceLock;
25
26use std::{
27    ffi::{CStr, CString},
28    os::raw::{c_char, c_int, c_void},
29    ptr,
30};
31
32use crate::bindings::{
33    asar_apiversion, asar_getalldefines, asar_getalllabels, asar_getdefine, asar_geterrors,
34    asar_getlabelval, asar_getmapper, asar_getprints, asar_getsymbolsfile, asar_getwarnings,
35    asar_getwrittenblocks, asar_math, asar_maxromsize, asar_patch, asar_patch_ex, asar_reset,
36    asar_resolvedefines, asar_version, definedata, errordata, labeldata, mappertype, memoryfile,
37    patchparams, warnsetting, writtenblockdata,
38};
39
40#[cfg(feature = "thread-safe")]
41fn global_asar_lock() -> &'static ReentrantMutex<()> {
42    static LOCK: OnceLock<ReentrantMutex<()>> = OnceLock::new();
43    LOCK.get_or_init(|| ReentrantMutex::new(()))
44}
45
46#[cfg(not(feature = "thread-safe"))]
47fn global_asar_lock() -> &'static FakeLock {
48    &FakeLock
49}
50
51#[cfg(not(feature = "thread-safe"))]
52struct FakeLock;
53
54#[cfg(not(feature = "thread-safe"))]
55impl FakeLock {
56    fn lock(&self) -> FakeLock {
57        FakeLock
58    }
59}
60
61/// Executes the closure with the Asar global lock.
62///
63/// This lock is recursive, so it can be used in nested calls without issues.
64///
65/// This is necessary to ensure that Asar's API is called in a thread-safe manner.
66///
67/// It is recommended to use this function in multithreaded environments, because Asar uses a lot of global state.
68///
69/// e.g. these 2 calls would be unsafe without the lock because patch stores defines, labels in global state.
70/// ```rust
71/// use asar_snes as asar;
72/// use asar_snes::with_asar_lock;
73/// use asar_snes::BasicPatchOptions;
74/// // thread 1
75/// let result = with_asar_lock(|| {
76///     asar::patching::patch(BasicPatchOptions::new(vec![0x00, 0x00, 0x00, 0x00].into(), "test.asm".into()))
77/// });
78///
79/// // thread 2
80/// let (result, labels) = with_asar_lock(|| {
81///     let result = asar::patching::patch(BasicPatchOptions::new(vec![0x00, 0x00, 0x00, 0x00].into(), "test2.asm".into()));
82///     let labels = asar::patching::labels();
83///     (result, labels)
84/// });
85/// ```
86///
87/// A lot of functions already use this lock internally, but if you are calling multiple functions in a row, it is recommended to call it manually since other threads might interfere between the calls.
88/// 
89/// # Note
90/// This function does something **only** if the `thread-safe` feature is **enabled**. Otherwise it is a no-op.
91pub fn with_asar_lock<F, R>(f: F) -> R
92where
93    F: FnOnce() -> R,
94{
95    let _lock = global_asar_lock().lock();
96    f()
97}
98
99/// Represents the ROM data, with a byte vector and the ROM length
100///
101/// Note that the ROM length may not be the same as the length of the data vector, it is the actual length of the ROM.
102///
103/// The [`RomData::length`] parameter is updated after a patch operation to reflect the new length of the ROM if it was modified.
104///
105/// Note that asar will not modify the length of the data vector, if the patch does not fit in the data vector, patching will fail.
106#[derive(Debug, Clone, Default)]
107pub struct RomData {
108    pub data: Vec<u8>,
109    pub length: usize,
110}
111
112/// Represents an error message from Asar.
113#[derive(Debug, Clone)]
114pub struct ErrorData {
115    pub fullerrdata: String,
116    pub rawerrdata: String,
117    pub block: String,
118    pub filename: String,
119    pub line: i32,
120    pub callerfilename: String,
121    pub callerline: i32,
122    pub errid: i32,
123}
124
125/// Represents a warning message from Asar.
126pub type WarningData = ErrorData;
127
128/// Represents a define from Asar, with its name and contents.
129#[derive(Debug, Clone)]
130pub struct Define {
131    pub name: String,
132    pub contents: String,
133}
134
135/// Represents a block of data written to the ROM by Asar as a consequence of a call to asar_patch or asar_patch_ex.
136/// It has the PC offset, the SNES offset and the number of bytes written.
137#[derive(Debug, Clone)]
138pub struct WrittenBlock {
139    pub pcoffset: i32,
140    pub snesoffset: i32,
141    pub numbytes: i32,
142}
143
144/// Represents a label from Asar, with its name and location.
145/// The location is the SNES address of the label.
146#[derive(Debug, Clone)]
147pub struct Label {
148    pub name: String,
149    pub location: i32,
150}
151
152/// Represents the basic options for a patch operation, only requiring the ROM data and the patch location.
153#[derive(Debug, Clone)]
154pub struct BasicPatchOptions {
155    romdata: RomData,
156    patchloc: String,
157}
158
159/// Represents the warn settings for a patch operation, with the warnid and whether it is enabled or not.
160#[derive(Debug, Clone)]
161pub struct WarnSetting {
162    pub warnid: String,
163    pub enabled: bool,
164}
165
166/// Represents the data for a memory file, the data can be binary or text.
167#[derive(Debug, Clone)]
168pub enum MemoryFileData {
169    Binary(Vec<u8>),
170    Text(String),
171}
172
173impl From<Vec<u8>> for MemoryFileData {
174    fn from(data: Vec<u8>) -> Self {
175        MemoryFileData::Binary(data)
176    }
177}
178
179impl From<String> for MemoryFileData {
180    fn from(data: String) -> Self {
181        MemoryFileData::Text(data)
182    }
183}
184
185impl From<&str> for MemoryFileData {
186    fn from(data: &str) -> Self {
187        MemoryFileData::Text(data.into())
188    }
189}
190
191/// Represents the memory file for a patch operation, with the filename and the data.
192#[derive(Debug, Clone)]
193pub struct MemoryFile {
194    pub filename: String,
195    pub data: MemoryFileData,
196}
197
198/// Represents the advanced options for a patch operation, requiring, at least, the ROM data and the patch location.
199/// Several options can be added to the patch operation, like include paths, defines, warning settings, memory files, etc.
200/// See the [`PatchOption`] enum for all the available options.
201/// Creation of this struct should be done with the [`AdvancedPatchOptions::new`] method.
202#[derive(Debug, Clone)]
203pub struct AdvancedPatchOptions {
204    includepaths: Vec<String>,
205    should_reset: bool,
206    additional_defines: Vec<Define>,
207    stdincludesfile: Option<String>,
208    stddefinesfile: Option<String>,
209    warning_settings: Vec<WarnSetting>,
210    memory_files: Vec<MemoryFile>,
211    override_checksum_gen: bool,
212    generate_checksum: bool,
213}
214
215pub type MapperType = mappertype;
216
217#[derive(Debug, Clone)]
218pub enum SymbolType {
219    WLA,
220    NoCash,
221}
222
223#[derive(Debug, Clone)]
224pub enum PatchResult {
225    Success(RomData, Vec<WarningData>),
226    Failure(Vec<ErrorData>),
227}
228
229/// Represents the options that can be added to a patch operation.
230#[derive(Debug, Clone)]
231pub enum PatchOption {
232    /// Adds an include path to the patch operation.
233    Include(String),
234    /// Adds a define to the patch operation.
235    Define(String, String),
236    /// Adds a warning setting to the patch operation.
237    Warning(String, bool),
238    /// Adds a memory file to the patch operation.
239    MemoryFile(String, MemoryFileData),
240    /// Adds a standard includes file to the patch operation.
241    StdIncludesFile(String),
242    /// Adds a standard defines file to the patch operation.
243    StdDefinesFile(String),
244    /// Overrides the checksum generation.
245    OverrideChecksumGen(bool),
246    /// Generates the checksum.
247    GenerateChecksum(bool),
248    /// Sets whether the patch operation should reset.
249    ShouldReset(bool),
250}
251
252impl RomData {
253    /// Creates a new RomData with the data provided.
254    pub fn from_vec(data: Vec<u8>) -> RomData {
255        let length = data.len();
256        RomData { data, length }
257    }
258
259    /// Creates a new RomData with the data provided.
260    pub fn new(data: Vec<u8>, length: usize) -> RomData {
261        RomData { data, length }
262    }
263}
264
265impl From<Vec<u8>> for RomData {
266    fn from(data: Vec<u8>) -> Self {
267        RomData::from_vec(data)
268    }
269}
270
271impl MemoryFile {
272    fn as_raw(&self) -> memoryfile {
273        let filename = CString::new(self.filename.clone()).unwrap();
274        let data = match &self.data {
275            MemoryFileData::Binary(d) => d.as_ptr() as *mut c_void,
276            MemoryFileData::Text(d) => d.as_ptr() as *mut c_void,
277        };
278        let size = match &self.data {
279            MemoryFileData::Binary(d) => d.len(),
280            MemoryFileData::Text(d) => d.len(),
281        };
282        memoryfile {
283            path: filename.into_raw(),
284            buffer: data,
285            length: size,
286        }
287    }
288}
289
290impl WarnSetting {
291    fn as_raw(&self) -> warnsetting {
292        let warnid = CString::new(self.warnid.clone()).unwrap();
293        warnsetting {
294            warnid: warnid.into_raw(),
295            enabled: self.enabled,
296        }
297    }
298}
299
300impl ErrorData {
301    fn from_raw(raw: &errordata) -> ErrorData {
302        ErrorData {
303            fullerrdata: unsafe { CStr::from_ptr(raw.fullerrdata) }
304                .to_string_lossy()
305                .into_owned(),
306            rawerrdata: unsafe { CStr::from_ptr(raw.rawerrdata) }
307                .to_string_lossy()
308                .into_owned(),
309            block: unsafe { CStr::from_ptr(raw.block) }
310                .to_string_lossy()
311                .into_owned(),
312            filename: if raw.filename.is_null() {
313                "".into()
314            } else {
315                unsafe { CStr::from_ptr(raw.filename) }
316                    .to_string_lossy()
317                    .into_owned()
318            },
319            line: raw.line,
320            callerfilename: if raw.callerfilename.is_null() {
321                "".into()
322            } else {
323                unsafe { CStr::from_ptr(raw.callerfilename) }
324                    .to_string_lossy()
325                    .into_owned()
326            },
327            callerline: raw.callerline,
328            errid: raw.errid,
329        }
330    }
331}
332
333impl Define {
334    fn from_raw(raw: &definedata) -> Define {
335        Define {
336            name: unsafe { CStr::from_ptr(raw.name) }
337                .to_string_lossy()
338                .into_owned(),
339            contents: unsafe { CStr::from_ptr(raw.contents) }
340                .to_string_lossy()
341                .into_owned(),
342        }
343    }
344    fn as_raw(&self) -> definedata {
345        let name = std::ffi::CString::new(self.name.clone()).unwrap();
346        let contents = std::ffi::CString::new(self.contents.clone()).unwrap();
347        definedata {
348            name: name.into_raw(),
349            contents: contents.into_raw(),
350        }
351    }
352}
353
354impl WrittenBlock {
355    fn from_raw(raw: &writtenblockdata) -> WrittenBlock {
356        WrittenBlock {
357            pcoffset: raw.pcoffset,
358            snesoffset: raw.snesoffset,
359            numbytes: raw.numbytes,
360        }
361    }
362}
363
364impl Label {
365    fn from_raw(raw: &labeldata) -> Label {
366        Label {
367            name: unsafe { CStr::from_ptr(raw.name) }
368                .to_string_lossy()
369                .into_owned(),
370            location: raw.location,
371        }
372    }
373}
374
375impl BasicPatchOptions {
376    /// Creates a new BasicPatchOptions with the ROM data and the patch location.
377    pub fn new(romdata: RomData, patchloc: String) -> BasicPatchOptions {
378        BasicPatchOptions { romdata, patchloc }
379    }
380}
381
382impl AdvancedPatchOptions {
383    /// Creates a new AdvancedPatchOptions, with all default values.
384    pub fn new() -> AdvancedPatchOptions {
385        AdvancedPatchOptions {
386            includepaths: Vec::new(),
387            should_reset: true,
388            additional_defines: Vec::new(),
389            stdincludesfile: None,
390            stddefinesfile: None,
391            warning_settings: Vec::new(),
392            memory_files: Vec::new(),
393            override_checksum_gen: false,
394            generate_checksum: false,
395        }
396    }
397
398    /// Creates a new AdvancedPatchOptions with the options provided.
399    pub fn from(options: Vec<PatchOption>) -> AdvancedPatchOptions {
400        AdvancedPatchOptions::new().options(options)
401    }
402
403    /// Adds an option to the patch operation.
404    pub fn option(mut self, option: PatchOption) -> AdvancedPatchOptions {
405        match option {
406            PatchOption::Include(path) => self.includepaths.push(path),
407            PatchOption::Define(name, contents) => {
408                self.additional_defines.push(Define { name, contents })
409            }
410            PatchOption::Warning(warnid, enabled) => {
411                self.warning_settings.push(WarnSetting { warnid, enabled })
412            }
413            PatchOption::MemoryFile(filename, data) => {
414                self.memory_files.push(MemoryFile { filename, data })
415            }
416            PatchOption::StdIncludesFile(filename) => self.stdincludesfile = Some(filename),
417            PatchOption::StdDefinesFile(filename) => self.stddefinesfile = Some(filename),
418            PatchOption::OverrideChecksumGen(override_checksum_gen) => {
419                self.override_checksum_gen = override_checksum_gen
420            }
421            PatchOption::GenerateChecksum(generate_checksum) => {
422                self.generate_checksum = generate_checksum
423            }
424            PatchOption::ShouldReset(should_reset) => self.should_reset = should_reset,
425        };
426        self
427    }
428
429    /// Adds multiple options to the patch operation.
430    pub fn options(mut self, options: Vec<PatchOption>) -> AdvancedPatchOptions {
431        for option in options {
432            self = self.option(option);
433        }
434        self
435    }
436}
437
438impl Default for AdvancedPatchOptions {
439    fn default() -> Self {
440        Self::new()
441    }
442}
443
444/// Returns the maximum ROM size that Asar can handle in bytes
445///
446/// This should normally be 16*1024*1024
447pub fn max_rom_size() -> i32 {
448    unsafe { asar_maxromsize() }
449}
450
451/// Returns the API version of Asar.
452pub fn api_version() -> i32 {
453    unsafe { asar_apiversion() }
454}
455
456/// Returns the version of Asar, in the format Major * 10000 + Minor * 100 + Revision.
457pub fn version() -> i32 {
458    unsafe { asar_version() }
459}
460
461/// Computes a math expression.
462///
463/// If the math expression is invalid, it returns an error message.
464pub fn math(math: &str) -> Result<f64, String> {
465    let math = CString::new(math).unwrap();
466    let mut err: *const i8 = std::ptr::null();
467    let result = unsafe { asar_math(math.as_ptr(), &mut err) };
468    if err.is_null() {
469        Ok(result)
470    } else {
471        Err(unsafe { CStr::from_ptr(err) }
472            .to_string_lossy()
473            .into_owned())
474    }
475}
476
477/// This is the raw patching API of asar, it is not recommended to use this directly as it does not prevent multiple calls to the API at the same time which may result in tramplings of the global state in asar's library.
478///
479/// e.g.
480///
481/// ```rust
482/// /// assuming that test.asm contains:
483/// /// !test = $18
484/// /// and that test2.asm contains:
485/// /// !test = $19
486/// use asar_snes::BasicPatchOptions;
487/// use asar_snes as asar;
488///
489/// let options1 = BasicPatchOptions::new(vec![].into(), "test.asm".into());
490/// let options2 = BasicPatchOptions::new(vec![].into(), "test2.asm".into());
491/// let result1 = asar::patching::patch(options1);
492/// let result2 = asar::patching::patch(options2);
493///
494/// let define = asar::patching::define("test");
495///
496/// println!("{:?}", define); // this will print $19, because the second patch operation overwrote the global state of the first patch operation.
497///
498/// ```
499///
500/// For this reason, it is recommended to use [`Patcher`] instead.
501///
502/// This module is however provided for users that want to use the raw API directly
503///
504/// remarks: all functions in this module use the global lock.
505pub mod patching {
506
507    use super::*;
508
509    /// Resets Asar, clearing all the errors, warnings and prints.
510    ///
511    /// Useful to clear the state of Asar between patch operations.
512    ///
513    /// Returns true if the reset was successful, false otherwise.
514    ///
515    /// If false is returned, you can check the errors with the [`errors`] function.
516    ///
517    /// remarks: This function uses the global lock.
518    #[use_asar_global_lock]
519    pub fn reset() -> bool {
520        unsafe { asar_reset() }
521    }
522
523    /// Patches the ROM data with the patch provided in the [`BasicPatchOptions`].
524    ///
525    /// Returns a [`PatchResult`] with the result of the patch operation.
526    ///
527    /// remarks: This function uses the global lock.
528    #[use_asar_global_lock]
529    pub fn patch(mut options: BasicPatchOptions) -> PatchResult {
530        let romdata = options.romdata.data.as_mut_ptr() as *mut c_char;
531        let buflen = options.romdata.data.len() as c_int;
532        let patchloc = CString::new(options.patchloc).unwrap();
533        let mut romsize = options.romdata.length as c_int;
534        let romlen: *mut c_int = &mut romsize;
535        let result = unsafe { asar_patch(patchloc.as_ptr(), romdata, buflen, romlen) };
536        let mut count: c_int = 0;
537        let warnings = unsafe { asar_getwarnings(&mut count) };
538        let warnings = unsafe { std::slice::from_raw_parts(warnings, count as usize) };
539        let warnings = warnings.iter().map(ErrorData::from_raw).collect();
540        if result {
541            options.romdata.length = romsize as usize;
542            PatchResult::Success(options.romdata, warnings)
543        } else {
544            let mut count: c_int = 0;
545            let errors = unsafe { asar_geterrors(&mut count) };
546            let errors = unsafe { std::slice::from_raw_parts(errors, count as usize) };
547            let errors = errors.iter().map(ErrorData::from_raw).collect();
548            PatchResult::Failure(errors)
549        }
550    }
551
552    #[use_asar_global_lock]
553    pub(crate) fn patch_ex_basic(
554        mut rom: RomData,
555        patch: String,
556        options: AdvancedPatchOptions,
557    ) -> (RomData, bool) {
558        let romdata = rom.data.as_mut_ptr() as *mut c_char;
559        let buflen = rom.data.len() as c_int;
560        let patchloc = CString::new(patch).unwrap();
561        let mut romsize = rom.length as c_int;
562        let romlen: *mut c_int = &mut romsize;
563
564        let mut definedata = options
565            .additional_defines
566            .iter()
567            .map(Define::as_raw)
568            .collect::<Vec<definedata>>();
569        let mut warning_settings = options
570            .warning_settings
571            .iter()
572            .map(WarnSetting::as_raw)
573            .collect::<Vec<warnsetting>>();
574        let mut memory_files = options
575            .memory_files
576            .iter()
577            .map(MemoryFile::as_raw)
578            .collect::<Vec<memoryfile>>();
579        let mut includepaths = options
580            .includepaths
581            .iter()
582            .map(|p| CString::new(p.clone()).unwrap().into_raw() as *const i8)
583            .collect::<Vec<_>>();
584
585        let stdincludesfile = options.stdincludesfile.map(|s| CString::new(s).unwrap());
586        let stddefinesfile = options.stddefinesfile.map(|s| CString::new(s).unwrap());
587
588        let params = patchparams {
589            structsize: std::mem::size_of::<patchparams>() as c_int,
590            buflen,
591            patchloc: patchloc.as_ptr(),
592            romdata,
593            romlen,
594            includepaths: includepaths.as_mut_ptr(),
595            numincludepaths: includepaths.len() as c_int,
596            should_reset: options.should_reset,
597            additional_defines: definedata.as_mut_ptr(),
598            additional_define_count: definedata.len() as c_int,
599            stdincludesfile: stdincludesfile.map_or(ptr::null(), |s| s.as_ptr()),
600            stddefinesfile: stddefinesfile.map_or(ptr::null(), |s| s.as_ptr()),
601            warning_settings: warning_settings.as_mut_ptr(),
602            warning_setting_count: warning_settings.len() as c_int,
603            memory_files: memory_files.as_mut_ptr(),
604            memory_file_count: memory_files.len() as c_int,
605            override_checksum_gen: options.override_checksum_gen,
606            generate_checksum: options.generate_checksum,
607        };
608        let result = unsafe { asar_patch_ex(&params) };
609
610        for define in definedata {
611            unsafe {
612                drop(CString::from_raw(define.name as *mut i8));
613                drop(CString::from_raw(define.contents as *mut i8));
614            }
615        }
616
617        for path in includepaths {
618            unsafe {
619                drop(CString::from_raw(path as *mut i8));
620            }
621        }
622
623        rom.length = romsize as usize;
624
625        (rom, result)
626    }
627
628    /// Patches the ROM data with the patch provided in the [`AdvancedPatchOptions`].
629    ///
630    /// Returns a [`PatchResult`] with the result of the patch operation.
631    ///
632    /// remarks: This function uses the global lock.
633    #[use_asar_global_lock]
634    pub fn patch_ex<T: Into<String>>(rom: RomData, patch: T, options: AdvancedPatchOptions) -> PatchResult {
635        let (romdata, result) = patch_ex_basic(rom, patch.into(), options);
636
637        let mut count: c_int = 0;
638        let warnings = unsafe { asar_getwarnings(&mut count) };
639        let warnings = unsafe { std::slice::from_raw_parts(warnings, count as usize) };
640        let warnings = warnings.iter().map(ErrorData::from_raw).collect();
641
642        if result {
643            PatchResult::Success(romdata, warnings)
644        } else {
645            let mut count: c_int = 0;
646            let errors = unsafe { asar_geterrors(&mut count) };
647            let errors = unsafe { std::slice::from_raw_parts(errors, count as usize) };
648            let errors = errors.iter().map(ErrorData::from_raw).collect();
649            PatchResult::Failure(errors)
650        }
651    }
652
653    /// Returns the errors from the latest api call (usually [`patch`] or [`patch_ex`]).
654    ///
655    /// remarks: This function uses the global lock.
656    #[use_asar_global_lock]
657    pub fn errors() -> Vec<ErrorData> {
658        let mut count: c_int = 0;
659        let errors = unsafe { asar_geterrors(&mut count) };
660        let errors = unsafe { std::slice::from_raw_parts(errors, count as usize) };
661        errors.iter().map(ErrorData::from_raw).collect()
662    }
663
664    /// Returns the warnings from the latest api call (usually [`patch`] or [`patch_ex`]).
665    ///
666    /// remarks: This function uses the global lock.
667    #[use_asar_global_lock]
668    pub fn warnings() -> Vec<ErrorData> {
669        let mut count: c_int = 0;
670        let errors = unsafe { asar_getwarnings(&mut count) };
671        let errors = unsafe { std::slice::from_raw_parts(errors, count as usize) };
672        errors.iter().map(ErrorData::from_raw).collect()
673    }
674
675    /// Returns the prints from the latest api call (usually [`patch`] or [`patch_ex`]).
676    ///
677    /// remarks: This function uses the global lock.
678    #[use_asar_global_lock]
679    pub fn prints() -> Vec<String> {
680        let mut count: c_int = 0;
681        let prints = unsafe { asar_getprints(&mut count) };
682        let prints = unsafe { std::slice::from_raw_parts(prints, count as usize) };
683        prints
684            .iter()
685            .map(|p| unsafe { CStr::from_ptr(*p) }.to_string_lossy().into_owned())
686            .collect()
687    }
688
689    /// Returns the labels from the latest api call (usually [`patch`] or [`patch_ex`]).
690    ///
691    /// remarks: This function uses the global lock.
692    #[use_asar_global_lock]
693    pub fn labels() -> Vec<Label> {
694        let mut count: c_int = 0;
695        let labels = unsafe { asar_getalllabels(&mut count) };
696        let labels = unsafe { std::slice::from_raw_parts(labels, count as usize) };
697        labels.iter().map(Label::from_raw).collect()
698    }
699
700    /// Returns the value of a label from the latest api call (usually [`patch`] or [`patch_ex`]).
701    ///
702    /// If the label is not found, it returns None.
703    ///
704    /// remarks: This function uses the global lock.
705    #[use_asar_global_lock]
706    pub fn label_value(name: &str) -> Option<i32> {
707        let name = CString::new(name).unwrap();
708        let value = unsafe { asar_getlabelval(name.as_ptr()) };
709        if value == -1 {
710            None
711        } else {
712            Some(value)
713        }
714    }
715
716    /// Returns the value of a define from the latest api call (usually [`patch`] or [`patch_ex`]).
717    ///
718    /// If the define is not found, it returns None.
719    #[use_asar_global_lock]
720    pub fn define(name: &str) -> Option<String> {
721        let name = CString::new(name).unwrap();
722        let def = unsafe { asar_getdefine(name.as_ptr()) };
723        if def.is_null() {
724            None
725        } else {
726            Some(
727                unsafe { CStr::from_ptr(def) }
728                    .to_string_lossy()
729                    .into_owned(),
730            )
731        }
732    }
733
734    /// Returns all the defines from the latest api call (usually [`patch`] or [`patch_ex`]).
735    ///
736    /// remarks: This function uses the global lock.
737    #[use_asar_global_lock]
738    pub fn defines() -> Vec<Define> {
739        let mut count: c_int = 0;
740        let defines = unsafe { asar_getalldefines(&mut count) };
741        let defines = unsafe { std::slice::from_raw_parts(defines, count as usize) };
742        defines.iter().map(Define::from_raw).collect()
743    }
744
745    /// Resolves the defines in the data provided.
746    ///
747    /// This function is not very useful and it has some issues, it is not recommended to use it.
748    ///
749    /// remarks: This function uses the global lock.
750    #[use_asar_global_lock]
751    pub fn resolve_defines(data: &str) -> String {
752        unsafe {
753            let data = CString::new(data).unwrap();
754            let resolved = asar_resolvedefines(data.as_ptr(), false);
755            CStr::from_ptr(resolved).to_string_lossy().into_owned()
756        }
757    }
758
759    /// Returns the blocks written to the ROM by Asar as a consequence of a call to [`patch`] or [`patch_ex`].
760    ///
761    /// remarks: This function uses the global lock.
762    #[use_asar_global_lock]
763    pub fn written_blocks() -> Vec<WrittenBlock> {
764        let mut count: c_int = 0;
765        let blocks = unsafe { asar_getwrittenblocks(&mut count) };
766        let blocks = unsafe { std::slice::from_raw_parts(blocks, count as usize) };
767        blocks.iter().map(WrittenBlock::from_raw).collect()
768    }
769
770    /// Returns the mapper type used in the latest api call (usually [`patch`] or [`patch_ex`]).
771    ///
772    /// If the mapper type is not recognized, it returns None.
773    ///
774    /// remarks: This function uses the global lock.
775    #[use_asar_global_lock]
776    pub fn mapper_type() -> Option<MapperType> {
777        let raw = unsafe { asar_getmapper() };
778        match raw {
779            MapperType::invalid_mapper => None,
780            _ => Some(raw),
781        }
782    }
783
784    /// Returns the symbols file for the specified symbol type.
785    ///
786    /// The symbol type can be WLA or NoCash.
787    ///
788    /// remarks: This function uses the global lock.
789    #[use_asar_global_lock]
790    pub fn symbols_file(symboltype: SymbolType) -> Option<String> {
791        let symboltype = match symboltype {
792            SymbolType::WLA => "wla",
793            SymbolType::NoCash => "nocash",
794        };
795        let symboltype = CString::new(symboltype).unwrap();
796        unsafe {
797            let file = asar_getsymbolsfile(symboltype.as_ptr());
798            if file.is_null() {
799                None
800            } else {
801                Some(CStr::from_ptr(file).to_string_lossy().into_owned())
802            }
803        }
804    }
805}
806#[cfg(feature = "thread-safe")]
807use parking_lot::ReentrantMutexGuard;
808
809/// The Patcher struct is a convenient wrapper around the [`patching`] api.
810///
811/// It wraps the patching functions as well as providing a way to gather all information about the result of the patch.
812///
813/// see [`Patcher::apply`] and [`ApplyResult`] for more information.
814#[derive(Debug, Clone)]
815pub struct Patcher {
816    options: Option<AdvancedPatchOptions>,
817}
818
819/// This type represents the result of a patch operation.
820///
821/// It contains the possibly modified ROM data and a boolean indicating whether the patch was successful or not.
822///
823/// see [`ApplyResult::success`]
824///
825/// ### Notes:
826///
827/// The following notes apply to the functions
828/// - [`ApplyResult::warnings`]
829/// - [`ApplyResult::errors`]
830/// - [`ApplyResult::prints`]
831/// - [`ApplyResult::labels`]
832/// - [`ApplyResult::label_value`]
833/// - [`ApplyResult::define`]
834/// - [`ApplyResult::defines`]
835/// - [`ApplyResult::written_blocks`]
836/// - [`ApplyResult::mapper_type`]
837/// - [`ApplyResult::symbols_file`]
838///
839/// The other functions are not affected by these notes.
840///
841/// - If the patch operation was *not* successful ([`ApplyResult::success`] returns false), they will return an empty vector/None/empty string if [`PatchOption::ShouldReset`] was set to true
842///   or the values from the previous patch operation if it was set to false.
843///  
844/// - If there were any call to [`patching::patch`] or [`patching::patch_ex`] between the [`Patcher::apply`] call that returned this [`ApplyResult`] and this call,
845///   this will return the warnings from the latest call instead of ones related to this [`ApplyResult`].   
846#[cfg(feature = "thread-safe")]
847pub struct ApplyResult<'a> {
848    romdata: RomData,
849    success: bool,
850    _guard: ReentrantMutexGuard<'a, ()>,
851}
852
853/// This type represents the result of a patch operation.
854///
855/// It contains the possibly modified ROM data and a boolean indicating whether the patch was successful or not.
856///
857/// see [`ApplyResult::success`] for more information.
858#[cfg(not(feature = "thread-safe"))]
859pub struct ApplyResult<'a> {
860    romdata: RomData,
861    success: bool,
862    _marker: std::marker::PhantomData<&'a ()>,
863}
864
865use std::sync::atomic::{AtomicBool, Ordering};
866
867static APPLYRESULT_ONCE_ALIVE: AtomicBool = AtomicBool::new(false);
868
869/// This error is returned when trying to call [`Patcher::apply`] while another [`ApplyResult`] is alive.
870///
871/// This is to prevent multiple patch operations from happening at the same time, since Asar uses a lot of global state.
872#[derive(Debug, Clone)]
873pub struct ConcurrentApplyError;
874
875impl fmt::Display for ConcurrentApplyError {
876    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
877        write!(f, "Cannot call `Patcher::apply` while another `ApplyResult` is alive, drop() it or consume it by calling `ApplyResult::romdata()`.")
878    }
879}
880
881impl Patcher {
882    /// Creates a new Patcher with default options.
883    pub fn new() -> Self {
884        Self { options: None }
885    }
886    /// Adds an option to the patch operation.
887    pub fn option(&mut self, option: PatchOption) {
888        self.options = Some(self.options.take().unwrap_or_default().option(option));
889    }
890    /// Replaces the options of the patch operation.
891    pub fn options(&mut self, options: AdvancedPatchOptions) {
892        self.options = Some(options);
893    }
894    /// Applies the patch to the ROM data
895    ///
896    /// Multiple patch operations cannot be done at the same time, this function will return an error if another [`ApplyResult`] is alive.
897    ///
898    /// See [`ConcurrentApplyError`] for more information.
899    ///
900    /// remarks: This function uses the global lock.
901    #[cfg(feature = "thread-safe")]
902    pub fn apply<'a, T: Into<String>>(
903        self,
904        rom: RomData,
905        patch: T,
906    ) -> Result<ApplyResult<'a>, ConcurrentApplyError> {
907        if APPLYRESULT_ONCE_ALIVE
908            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
909            .is_err()
910        {
911            return Err(ConcurrentApplyError);
912        }
913
914        let guard = global_asar_lock().lock();
915        let (romdata, result) =
916            patching::patch_ex_basic(rom, patch.into(), self.options.unwrap_or_default());
917
918        Ok(ApplyResult {
919            romdata,
920            success: result,
921            _guard: guard,
922        })
923    }
924
925    /// Applies the patch to the ROM data
926    ///
927    /// Multiple patch operations cannot be done at the same time, this function will return an error if another [`ApplyResult`] is alive.
928    ///
929    /// See [`ConcurrentApplyError`] for more information.
930    #[cfg(not(feature = "thread-safe"))]
931    pub fn apply<'a, T: Into<String>>(
932        self,
933        rom: RomData,
934        patch: T,
935    ) -> Result<ApplyResult<'a>, ConcurrentApplyError> {
936        if APPLYRESULT_ONCE_ALIVE
937            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
938            .is_err()
939        {
940            return Err(ConcurrentApplyError);
941        }
942
943        let (romdata, result) =
944            patching::patch_ex_basic(rom, patch.into(), self.options.unwrap_or_default());
945
946        Ok(ApplyResult {
947            romdata,
948            success: result,
949            _marker: std::marker::PhantomData,
950        })
951    }
952}
953
954impl Default for Patcher {
955    fn default() -> Self {
956        Self::new()
957    }
958}
959
960impl ApplyResult<'_> {
961    /// Returns whether the patch operation was successful or not.
962    pub fn success(&self) -> bool {
963        self.success
964    }
965
966    /// Returns the warnings from the apply operation.
967
968    pub fn warnings(&self) -> Vec<WarningData> {
969        patching::warnings()
970    }
971
972    /// Returns the errors from the apply operation.
973    ///
974    /// See the notes in the [`ApplyResult`] type for more information.
975    pub fn errors(&self) -> Vec<ErrorData> {
976        patching::errors()
977    }
978
979    /// Returns the prints from the apply operation.        
980    pub fn prints(&self) -> Vec<String> {
981        patching::prints()
982    }
983
984    /// Returns the labels from the apply operation.        
985    ///
986    /// See the notes in the [`ApplyResult`] type for more information.  
987    pub fn labels(&self) -> Vec<Label> {
988        patching::labels()
989    }
990
991    /// Returns the value of a label from the apply operation.
992    ///
993    /// See the notes in the [`ApplyResult`] type for more information.
994    pub fn label_value(&self, name: &str) -> Option<i32> {
995        patching::label_value(name)
996    }
997
998    /// Returns the value of a define from the apply operation.
999    ///
1000    /// See the notes in the [`ApplyResult`] type for more information.
1001    pub fn define(&self, name: &str) -> Option<String> {
1002        patching::define(name)
1003    }
1004
1005    /// Returns the defines from the apply operation.
1006    ///
1007    /// See the notes in the [`ApplyResult`] type for more information.
1008    pub fn defines(&self) -> Vec<Define> {
1009        patching::defines()
1010    }
1011
1012    /// Returns the written blocks from the apply operation.
1013    ///
1014    /// See the notes in the [`ApplyResult`] type for more information.
1015    pub fn written_blocks(&self) -> Vec<WrittenBlock> {
1016        patching::written_blocks()
1017    }
1018
1019    /// Returns the mapper type from the apply operation.
1020    ///
1021    /// See the notes in the [`ApplyResult`] type for more information.
1022    pub fn mapper_type(&self) -> Option<MapperType> {
1023        patching::mapper_type()
1024    }
1025
1026    /// Returns the symbols file from the apply operation.
1027    ///
1028    /// See the notes in the [`ApplyResult`] type for more information.
1029    pub fn symbols_file(&self, symboltype: SymbolType) -> Option<String> {
1030        patching::symbols_file(symboltype)
1031    }
1032
1033    /// Consumes the ApplyResult and returns the ROM data.
1034    ///
1035    /// This will reset Asar, clearing all the errors, warnings and prints.
1036    ///
1037    /// Calling this method will allow another patch operation to be done with the [`Patcher::apply`] method.
1038    pub fn romdata(mut self) -> RomData {
1039        let romdata = std::mem::take(&mut self.romdata);
1040        APPLYRESULT_ONCE_ALIVE.store(false, Ordering::SeqCst);
1041        romdata
1042    }
1043}
1044
1045impl Drop for ApplyResult<'_> {
1046    fn drop(&mut self) {
1047        patching::reset();
1048    }
1049}