Skip to main content

idalib/
idb.rs

1use std::ffi::CString;
2use std::marker::PhantomData;
3use std::mem::MaybeUninit;
4#[cfg(not(feature = "plugin"))]
5use std::path::{Path, PathBuf};
6
7use crate::ffi::BADADDR;
8use crate::ffi::bytes::*;
9use crate::ffi::comments::{append_cmt, idalib_get_cmt, set_cmt};
10use crate::ffi::conversions::idalib_ea2str;
11use crate::ffi::entry::{get_entry, get_entry_ordinal, get_entry_qty};
12use crate::ffi::func::{
13    add_func, del_func, get_func, get_func_qty, getn_func, idalib_get_func_cmt, idalib_set_func_cmt,
14};
15#[cfg(not(feature = "plugin"))]
16use crate::ffi::hexrays::term_hexrays_plugin;
17use crate::ffi::hexrays::{change_hexrays_config, decompile_function, init_hexrays_plugin};
18#[cfg(not(feature = "plugin"))]
19use crate::ffi::ida::{auto_wait, close_database_with, open_database_quiet};
20use crate::ffi::ida::{make_signatures, set_screen_ea};
21use crate::ffi::insn::decode;
22use crate::ffi::plugin::find_plugin;
23use crate::ffi::processor::get_ph;
24use crate::ffi::search::{idalib_find_defined, idalib_find_imm, idalib_find_text};
25use crate::ffi::segment::{get_segm_by_name, get_segm_qty, getnseg, getseg};
26use crate::ffi::typeinf::{idalib_format_cfunc_decls, idalib_format_decls};
27use crate::ffi::util::{get_imagebase, is_align_insn, next_head, prev_head, str2reg};
28use crate::ffi::xref::{xrefblk_t, xrefblk_t_first_from, xrefblk_t_first_to};
29
30use crate::bookmarks::Bookmarks;
31use crate::decompiler::CFunction;
32use crate::func::{Function, FunctionId};
33use crate::insn::{Insn, Register};
34use crate::meta::{Metadata, MetadataMut};
35use crate::name::NameList;
36use crate::plugin::Plugin;
37use crate::processor::Processor;
38use crate::segment::{Segment, SegmentId};
39use crate::strings::StringList;
40use crate::typeinf::FormatDeclsOptions;
41use crate::xref::{XRef, XRefQuery};
42use crate::{Address, AddressFlags, IDAError, IDARuntimeHandle, prepare_library};
43
44pub struct IDB {
45    #[cfg(not(feature = "plugin"))]
46    path: PathBuf,
47    #[cfg(not(feature = "plugin"))]
48    save: bool,
49    decompiler: bool,
50    _guard: IDARuntimeHandle,
51    _marker: PhantomData<*const ()>,
52}
53
54#[derive(Debug, Clone)]
55#[cfg(not(feature = "plugin"))]
56pub struct IDBOpenOptions {
57    idb: Option<PathBuf>,
58    ftype: Option<String>,
59
60    save: bool,
61    auto_analyse: bool,
62}
63
64#[cfg(not(feature = "plugin"))]
65impl Default for IDBOpenOptions {
66    fn default() -> Self {
67        Self {
68            idb: None,
69            ftype: None,
70            save: false,
71            auto_analyse: true,
72        }
73    }
74}
75
76#[cfg(not(feature = "plugin"))]
77impl IDBOpenOptions {
78    pub fn new() -> Self {
79        Self::default()
80    }
81
82    pub fn idb(&mut self, path: impl AsRef<Path>) -> &mut Self {
83        self.idb = Some(path.as_ref().to_owned());
84        self
85    }
86
87    pub fn save(&mut self, save: bool) -> &mut Self {
88        self.save = save;
89        self
90    }
91
92    pub fn file_type(&mut self, ftype: impl AsRef<str>) -> &mut Self {
93        self.ftype = Some(ftype.as_ref().to_owned());
94        self
95    }
96
97    pub fn auto_analyse(&mut self, auto_analyse: bool) -> &mut Self {
98        self.auto_analyse = auto_analyse;
99        self
100    }
101
102    pub fn open(&self, path: impl AsRef<Path>) -> Result<IDB, IDAError> {
103        let mut args = Vec::new();
104
105        if let Some(ftype) = self.ftype.as_ref() {
106            args.push(format!("-T{ftype}"));
107        }
108
109        if let Some(idb_path) = self.idb.as_ref() {
110            args.push("-c".to_owned());
111            args.push(format!("-o{}", idb_path.display()));
112        }
113
114        IDB::open_full_with(path, self.auto_analyse, self.save, &args)
115    }
116}
117
118impl IDB {
119    #[cfg(not(feature = "plugin"))]
120    pub fn open(path: impl AsRef<Path>) -> Result<Self, IDAError> {
121        Self::open_with(path, true, false)
122    }
123
124    #[cfg(not(feature = "plugin"))]
125    pub fn open_with(
126        path: impl AsRef<Path>,
127        auto_analyse: bool,
128        save: bool,
129    ) -> Result<Self, IDAError> {
130        Self::open_full_with(path, auto_analyse, save, &[] as &[&str])
131    }
132
133    #[cfg(not(feature = "plugin"))]
134    fn open_full_with(
135        path: impl AsRef<Path>,
136        auto_analyse: bool,
137        save: bool,
138        args: &[impl AsRef<str>],
139    ) -> Result<Self, IDAError> {
140        let _guard = prepare_library();
141        let path = path.as_ref();
142
143        if !path.exists() || !path.is_file() {
144            return Err(IDAError::not_found(path));
145        }
146
147        open_database_quiet(path, auto_analyse, args)?;
148
149        let decompiler = unsafe { init_hexrays_plugin(0.into()) };
150
151        Ok(Self {
152            path: path.to_owned(),
153            save,
154            decompiler,
155            _guard,
156            _marker: PhantomData,
157        })
158    }
159
160    #[cfg(feature = "plugin")]
161    pub fn current() -> Result<Self, IDAError> {
162        let _guard = prepare_library();
163
164        Ok(Self {
165            decompiler: unsafe { init_hexrays_plugin(0.into()) },
166            _guard,
167            _marker: PhantomData,
168        })
169    }
170
171    #[cfg(not(feature = "plugin"))]
172    pub fn path(&self) -> &Path {
173        &self.path
174    }
175
176    #[cfg(not(feature = "plugin"))]
177    pub fn save_on_close(&mut self, status: bool) {
178        self.save = status;
179    }
180
181    #[cfg(not(feature = "plugin"))]
182    pub fn auto_wait(&mut self) -> bool {
183        unsafe { auto_wait() }
184    }
185
186    pub fn set_screen_address(&mut self, ea: Address) {
187        set_screen_ea(ea.into());
188    }
189
190    pub fn make_signatures(&mut self, only_pat: bool) -> Result<(), IDAError> {
191        make_signatures(only_pat)
192    }
193
194    pub fn format_decls(&self) -> Result<String, IDAError> {
195        // `INCL_DEPS` pulls in definitions reachable by value, while `DEF_FWD`
196        // covers types only reachable through pointers.
197        self.format_decls_with(FormatDeclsOptions::INCL_DEPS | FormatDeclsOptions::DEF_FWD)
198    }
199
200    pub fn format_decls_with(&self, options: FormatDeclsOptions) -> Result<String, IDAError> {
201        unsafe { idalib_format_decls(options.bits()) }.map_err(IDAError::ffi)
202    }
203
204    pub fn format_cfunc_decls<'a>(&'a self, cfunc: &CFunction<'a>) -> Result<String, IDAError> {
205        // `INCL_DEPS` pulls in definitions reachable by value, while `DEF_FWD`
206        // covers types only reachable through pointers.
207        self.format_cfunc_decls_with(
208            cfunc,
209            FormatDeclsOptions::INCL_DEPS | FormatDeclsOptions::DEF_FWD,
210        )
211    }
212
213    pub fn format_cfunc_decls_with<'a>(
214        &'a self,
215        cfunc: &CFunction<'a>,
216        options: FormatDeclsOptions,
217    ) -> Result<String, IDAError> {
218        unsafe { idalib_format_cfunc_decls(cfunc.as_ptr(), options.bits()) }.map_err(IDAError::ffi)
219    }
220
221    pub fn decompiler_available(&self) -> bool {
222        self.decompiler
223    }
224
225    pub fn modify_decompiler_config(&mut self, directive: impl AsRef<str>) -> Result<(), IDAError> {
226        if !self.decompiler {
227            return Err(IDAError::ffi_with("no decompiler available"));
228        }
229
230        let directive = directive.as_ref();
231        let s = CString::new(directive).map_err(IDAError::ffi)?;
232
233        if unsafe { change_hexrays_config(s.as_ptr()) } {
234            Ok(())
235        } else {
236            Err(IDAError::ffi_with(format!(
237                "failed to apply hexrays config directive `{directive}`"
238            )))
239        }
240    }
241
242    pub fn meta(&self) -> Metadata<'_> {
243        Metadata::new()
244    }
245
246    pub fn meta_mut(&mut self) -> MetadataMut<'_> {
247        MetadataMut::new()
248    }
249
250    pub fn processor(&self) -> Processor<'_> {
251        let ptr = unsafe { get_ph() };
252        Processor::from_ptr(ptr)
253    }
254
255    pub fn entries(&self) -> EntryPointIter<'_> {
256        let limit = unsafe { get_entry_qty() };
257        EntryPointIter {
258            index: 0,
259            limit,
260            _marker: PhantomData,
261        }
262    }
263
264    pub fn function_at(&self, ea: Address) -> Option<Function<'_>> {
265        let ptr = unsafe { get_func(ea.into()) };
266
267        if ptr.is_null() {
268            return None;
269        }
270
271        Some(Function::from_ptr(ptr))
272    }
273
274    pub fn add_function(&mut self, start: Address) -> Result<(), IDAError> {
275        self.add_function_with(start, BADADDR.into())
276    }
277
278    pub fn add_function_with(&mut self, start: Address, end: Address) -> Result<(), IDAError> {
279        if unsafe { add_func(start.into(), end.into()) } {
280            Ok(())
281        } else {
282            Err(IDAError::ffi_with(format!(
283                "failed to add function at {start:#x}"
284            )))
285        }
286    }
287
288    pub fn remove_function(&mut self, start: Address) -> Result<(), IDAError> {
289        if unsafe { del_func(start.into()) } {
290            Ok(())
291        } else {
292            Err(IDAError::ffi_with(format!(
293                "failed to delete function at {start:#x}"
294            )))
295        }
296    }
297
298    pub fn next_head(&self, ea: Address) -> Option<Address> {
299        self.next_head_with(ea, BADADDR.into())
300    }
301
302    pub fn next_head_with(&self, ea: Address, max_ea: Address) -> Option<Address> {
303        let next = unsafe { next_head(ea.into(), max_ea.into()) };
304        if next == BADADDR {
305            None
306        } else {
307            Some(next.into())
308        }
309    }
310
311    pub fn prev_head(&self, ea: Address) -> Option<Address> {
312        self.prev_head_with(ea, 0)
313    }
314
315    pub fn prev_head_with(&self, ea: Address, min_ea: Address) -> Option<Address> {
316        let prev = unsafe { prev_head(ea.into(), min_ea.into()) };
317        if prev == BADADDR {
318            None
319        } else {
320            Some(prev.into())
321        }
322    }
323
324    pub fn insn_at(&self, ea: Address) -> Option<Insn> {
325        let insn = decode(ea.into())?;
326        Some(Insn::from_repr(insn))
327    }
328
329    pub fn decompile<'a>(&'a self, f: &Function<'a>) -> Result<CFunction<'a>, IDAError> {
330        self.decompile_with(f, false)
331    }
332
333    pub fn decompile_with<'a>(
334        &'a self,
335        f: &Function<'a>,
336        all_blocks: bool,
337    ) -> Result<CFunction<'a>, IDAError> {
338        if !self.decompiler {
339            return Err(IDAError::ffi_with("no decompiler available"));
340        }
341
342        Ok(unsafe {
343            decompile_function(f.start_address().into(), all_blocks)
344                .map(|f| CFunction::new(f).expect("null pointer checked"))?
345        })
346    }
347
348    pub fn function_by_id(&self, id: FunctionId) -> Option<Function<'_>> {
349        let ptr = unsafe { getn_func(id) };
350
351        if ptr.is_null() {
352            return None;
353        }
354
355        Some(Function::from_ptr(ptr))
356    }
357
358    pub fn functions<'a>(&'a self) -> impl Iterator<Item = (FunctionId, Function<'a>)> + 'a {
359        (0..self.function_count()).filter_map(|id| self.function_by_id(id).map(|f| (id, f)))
360    }
361
362    pub fn function_count(&self) -> usize {
363        unsafe { get_func_qty() }
364    }
365
366    pub fn segment_at(&self, ea: Address) -> Option<Segment<'_>> {
367        let ptr = unsafe { getseg(ea.into()) };
368
369        if ptr.is_null() {
370            return None;
371        }
372
373        Some(Segment::from_ptr(ptr))
374    }
375
376    pub fn segment_by_id(&self, id: SegmentId) -> Option<Segment<'_>> {
377        let ptr = unsafe { getnseg((id as i32).into()) };
378
379        if ptr.is_null() {
380            return None;
381        }
382
383        Some(Segment::from_ptr(ptr))
384    }
385
386    pub fn segment_by_name(&self, name: impl AsRef<str>) -> Option<Segment<'_>> {
387        let s = CString::new(name.as_ref()).ok()?;
388        let ptr = unsafe { get_segm_by_name(s.as_ptr()) };
389
390        if ptr.is_null() {
391            return None;
392        }
393
394        Some(Segment::from_ptr(ptr))
395    }
396
397    pub fn segments<'a>(&'a self) -> impl Iterator<Item = (SegmentId, Segment<'a>)> + 'a {
398        (0..self.segment_count()).filter_map(|id| self.segment_by_id(id).map(|s| (id, s)))
399    }
400
401    pub fn segment_count(&self) -> usize {
402        unsafe { get_segm_qty().0 as _ }
403    }
404
405    pub fn register_by_name(&self, name: impl AsRef<str>) -> Option<Register> {
406        let s = CString::new(name.as_ref()).ok()?;
407        let id = unsafe { str2reg(s.as_ptr()).0 };
408
409        if id == -1 { None } else { Some(id as _) }
410    }
411
412    pub fn insn_alignment_at(&self, ea: Address) -> Option<usize> {
413        let align = unsafe { is_align_insn(ea.into()).0 };
414        if align == 0 { None } else { Some(align as _) }
415    }
416
417    pub fn first_xref_from(&self, ea: Address, flags: XRefQuery) -> Option<XRef<'_>> {
418        let mut xref = MaybeUninit::<xrefblk_t>::zeroed();
419        let found =
420            unsafe { xrefblk_t_first_from(xref.as_mut_ptr(), ea.into(), flags.bits().into()) };
421
422        if found {
423            Some(XRef::from_repr(unsafe { xref.assume_init() }))
424        } else {
425            None
426        }
427    }
428
429    pub fn first_xref_to(&self, ea: Address, flags: XRefQuery) -> Option<XRef<'_>> {
430        let mut xref = MaybeUninit::<xrefblk_t>::zeroed();
431        let found =
432            unsafe { xrefblk_t_first_to(xref.as_mut_ptr(), ea.into(), flags.bits().into()) };
433
434        if found {
435            Some(XRef::from_repr(unsafe { xref.assume_init() }))
436        } else {
437            None
438        }
439    }
440
441    pub fn get_cmt(&self, ea: Address) -> Option<String> {
442        self.get_cmt_with(ea, false)
443    }
444
445    pub fn get_cmt_with(&self, ea: Address, rptble: bool) -> Option<String> {
446        let s = unsafe { idalib_get_cmt(ea.into(), rptble) };
447
448        if s.is_empty() { None } else { Some(s) }
449    }
450
451    pub fn get_func_cmt(&self, ea: Address) -> Option<String> {
452        self.get_func_cmt_with(ea, false)
453    }
454
455    pub fn get_func_cmt_with(&self, ea: Address, rptble: bool) -> Option<String> {
456        let f = self.function_at(ea)?;
457        let s = unsafe { idalib_get_func_cmt(f.as_ptr() as _, rptble) }.ok()?;
458
459        if s.is_empty() { None } else { Some(s) }
460    }
461
462    pub fn set_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
463        self.set_cmt_with(ea, comm, false)
464    }
465
466    pub fn set_cmt_with(
467        &self,
468        ea: Address,
469        comm: impl AsRef<str>,
470        rptble: bool,
471    ) -> Result<(), IDAError> {
472        let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
473        if unsafe { set_cmt(ea.into(), s.as_ptr(), rptble) } {
474            Ok(())
475        } else {
476            Err(IDAError::ffi_with(format!(
477                "failed to set comment at {ea:#x}"
478            )))
479        }
480    }
481
482    pub fn set_func_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
483        self.set_func_cmt_with(ea, comm, false)
484    }
485
486    pub fn set_func_cmt_with(
487        &self,
488        ea: Address,
489        comm: impl AsRef<str>,
490        rptble: bool,
491    ) -> Result<(), IDAError> {
492        let f = self
493            .function_at(ea)
494            .ok_or_else(|| IDAError::ffi_with(format!("no function found at address {ea:#x}")))?;
495        let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
496        if unsafe { idalib_set_func_cmt(f.as_ptr() as _, s.as_ptr(), rptble) } {
497            Ok(())
498        } else {
499            Err(IDAError::ffi_with(format!(
500                "failed to set function comment at {ea:#x}"
501            )))
502        }
503    }
504
505    pub fn append_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
506        self.append_cmt_with(ea, comm, false)
507    }
508
509    pub fn append_cmt_with(
510        &self,
511        ea: Address,
512        comm: impl AsRef<str>,
513        rptble: bool,
514    ) -> Result<(), IDAError> {
515        let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
516        if unsafe { append_cmt(ea.into(), s.as_ptr(), rptble) } {
517            Ok(())
518        } else {
519            Err(IDAError::ffi_with(format!(
520                "failed to append comment at {ea:#x}"
521            )))
522        }
523    }
524
525    pub fn remove_cmt(&self, ea: Address) -> Result<(), IDAError> {
526        self.remove_cmt_with(ea, false)
527    }
528
529    pub fn remove_cmt_with(&self, ea: Address, rptble: bool) -> Result<(), IDAError> {
530        if unsafe { set_cmt(ea.into(), c"".as_ptr(), rptble) } {
531            Ok(())
532        } else {
533            Err(IDAError::ffi_with(format!(
534                "failed to remove comment at {ea:#x}"
535            )))
536        }
537    }
538
539    pub fn remove_func_cmt(&self, ea: Address) -> Result<(), IDAError> {
540        self.remove_func_cmt_with(ea, false)
541    }
542
543    pub fn remove_func_cmt_with(&self, ea: Address, rptble: bool) -> Result<(), IDAError> {
544        let f = self
545            .function_at(ea)
546            .ok_or_else(|| IDAError::ffi_with(format!("no function found at address {ea:#x}")))?;
547        if unsafe { idalib_set_func_cmt(f.as_ptr(), c"".as_ptr(), rptble) } {
548            Ok(())
549        } else {
550            Err(IDAError::ffi_with(format!(
551                "failed to remove comment at {ea:#x}"
552            )))
553        }
554    }
555
556    pub fn bookmarks(&self) -> Bookmarks<'_> {
557        Bookmarks::new(self)
558    }
559
560    pub fn find_text(&self, start_ea: Address, text: impl AsRef<str>) -> Option<Address> {
561        let s = CString::new(text.as_ref()).ok()?;
562        let addr = unsafe { idalib_find_text(start_ea.into(), s.as_ptr()) };
563        if addr == BADADDR {
564            None
565        } else {
566            Some(addr.into())
567        }
568    }
569
570    pub fn find_text_iter<'a, T>(&'a self, text: T) -> impl Iterator<Item = Address> + 'a
571    where
572        T: AsRef<str> + 'a,
573    {
574        let mut cur = Some(0u64);
575        std::iter::from_fn(move || {
576            let found = self.find_text(cur?, text.as_ref())?;
577            cur = self.find_defined(found);
578            Some(found)
579        })
580    }
581
582    pub fn find_imm(&self, start_ea: Address, imm: u32) -> Option<Address> {
583        let addr = unsafe { idalib_find_imm(start_ea.into(), imm.into()) };
584        if addr == BADADDR {
585            None
586        } else {
587            Some(addr.into())
588        }
589    }
590
591    pub fn find_imm_iter<'a>(&'a self, imm: u32) -> impl Iterator<Item = Address> + 'a {
592        let mut cur = 0u64;
593        std::iter::from_fn(move || {
594            cur = self.find_imm(cur, imm)?;
595            Some(cur)
596        })
597    }
598
599    pub fn find_defined(&self, start_ea: Address) -> Option<Address> {
600        let addr = unsafe { idalib_find_defined(start_ea.into()) };
601        if addr == BADADDR {
602            None
603        } else {
604            Some(addr.into())
605        }
606    }
607
608    pub fn strings(&self) -> StringList<'_> {
609        StringList::new(self)
610    }
611
612    pub fn names(&self) -> crate::name::NameList<'_> {
613        NameList::new(self)
614    }
615
616    pub fn address_to_string(&self, ea: Address) -> Option<String> {
617        let s = unsafe { idalib_ea2str(ea.into()) };
618
619        if s.is_empty() { None } else { Some(s) }
620    }
621
622    pub fn flags_at(&self, ea: Address) -> AddressFlags<'_> {
623        AddressFlags::new(unsafe { get_flags(ea.into()) })
624    }
625
626    pub fn image_base(&self) -> Address {
627        unsafe { get_imagebase() }.into()
628    }
629
630    pub fn get_byte(&self, ea: Address) -> u8 {
631        unsafe { idalib_get_byte(ea.into()) }
632    }
633
634    pub fn get_word(&self, ea: Address) -> u16 {
635        unsafe { idalib_get_word(ea.into()) }
636    }
637
638    pub fn get_dword(&self, ea: Address) -> u32 {
639        unsafe { idalib_get_dword(ea.into()) }
640    }
641
642    pub fn get_qword(&self, ea: Address) -> u64 {
643        unsafe { idalib_get_qword(ea.into()) }
644    }
645
646    pub fn get_bytes(&self, ea: Address, size: usize) -> Vec<u8> {
647        let mut buf = Vec::with_capacity(size);
648
649        let Ok(new_len) = (unsafe { idalib_get_bytes(ea.into(), &mut buf) }) else {
650            return Vec::with_capacity(0);
651        };
652
653        unsafe {
654            buf.set_len(new_len);
655        }
656
657        buf
658    }
659
660    pub fn find_plugin(
661        &self,
662        name: impl AsRef<str>,
663        load_if_needed: bool,
664    ) -> Result<Plugin<'_>, IDAError> {
665        let plugin = CString::new(name.as_ref()).map_err(IDAError::ffi)?;
666        let ptr = unsafe { find_plugin(plugin.as_ptr(), load_if_needed) };
667
668        if ptr.is_null() {
669            Err(IDAError::ffi_with(format!(
670                "failed to load {} plugin",
671                name.as_ref()
672            )))
673        } else {
674            Ok(Plugin::from_ptr(ptr as *const _))
675        }
676    }
677
678    pub fn load_plugin(&self, name: impl AsRef<str>) -> Result<Plugin<'_>, IDAError> {
679        self.find_plugin(name, true)
680    }
681}
682
683#[cfg(not(feature = "plugin"))]
684impl Drop for IDB {
685    fn drop(&mut self) {
686        if self.decompiler {
687            unsafe {
688                term_hexrays_plugin();
689            }
690        }
691        close_database_with(self.save);
692    }
693}
694
695pub struct EntryPointIter<'a> {
696    index: usize,
697    limit: usize,
698    _marker: PhantomData<&'a IDB>,
699}
700
701impl<'a> Iterator for EntryPointIter<'a> {
702    type Item = Address;
703
704    fn next(&mut self) -> Option<Self::Item> {
705        while self.index < self.limit {
706            let index = self.index;
707            self.index += 1;
708
709            let ordinal = unsafe { get_entry_ordinal(index) };
710            let addr = unsafe { get_entry(ordinal) };
711
712            if addr != BADADDR {
713                return Some(addr.into());
714            }
715        }
716
717        None
718    }
719
720    fn size_hint(&self) -> (usize, Option<usize>) {
721        let lim = self.limit - self.index;
722        (0, Some(lim))
723    }
724}