Skip to content

Commit

Permalink
Add ArrayWString (#41)
Browse files Browse the repository at this point in the history
This adds `ArrayWString`, which similar to `ArrayCString` is an inline
nul-terminated string that can be read from a process. Unlike
`ArrayCString`, which is backed by individual bytes, `ArrayWString` is
backed by 16-bit characters.
  • Loading branch information
CryZe committed Jun 28, 2023
1 parent be2ae32 commit ef39302
Showing 1 changed file with 93 additions and 1 deletion.
94 changes: 93 additions & 1 deletion src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use bytemuck::{Pod, Zeroable};

pub use arrayvec::ArrayString;

use crate::FromEndian;

/// A nul-terminated string that is stored in an array of a fixed size `N`. This
/// can be read from a process's memory.
#[derive(Copy, Clone)]
Expand All @@ -29,6 +31,15 @@ impl<const N: usize> ArrayCString<N> {
pub fn validate_utf8(&self) -> Result<&str, str::Utf8Error> {
str::from_utf8(self.as_bytes())
}

/// Checks whether the string matches the given text. This is faster than
/// calling [`as_bytes`](Self::as_bytes) and then comparing, because it can
/// use the length information of the parameter.
pub fn matches(&self, text: impl AsRef<[u8]>) -> bool {
let bytes = text.as_ref();
!self.0.get(bytes.len()).is_some_and(|&b| b != 0)
&& self.0.get(..bytes.len()).is_some_and(|s| s == bytes)
}
}

impl<const N: usize> Default for ArrayCString<N> {
Expand All @@ -47,7 +58,7 @@ impl<const N: usize> ops::Deref for ArrayCString<N> {

impl<const N: usize> PartialEq for ArrayCString<N> {
fn eq(&self, other: &Self) -> bool {
<[u8]>::eq(self, &**other)
self.matches(&**other)
}
}

Expand All @@ -57,3 +68,84 @@ impl<const N: usize> Eq for ArrayCString<N> {}
unsafe impl<const N: usize> Pod for ArrayCString<N> {}
/// SAFETY: The type is transparent over an array of `N` bytes, which is `Zeroable`.
unsafe impl<const N: usize> Zeroable for ArrayCString<N> {}

impl<const N: usize> FromEndian for ArrayCString<N> {
fn from_be(&self) -> Self {
*self
}
fn from_le(&self) -> Self {
*self
}
}

/// A nul-terminated wide string (16-bit characters) that is stored in an array
/// of a fixed size of `N` characters. This can be read from a process's memory.
#[derive(Copy, Clone)]
#[repr(transparent)]
pub struct ArrayWString<const N: usize>([u16; N]);

impl<const N: usize> ArrayWString<N> {
/// Creates a new empty nul-terminated wide string.
pub const fn new() -> Self {
Self([0; N])
}

/// Returns the 16-bit characters of the string up until (but excluding) the
/// nul-terminator. If there is no nul-terminator, all bytes are returned.
pub fn as_slice(&self) -> &[u16] {
let len = self.0.iter().position(|&b| b == 0).unwrap_or(N);
&self.0[..len]
}

/// Checks whether the string matches the given text. This is faster than
/// calling [`as_slice`](Self::as_slice) and then comparing, because it can
/// use the length information of the parameter.
pub fn matches(&self, text: impl AsRef<[u16]>) -> bool {
let bytes = text.as_ref();
!self.0.get(bytes.len()).is_some_and(|&b| b != 0)
&& self.0.get(..bytes.len()).is_some_and(|s| s == bytes)
}

/// Checks whether the string matches the given text. This dynamically
/// re-encodes the passed in text to UTF-16, which is not as fast as
/// [`matches`](Self::matches).
pub fn matches_str(&self, text: &str) -> bool {
self.as_slice().iter().copied().eq(text.encode_utf16())
}
}

impl<const N: usize> Default for ArrayWString<N> {
fn default() -> Self {
Self::new()
}
}

impl<const N: usize> ops::Deref for ArrayWString<N> {
type Target = [u16];

fn deref(&self) -> &Self::Target {
self.as_slice()
}
}

impl<const N: usize> PartialEq for ArrayWString<N> {
fn eq(&self, other: &Self) -> bool {
self.matches(&**other)
}
}

impl<const N: usize> Eq for ArrayWString<N> {}

/// SAFETY: The type is transparent over an array of `N` u16s, which is `Pod`.
unsafe impl<const N: usize> Pod for ArrayWString<N> {}
/// SAFETY: The type is transparent over an array of `N` u16s, which is `Zeroable`.
unsafe impl<const N: usize> Zeroable for ArrayWString<N> {}

impl<const N: usize> FromEndian for ArrayWString<N> {
fn from_be(&self) -> Self {
Self(self.0.map(|x| x.from_be()))
}
fn from_le(&self) -> Self {
Self(self.0.map(|x| x.from_le()))
}
}

0 comments on commit ef39302

Please sign in to comment.