-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathzxd.zig
290 lines (246 loc) · 11 KB
/
zxd.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
const std = @import("std");
const pr = std.io.getStdOut().writer();
const eql = std.mem.eql;
const ip = std.ascii.isPrint;
const OutputType = enum {
hex,
binary,
plain,
};
const DataContainer = struct {
allocator: std.mem.Allocator,
inputBuffer: []const u8 = undefined,
outputBuffer: []const u8 = undefined,
outputType: OutputType = OutputType.hex,
startOffset: usize = 0,
endOffset: ?usize = null,
bytesPerLine: usize = 32, // calculated by taking the number of bits and multiplying by 6, need to make that make more sense at some point
wordSize: usize = 4, //number of characters per item (in a column)
outputToFile: bool = false,
outputFilename: []const u8 = undefined,
uppercaseHex: bool = false,
const Self = @This();
fn deinit(self: Self) void {
self.allocator.free(self.inputBuffer);
self.allocator.free(self.outputBuffer);
}
};
pub fn openFile(filename: []const u8, allocator: std.mem.Allocator, buffer: *[]const u8) void {
// Get file handler and defer it closing
const file = std.fs.cwd().openFile(filename, .{}) catch |err| {
std.log.err("Failed to open file: {s}\n", .{@errorName(err)});
return;
};
defer file.close();
const stat = file.stat() catch |err| {
std.log.err("Failed to read file metadata: {s}\n", .{@errorName(err)});
return;
};
buffer.* = file.readToEndAlloc(allocator, stat.size) catch |err| {
std.log.err("Failed to read file: {s}\n", .{@errorName(err)});
return;
};
}
pub fn createAndPrepFile(container: DataContainer) !void {
const file = std.fs.cwd().createFile(container.outputFilename, .{}) catch |err| {
std.log.err("Failed to create file for output: {s}\n", .{@errorName(err)});
return;
};
defer file.close();
file.writer().print("SETTINGS:{}:{d}\n\n", .{ container.outputType, container.bytesPerLine }) catch |err| {
std.log.err("Failed to write settings string to file: {s}\n", .{@errorName(err)});
return;
};
// very basic write to file
// would be much better if I could set pr to the file handler
var idx: usize = 0;
while (idx < container.outputBuffer.len) : (idx += container.wordSize) {
file.writer().print("{s} ", .{container.outputBuffer[idx..(idx + container.wordSize)]}) catch |err| {
std.log.err("Failed to write output to file: {s}\n", .{@errorName(err)});
return;
};
}
// container.printer = file.writer();
}
fn bytesToHex(bytes: []const u8, allocator: std.mem.Allocator, uppercase: bool) ![]const u8 {
var result = std.ArrayList(u8).init(allocator);
defer result.deinit();
for (bytes) |byte| {
if (uppercase) try result.writer().print("{X:0>2}", .{byte}) else try result.writer().print("{x:0>2}", .{byte});
}
return result.toOwnedSlice();
}
fn bytesToBinary(bytes: []const u8, allocator: std.mem.Allocator) ![]const u8 {
var result = std.ArrayList(u8).init(allocator);
defer result.deinit();
for (bytes) |byte| {
try result.writer().print("{b:0>8}", .{byte});
}
return result.toOwnedSlice();
}
fn printOutputBuffer(container: DataContainer) !void {
var startOffset = container.startOffset;
const endOffset = if (container.endOffset) |eO| (startOffset + eO) else container.outputBuffer.len;
var asciiStart: usize = undefined;
var asciiEnd: usize = undefined;
while (startOffset < endOffset) {
// Figure out how big the current line/chunk is
const chunk_size = @min(endOffset - startOffset, container.bytesPerLine);
const chunk = container.outputBuffer[startOffset..(startOffset + chunk_size)];
// Convert the translated position to ascii position
switch (container.outputType) {
OutputType.hex => {
asciiStart = startOffset / 2;
asciiEnd = asciiStart + (chunk_size / 2);
},
OutputType.binary => {
asciiStart = startOffset / 8;
asciiEnd = asciiStart + (chunk_size / 8);
},
OutputType.plain => {
try pr.print("{s}", .{container.outputBuffer});
break;
},
}
// Print byte offset
try pr.print("{x:0>8}: ", .{startOffset});
// Print the converted data
var j: usize = 0;
while (j < chunk_size) : (j += container.wordSize) {
try pr.print("{s} ", .{if (j + container.wordSize <= chunk_size) chunk[j..(j + container.wordSize)] else chunk[j..]});
}
// Pad output if shorter line
if (chunk_size < container.bytesPerLine) {
const missing_groups = (container.bytesPerLine - chunk_size) / (container.wordSize);
const padding_spaces = missing_groups * (container.wordSize + 1); // wordSize + 1 space
for (0..padding_spaces) |_| {
try pr.print(" ", .{});
}
}
// Print the associated ascii
try pr.print("{s}", .{"\t"});
for (container.inputBuffer[asciiStart..asciiEnd]) |char| {
if (ip(char)) {
try pr.print("{c}", .{char});
} else {
try pr.print("", .{});
}
}
try pr.print("\n", .{});
// Update the position
startOffset += chunk_size;
}
}
fn processCommandLineArgs(args: *std.process.ArgIterator, container: *DataContainer) !void {
while (args.next()) |arg| {
if (eql(u8, arg, "--help") or eql(u8, arg, "-h")) {
try pr.print("Usage: zxd [flags] [file]\n\n", .{});
try pr.print("Flags:\n", .{});
try pr.print(" -h, --help Print this help message\n", .{});
try pr.print(" -H, --hex Print data in hexadecimal format (default)\n", .{});
try pr.print(" -p, --plain Print data in plain (unspaced) hexadecimal format\n", .{});
try pr.print(" -u, --uppercase Print hexadecimal with uppercase characters\n", .{});
try pr.print(" -b, --binary Print data in binary format\n", .{});
try pr.print(" -f, --file Read input from specified file\n", .{});
try pr.print(" -- Read input from stdin\n", .{});
try pr.print(" -l, --length Specify number of bytes to read\n", .{});
try pr.print(" -s, --start Start reading from specified offset\n", .{});
try pr.print(" -c, --per-line Control the number of bytes per line\n", .{});
try pr.print(" -o, --output Specify a file for program output (stdout by default)\n", .{});
try pr.print(" -g, --group Specify how many bytes to group together (has to come after format specifier)\n", .{});
std.process.exit(0);
} else if (eql(u8, arg, "--hex") or eql(u8, arg, "-H")) {
container.outputType = OutputType.hex;
container.wordSize = 4;
container.bytesPerLine = 32;
} else if (eql(u8, arg, "--binary") or eql(u8, arg, "-b")) {
container.outputType = OutputType.binary;
container.wordSize = 8;
container.bytesPerLine = 48;
} else if (eql(u8, arg, "--plain") or eql(u8, arg, "-p")) {
container.outputType = OutputType.plain;
} else if (eql(u8, arg, "--reverse") or eql(u8, arg, "-r")) {
// container.outputType = OutputType.ascii;
try pr.print("Feature not ready yet\n", .{});
std.process.exit(0);
} else if (eql(u8, arg, "--uppercase") or eql(u8, arg, "-u")) {
container.uppercaseHex = true;
} else if (eql(u8, arg, "--file") or eql(u8, arg, "-f")) {
openFile(args.next().?, container.allocator, &container.inputBuffer);
} else if (eql(u8, arg, "--length") or eql(u8, arg, "-l")) {
container.endOffset = std.fmt.parseInt(usize, args.next().?, 10) catch |err| blk: {
std.log.err("Couldn't set value: {s}\n", .{@errorName(err)});
break :blk null;
};
} else if (eql(u8, arg, "--start") or eql(u8, arg, "-s")) {
container.startOffset = std.fmt.parseInt(u8, args.next().?, 10) catch |err| blk: {
std.log.err("Couldn't set value: {s}\n", .{@errorName(err)});
break :blk 0;
};
} else if (eql(u8, arg, "--per-line") or eql(u8, arg, "-c")) {
container.bytesPerLine = std.fmt.parseInt(u8, args.next().?, 10) catch |err| blk: {
std.log.err("Couldn't set value: {s}\n", .{@errorName(err)});
break :blk 32;
};
} else if (eql(u8, arg, "--output") or eql(u8, arg, "-o")) {
container.outputToFile = true;
container.outputFilename = args.next().?;
} else if (eql(u8, arg, "--")) {
const input = args.next().?;
container.inputBuffer = try container.allocator.dupe(u8, input);
} else if (eql(u8, arg, "--group") or eql(u8, arg, "-g")) {
const inputValue = std.fmt.parseInt(usize, args.next().?, 10) catch |err| blk: {
std.log.err("Couldn't get value: {s}\n", .{@errorName(err)});
break :blk 4;
};
if (inputValue < 1) {
std.log.err("Couldn't set value because it is less than 1\n", .{});
container.wordSize = 4;
} else {
container.wordSize = inputValue * 2;
}
}
}
}
pub fn readFromStdin(allocator: std.mem.Allocator, buffer: *[]const u8) !void {
const stdin = std.io.getStdIn();
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
const reader = stdin.reader();
var readBuf: [4096]u8 = undefined;
while (true) {
const bytesRead = try reader.read(&readBuf);
if (bytesRead == 0) break;
try buf.appendSlice(readBuf[0..bytesRead]);
}
buffer.* = try buf.toOwnedSlice();
}
pub fn main() !void {
// Init allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const deinit_status = gpa.deinit();
if (deinit_status == .leak) std.testing.expect(false) catch @panic("TEST FAIL");
}
// Handle CLI args
var args = try std.process.argsWithAllocator(gpa.allocator());
defer args.deinit();
_ = args.skip();
var container: DataContainer = .{ .allocator = gpa.allocator() };
defer container.deinit();
const stdin = std.io.getStdIn();
if (!stdin.isTty()) {
try readFromStdin(container.allocator, &container.inputBuffer);
}
try processCommandLineArgs(&args, &container);
container.outputBuffer = switch (container.outputType) {
OutputType.hex, OutputType.plain => try bytesToHex(container.inputBuffer, container.allocator, container.uppercaseHex),
OutputType.binary => try bytesToBinary(container.inputBuffer, container.allocator),
};
std.debug.print("input buf: {s}\n\n", .{container.inputBuffer});
if (container.outputToFile) {
try createAndPrepFile(container);
} else {
try printOutputBuffer(container);
}
}