Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //! A tool for reading and writing Garmin .SET files.
- //!
- //! # Usage
- //!
- //! Dump the contents of a .SET file:
- //! ```bash
- //! bazel run //device:set_file_tool -- path/to/settings.SET --dump
- //! ```
- //!
- //! Set a key/value pair in a .SET file:
- //! ```bash
- //! bazel run //device:set_file_tool -- path/to/settings.SET set "KeyName" "Value"
- //! ```
- use anyhow::{anyhow, Context, Result};
- use clap::{Parser, Subcommand};
- use std::collections::BTreeMap;
- use std::fs::File;
- use std::io::{Read, Write};
- use std::path::PathBuf;
- const STRING_BLOCK_SENTINEL: u32 = 0xABCDABCD;
- const DATA_BLOCK_SENTINEL: u32 = 0xDA7ADA7A;
- const TYPE_NULL: u8 = 0;
- const TYPE_INT: u8 = 1;
- const TYPE_FLOAT: u8 = 2;
- const TYPE_STRING: u8 = 3;
- const TYPE_ARRAY: u8 = 5;
- const TYPE_BOOLEAN: u8 = 9;
- const TYPE_HASH: u8 = 11;
- const TYPE_LONG: u8 = 14;
- const TYPE_DOUBLE: u8 = 15;
- // const TYPE_CHAR: u8 = 19;
- // const TYPE_BYTEARRAY: u8 = 20;
- #[derive(Debug, Clone)]
- enum MonkeyType {
- Null,
- Bool(bool),
- Int(i32),
- Float(f32),
- Long(i64),
- Double(f64),
- String(i32, Option<String>), // offset, value
- Array(Vec<MonkeyType>),
- Hash(Vec<(MonkeyType, MonkeyType)>), // Preserves insertion order
- }
- impl MonkeyType {
- fn serialize(&self) -> Vec<u8> {
- let mut buf = Vec::new();
- match self {
- MonkeyType::Null => {
- buf.push(TYPE_NULL);
- }
- MonkeyType::Bool(val) => {
- buf.push(TYPE_BOOLEAN);
- buf.push(if *val { 1 } else { 0 });
- }
- MonkeyType::Int(val) => {
- buf.push(TYPE_INT);
- buf.extend_from_slice(&val.to_be_bytes());
- }
- MonkeyType::Float(val) => {
- buf.push(TYPE_FLOAT);
- buf.extend_from_slice(&val.to_be_bytes());
- }
- MonkeyType::Long(val) => {
- buf.push(TYPE_LONG);
- buf.extend_from_slice(&val.to_be_bytes());
- }
- MonkeyType::Double(val) => {
- buf.push(TYPE_DOUBLE);
- buf.extend_from_slice(&val.to_be_bytes());
- }
- MonkeyType::String(offset, _) => {
- buf.push(TYPE_STRING);
- buf.extend_from_slice(&offset.to_be_bytes());
- }
- MonkeyType::Array(items) => {
- buf.push(TYPE_ARRAY);
- buf.extend_from_slice(&(items.len() as i32).to_be_bytes());
- for item in items {
- buf.extend(item.serialize());
- }
- }
- MonkeyType::Hash(items) => {
- buf.push(TYPE_HASH);
- buf.extend_from_slice(&(items.len() as i32).to_be_bytes());
- for (k, v) in items {
- buf.extend(k.serialize());
- buf.extend(v.serialize());
- }
- }
- }
- buf
- }
- }
- struct StringBlock {
- strings: BTreeMap<i32, String>,
- }
- impl StringBlock {
- fn parse(data: &[u8]) -> Result<Self> {
- let mut strings = BTreeMap::new();
- let mut offset = 0;
- while offset < data.len() {
- if offset + 2 > data.len() {
- break;
- }
- let size = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
- offset += 2;
- if offset + size > data.len() + 1 {
- // size includes null, so actual data is size-1
- break;
- }
- // The key is the offset relative to the start of the String Block Data.
- let str_start = offset;
- let str_len = if size > 0 { size - 1 } else { 0 };
- let s_bytes = &data[str_start..str_start + str_len];
- let s = String::from_utf8_lossy(s_bytes).to_string();
- strings.insert((offset - 2) as i32, s);
- offset += size;
- }
- Ok(Self { strings })
- }
- fn get(&self, offset: i32) -> Option<&String> {
- self.strings.get(&offset)
- }
- fn serialize(&self) -> Vec<u8> {
- let mut buf = Vec::new();
- for (_, s) in &self.strings {
- let s_bytes = s.as_bytes();
- let size = s_bytes.len() + 1;
- buf.extend_from_slice(&(size as u16).to_be_bytes());
- buf.extend_from_slice(s_bytes);
- buf.push(0);
- }
- buf
- }
- fn add_string(&mut self, s: String) -> i32 {
- // Check if string already exists
- for (&offset, existing_s) in &self.strings {
- if existing_s == &s {
- return offset;
- }
- }
- let next_offset = if let Some((&last_offset, last_string)) = self.strings.iter().last() {
- // entry size is 2 (header) + len + 1 (null).
- last_offset + 2 + last_string.len() as i32 + 1
- } else {
- 0
- };
- self.strings.insert(next_offset, s);
- next_offset
- }
- }
- struct SetFile {
- string_block: Option<StringBlock>,
- data: Vec<MonkeyType>,
- }
- impl SetFile {
- fn add_string(&mut self, s: String) -> i32 {
- if self.string_block.is_none() {
- self.string_block = Some(StringBlock {
- strings: BTreeMap::new(),
- });
- }
- self.string_block.as_mut().unwrap().add_string(s)
- }
- fn parse(data: &[u8]) -> Result<Self> {
- let mut offset = 0;
- let mut string_block = None;
- // Optional String Block
- if data.len() >= 8 {
- let sentinel = u32::from_be_bytes(data[offset..offset + 4].try_into()?);
- if sentinel == STRING_BLOCK_SENTINEL {
- offset += 4;
- let size = u32::from_be_bytes(data[offset..offset + 4].try_into()?) as usize;
- offset += 4;
- let sb_data = &data[offset..offset + size];
- string_block = Some(StringBlock::parse(sb_data)?);
- offset += size;
- }
- }
- // Data Block
- if data.len() < offset + 8 {
- return Err(anyhow!("File too short for data block"));
- }
- let sentinel = u32::from_be_bytes(data[offset..offset + 4].try_into()?);
- if sentinel != DATA_BLOCK_SENTINEL {
- return Err(anyhow!(
- "Expected data block sentinel {:#X}, got {:#X}",
- DATA_BLOCK_SENTINEL,
- sentinel
- ));
- }
- offset += 4;
- let size = u32::from_be_bytes(data[offset..offset + 4].try_into()?) as usize;
- offset += 4;
- let end = offset + size;
- let mut data_objs = Vec::new();
- while offset < end {
- let (obj, bytes_read) = Self::parse_object(&data[offset..end], string_block.as_ref())?;
- data_objs.push(obj);
- offset += bytes_read;
- }
- Ok(Self {
- string_block,
- data: data_objs,
- })
- }
- fn parse_object(
- data: &[u8],
- string_block: Option<&StringBlock>,
- ) -> Result<(MonkeyType, usize)> {
- let type_id = data[0];
- match type_id {
- TYPE_NULL => Ok((MonkeyType::Null, 1)),
- TYPE_BOOLEAN => {
- let val = data[1] != 0;
- Ok((MonkeyType::Bool(val), 2))
- }
- TYPE_INT => {
- let val = i32::from_be_bytes(data[1..5].try_into()?);
- Ok((MonkeyType::Int(val), 5))
- }
- TYPE_LONG => {
- let val = i64::from_be_bytes(data[1..9].try_into()?);
- Ok((MonkeyType::Long(val), 9))
- }
- TYPE_FLOAT => {
- let val = f32::from_be_bytes(data[1..5].try_into()?);
- Ok((MonkeyType::Float(val), 5))
- }
- TYPE_DOUBLE => {
- let val = f64::from_be_bytes(data[1..9].try_into()?);
- Ok((MonkeyType::Double(val), 9))
- }
- TYPE_STRING => {
- let offset = i32::from_be_bytes(data[1..5].try_into()?);
- let val = string_block.and_then(|sb| sb.get(offset).cloned());
- Ok((MonkeyType::String(offset, val), 5))
- }
- TYPE_HASH => {
- let count = i32::from_be_bytes(data[1..5].try_into()?);
- let mut offset = 5;
- let mut items = Vec::new();
- for _ in 0..count {
- let (k, k_size) = Self::parse_object(&data[offset..], string_block)?;
- offset += k_size;
- let (v, v_size) = Self::parse_object(&data[offset..], string_block)?;
- offset += v_size;
- items.push((k, v));
- }
- Ok((MonkeyType::Hash(items), offset))
- }
- TYPE_ARRAY => {
- let count = i32::from_be_bytes(data[1..5].try_into()?);
- let mut offset = 5;
- let mut items = Vec::new();
- for _ in 0..count {
- let (item, item_size) = Self::parse_object(&data[offset..], string_block)?;
- offset += item_size;
- items.push(item);
- }
- Ok((MonkeyType::Array(items), offset))
- }
- _ => Err(anyhow!("Unknown type ID: {}", type_id)),
- }
- }
- fn serialize(&self) -> Vec<u8> {
- let mut result = Vec::new();
- if let Some(sb) = &self.string_block {
- let sb_data = sb.serialize();
- if !sb_data.is_empty() {
- result.extend_from_slice(&STRING_BLOCK_SENTINEL.to_be_bytes());
- result.extend_from_slice(&(sb_data.len() as u32).to_be_bytes());
- result.extend(sb_data);
- }
- }
- let mut data_bytes = Vec::new();
- for obj in &self.data {
- data_bytes.extend(obj.serialize());
- }
- result.extend_from_slice(&DATA_BLOCK_SENTINEL.to_be_bytes());
- result.extend_from_slice(&(data_bytes.len() as u32).to_be_bytes());
- result.extend(data_bytes);
- result
- }
- fn print_structure(&self) {
- println!("=== String Block ===");
- if let Some(sb) = &self.string_block {
- for (offset, s) in &sb.strings {
- println!(" {:4}: \"{}\"", offset, s);
- }
- } else {
- println!(" (none)");
- }
- println!("\n=== Data Block ===");
- for (i, obj) in self.data.iter().enumerate() {
- println!(" [{}] {:?}", i, obj);
- }
- }
- fn garbage_collect(&mut self) {
- let mut new_sb = StringBlock {
- strings: BTreeMap::new(),
- };
- for obj in &mut self.data {
- Self::gc_visit(obj, &mut new_sb);
- }
- self.string_block = Some(new_sb);
- }
- fn gc_visit(obj: &mut MonkeyType, sb: &mut StringBlock) {
- match obj {
- MonkeyType::String(offset, Some(s)) => {
- *offset = sb.add_string(s.clone());
- }
- MonkeyType::Array(items) => {
- for item in items {
- Self::gc_visit(item, sb);
- }
- }
- MonkeyType::Hash(items) => {
- for (k, v) in items {
- Self::gc_visit(k, sb);
- Self::gc_visit(v, sb);
- }
- }
- _ => {}
- }
- }
- }
- #[derive(Parser)]
- #[command(author, version, about, long_about = None)]
- struct Cli {
- #[arg(value_name = "FILE")]
- filename: PathBuf,
- #[command(subcommand)]
- command: Option<Commands>,
- }
- #[derive(Subcommand)]
- enum Commands {
- Dump,
- Set { key: String, value: String },
- }
- fn main() -> Result<()> {
- let cli = Cli::parse();
- let mut file = File::open(&cli.filename).context("Failed to open file")?;
- let mut data = Vec::new();
- file.read_to_end(&mut data).context("Failed to read file")?;
- let mut parser = SetFile::parse(&data)?;
- match &cli.command {
- Some(Commands::Dump) | None => {
- parser.print_structure();
- }
- Some(Commands::Set { key, value }) => {
- if parser.data.is_empty() {
- return Err(anyhow!("Data is empty, cannot set value"));
- }
- // Find the key in the first Hash object
- let target_index = if let MonkeyType::Hash(items) = &parser.data[0] {
- items.iter().position(|(k, _)| {
- if let MonkeyType::String(_, Some(s)) = k {
- s == key
- } else {
- false
- }
- })
- } else {
- return Err(anyhow!("Expected first data element to be a hash"));
- };
- if let Some(idx) = target_index {
- let new_val = if value.eq_ignore_ascii_case("true") {
- MonkeyType::Bool(true)
- } else if value.eq_ignore_ascii_case("false") {
- MonkeyType::Bool(false)
- } else if let Ok(n) = value.parse::<i32>() {
- MonkeyType::Int(n)
- } else if let Ok(f) = value.parse::<f32>() {
- MonkeyType::Float(f)
- } else {
- let offset = parser.add_string(value.clone());
- MonkeyType::String(offset, Some(value.clone()))
- };
- if let MonkeyType::Hash(items) = &mut parser.data[0] {
- items[idx].1 = new_val;
- }
- println!("Updated {} = {}", key, value);
- parser.garbage_collect();
- let output_data = parser.serialize();
- let mut outfile =
- File::create(&cli.filename).context("Failed to create output file")?;
- outfile
- .write_all(&output_data)
- .context("Failed to write output file")?;
- } else {
- return Err(anyhow!("Key '{}' not found in settings", key));
- }
- }
- }
- Ok(())
- }
Advertisement
Add Comment
Please, Sign In to add comment