From ab4b0f92bba8bbbfe7fecdad7e6df0d627e25a64 Mon Sep 17 00:00:00 2001 From: nori li <50680474+thenorili@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:13:05 -0700 Subject: [PATCH] Add output format for the game "Turing Complete" Players of the game "Turing Complete" often use customasm to design a richer assembly language than the game provides and then copy the resulting binary into the game. This change introduces an annotated format compatible with the Turing Complete assembly editor, using '#' for comments and '0x' or '0b' to prefix data groups. --- src/driver.rs | 12 ++++ src/usage_help.md | 9 ++- src/util/bitvec_format.rs | 138 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/driver.rs b/src/driver.rs index e921f429..9ea66dc9 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -25,6 +25,10 @@ struct CommandOutput pub enum OutputFormat { Binary, + TMGame { + base: usize, + group: usize, + }, Annotated { base: usize, group: usize, @@ -568,6 +572,11 @@ pub fn parse_output_format( group: get_arg_usize("group", 2, check_nonzero)?, }, + "tmgame" => OutputFormat::TMGame{ + base: get_arg_usize("base", 16, check_valid_base)?, + group: get_arg_usize("group", 2, check_nonzero)?, + }, + "annotatedhex" => OutputFormat::Annotated { base: 16, group: 2, @@ -730,6 +739,9 @@ pub fn format_output( OutputFormat::Annotated { base, group } => output.format_annotated(fileserver, base, group), + OutputFormat::TMGame { base, group } => + output.format_tcgame(fileserver, base, group), + OutputFormat::BinStr => output.format_binstr(), OutputFormat::HexStr => output.format_hexstr(), diff --git a/src/usage_help.md b/src/usage_help.md index f88d4192..8e37a15c 100644 --- a/src/usage_help.md +++ b/src/usage_help.md @@ -61,7 +61,14 @@ Examples: * `annotated,base:16,group:2` Annotates the output data with snippets - of the source code. + of the source code. Supports base 2 and 16. +* `tcgame,base:16,group:2` + Annotates the output data with snippets + of the source code in a format compatible + with the assembly editor for the game + 'Turing Complete'. Supports base 2 and 16. + Comments out annotations with '#' and prefixes + each group with 0x or 0b. * `annotatedbin` Same as: `annotated,base:2,group:8` diff --git a/src/util/bitvec_format.rs b/src/util/bitvec_format.rs index ed2cb251..b17f79f4 100644 --- a/src/util/bitvec_format.rs +++ b/src/util/bitvec_format.rs @@ -357,6 +357,144 @@ impl util::BitVec result } + // Turing Complete is a game in which you advance from nand gates to + // computer architecture. Its assembly editor uses `#` comments, + // `0b` and `0x` prefixes for binary and hex, and groups of 8 or 8x4 bytes. + // This format produces annotated bytecode that meets these constraints. + pub fn format_tcgame( + &self, + fileserver: &dyn util::FileServer, + base: usize, + digits_per_group: usize) + -> String + { + let mut result = String::new(); + assert!(base == 2 || base == 16); + let prefix: &str = if base == 2 { "0b" } else { "0x" }; + let comment: &str = "#"; + + let bits_per_digit = (base - 1).count_ones() as usize; + let bits_per_group = digits_per_group * bits_per_digit; + + let mut outp_width = 2; + let mut outp_bit_width = 1; + let mut addr_width = 4; + let mut content_width = (digits_per_group + 1) * 1 - 1; + + let mut sorted_spans = self.spans.clone(); + sorted_spans.sort_by(|a, b| + a.offset.cmp(&b.offset)); + + for span in &sorted_spans + { + if let Some(offset) = span.offset + { + outp_width = std::cmp::max( + outp_width, + format!("{:x}", offset / bits_per_group).len()); + + outp_bit_width = std::cmp::max( + outp_bit_width, + format!("{:x}", offset % bits_per_group).len()); + + addr_width = std::cmp::max( + addr_width, + format!("{:x}", span.addr).len()); + + let data_digits = span.size / bits_per_digit + if span.size % bits_per_digit == 0 { 0 } else { 1 }; + let this_content_width = data_digits + data_digits / digits_per_group; + + if this_content_width > 1 && this_content_width <= (digits_per_group + 1) * 5 + { + content_width = std::cmp::max( + content_width, + this_content_width - 1); + } + } + } + // differs from format-annotated only in the pound + result.push_str(&format!("{comment} {:>1$} |", "outp", outp_width + outp_bit_width + 1)); + result.push_str(&format!(" {:>1$} |", "addr", addr_width)); + result.push_str(&format!(" data (base {})", base)); + result.push_str("\n"); + result.push_str("\n"); + + let mut prev_file_handle = util::FileServerHandle::MAX; + let mut prev_file_chars = "".to_string(); + + for span in &sorted_spans + { + result.push_str(&format!("{comment} ")); + // offset + if let Some(offset) = span.offset + { + result.push_str(&format!(" {:1$x}", offset / bits_per_group, outp_width)); + result.push_str(&format!(":{:1$x} | ", offset % bits_per_group, outp_bit_width)); + } + else + { + result.push_str(&format!(" {:>1$}", "--", outp_width)); + result.push_str(&format!(":{:>1$} | ", "-", outp_bit_width)); + } + + // addr + result.push_str(&format!("{:1$x} \n", span.addr, addr_width)); + + // instruction excerpt + if span.span.file_handle != prev_file_handle + { + prev_file_handle = span.span.file_handle; + prev_file_chars = fileserver + .get_str_unwrap(prev_file_handle); + } + + // FIXME - probably don't want the span locations + let span_location = span.span.location().unwrap(); + let char_counter = util::CharCounter::new(&prev_file_chars); + result.push_str(&format!("{comment} {}\n", char_counter.get_excerpt(span_location.0, span_location.1))); + + + // bits + let mut contents_str = String::new(); + let digit_num = span.size / bits_per_digit + if span.size % bits_per_digit == 0 { 0 } else { 1 }; + for digit_index in 0..digit_num + { + if digit_index % digits_per_group == 0 + { + if digit_index > 0 + { + contents_str.push_str(" "); + } + contents_str.push_str(prefix); + } + + let mut digit = 0; + for bit_index in 0..bits_per_digit + { + let i = span.offset.unwrap() + digit_index * bits_per_digit + bit_index; + let bit = self.read_bit(i); + + digit <<= 1; + digit |= if bit { 1 } else { 0 }; + } + + let c = if digit < 10 + { ('0' as u8 + digit) as char } + else + { ('a' as u8 + digit - 10) as char }; + + contents_str.push(c); + } + + + result.push_str(&format!("{:1$}", contents_str, content_width)); + // moved the excerpt before the instruction + // result.push_str(&format!(" ; {}", char_counter.get_excerpt(span_location.0, span_location.1))); + result.push_str("\n"); + } + result + } + pub fn format_annotated( &self,