String Conversion Revamp
- String/&str to C char logic is revamped
This commit is contained in:
13
src/archive.rs
Executable file → Normal file
13
src/archive.rs
Executable file → Normal file
@@ -1,9 +1,10 @@
|
||||
use crate::{
|
||||
debug::{debug_objects, find_error_type, merge_members},
|
||||
debug::{debug_objects, merge_members},
|
||||
parse_objects,
|
||||
structs::{CharVec, Debugging, ParsingError, StringPtr, ULDDObj, ULDDObjResult},
|
||||
structs::{CharVec, Debugging, ParsingError, ULDDObj, ULDDObjResult},
|
||||
};
|
||||
use goblin::archive::Archive;
|
||||
use crate::impls::{ErrorToInt, StringToCString};
|
||||
|
||||
pub(crate) fn parse_archive<'a>(
|
||||
file_name: &'a str,
|
||||
@@ -25,13 +26,13 @@ pub(crate) fn parse_archive<'a>(
|
||||
error)).print(debugging);
|
||||
return objects.push(ULDDObjResult {
|
||||
error: ParsingError {
|
||||
code: find_error_type(&error),
|
||||
explanation: StringPtr::from(error.to_string()).0,
|
||||
code: error.to_int(),
|
||||
explanation: error.to_c_string(),
|
||||
},
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names),
|
||||
file_type: StringPtr::from("Archive").0,
|
||||
file_type: "Archive".to_c_string(),
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
|
||||
14
src/coff.rs
Executable file → Normal file
14
src/coff.rs
Executable file → Normal file
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
debug::debug_objects,
|
||||
structs::{CharVec, ParsingError, StringPtr, ULDDObj, ULDDObjResult},
|
||||
structs::{CharVec, ParsingError, ULDDObj, ULDDObjResult},
|
||||
types::PE_ARCH,
|
||||
};
|
||||
use goblin::pe::{
|
||||
@@ -8,6 +8,8 @@ use goblin::pe::{
|
||||
Coff,
|
||||
};
|
||||
use std::ptr::null_mut;
|
||||
use crate::debug::option_to_c_string;
|
||||
use crate::impls::StringToCString;
|
||||
|
||||
pub(crate) fn parse_coff(
|
||||
file_name: &str,
|
||||
@@ -19,17 +21,17 @@ pub(crate) fn parse_coff(
|
||||
let is_64 = coff.header.characteristics & IMAGE_FILE_32BIT_MACHINE != IMAGE_FILE_32BIT_MACHINE;
|
||||
let is_stripped =
|
||||
coff.header.characteristics & IMAGE_FILE_DEBUG_STRIPPED == IMAGE_FILE_DEBUG_STRIPPED;
|
||||
let cpu_type = StringPtr::from(PE_ARCH.get(&coff.header.machine)).0;
|
||||
let cpu_type = option_to_c_string(PE_ARCH.get(&coff.header.machine));
|
||||
debug_objects(file_name, member_names, "a COFF binary", debugging);
|
||||
ULDDObjResult {
|
||||
error: ParsingError::default(),
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names),
|
||||
executable_format: StringPtr::from("COFF").0,
|
||||
executable_format: "COFF".to_c_string(),
|
||||
is_64,
|
||||
os_type: StringPtr::from("Windows").0,
|
||||
file_type: StringPtr::from("Windows object file").0,
|
||||
os_type: "Windows".to_c_string(),
|
||||
file_type: "Windows object file".to_c_string(),
|
||||
is_stripped,
|
||||
cpu_type,
|
||||
cpu_subtype: null_mut(),
|
||||
|
||||
17
src/debug.rs
17
src/debug.rs
@@ -1,16 +1,11 @@
|
||||
use goblin::error::Error as ObjectError;
|
||||
|
||||
use std::ffi::c_char;
|
||||
use std::fmt::Display;
|
||||
use std::ptr::null_mut;
|
||||
use crate::impls::StringToCString;
|
||||
use crate::structs::Debugging;
|
||||
|
||||
pub(crate) fn find_error_type(error: &ObjectError) -> i64 {
|
||||
match error {
|
||||
ObjectError::Malformed(_) => -1,
|
||||
ObjectError::BadMagic(_) => -2,
|
||||
ObjectError::Scroll(_) => -3,
|
||||
ObjectError::BufferTooShort(_, _) => -4,
|
||||
ObjectError::IO(_) => -5,
|
||||
_ => -6,
|
||||
}
|
||||
pub(crate) fn option_to_c_string<T>(option: Option<T>) -> *mut c_char where T: Display {
|
||||
option.map(|v| v.to_c_string()).unwrap_or(null_mut())
|
||||
}
|
||||
|
||||
pub(crate) fn merge_members(member_names: &mut [&str]) -> String {
|
||||
|
||||
18
src/elf.rs
Executable file → Normal file
18
src/elf.rs
Executable file → Normal file
@@ -2,10 +2,12 @@ use std::ptr::null_mut;
|
||||
|
||||
use crate::{
|
||||
debug::debug_objects,
|
||||
structs::{CharVec, ParsingError, StringPtr, ULDDObj, ULDDObjResult},
|
||||
structs::{CharVec, ParsingError, ULDDObj, ULDDObjResult},
|
||||
types::{ElfFileType, ElfOS, E_MACHINE, E_TYPE},
|
||||
};
|
||||
use goblin::elf::Elf;
|
||||
use crate::debug::option_to_c_string;
|
||||
use crate::impls::StringToCString;
|
||||
|
||||
fn find_os_from_strtab_elf(elf: &Elf<'_>, pat: &[&str]) -> bool {
|
||||
[
|
||||
@@ -59,7 +61,7 @@ fn find_os_elf(elf: &Elf<'_>, os_abi: u8) -> (ElfOS, *mut i8) {
|
||||
}
|
||||
};
|
||||
|
||||
(os, StringPtr::from(os.to_string()).0)
|
||||
(os, os.to_c_string())
|
||||
}
|
||||
|
||||
fn find_linux_vdso(e_machine: u16, bit_type: bool) -> Option<&'static str> {
|
||||
@@ -105,21 +107,21 @@ pub(crate) fn parse_elf(
|
||||
debugging: bool,
|
||||
) -> ULDDObjResult {
|
||||
let mut elf = elf;
|
||||
let cpu_type = StringPtr::from(E_MACHINE.get(&elf.header.e_machine)).0;
|
||||
let cpu_type = option_to_c_string(E_MACHINE.get(&elf.header.e_machine));
|
||||
let file_type = match E_TYPE.get(&elf.header.e_type) {
|
||||
_ if elf.header.e_type == 0x03 && elf.interpreter.is_some() => {
|
||||
StringPtr::from(ElfFileType::Executable.to_string()).0
|
||||
ElfFileType::Executable.to_c_string()
|
||||
}
|
||||
rest => StringPtr::from(rest).0,
|
||||
rest => option_to_c_string(rest),
|
||||
};
|
||||
let interpreter = StringPtr::from(elf.interpreter).0;
|
||||
let interpreter = option_to_c_string(elf.interpreter);
|
||||
debug_objects(file_name, member_names, "an ELF binary", debugging);
|
||||
ULDDObjResult {
|
||||
error: ParsingError::default(),
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names),
|
||||
executable_format: StringPtr::from("ELF").0,
|
||||
executable_format: "ELF".to_c_string(),
|
||||
is_64: elf.is_64,
|
||||
os_type: find_os_elf(&elf, os_abi).1,
|
||||
file_type,
|
||||
|
||||
57
src/impls.rs
Executable file → Normal file
57
src/impls.rs
Executable file → Normal file
@@ -1,10 +1,23 @@
|
||||
use crate::{
|
||||
structs::{CharVec, Debugging, ParsingError, StringPtr, ULDDObj},
|
||||
structs::{CharVec, Debugging, ParsingError, ULDDObj},
|
||||
ULDDObjResult, ULDDObjResultVec,
|
||||
};
|
||||
use anstream::{eprintln as a_eprintln, println as a_println};
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{fmt::Display, mem::ManuallyDrop, ptr::null_mut, ffi::{c_char, CString}};
|
||||
use std::{
|
||||
ffi::{c_char, CString},
|
||||
fmt::Display,
|
||||
mem::ManuallyDrop,
|
||||
ptr::null_mut,
|
||||
};
|
||||
|
||||
pub trait StringToCString {
|
||||
fn to_c_string(self) -> *mut c_char;
|
||||
}
|
||||
|
||||
pub trait ErrorToInt {
|
||||
fn to_int(&self) -> i64;
|
||||
}
|
||||
|
||||
impl From<Vec<*mut c_char>> for CharVec {
|
||||
fn from(value: Vec<*mut c_char>) -> Self {
|
||||
@@ -52,7 +65,7 @@ impl From<Vec<&str>> for CharVec {
|
||||
CString::from_vec_unchecked(item.to_string().into_bytes()).into_raw()
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
CharVec::from(vector)
|
||||
}
|
||||
}
|
||||
@@ -65,14 +78,17 @@ impl From<&mut Vec<&str>> for CharVec {
|
||||
CString::from_vec_unchecked(item.to_string().into_bytes()).into_raw()
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
CharVec::from(vector)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for StringPtr {
|
||||
fn from(value: String) -> Self {
|
||||
let mut value = value;
|
||||
impl<T> StringToCString for T
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn to_c_string(self) -> *mut c_char {
|
||||
let mut value = self.to_string();
|
||||
value.push('\0');
|
||||
let c_string = match CString::from_vec_with_nul(value.into_bytes()) {
|
||||
Ok(string) => string,
|
||||
@@ -81,25 +97,20 @@ impl From<String> for StringPtr {
|
||||
panic!("{}", error)
|
||||
}
|
||||
};
|
||||
StringPtr(c_string.into_raw())
|
||||
c_string.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for StringPtr {
|
||||
fn from(value: &str) -> Self {
|
||||
StringPtr::from(value.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for StringPtr
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn from(value: Option<T>) -> Self {
|
||||
let Some(t) = value else {
|
||||
return StringPtr(null_mut());
|
||||
};
|
||||
StringPtr::from(t.to_string())
|
||||
impl ErrorToInt for goblin::error::Error {
|
||||
fn to_int(&self) -> i64 {
|
||||
match self {
|
||||
goblin::error::Error::Malformed(_) => -1,
|
||||
goblin::error::Error::BadMagic(_) => -2,
|
||||
goblin::error::Error::Scroll(_) => -3,
|
||||
goblin::error::Error::BufferTooShort(_, _) => -4,
|
||||
goblin::error::Error::IO(_) => -5,
|
||||
_ => -6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
src/lib.rs
Executable file → Normal file
22
src/lib.rs
Executable file → Normal file
@@ -30,7 +30,7 @@
|
||||
//!
|
||||
use archive::parse_archive;
|
||||
use coff::parse_coff;
|
||||
use debug::{find_error_type, merge_members};
|
||||
use debug::merge_members;
|
||||
use elf::parse_elf;
|
||||
use goblin::Object;
|
||||
use mach::parse_mach;
|
||||
@@ -38,8 +38,10 @@ use owo_colors::OwoColorize;
|
||||
use pe::parse_pe;
|
||||
use std::ffi::{c_char, CStr, CString};
|
||||
use structs::{
|
||||
CharVec, Debugging, ParsingError, StringPtr, ULDDObj, ULDDObjResult, ULDDObjResultVec,
|
||||
CharVec, Debugging, ParsingError, ULDDObj, ULDDObjResult, ULDDObjResultVec,
|
||||
};
|
||||
use crate::impls::{ErrorToInt, StringToCString};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod archive;
|
||||
#[doc(hidden)]
|
||||
@@ -101,10 +103,10 @@ fn parse_objects<'a>(
|
||||
objects.push(ULDDObjResult {
|
||||
error: ParsingError {
|
||||
code: magic_number as i64,
|
||||
explanation: StringPtr::from(msg).0,
|
||||
explanation: msg.to_c_string(),
|
||||
},
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -128,10 +130,10 @@ fn parse_objects<'a>(
|
||||
objects.push(ULDDObjResult {
|
||||
error: ParsingError {
|
||||
code: -7,
|
||||
explanation: StringPtr::from(msg).0,
|
||||
explanation: msg.to_c_string(),
|
||||
},
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -149,11 +151,11 @@ fn parse_objects<'a>(
|
||||
|
||||
objects.push(ULDDObjResult {
|
||||
error: ParsingError {
|
||||
code: find_error_type(&error),
|
||||
explanation: StringPtr::from(error.to_string()).0,
|
||||
code: error.to_int(),
|
||||
explanation: error.to_c_string(),
|
||||
},
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -225,7 +227,7 @@ unsafe fn drop_c_string(ptr: *mut i8) {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is designed for deallocating [`ULDDObjResultVec`] created by rust. Trying to deallocating [`ULDDObjResultVec`] created by other languages may result with errors.
|
||||
/// This function is designed for deallocating [`ULDDObjResultVec`] created by rust. Trying to deallocate [`ULDDObjResultVec`] created by other languages may result with errors.
|
||||
///
|
||||
/// It is null pointer-safe.
|
||||
///
|
||||
|
||||
36
src/mach.rs
Executable file → Normal file
36
src/mach.rs
Executable file → Normal file
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
debug::{debug_objects, find_error_type, merge_members},
|
||||
structs::{CharVec, Debugging, ParsingError, StringPtr, ULDDObj, ULDDObjResult},
|
||||
debug::{debug_objects, merge_members},
|
||||
structs::{CharVec, Debugging, ParsingError, ULDDObj, ULDDObjResult},
|
||||
types::{
|
||||
MachOCpuType, MachOOs, MACH_O_ARM_CPU_SUBTYPE, MACH_O_CPUTYPE, MACH_O_FILE_TYPE,
|
||||
MACH_O_X86_CPU_SUBTYPE,
|
||||
@@ -8,6 +8,8 @@ use crate::{
|
||||
};
|
||||
use goblin::mach::{load_command::CommandVariant::BuildVersion, Mach, MachO};
|
||||
use std::ptr::null_mut;
|
||||
use crate::debug::option_to_c_string;
|
||||
use crate::impls::{ErrorToInt, StringToCString};
|
||||
|
||||
fn find_os_mach(mach: &MachO<'_>) -> *mut i8 {
|
||||
for lc in &mach.load_commands {
|
||||
@@ -27,7 +29,7 @@ fn find_os_mach(mach: &MachO<'_>) -> *mut i8 {
|
||||
0x0C => MachOOs::AppleVisionProSimulator,
|
||||
_ => return null_mut(),
|
||||
};
|
||||
return StringPtr::from(os.to_string()).0;
|
||||
return os.to_c_string();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,13 +102,13 @@ pub(crate) fn parse_mach<'a>(
|
||||
|
||||
return objects.push(ULDDObjResult {
|
||||
error: ParsingError {
|
||||
code: find_error_type(&error),
|
||||
explanation: StringPtr::from(error.to_string()).0,
|
||||
code: error.to_int(),
|
||||
explanation: error.to_c_string(),
|
||||
},
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names),
|
||||
executable_format: StringPtr::from("Mach-O").0,
|
||||
executable_format: "Mach-O".to_c_string(),
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
@@ -129,13 +131,13 @@ pub(crate) fn parse_mach<'a>(
|
||||
error)).print(debugging);
|
||||
objects.push(ULDDObjResult {
|
||||
error: ParsingError {
|
||||
code: find_error_type(&error),
|
||||
explanation: StringPtr::from(error.to_string()).0,
|
||||
code: error.to_int(),
|
||||
explanation: error.to_c_string(),
|
||||
},
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names.clone()),
|
||||
executable_format: StringPtr::from("Mach-O").0,
|
||||
executable_format: "Mach-O".to_c_string(),
|
||||
..Default::default()
|
||||
},
|
||||
})
|
||||
@@ -156,22 +158,22 @@ fn parse_mach_o(
|
||||
debugging: bool,
|
||||
) -> ULDDObjResult {
|
||||
let mut mach_o = mach_o;
|
||||
let file_type = StringPtr::from(MACH_O_FILE_TYPE.get(&mach_o.header.filetype)).0;
|
||||
let file_type = option_to_c_string(MACH_O_FILE_TYPE.get(&mach_o.header.filetype));
|
||||
let (cpu_type, cpu_subtype) = {
|
||||
if let Some(mach_o_cpu_type) = MACH_O_CPUTYPE.get(&mach_o.header.cputype) {
|
||||
let mach_o_cpu_subtype = {
|
||||
match mach_o_cpu_type {
|
||||
MachOCpuType::ARM | MachOCpuType::ARM64 => {
|
||||
StringPtr::from(MACH_O_ARM_CPU_SUBTYPE.get(&mach_o.header.cpusubtype)).0
|
||||
option_to_c_string(MACH_O_ARM_CPU_SUBTYPE.get(&mach_o.header.cpusubtype))
|
||||
}
|
||||
MachOCpuType::X86 | MachOCpuType::X86_64 => {
|
||||
StringPtr::from(MACH_O_X86_CPU_SUBTYPE.get(&mach_o.header.cpusubtype)).0
|
||||
option_to_c_string(MACH_O_X86_CPU_SUBTYPE.get(&mach_o.header.cpusubtype))
|
||||
}
|
||||
_ => null_mut(),
|
||||
}
|
||||
};
|
||||
(
|
||||
StringPtr::from(mach_o_cpu_type.to_string()).0,
|
||||
mach_o_cpu_type.to_c_string(),
|
||||
mach_o_cpu_subtype,
|
||||
)
|
||||
} else {
|
||||
@@ -190,9 +192,9 @@ fn parse_mach_o(
|
||||
ULDDObjResult {
|
||||
error: ParsingError::default(),
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names),
|
||||
executable_format: StringPtr::from("Mach-O").0,
|
||||
executable_format: "Mach-O".to_c_string(),
|
||||
is_64: mach_o.is_64,
|
||||
os_type: find_os_mach(&mach_o),
|
||||
file_type,
|
||||
|
||||
18
src/pe.rs
Executable file → Normal file
18
src/pe.rs
Executable file → Normal file
@@ -1,10 +1,12 @@
|
||||
use crate::{
|
||||
debug::debug_objects,
|
||||
structs::{CharVec, ParsingError, StringPtr, ULDDObj, ULDDObjResult},
|
||||
structs::{CharVec, ParsingError, ULDDObj, ULDDObjResult},
|
||||
types::{PeOS, PeSubsystem, PE_ARCH, PE_SUBSYSTEM},
|
||||
};
|
||||
use goblin::pe::{characteristic::IMAGE_FILE_DEBUG_STRIPPED, PE};
|
||||
use std::ptr::null_mut;
|
||||
use crate::debug::option_to_c_string;
|
||||
use crate::impls::StringToCString;
|
||||
|
||||
fn find_os_pe(pe: &PE<'_>) -> *mut i8 {
|
||||
let Some(optional_header) = pe
|
||||
@@ -32,7 +34,7 @@ fn find_os_pe(pe: &PE<'_>) -> *mut i8 {
|
||||
PeSubsystem::Unknown => return null_mut(),
|
||||
};
|
||||
|
||||
StringPtr::from(os.to_string()).0
|
||||
os.to_c_string()
|
||||
}
|
||||
|
||||
pub(crate) fn parse_pe(
|
||||
@@ -43,7 +45,7 @@ pub(crate) fn parse_pe(
|
||||
) -> ULDDObjResult {
|
||||
let is_stripped = pe.header.coff_header.characteristics & IMAGE_FILE_DEBUG_STRIPPED
|
||||
== IMAGE_FILE_DEBUG_STRIPPED;
|
||||
let cpu_type = StringPtr::from(PE_ARCH.get(&pe.header.coff_header.machine)).0;
|
||||
let cpu_type = option_to_c_string(PE_ARCH.get(&pe.header.coff_header.machine));
|
||||
let file_type = pe
|
||||
.header
|
||||
.optional_header
|
||||
@@ -57,27 +59,27 @@ pub(crate) fn parse_pe(
|
||||
.windows_fields
|
||||
.minor_operating_system_version;
|
||||
let linker_version = format!("{}.{}", linker_major_version, linker_minor_version);
|
||||
StringPtr::from(linker_version).0
|
||||
linker_version.to_c_string()
|
||||
} else {
|
||||
null_mut()
|
||||
}
|
||||
};
|
||||
let executable_format = if pe.is_64 {
|
||||
debug_objects(file_name, member_names, "a PE32+ binary", debugging);
|
||||
StringPtr::from("PE32+").0
|
||||
"PE32+".to_c_string()
|
||||
} else {
|
||||
debug_objects(file_name, member_names, "a PE32 binary", debugging);
|
||||
StringPtr::from("PE32").0
|
||||
"PE32".to_c_string()
|
||||
};
|
||||
ULDDObjResult {
|
||||
error: ParsingError::default(),
|
||||
obj: ULDDObj {
|
||||
file_name: StringPtr::from(file_name).0,
|
||||
file_name: file_name.to_c_string(),
|
||||
member_name: CharVec::from(member_names),
|
||||
executable_format,
|
||||
is_64: pe.is_64,
|
||||
os_type: find_os_pe(&pe),
|
||||
file_type: StringPtr::from(file_type).0,
|
||||
file_type: option_to_c_string(file_type),
|
||||
is_stripped,
|
||||
cpu_type,
|
||||
cpu_subtype: null_mut(),
|
||||
|
||||
3
src/structs.rs
Executable file → Normal file
3
src/structs.rs
Executable file → Normal file
@@ -99,9 +99,6 @@ pub struct ULDDObjResultVec {
|
||||
pub vec: *mut ULDDObjResult,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct StringPtr(pub *mut i8);
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(crate) enum Debugging {
|
||||
Info(String),
|
||||
|
||||
0
src/types.rs
Executable file → Normal file
0
src/types.rs
Executable file → Normal file
Reference in New Issue
Block a user