@Sze: Oh, this is elegant. Thank you! I’ve tried to implement it, here:
// xencode.zig: Human-readable plain-text encoding for binary files
// CLD rev. 2025-07-11_16:20
const std = @import("std");
const builtin = @import("builtin");
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const ap_allocator = arena.allocator();
var tw: u8 = 65; // output text width
var twct: u8 = 1; // tw counter
pub fn main() !void {
defer arena.deinit();
const args = try std.process.argsAlloc(ap_allocator);
if (args.len < 2) {
showHelp();
return;
}
var omode: i8 = 0; // output mode: -1=printable only,
// 0=suppress XyWrite readability aids
var i: u8 = 1;
var byte2: u16 = undefined;
var byte3: u16 = undefined;
const tab: []const u8 = "{tab}";
const crlf0: []const u8 = "[013+010]";
const crlf: []const u8 = "[cr|lf]";
const lbrc: []const u8 = "{091}";
const rbrc: []const u8 = "{093}";
const lguil: []const u8 = "{<}";
const rguil: []const u8 = "{>}";
const spce: []const u8 = "{032}";
var ctr: u8 = 0;
var n: u32 = 0;
var use_file: bool = false;
// Poll command-line options (all -# option args must come first)
while (i < args.len) {
if (std.mem.eql(u8, args[i], "-?")) {
showHelp();
return;
}
if (std.mem.eql(u8, args[i], "/?")) {
showHelp();
return;
}
if (std.mem.eql(u8, args[i], "--help")) {
showHelp();
return;
}
if (std.mem.eql(u8, args[i], "-a")) {
omode = -1;
i += 1;
continue;
}
if (std.mem.eql(u8, args[i], "/a")) {
omode = -1;
i += 1;
continue;
}
if (std.mem.eql(u8, args[i], "-x")) {
omode = 1;
i += 1;
continue;
}
if (std.mem.eql(u8, args[i], "/x")) {
omode = 1;
i += 1;
continue;
}
if (std.mem.eql(u8, args[i], "-w")) {
if (i + 1 <= args.len - 1) {
const twa = try std.fmt.parseInt(u8, args[i + 1], 10);
tw = twa;
i += 2;
continue;
}
}
if (std.mem.eql(u8, args[i], "/w")) {
if (i + 1 <= args.len - 1) {
const twa = try std.fmt.parseInt(u8, args[i + 1], 10);
tw = twa;
i += 2;
continue;
}
}
break;
}
if (i > args.len - 1) {
showHelp();
return;
}
const file_in: []u8 = args[i];
std.fs.cwd().access(file_in, .{}) catch |err| {
switch (err) {
error.FileNotFound => {
std.debug.print("File not found: \"{s}\"\n", .{file_in});
return;
},
else => unreachable,
}
};
var file_ex: []u8 = undefined;
if (i + 1 == args.len - 1) {
file_ex = args[i + 1];
use_file = true;
}
const data: []u8 = try fileRead(file_in);
const file = if(use_file) try std.fs.cwd().createFile(file_ex, .{})
else undefined;
defer if(use_file) file.close();
var buffered = std.io.bufferedWriter(if(use_file) file.writer()
else std.io.getStdOut().writer());
const out = buffered.writer();
try out.writeAll("XPLeNCODE v2.0 (xencode.exe)");
try addNewline(out);
try out.writeAll("b-gin [UNTITLED]");
try addNewline(out);
while (n < data.len) {
if (omode < 0) {
if (data[n] > 32 and data[n] < 127) {
try writeByteLnBk(data[n], out);
}
else if (data[n] == 32) {
if (twct > 1 and twct < tw - 1) try writeByteLnBk(' ', out)
else try writeAllLnBk(spce, out);
} else {
try writeByteLnBk('.', out);
}
n += 1;
continue;
}
ctr = 0;
try switch (data[n]) {
9 => {
switch (omode) {
0 => try putCharNumBr(9, out),
1 => try writeAllLnBk(tab, out),
else => try writeByteLnBk('.', out),
}
},
13 => {
if (omode < 0) {
try writeByteLnBk('.', out);
n += 1;
continue;
}
if (data[n + 1] == 10) {
if (omode > 0) {
try writeAllLnBk(crlf, out);
} else {
try writeAllLnBk(crlf0, out);
}
n += 1;
} else {
try putCharNumBr(data[n], out);
}
},
32 => {
if (twct > 1 and twct < tw - 1) try writeByteLnBk(' ', out)
else try writeAllLnBk(spce, out);
},
33...90 => try writeByteLnBk(data[n], out),
91 => {
if (omode > 0) try writeAllLnBk(lbrc, out)
else try writeByteLnBk(data[n], out);
},
92 => try writeByteLnBk(data[n], out),
93 => {
if (omode > 0) try writeAllLnBk(rbrc, out)
else try writeByteLnBk(data[n], out);
},
94...122 => try writeByteLnBk(data[n], out),
123 => putCharNumBr(data[n], out),
124 => try writeByteLnBk(data[n], out),
125 => putCharNumBr(data[n], out),
174 => {
if (omode > 0) try writeAllLnBk(lguil, out)
else try putCharNumBr(data[n], out);
},
175 => {
if (omode > 0) try writeAllLnBk(rguil, out)
else try putCharNumBr(data[n], out);
},
254 => { // Speedo charset
if (omode < 1) {
try putCharNumBr(data[n], out);
n += 1;
continue;
}
if (data.len - n < 3) {
try putCharNumBr(data[n], out);
n += 1;
continue;
}
byte2 = data[n + 1];
byte3 = data[n + 2];
if (byte3 < 2 or (byte3 < 3 and byte2 < 232)) {
try putXySpeedoChar(byte2, byte3, out);
n += 3;
} else {
// Generic 3-byter [254+nnn+nnn]
try put3byterBr(254, byte2, byte3, out);
n += 3;
continue;
}
},
255 => {
if (omode < 1) {
try putCharNumBr(data[n], out);
n += 1;
continue;
}
if (data.len - n < 3) {
try putCharNumBr(data[n], out);
n += 1;
continue;
}
byte2 = data[n + 1];
byte3 = data[n + 2];
// 3-byte functions
if (byte2 > 127 and byte2 < 131 and byte3 % 2 == 1) {
try putXyFunc(byte2, byte3, out);
n += 3;
continue;
}
// Search wildcards
else if ((byte2 == 192 and (byte3 == 145 or byte3 == 153 or byte3 == 155 or byte3 == 173 or byte3 == 174 or (byte3 >= 176 and byte3 <= 185) or byte3 == 193 or byte3 == 204 or byte3 == 206 or byte3 == 207 or byte3 == 211 or byte3 == 215 or byte3 == 216)) or (byte2 == 193 and (byte3 == 46 or byte3 == 47))) {
try putXyWildcard(byte3, out);
n += 3;
continue;
} else {
// Generic 3-byter [255+nnn+nnn]
try put3byterBr(255, byte2, byte3, out);
n += 3;
continue;
}
},
else => try putCharNumBr(data[n], out),
};
n += 1;
}
if (twct > 1) try addNewline(out);
try out.writeAll("-nd");
try addNewline(out);
try out.writeAll("XPLeNCODE");
try addNewline(out);
try buffered.flush();
}
pub fn showHelp() void {
std.debug.print("Human-readable Plain-text Encoding for Binary Files\n [CLD rev. 2025-07-11]\n\nUsage (optional arguments first):\nxencode [-a|-x] [-w <number>] file_in [file_out] | [-?]\n\nIf file_out is omitted, output is directed to stdout.\n\nOptions:\n -a outputs Ascii 32-126 only (not decodable)\n -x applies XyWrite readability aids to output\n -w <number> changes text width of output to <number>\n (default = 65 characters per line)\n -? shows this help\n", .{});
}
pub fn fileRead(fname: []const u8) ![]u8 {
const fileContents = try std.fs.cwd().readFileAlloc(
ap_allocator,
fname,
std.math.maxInt(u32));
return fileContents;
}
pub fn writeByteLnBk(byte: u8, wr: anytype) !void {
try wr.writeByte(byte);
twct += 1;
if (twct > tw) {
if (builtin.target.os.tag == .windows) try wr.writeByte(13);
try wr.writeByte(10);
twct = 1;
}
}
pub fn writeAllLnBk(bytes: []const u8, wr: anytype) !void {
for (bytes) |b| try writeByteLnBk(b, wr);
}
pub fn addNewline(wr: anytype) !void {
if (builtin.target.os.tag == .windows) try wr.writeByte(13);
try wr.writeByte(10);
}
pub fn putCharNumBr(char_in: u8, wr: anytype) !void {
var buffer: [5]u8 = undefined;
const charnum: []u8 = try std.fmt.bufPrint(
&buffer,
"{{{d:0>3}}}",
.{char_in});
try writeAllLnBk(charnum, wr);
}
pub fn put3byterBr(byte1: u16, byte2: u16, byte3: u16, wr: anytype) !void {
var buffer: [13]u8 = undefined;
const brace3: []u8 = try std.fmt.bufPrint(
&buffer,
"[{d:0>3}+{d:0>3}+{d:0>3}]",
.{byte1, byte2, byte3});
try writeAllLnBk(brace3, wr);
}
pub fn putXyFunc(byte_2: u16, byte_3: u16, wr: anytype) !void {
const index: u32 = (256 * (byte_2 % 128) / 2) + (byte_3 / 2);
var afunc: [5]u8 = undefined;
const func_no = [_]u8{'[', '2', '5', '5', '+', '1', '2', '9', '+',
'1', '6', '3', ']'};
const func_nos = func_no[0..];
const xyfuncs = [_]*const [2:0]u8{
"@0", "@1", "@2", "@3", "@4", "@5", "@6", "@7", "@8", "@9", "@A", "@B",
"@C", "@D", "@E", "@F", "@G", "@H", "@I", "@J", "@K", "@L", "@M", "@N",
"@O", "@P", "@Q", "@R", "@S", "@T", "@U", "@V", "@W", "@X", "@Y", "@Z",
"AD", "AS", "BF", "BK", "BS", "CC", "CD", "CH", "CI", "CL", "CM", "CN",
"CP", "CR", "CS", "CU", "DC", "DF", "GH", "DL", "DP", "DS", "DW", "EL",
"ER", "EX", "GT", "HM", "M0", "M1", "M2", "M3", "M4", "M5", "M6", "M7",
"M8", "MD", "MU", "MV", "NC", "NL", "NK", "NP", "NR", "NS", "NT", "NW",
"PC", "PD", "PL", "PP", "PR", "PS", "PT", "PU", "PW", "R0", "R1", "R2",
"R3", "R4", "R5", "R6", "R7", "R8", "R9", "RC", "RD", "RE", "RL", "RP",
"RS", "RV", "RW", "SD", "SH", "SI", "SK", "SM", "SN", "SS", "SU", "SV",
"TF", "TI", "TN", "TS", "UD", "WA", "WC", "WL", "WN", "WS", "WX", "WW",
"XC", "XD", "DT", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "SP", "BC",
"LB", "LE", "NF", "PF", "TP", "BD", "MS", "NM", "LD", "LL", "LR", "LU",
"UP", "FF", "YD", "DO", "DX", "MK", "SO", "OP", "WZ", "NX", "SW", "FD",
"FM", "TL", "TR", "TE", "ED", "EE", "HC", "EC", "MC", "#1", "#2", "#3",
"#4", "#5", "#6", "#7", "#8", "#9", "$1", "$2", "$3", "$4", "$5", "$6",
"$7", "$8", "$9", "DR", "EN", "C0", "C1", "C2", "C3", "C4", "C5", "C6",
"C7", "C8", "C9", "EF", "IB", "NO", "NI", "CO", "$0", "LS", "XP", "WG",
"XM", "&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&A",
"&B", "&C", "&D", "&E", "&F", "&G", "&H", "&I", "&J", "&K", "&L", "&M",
"&N", "&O", "&P", "&Q", "&R", "&S", "&T", "&U", "&V", "&W", "&X", "&Y",
"&Z", "HL", "$A", "$B", "$C", "$D", "$E", "$F", "$G", "$H", "$I", "$J",
"$K", "$L", "$M", "$N", "$O", "$P", "$Q", "$R", "$S", "$T", "$U", "$V",
"$W", "$X", "$Y", "$Z", "XX", "H@", "VH", "MW", "QH", "DK", "SR", "SC",
"TG", "H1", "JH", "DZ", "DD", "DM", "LT", "RK", "NN", "MT", "ET", "ZT",
"T1", "TT", "<<", ">>", "IT", "SL", "SF", "FL", "FR", "FC", "SY", "ME",
"AC", "FS", "TW", "MI", "RO", "NB", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6",
"Q7", "Q8", "TO", "IR", "AR", "AX", "DB", "DE", "HF", "SA", "OV", "TC",
"TB", "JM", "SG", "XH", "FT", "BX", "MN", "CB", "M9", "MZ", "ZZ", "RX",
"ST", "KF", "JC", "AK", "TM", "NU", "B4", "QP", "HG", "US", "XE", "ES",
"RB", "S-", "S+", "**", "BN", "RU", "CF", "UI", "XS", "EA", "BT", "KD",
"DN", "HI", "WH", "XN", "FX", "UN", "MX", "AZ", "BR", "HK", "#X", "??",
"BM", "JR", "XO", "XW", "TX", "LF", "LO", "BL", "XT", "WT", "IC", "CT",
"VB", "-D", "WD", "RM", "LM", "aL", "aR", "aB", "aE", "MP", "mN", "QL",
"QR", "MF"};
if (index < xyfuncs.len) {
afunc[0] = '[';
afunc[1] = xyfuncs[index][0];
afunc[2] = xyfuncs[index][1];
if (afunc[1] == 'N' and afunc[2] == 'O') {
try writeAllLnBk(func_nos, wr); // workaround for flaky func NO
return;
}
afunc[3] = '_';
afunc[4] = ']';
try writeAllLnBk(afunc[0..], wr);
}
}
pub fn putXyWildcard(byte_3: u16, wr: anytype) !void {
var wild: [4]u8 = undefined;
wild[0] = '[';
wild[1] = 'w';
var wild1: [5]u8 = undefined;
wild1[0] = '[';
wild1[1] = 'w';
var wilddot = [_]u8{
'[', '2', '5', '5', '+', '1', '9', '2', '+', '1', '7', '4', ']'};
var c: u3 = 2;
switch (byte_3) {
46 => {wild[c] = '<'; c += 1;},
47 => {wild[c] = '>'; c += 1;},
145 => {wild1[c] = '1'; wild1[c + 1] = '3'; c += 2;},
153 => {wild1[c] = '1'; wild1[c + 1] = '0'; c += 2;},
155 => {wild[c] = 'C'; c += 1;},
173 => {wild[c] = '-'; c += 1;},
174 => {wild[c] = '.'; c += 1;},
176 => {wild[c] = '0'; c += 1;},
177 => {wild[c] = '1'; c += 1;},
178 => {wild[c] = '2'; c += 1;},
179 => {wild[c] = '3'; c += 1;},
180 => {wild[c] = '4'; c += 1;},
181 => {wild[c] = '5'; c += 1;},
182 => {wild[c] = '6'; c += 1;},
183 => {wild[c] = '7'; c += 1;},
184 => {wild[c] = '8'; c += 1;},
185 => {wild[c] = '9'; c += 1;},
193 => {wild[c] = 'A'; c += 1;},
204 => {wild[c] = 'L'; c += 1;},
206 => {wild[c] = 'N'; c += 1;},
207 => {wild[c] = 'O'; c += 1;},
211 => {wild[c] = 'S'; c += 1;},
215 => {wild[c] = 'W'; c += 1;},
216 => {wild[c] = 'X'; c += 1;},
else => wild[0] = 0,
}
if (wild[2] == '.') {
try writeAllLnBk(wilddot[0..], wr);
return;
}
if (c > 3) {
wild1[c] = ']';
try writeAllLnBk(wild1[0..], wr);
} else {
wild[c] = ']';
try writeAllLnBk(wild[0..], wr);
}
}
pub fn putXySpeedoChar(byte_2: u16, byte_3: u16, wr: anytype) !void {
const index: u32 = (256 * (1 + byte_3)) + byte_2;
var buffer: [5]u8 = undefined;
const speedo: []u8 = try std.fmt.bufPrint(
&buffer,
"[{d}]", .{index});
try writeAllLnBk(speedo, wr);
}