Ports rust_g -> rustlibs: logging, toml, dmi, json, and noisegen (#28858)

* Rustlibs logging, toml, dmi, and dbpnoise

* missed one

* Hopefully fix logging utf-8 decode errors

* Fuck

* Build Rust library

* ports rust_g json validator

* rustlibs_file clippy lint

* Build Rust library

* fix merge conflict

---------

Co-authored-by: paradisess13[bot] <165046124+paradisess13[bot]@users.noreply.github.com>
This commit is contained in:
chuga-git
2025-04-11 17:45:45 +00:00
committed by GitHub
co-authored by paradisess13[bot] <165046124+paradisess13[bot]@users.noreply.github.com>
parent 4094c80116
commit 5c8ba2ee4e
25 changed files with 558 additions and 106 deletions
+6
View File
@@ -2,6 +2,12 @@ mod logging;
mod mapmanip;
mod milla;
mod redis_pubsub;
mod rustlibs_dmi;
mod rustlibs_file;
mod rustlibs_json;
mod rustlibs_logging;
mod rustlibs_noisegen;
mod rustlibs_toml;
#[cfg(all(not(feature = "byond-515"), not(feature = "byond-516")))]
compile_error!("Please specify byond-515 or byond-516 as a feature to specify BYOND version.");
+30
View File
@@ -0,0 +1,30 @@
use byondapi::value::ByondValue;
use png::{Decoder, Encoder};
use std::fs::File;
#[byondapi::bind]
fn dmi_strip_metadata(path: ByondValue) -> eyre::Result<ByondValue> {
strip_metadata(&path.get_string()?)?;
Ok(ByondValue::null())
}
fn strip_metadata(path: &str) -> eyre::Result<()> {
let mut reader = Decoder::new(File::open(path)?).read_info()?;
let mut buf = vec![0; reader.output_buffer_size()];
let frame_info = reader.next_frame(&mut buf)?;
let mut encoder = Encoder::new(File::create(path)?, frame_info.width, frame_info.height);
encoder.set_color(frame_info.color_type);
encoder.set_depth(frame_info.bit_depth);
let reader_info = reader.info();
if let Some(palette) = reader_info.palette.clone() {
encoder.set_palette(palette);
}
if let Some(trns_chunk) = reader_info.trns.clone() {
encoder.set_palette(trns_chunk);
}
let mut writer = encoder.write_header()?;
Ok(writer.write_image_data(&buf)?)
}
+46
View File
@@ -0,0 +1,46 @@
use byondapi::value::ByondValue;
use std::{
fs::File,
io::{BufReader, BufWriter, Read, Write},
};
#[byondapi::bind]
fn file_read(path: ByondValue) -> eyre::Result<ByondValue> {
read(&path.get_string()?)
}
fn read(path: &str) -> eyre::Result<ByondValue> {
let file = File::open(path)?;
let metadata = file.metadata()?;
let mut file = BufReader::new(file);
let mut content = String::with_capacity(metadata.len() as usize);
file.read_to_string(&mut content)?;
let content = content.replace('\r', "");
Ok(content.try_into()?)
}
#[byondapi::bind]
fn file_write(data: ByondValue, path: ByondValue) -> eyre::Result<ByondValue> {
let data: String = data.get_string()?;
let path: String = path.get_string()?;
write(&data, &path)
}
fn write(data: &str, path: &str) -> eyre::Result<ByondValue> {
let path: &std::path::Path = path.as_ref();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut file = BufWriter::new(File::create(path)?);
let written = file.write(data.as_bytes())? as f32;
file.flush()?;
file.into_inner()
.map_err(|e| std::io::Error::new(e.error().kind(), e.error().to_string()))?
.sync_all()?;
Ok(written.into())
}
+77
View File
@@ -0,0 +1,77 @@
use byondapi::value::ByondValue;
use serde_json::Value;
use std::cmp;
const VALID_JSON_MAX_RECURSION_DEPTH: usize = 8;
#[byondapi::bind]
fn json_is_valid(text: ByondValue) -> eyre::Result<ByondValue> {
let value = match serde_json::from_str::<Value>(&text.get_string()?) {
Ok(value) => value,
Err(_) => return Ok("false".try_into()?),
};
Ok(get_recursion_level(&value).is_ok().to_string().try_into()?)
}
/// Gets the recursion level of the given value
/// If it is above `VALID_JSON_MAX_RECURSION_DEPTH`, returns Err(())
fn get_recursion_level(value: &Value) -> Result<usize, ()> {
let values: Vec<&Value> = match value {
Value::Array(array) => array.iter().collect(),
Value::Object(map) => map.values().collect(),
_ => return Ok(0),
};
let mut max_recursion_level = 0;
for value in values {
max_recursion_level = cmp::max(max_recursion_level, get_recursion_level(value)?);
}
max_recursion_level += 1;
if max_recursion_level >= VALID_JSON_MAX_RECURSION_DEPTH {
Err(())
} else {
Ok(max_recursion_level)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_recursion_level() {
assert_eq!(
get_recursion_level(&serde_json::from_str("[]").unwrap()),
Ok(1)
);
assert_eq!(
get_recursion_level(&serde_json::from_str("[[]]").unwrap()),
Ok(2)
);
assert_eq!(
get_recursion_level(&serde_json::from_str("[[[]]]").unwrap()),
Ok(3)
);
}
#[test]
fn test_get_recursion_level_max_depth() {
assert_eq!(
get_recursion_level(
&serde_json::from_str(&format!(
"{}{}",
"[".repeat(VALID_JSON_MAX_RECURSION_DEPTH),
"]".repeat(VALID_JSON_MAX_RECURSION_DEPTH)
))
.unwrap()
),
Err(())
);
}
}
+58
View File
@@ -0,0 +1,58 @@
use byondapi::value::ByondValue;
use chrono::Utc;
use eyre::Context;
use std::{
cell::RefCell,
collections::hash_map::{Entry, HashMap},
ffi::OsString,
fs,
fs::{File, OpenOptions},
io::Write,
path::Path,
};
thread_local! {
static FILE_MAP: RefCell<HashMap<OsString, File>> = RefCell::new(HashMap::new());
}
#[byondapi::bind]
fn log_write(path: ByondValue, data: ByondValue) -> eyre::Result<ByondValue> {
FILE_MAP.with(|cell| -> eyre::Result<ByondValue> {
let mut map = cell.borrow_mut();
let path = path.get_string()?;
let path = Path::new(&path as &str);
let file = match map.entry(path.into()) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => e.insert(open(path)?),
};
// Byond will happily send over invalid bytes from unpurged text macros, which Rust does not like
// This circumvents ByondValue::get_string() to strip utf-8 before it can cause a panic
let data = data
.get_cstring()
.map(|cstring| cstring.to_string_lossy().into_owned())
.wrap_err(format!("UTF-8 decode error: {:#?}", data))?;
let iter = data.split('\n');
for line in iter {
writeln!(file, "[{}] {}", Utc::now().format("%FT%T"), line)?;
}
Ok(ByondValue::null())
})
}
#[byondapi::bind]
fn log_close_all() -> eyre::Result<ByondValue> {
FILE_MAP.with(|cell| {
let mut map = cell.borrow_mut();
map.clear();
});
Ok(ByondValue::null())
}
fn open(path: &Path) -> eyre::Result<File> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
Ok(OpenOptions::new().append(true).create(true).open(path)?)
}
+61
View File
@@ -0,0 +1,61 @@
use byondapi::value::ByondValue;
use dbpnoise::gen_noise;
#[byondapi::bind]
fn dbp_generate(
seed: ByondValue,
accuracy: ByondValue,
stamp_size: ByondValue,
world_size: ByondValue,
lower_range: ByondValue,
upper_range: ByondValue,
) -> eyre::Result<ByondValue> {
Ok(gen_dbp_noise(
&seed.get_string()?,
&accuracy.get_string()?,
&stamp_size.get_string()?,
&world_size.get_string()?,
&lower_range.get_string()?,
&upper_range.get_string()?,
)?
.try_into()?)
}
fn gen_dbp_noise(
seed: &str,
accuracy: &str,
stamp_size: &str,
world_size: &str,
lower_range: &str,
upper_range: &str,
) -> eyre::Result<String> {
let map: Vec<Vec<bool>> = gen_noise(
seed,
accuracy.parse::<usize>()?,
stamp_size.parse::<usize>()?,
world_size.parse::<usize>()?,
lower_range.parse::<f32>()?,
upper_range.parse::<f32>()?,
);
let mut result = String::new();
for row in map {
for cell in row {
result.push(if cell { '1' } else { '0' });
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::gen_dbp_noise;
const TEST_SEED: &str = "meowrrpmraoooow~";
#[test]
fn test_gen_dbp_noise() {
let value = gen_dbp_noise(TEST_SEED, "360", "4", "25", "0.1", "1.1").unwrap();
println!("(length: {})", value.len());
println!("{}", value);
assert_eq!(value.len(), 625);
}
}
+41
View File
@@ -0,0 +1,41 @@
use byondapi::value::ByondValue;
#[byondapi::bind]
fn toml_file_to_json(path: ByondValue) -> eyre::Result<ByondValue> {
let path: String = path.get_string()?;
Ok(serde_json::to_string(&match toml_file_to_json_impl(&path) {
Ok(value) => serde_json::json!({
"success": true, "content": value
}),
Err(error) => serde_json::json!({
"success": false, "content": error.to_string()
}),
})?
.try_into()?)
}
fn toml_file_to_json_impl(path: &str) -> eyre::Result<String> {
Ok(serde_json::to_string(&toml::from_str::<toml::Value>(
&std::fs::read_to_string(path)?,
)?)?)
}
#[byondapi::bind]
fn toml_encode(value: ByondValue) -> eyre::Result<ByondValue> {
let path: String = value.get_string()?;
Ok(serde_json::to_string(&match toml_encode_impl(&path) {
Ok(value) => serde_json::json!({
"success": true, "content": value
}),
Err(error) => serde_json::json!({
"success": false, "content": error.to_string()
}),
})?
.try_into()?)
}
fn toml_encode_impl(value: &str) -> eyre::Result<String> {
Ok(toml::to_string_pretty(
&serde_json::from_str::<toml::Value>(value)?,
)?)
}