///|
/// MoonBit Hex Editor — Wasm CLI (lightweight, no TUI)
/// Provides core analysis functions without native terminal dependencies.
fn main {
let all_args = @env.args()
// Skip program name (args[0])
let args : Array[String] = []
for i = 1; i < all_args.length(); i = i + 1 {
args.push(all_args[i])
}
if args.length() == 0 {
print_help()
return
}
let cmd = args[0]
match cmd {
"view" => cmd_view(args)
"struct" => cmd_struct(args)
"scan" => cmd_scan(args)
"hash" => cmd_hash(args)
"strings" => cmd_strings(args)
"entropy" => cmd_entropy(args)
"search" => cmd_search(args)
"encode" => cmd_encode(args)
"decode" => cmd_decode(args)
"help" => print_help()
_ =>
if cmd.contains(".") || cmd.contains("/") || cmd.contains("\\") {
cmd_view([cmd, cmd])
} else {
print_help()
}
}
}
///|
fn load(path : String) -> Bytes? {
let mut data : Bytes = Bytes::new(0)
try {
data = @fs.read_file_to_bytes(path)
} catch {
_ => {
println("Error: cannot read " + path)
return None
}
}
Some(data)
}
///|
fn find_arg(args : Array[String], cmd : String) -> String? {
for i = 0; i < args.length(); i = i + 1 {
if args[i] != cmd &&
(args[i].length() == 0 || args[i].get_char(0).unwrap() != '-') {
return Some(args[i])
}
}
None
}
///|
fn cmd_view(args : Array[String]) -> Unit {
let path = match find_arg(args, "view") {
Some(p) => p
None => {
println("Usage: view ")
return
}
}
let data = match load(path) {
Some(d) => d
None => return
}
let buf = @lib.HexBuffer::from_bytes(data)
println(@lib.format_buffer_info(buf))
println("")
let len = if buf.length() > 256 { 256 } else { buf.length() }
println(@lib.format_hex_dump(buf, length=len))
if buf.length() > 256 {
println("... (\{buf.length() - 256} more bytes)")
}
}
///|
fn cmd_struct(args : Array[String]) -> Unit {
let path = match find_arg(args, "struct") {
Some(p) => p
None => {
println("Usage: struct ")
return
}
}
let data = match load(path) {
Some(d) => d
None => return
}
let fields = @lib.parse_structure(data)
if fields.length() == 0 {
println("No known structure detected.")
return
}
println("File: \{path} (\{@lib.format_size(data.length())})")
println("")
println(@lib.format_structure(fields))
}
///|
fn cmd_scan(args : Array[String]) -> Unit {
let path = match find_arg(args, "scan") {
Some(p) => p
None => {
println("Usage: scan ")
return
}
}
let data = match load(path) {
Some(d) => d
None => return
}
let matches = @lib.scan_signatures(data)
for i = 0; i < matches.length(); i = i + 1 {
let m = matches[i]
println(
"0x\{@lib.to_hex_string(m.offset, width=8)} \{m.name} (\{@lib.format_size(m.size)})",
)
}
println("\{matches.length()} signatures found")
}
///|
fn cmd_hash(args : Array[String]) -> Unit {
let path = match find_arg(args, "hash") {
Some(p) => p
None => {
println("Usage: hash ")
return
}
}
let data = match load(path) {
Some(d) => d
None => return
}
let results = @lib.hash_all(data)
println("File: \{path} (\{@lib.format_size(data.length())})")
for i = 0; i < results.length(); i = i + 1 {
println("\{results[i].name} \{results[i].value}")
}
}
///|
fn cmd_strings(args : Array[String]) -> Unit {
let path = match find_arg(args, "strings") {
Some(p) => p
None => {
println("Usage: strings ")
return
}
}
let data = match load(path) {
Some(d) => d
None => return
}
let matches = @lib.find_strings(data)
for i = 0; i < matches.length(); i = i + 1 {
println(
"0x\{@lib.to_hex_string(matches[i].offset, width=8)} \{matches[i].text}",
)
}
println("\{matches.length()} strings found")
}
///|
fn cmd_entropy(args : Array[String]) -> Unit {
let path = match find_arg(args, "entropy") {
Some(p) => p
None => {
println("Usage: entropy ")
return
}
}
let data = match load(path) {
Some(d) => d
None => return
}
let blocks = @lib.entropy_scan(data)
for i = 0; i < blocks.length(); i = i + 1 {
let b = blocks[i]
let ev = (b.entropy * 10.0).to_int()
let es = (ev / 10).to_string() + "." + (ev % 10).to_string()
println("0x\{@lib.to_hex_string(b.start, width=8)} \{es}")
}
}
///|
fn cmd_search(args : Array[String]) -> Unit {
let path = match find_arg(args, "search") {
Some(p) => p
None => {
println("Usage: search ")
return
}
}
let mut pattern = ""
let mut found_file = false
for i = 0; i < args.length(); i = i + 1 {
if args[i] != "search" &&
(args[i].length() == 0 || args[i].get_char(0).unwrap() != '-') {
if !found_file {
found_file = true
} else {
pattern = args[i]
break
}
}
}
if pattern == "" {
println("Usage: search ")
return
}
let data = match load(path) {
Some(d) => d
None => return
}
match @lib.find_hex_pattern(data, pattern) {
Some((offsets, _lens)) => {
println("Found \{offsets.length()} match(es):")
let show = if offsets.length() < 10 { offsets.length() } else { 10 }
for i = 0; i < show; i = i + 1 {
println(" #\{i + 1} @ 0x\{@lib.to_hex_string(offsets[i], width=8)}")
}
if offsets.length() > show {
println(" ... +\{offsets.length() - show} more")
}
}
None =>
match @lib.find_text_pattern(data, pattern) {
Some((offsets, _lens)) => {
println("Found \{offsets.length()} match(es):")
let show = if offsets.length() < 10 { offsets.length() } else { 10 }
for i = 0; i < show; i = i + 1 {
println(
" #\{i + 1} @ 0x\{@lib.to_hex_string(offsets[i], width=8)}",
)
}
}
None => println("Invalid pattern")
}
}
}
///|
fn cmd_encode(args : Array[String]) -> Unit {
let enc_type = match find_arg(args, "encode") {
Some(t) => t
None => {
println("Usage: encode ")
return
}
}
let mut input = ""
let mut found_type = false
for i = 0; i < args.length(); i = i + 1 {
if args[i] != "encode" &&
(args[i].length() == 0 || args[i].get_char(0).unwrap() != '-') {
if !found_type {
found_type = true
} else {
input = args[i]
break
}
}
}
if input == "" {
println("Usage: encode ")
return
}
let data = Bytes::makei(input.length(), fn(i) {
let c = input.get_char(i).unwrap().to_int()
if c <= 127 {
c.to_byte()
} else {
b'?'
}
})
let result = match enc_type {
"base64" => @lib.base64_encode(data)
"url" => @lib.url_encode(data)
"unicode" => @lib.unicode_encode(data)
"hex" => @lib.hex_encode(data)
_ => {
println("Unknown type. Supported: base64, url, unicode, hex")
return
}
}
println(result)
}
///|
fn cmd_decode(args : Array[String]) -> Unit {
let dec_type = match find_arg(args, "decode") {
Some(t) => t
None => {
println("Usage: decode ")
return
}
}
let mut input = ""
let mut found_type = false
for i = 0; i < args.length(); i = i + 1 {
if args[i] != "decode" &&
(args[i].length() == 0 || args[i].get_char(0).unwrap() != '-') {
if !found_type {
found_type = true
} else {
input = args[i]
break
}
}
}
if input == "" {
println("Usage: decode ")
return
}
let decoded : Bytes? = match dec_type {
"base64" => @lib.base64_decode(input)
"url" => @lib.url_decode(input)
"unicode" => @lib.unicode_decode(input)
"hex" => @lib.hex_decode(input)
_ => {
println("Unknown type. Supported: base64, url, unicode, hex")
return
}
}
match decoded {
Some(bytes) => {
let sb = StringBuilder()
for i = 0; i < bytes.length(); i = i + 1 {
let b = bytes[i].to_int()
if b >= 0x20 && b <= 0x7E {
sb.write_char(b.to_char().unwrap())
} else {
sb.write_char('.')
}
}
println(sb.to_string())
}
None => println("Invalid input")
}
}
///|
fn print_help() -> Unit {
println("MoonBit Hex Editor — Wasm CLI")
println("")
println("Commands:")
println(" view Hex dump (first 256 bytes)")
println(" struct Structure analysis")
println(" scan Signature scan")
println(" hash Hash (MD5/SHA1/SHA256/SHA224/SM3)")
println(" strings String extraction")
println(" entropy Entropy analysis")
println(" search Search (hex: FF ?? 00 | text)")
println(" encode Encode (base64/url/unicode/hex)")
println(" decode Decode (base64/url/unicode/hex)")
println(" help This help")
}