From 082f0c6f8f2f9455ddf8b5006c80f33e28ecbc29 Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Mon, 16 Feb 2026 19:19:47 -0600 Subject: [PATCH] feat: Implement terrain SVG export functionality via FFI and integrate it into the Arma server extension's command groups. --- arma/server/extension/src/lib.rs | 2 + arma/server/extension/src/terrain.rs | 183 +++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 arma/server/extension/src/terrain.rs diff --git a/arma/server/extension/src/lib.rs b/arma/server/extension/src/lib.rs index 1d71c7e..e5665bf 100644 --- a/arma/server/extension/src/lib.rs +++ b/arma/server/extension/src/lib.rs @@ -21,6 +21,7 @@ pub mod locker; mod log; pub mod org; pub mod redis; +pub mod terrain; pub mod v_garage; pub mod v_locker; @@ -66,6 +67,7 @@ fn init() -> Extension { .group("icom", icom::group()) .group("locker", locker::group()) .group("org", org::group()) + .group("terrain", terrain::group()) .group( "owned", Group::new() diff --git a/arma/server/extension/src/terrain.rs b/arma/server/extension/src/terrain.rs new file mode 100644 index 0000000..7c8eeaa --- /dev/null +++ b/arma/server/extension/src/terrain.rs @@ -0,0 +1,183 @@ +//! Terrain SVG export functionality via FFI to external C++ library. +//! +//! Provides commands to export terrain data to SVG format with various +//! rendering options (location names, grid, contour lines, etc.). + +use arma_rs::Group; +use std::ffi::CString; +use std::os::raw::{c_char, c_void}; + +#[cfg(target_os = "windows")] +#[link(name = "kernel32")] +unsafe extern "stdcall" { + fn GetModuleHandleA(lpModuleName: *const u8) -> *mut c_void; + fn LoadLibraryA(lpLibFileName: *const u8) -> *mut c_void; + fn GetProcAddress(hModule: *mut c_void, lpProcName: *const u8) -> *mut c_void; +} + +/// Mangled C++ name for the ExportSVG function. +/// Format: `?ExportSVG@@YAXPEBD_N11111@Z` matches signature: +/// `void ExportSVG(const char*, bool, bool, bool, bool, bool, bool)` +const EXPORT_SVG_PROC_NAME: &[u8] = b"?ExportSVG@@YAXPEBD_N11111@Z\0"; + +/// Name of the DLL containing the ExportSVG function. +/// Update this to match your actual DLL name. +const TERRAIN_DLL_NAME: &[u8] = b"TerrainExport.dll\0"; + +type FnExportSVG = + extern "stdcall" fn(*const c_char, bool, bool, bool, bool, bool, bool) -> *const c_void; + +/// Creates the Arma 3 command group for the terrain module. +/// +/// Registers the `exportSVG` command with the Arma 3 extension. +pub fn group() -> Group { + Group::new().command("exportSVG", export_svg) +} + +/// Exports terrain data to an SVG file with configurable rendering options. +/// +/// # Parameters +/// - `file_path`: Output SVG file path +/// - `draw_location_names`: Include location/place names +/// - `draw_grid`: Include grid overlay +/// - `draw_contourlines`: Include elevation contour lines +/// - `draw_tree_objects`: Include vegetation/tree objects +/// - `draw_mountain_heightpoints`: Include mountain peak elevation markers +/// - `simple_roads`: Use simplified road rendering +/// +/// # Returns +/// - `Ok(())` on success +/// - `Err(String)` with error message on failure +#[cfg(target_os = "windows")] +pub fn export_terrain_svg( + file_path: String, + draw_location_names: bool, + draw_grid: bool, + draw_contourlines: bool, + draw_tree_objects: bool, + draw_mountain_heightpoints: bool, + simple_roads: bool, +) -> Result<(), String> { + unsafe { + // Try to get already loaded module, or load the DLL + let module = GetModuleHandleA(TERRAIN_DLL_NAME.as_ptr()); + let module = if module.is_null() { + let loaded = LoadLibraryA(TERRAIN_DLL_NAME.as_ptr()); + if loaded.is_null() { + return Err(format!( + "Failed to load DLL: {}", + std::str::from_utf8_unchecked(&TERRAIN_DLL_NAME[..TERRAIN_DLL_NAME.len() - 1]) + )); + } + loaded + } else { + module + }; + + // Get the ExportSVG function address + let export_svg_proc = GetProcAddress(module, EXPORT_SVG_PROC_NAME.as_ptr()); + if export_svg_proc.is_null() { + return Err("Failed to find ExportSVG function in DLL".to_string()); + } + + // Convert to function pointer + let export_svg: FnExportSVG = std::mem::transmute(export_svg_proc); + + // Convert file path to C string + let file_path_cstr = + CString::new(file_path).map_err(|e| format!("Invalid file path: {}", e))?; + + // Call the export function + export_svg( + file_path_cstr.as_ptr(), + draw_location_names, + draw_grid, + draw_contourlines, + draw_tree_objects, + draw_mountain_heightpoints, + simple_roads, + ); + + Ok(()) + } +} + +#[cfg(not(target_os = "windows"))] +pub fn export_terrain_svg( + _file_path: String, + _draw_location_names: bool, + _draw_grid: bool, + _draw_contourlines: bool, + _draw_tree_objects: bool, + _draw_mountain_heightpoints: bool, + _simple_roads: bool, +) -> Result<(), String> { + Err("Terrain SVG export is only available on Windows".to_string()) +} + +#[derive(serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct ExportSvgOptions { + file_path: String, + #[serde(default)] + draw_location_names: bool, + #[serde(default)] + draw_grid: bool, + #[serde(default)] + draw_contourlines: bool, + #[serde(default)] + draw_tree_objects: bool, + #[serde(default)] + draw_mountain_heightpoints: bool, + #[serde(default)] + simple_roads: bool, +} + +/// Arma command handler for terrain SVG export. +/// +/// # SQF Usage +/// ```sqf +/// ["forge", ["terrain", "exportSVG", "{ +/// ""filePath"": ""C:\\path\\to\\output.svg"", +/// ""drawLocationNames"": true, +/// ""drawGrid"": true, +/// ""drawContourlines"": true, +/// ""drawTreeObjects"": false, +/// ""drawMountainHeightpoints"": true, +/// ""simpleRoads"": false +/// }"]] call forge_fnc_callExtension; +/// ``` +fn export_svg(options_json: String) -> String { + // Parse options from JSON + let options: ExportSvgOptions = match serde_json::from_str(&options_json) { + Ok(opts) => opts, + Err(e) => { + return serde_json::json!({ + "status": "error", + "message": format!("Invalid JSON options: {}", e) + }) + .to_string(); + } + }; + + match export_terrain_svg( + options.file_path, + options.draw_location_names, + options.draw_grid, + options.draw_contourlines, + options.draw_tree_objects, + options.draw_mountain_heightpoints, + options.simple_roads, + ) { + Ok(_) => serde_json::json!({ + "status": "success", + "message": "Terrain exported successfully" + }) + .to_string(), + Err(e) => serde_json::json!({ + "status": "error", + "message": e + }) + .to_string(), + } +}