use aho_corasick::AhoCorasick;
use base64::{DecodeError, Engine};
use std::{
env,
error::Error,
fmt::{Debug, Display},
path::{Path, PathBuf},
};
pub mod crypto;
pub mod io;
pub mod rand;
pub mod structs;
pub const LINUX_GD_FILES: &str = "~/.local/share/Steam/steamapps/compatdata/322170/pfx/drive_c/users/steamuser/AppData/Local/GeometryDash";
macro_rules! count_exprs {
() => { 0 };
($e:expr) => { 1 };
($e:expr, $($es:expr),+) => { 1 + count_exprs!($($es),*) };
}
macro_rules! build_plist_tags {
($($find:expr => $replace:expr),* $(,)?) => {
const PLIST_TAGS_FIND: [&str; count_exprs!($($find),*)] = [$($find),*];
const PLIST_TAGS_REPLACE: [&str; count_exprs!($($replace),*)] = [$($replace),*];
};
}
build_plist_tags! {
"<k>" => "<key>",
"</k>" => "</key>",
"<i>" => "<integer>",
"</i>" => "</integer>",
"<d>" => "<dict>",
"</d>" => "</dict>",
"<d />" => "<dict />",
"<t/>" => "<true/>",
"<f/>" => "<false/>",
"<t />" => "<true />",
"<f />" => "<false />",
"<s>" => "<string>",
"</s>" => "</string>",
"<r>" => "<real>",
"</r>" => "</real>",
}
#[derive(Debug)]
#[non_exhaustive]
pub enum GDError {
Io(std::io::Error),
DecodeError(DecodeError),
BadPlist(plist::Error),
CorruptedSavefile(String),
MissingSavefile,
AhoCorasick(aho_corasick::BuildError),
FromUtf8Error(std::string::FromUtf8Error),
}
impl Error for GDError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io(e) => Some(e),
Self::DecodeError(e) => e.source(),
Self::BadPlist(e) => e.source(),
Self::AhoCorasick(e) => Some(e),
Self::FromUtf8Error(e) => Some(e),
Self::CorruptedSavefile(_) | Self::MissingSavefile => None,
}
}
}
impl From<DecodeError> for GDError {
fn from(value: DecodeError) -> Self {
Self::DecodeError(value)
}
}
impl From<std::io::Error> for GDError {
fn from(value: std::io::Error) -> Self {
Self::Io(value)
}
}
impl From<plist::Error> for GDError {
fn from(value: plist::Error) -> Self {
Self::BadPlist(value)
}
}
impl From<aho_corasick::BuildError> for GDError {
fn from(value: aho_corasick::BuildError) -> Self {
Self::AhoCorasick(value)
}
}
impl From<std::string::FromUtf8Error> for GDError {
fn from(value: std::string::FromUtf8Error) -> Self {
Self::FromUtf8Error(value)
}
}
impl From<GDError> for std::fmt::Error {
fn from(_: GDError) -> Self {
std::fmt::Error
}
}
impl Display for GDError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DecodeError(d) => write!(f, "File decode failed: {d}"),
Self::BadPlist(p) => write!(f, "Bad plist: {p}"),
Self::Io(io) => write!(f, "IO failed: {io}"),
Self::AhoCorasick(a) => write!(f, "AhoCorasick UTF-8 build error: {a}"),
Self::FromUtf8Error(e) => write!(f, "UTF-8 conversion error: {e}"),
Self::CorruptedSavefile(e) => write!(f, "Corrupted savefile: {e}"),
Self::MissingSavefile => write!(f, "No available save file found!"),
}
}
}
#[must_use]
pub fn get_cclocallevels_path() -> Option<PathBuf> {
if let Ok(local_appdata) = env::var("LOCALAPPDATA")
&& Path::new(&local_appdata).exists()
{
Some(format!("{local_appdata}/GeometryDash/CCLocalLevels.dat").into())
} else {
let linux_path = PathBuf::from(format!("{LINUX_GD_FILES}/CCLocalLevels.dat"));
if linux_path.exists() {
return Some(linux_path);
}
None
}
}
pub fn get_ccgamemanager_path() -> Option<PathBuf> {
if let Ok(local_appdata) = env::var("LOCALAPPDATA") {
let path = PathBuf::from(format!("{local_appdata}/GeometryDash/CCGameManager.dat"));
if path.exists() {
return Some(path);
}
}
let linux_path = PathBuf::from(format!("{LINUX_GD_FILES}/CCGameManager.dat"));
if linux_path.exists() {
return Some(linux_path);
}
None
}
pub fn proper_plist_tags(s: String) -> Result<String, GDError> {
let ac = AhoCorasick::new(PLIST_TAGS_FIND)?;
Ok(ac.replace_all(&s, &PLIST_TAGS_REPLACE))
}
#[inline]
pub fn b64_decode<T: AsRef<[u8]>>(encoded: T) -> Result<Vec<u8>, GDError> {
Ok(base64::engine::general_purpose::URL_SAFE.decode(encoded)?)
}
#[inline]
#[must_use]
pub fn b64_encode<T: AsRef<[u8]>>(encoded: T) -> String {
base64::engine::general_purpose::URL_SAFE.encode(encoded)
}