use crate::io_uri;
use crate::sys::{self, RConfig, RCore, RCorePluginSession};
use std::ffi::{CStr, CString};
use std::path::PathBuf;
#[derive(Clone, Copy)]
pub struct Core {
ptr: *mut RCore,
}
impl Core {
pub unsafe fn from_ptr(ptr: *mut RCore) -> Option<Self> {
(!ptr.is_null()).then_some(Self { ptr })
}
pub unsafe fn from_session(session: *mut RCorePluginSession) -> Option<Self> {
let session = unsafe { session.as_ref()? };
unsafe { Self::from_ptr(session.core) }
}
pub const fn as_ptr(self) -> *mut RCore {
self.ptr
}
fn config(self) -> *mut RConfig {
unsafe { sys::r2_rust_core_config(self.ptr) }
}
pub fn cmd(self, command: &str) {
let command = c_string(command);
unsafe {
sys::r_core_cmd0(self.ptr, command.as_ptr());
}
}
pub fn cmd_lines(self, commands: &str) {
let commands = c_string(commands);
unsafe {
sys::r_core_cmd_lines(self.ptr, commands.as_ptr());
}
}
pub fn cmd_str(self, command: &str) -> String {
let command = c_string(command);
unsafe {
let raw = sys::r_core_cmd_str(self.ptr, command.as_ptr());
if raw.is_null() {
return String::new();
}
let output = CStr::from_ptr(raw).to_string_lossy().trim().to_owned();
sys::free(raw.cast());
output
}
}
pub fn echo(self, message: &str) {
let escaped = message.replace(
[
'\'', '"', ';', '|', '@', '`', '[', ']', '*', '~', '?', '(', ')',
],
" ",
);
let escaped = escaped.split_whitespace().collect::<Vec<_>>().join(" ");
if !escaped.is_empty() {
self.cmd(&format!("?e {escaped}"));
}
}
pub fn current_file(self) -> Option<PathBuf> {
let opened = first_nonempty_line(&self.cmd_str("o."))?;
let opened = std::path::Path::new(&opened);
if let Some(path) = io_uri::on_disk_path(opened) {
return Some(path);
}
if let Some(path) = io_uri::ptrace_exe(opened) {
return Some(path);
}
if let Some(path) = first_nonempty_line(&self.cmd_str("i~^file[1]")) {
if is_plausible_path(&path) {
return Some(path.into());
}
}
if let Some(path) = file_info_path(&self.cmd_str("i~^file")) {
if is_plausible_path(&path) {
return Some(path.into());
}
}
None
}
pub fn baddr(self) -> u64 {
parse_r2_u64(&self.cmd_str("e bin.baddr")).unwrap_or(0)
}
pub fn cfg_bool(self, key: &str, default: bool) -> bool {
let config = self.config();
if config.is_null() {
return default;
}
let key = c_string(key);
unsafe {
if sys::r_config_node_get(config, key.as_ptr()).is_null() {
default
} else {
sys::r_config_get_b(config, key.as_ptr())
}
}
}
pub fn set_cfg_bool(self, key: &str, value: bool, description: &str) {
let config = self.config();
if config.is_null() {
return;
}
let key = c_string(key);
unsafe {
sys::r_config_lock(config, false);
let node = sys::r_config_set_b(config, key.as_ptr(), value);
if !node.is_null() && !description.is_empty() {
let description = c_string(description);
sys::r_config_desc(config, key.as_ptr(), description.as_ptr());
}
sys::r_config_lock(config, true);
}
}
pub fn cfg_str(self, key: &str) -> String {
let config = self.config();
if config.is_null() {
return String::new();
}
let key = c_string(key);
unsafe {
let value = sys::r_config_get(config, key.as_ptr());
if value.is_null() {
String::new()
} else {
CStr::from_ptr(value).to_string_lossy().into_owned()
}
}
}
pub fn set_cfg_str(self, key: &str, value: &str) {
let config = self.config();
if config.is_null() {
return;
}
let key = c_string(key);
let value = c_string(value);
unsafe {
sys::r_config_set(config, key.as_ptr(), value.as_ptr());
}
}
}
pub fn parse_r2_u64(value: &str) -> Option<u64> {
let value = value.trim();
if value.is_empty() {
return None;
}
if let Some(hex) = value
.strip_prefix("0x")
.or_else(|| value.strip_prefix("0X"))
{
u64::from_str_radix(hex, 16).ok()
} else {
value.parse().ok()
}
}
fn c_string(value: &str) -> CString {
CString::new(value.replace('\0', "")).expect("interior NULs were removed")
}
fn first_nonempty_line(value: &str) -> Option<String> {
value
.lines()
.map(str::trim)
.find(|line| !line.is_empty())
.map(str::to_owned)
}
fn is_plausible_path(value: &str) -> bool {
let value = value.trim();
!value.is_empty()
&& !value.starts_with("ERROR")
&& !value.starts_with("WARN")
&& (value.starts_with('/') || value.starts_with('.') || value.contains('/'))
}
fn file_info_path(value: &str) -> Option<String> {
value.lines().find_map(|line| {
let rest = line.trim().strip_prefix("file")?.trim();
(!rest.is_empty()).then(|| rest.to_owned())
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_radare2_numbers() {
assert_eq!(parse_r2_u64("0x400000"), Some(0x400000));
assert_eq!(parse_r2_u64("4194304"), Some(0x400000));
assert_eq!(parse_r2_u64(""), None);
}
#[test]
fn extracts_file_info_paths() {
assert_eq!(
file_info_path("file /tmp/hello\ntype EXEC"),
Some("/tmp/hello".into())
);
assert_eq!(
first_nonempty_line("/tmp/hello\nHello world!"),
Some("/tmp/hello".into())
);
assert!(is_plausible_path("/tmp/ip-server"));
assert!(!is_plausible_path("ERROR: No file selected"));
assert!(!is_plausible_path("Hello world!"));
}
}