diff --git a/README.md b/README.md index b395fd8..9817589 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ 00000130 00 00 00 00 00 00 00 00 ac 2a 07 00 18 00 00 00 .........*...... 00000140 68 62 06 00 a0 00 00 00 00 00 00 00 00 00 00 00 hb.............. 00000150 c8 32 07 00 cc 02 00 00 00 00 00 00 00 00 00 00 .2.............. - 352 B | 0 B/ 708.00 KB | 0.049% + 352 B | 0 B/ 708.00 KB | 0.0495% ``` ddhx is a quick and dirty TUI hexadecimal viewer meant to replace my diff --git a/docs/commands.md b/docs/commands.md index 77d4f3a..128d23c 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -5,7 +5,7 @@ Notes: - Some commands take command parameters, e.g. `search u8 0xdd`. -- Some commands have _aliases_, e.g. `sb 0xdd` is the same as `search u8 0xdd`. +- Some commands have _aliases_, e.g. `sb 0xdd` is the same as `search u8 0xdd` and `search byte 0xdd`. - Some commands have a shortcut, e.g. pressing `r` while outside of command mode executes `refresh`. @@ -14,12 +14,12 @@ | Command | Sub-command | Alias | Description | |---|---|---|---| | search | u8 | sb | Search one byte | -| | u16 | | Search a 2-byte value (LSB) | -| | u32 | | Search a 4-byte value (LSB) | -| | u64 | | Search a 8-byte value (LSB) | +| | u16 | sw | Search a 2-byte value | +| | u32 | sd | Search a 4-byte value | +| | u64 | sl | Search a 8-byte value | | | utf8 | ss | Search an UTF-8 string | -| | utf16 | sw | Search an UTF-16LE string | -| | utf32 | | Search an UTF-32LE string | +| | utf16 | sws | Search an UTF-16LE string | +| | utf32 | sds | Search an UTF-32LE string | | goto | | g | Goto to a specific file location or jump to a relative offset (shortcut: g) | | info | | i | Print file information on screen (shortcut: i) | | offset | | o | Change display mode (hex, dec, oct), same as `set offset` | diff --git a/src/ddhx/ddhx.d b/src/ddhx/ddhx.d index 62a4bfc..3e1c1b7 100644 --- a/src/ddhx/ddhx.d +++ b/src/ddhx/ddhx.d @@ -102,9 +102,9 @@ ddhxUpdateOffsetbar; if (ddhxDrawRaw < globals.termHeight - 2) - ddhxUpdateInfobar; + ddhxUpdateStatusbar; else - ddhxUpdateInfobarRaw; + ddhxUpdateStatusbarRaw; InputInfo k; L_KEY: @@ -270,14 +270,14 @@ /// Refresh the entire screen void ddhxRefresh() { ddhxPrepBuffer(); + input.seek(input.position); globals.buffer = input.read(); conclear(); ddhxUpdateOffsetbar(); - ddhxDrawRaw(); if (ddhxDrawRaw() < conheight - 2) - ddhxUpdateInfobar; + ddhxUpdateStatusbar; else - ddhxUpdateInfobarRaw; + ddhxUpdateStatusbarRaw; } /// Update the upper offset bar. @@ -309,15 +309,15 @@ } /// Update the bottom current information bar. -void ddhxUpdateInfobar() { +void ddhxUpdateStatusbar() { conpos(0, conheight - 1); - ddhxUpdateInfobarRaw; + ddhxUpdateStatusbarRaw; } /// Updates information bar without cursor position call. -void ddhxUpdateInfobarRaw() { +void ddhxUpdateStatusbarRaw() { char[32] c = void, t = void; - with (globals) writef(" %*s | %*s | %*s | %7.3f%%", + with (globals) writef(" %*s | %*s/%*s | %7.4f%%", 7, formatsize(c, input.bufferSize), // Buffer size 10, formatsize(t, input.position), // Formatted position 10, fileSizeString, // Total file size @@ -349,9 +349,9 @@ input.seek(pos); globals.buffer = input.read(); if (ddhxDraw < conheight - 2) - ddhxUpdateInfobar(); + ddhxUpdateStatusbar(); else - ddhxUpdateInfobarRaw(); + ddhxUpdateStatusbarRaw(); } else ddhxMsgLow("Navigation disabled, buffer too small"); } diff --git a/src/ddhx/error.d b/src/ddhx/error.d index d991acf..f673fd2 100644 --- a/src/ddhx/error.d +++ b/src/ddhx/error.d @@ -5,8 +5,12 @@ unknown, exception, fileEmpty, + inputEmpty, invalidParameter, invalidNumber, + notFound, + overflow, + unparsable, eof, } @@ -32,15 +36,13 @@ switch (errorCode) with (DdhxError) { case exception: return errorMsg; case fileEmpty: return "File is empty."; + case inputEmpty: return "Input is empty."; case invalidParameter: return "Parameter is invalid."; case eof: return "Unexpected end of file (EOF)."; + case notFound: return "Input not found."; + case overflow: return "Integer overflow."; + case unparsable: return "Integer could not be parsed."; case none: return "No errors occured."; default: return "Unknown error occured."; } } - -int ddhxPrintError(string func = __FUNCTION__) { - import std.stdio : stderr, writefln; - - return 0; -} \ No newline at end of file diff --git a/src/ddhx/input.d b/src/ddhx/input.d index 2877d4a..58c25bb 100644 --- a/src/ddhx/input.d +++ b/src/ddhx/input.d @@ -37,9 +37,10 @@ size = file.size(); if (size == 0) return ddhxError(DdhxError.fileEmpty); - read = &readFile; - seek = &seekFile; mode = InputMode.file; + seek = &seekFile; + read = &readFile; + readBuffer = &readBufferFile; return 0; } catch (Exception ex) { return ddhxError(ex); @@ -51,19 +52,21 @@ if (size == 0) return ddhxError(DdhxError.fileEmpty); mmfile = new MmFile(path, MmFile.Mode.read, 0, mmAddress); - read = &readMmfile; - seek = &seekMmfile; mode = InputMode.mmfile; + seek = &seekMmfile; + read = &readMmfile; + readBuffer = &readBufferMmfile; return 0; } catch (Exception ex) { return ddhxError(ex); } } int openStdin() { - read = &readStdin; - seek = null; - bufferSize = DEFAULT_BUFFER_SIZE; mode = InputMode.stdin; + seek = null; + read = &readStdin; + readBuffer = &readBufferStdin; + bufferSize = DEFAULT_BUFFER_SIZE; return 0; } @@ -98,6 +101,18 @@ return stdin.rawRead(fBuffer); } + ubyte[] delegate(ubyte[]) readBuffer; + + private ubyte[] readBufferFile(ubyte[] buffer) { + return file.rawRead(buffer); + } + private ubyte[] readBufferMmfile(ubyte[] buffer) { + return cast(ubyte[])mmfile[position..position+buffer.length]; + } + private ubyte[] readBufferStdin(ubyte[] buffer) { + return stdin.rawRead(buffer); + } + const(char)[] formatSize() { __gshared char[32] b; return mode == InputMode.stdin ? "--" : formatsize(b, size); diff --git a/src/ddhx/menu.d b/src/ddhx/menu.d index 981d9dc..b1092a0 100644 --- a/src/ddhx/menu.d +++ b/src/ddhx/menu.d @@ -4,6 +4,9 @@ import core.stdc.stdio : printf; import ddhx.ddhx, ddhx.terminal, ddhx.settings, ddhx.searcher, ddhx.error; +//TODO: search auto ... +// Auto-guess type (integer/"string"/byte array/etc.) + /** * Internal command prompt. * Params: prepend = Initial command @@ -23,12 +26,8 @@ if (prepend) write(prepend); -// size_t argc; -// char[1024] inbuf = void; -// string[12] argv = void; -// const size_t inbufl = readln(inbuf); - //TODO: GC-free merge prepend and readln(buf), then split + //TODO: Smarter argv handling with quotes string[] argv = cast(string[])(prepend ~ readln[0..$-1]).split; // split ' ', no empty entries ddhxUpdateOffsetbar; @@ -36,10 +35,12 @@ const size_t argc = argv.length; if (argc == 0) return; + int error; + string value = void; switch (argv[0]) { case "g", "goto": if (argc <= 1) { - ddhxMsgLow("Missing position (number)"); + ddhxMsgLow("Missing argument (position)"); break; } switch (argv[1]) { @@ -55,81 +56,99 @@ break; case "s", "search": // Search if (argc <= 1) { - ddhxMsgLow("Missing data type"); + ddhxMsgLow("Missing argument (type)"); break; } if (argc <= 2) { - ddhxMsgLow("Missing data argument"); + ddhxMsgLow("Missing argument (needle)"); break; } - - string value = argv[2]; + + value = argv[2]; switch (argv[1]) { case "u8", "byte": - argv[1] = value; - goto SEARCH_BYTE; + error = search!ubyte(value); + break; case "u16", "short": - search!ushort(value); + error = search!ushort(value); break; case "u32", "int": - search!uint(value); + error = search!uint(value); break; case "u64", "long": - search!ulong(value); + error = search!ulong(value); break; case "utf8", "string": - search!string(value); + error = search!string(value); break; case "utf16", "wstring": - search!wstring(value); + error = search!wstring(value); break; case "utf32", "dstring": - search!dstring(value); + error = search!dstring(value); break; default: ddhxMsgLow("Invalid type (%s)", argv[1]); break; } break; // "search" + case "sb": // Search byte + if (argc <= 1) { + ddhxMsgLow("Missing argument (u8)"); + break; + } + error = search!ubyte(argv[1]); + break; + case "sw": // Search word + if (argc <= 1) { + ddhxMsgLow("Missing argument (u8)"); + break; + } + error = search!ushort(argv[1]); + break; + case "sd": // Search dword + if (argc <= 1) { + ddhxMsgLow("Missing argument (u8)"); + break; + } + error = search!uint(argv[1]); + break; + case "sl": // Search long + if (argc <= 1) { + ddhxMsgLow("Missing argument (u8)"); + break; + } + error = search!ulong(argv[1]); + break; case "ss": // Search ASCII/UTF-8 string if (argc <= 1) { ddhxMsgLow("Missing argument (string)"); break; } - search!string(argv[1]); + error = search!string(argv[1]); break; - case "sw": // Search UTF-16 string + case "sws": // Search UTF-16 string if (argc <= 1) { ddhxMsgLow("Missing argument (wstring)"); break; } - search!wstring(argv[1]); + error = search!wstring(argv[1]); break; - case "sd": // Search UTF-32 string + case "sds": // Search UTF-32 string if (argc <= 1) { ddhxMsgLow("Missing argument (dstring)"); break; } - search!dstring(argv[1]); - break; - case "sb": // Search byte -SEARCH_BYTE: - if (argc <= 1) { - ddhxMsgLow("Missing argument (u8)"); - break; - } - search!ubyte(argv[1]); + error = search!dstring(argv[1]); break; case "i", "info": ddhxMsgFileInfo; break; case "o", "offset": if (argc <= 1) { - ddhxMsgLow("Missing offset"); + ddhxMsgLow("Missing argument (offset)"); break; } - if (optionOffset(argv[1])) { - ddhxMsgLow(ddhxErrorMsg); + if ((error = optionOffset(argv[1])) != 0) break; - } ddhxUpdateOffsetbar; ddhxDrawRaw; break; @@ -148,8 +167,8 @@ case "set": if (argc <= 2) { ddhxMsgLow(argc <= 1 ? - "Missing setting" : - "Missing setting option"); + "Missing argument (setting)" : + "Missing argument (value)"); break; } switch (argv[1]) { @@ -182,4 +201,7 @@ break; default: ddhxMsgLow("Unknown command: %s", argv[0]); break; } -} \ No newline at end of file + + if (error) + ddhxMsgLow(ddhxErrorMsg); +} diff --git a/src/ddhx/searcher.d b/src/ddhx/searcher.d index 31aaab5..933a097 100644 --- a/src/ddhx/searcher.d +++ b/src/ddhx/searcher.d @@ -9,7 +9,7 @@ import std.stdio; import std.encoding : transcode; import core.bitop; -import ddhx.ddhx, ddhx.utils; +import ddhx.ddhx, ddhx.utils, ddhx.error; // NOTE: core.bitop.byteswap only appeared recently pragma(inline, true) @@ -17,127 +17,127 @@ return cast(ushort)((v << 8) | (v >> 8)); } -void search(T)(string v, bool invert = false) { +int search(T)(string v) { static if (is(T == ubyte)) { long l = void; - if (unformat(v, l) == false) { - ddhxMsgLow("Could not parse number"); - return; - } - if (l < byte.min || l > ubyte.max) { - ddhxMsgLow("Integer too large for a byte"); - return; - } - byte data = cast(byte)l; - searchInternal(&data, byte.sizeof, "u8"); + if (unformat(v, l) == false) + return ddhxError(DdhxError.unparsable); + if (l < byte.min || l > ubyte.max) + return ddhxError(DdhxError.overflow); + ubyte data = cast(ubyte)l; + return search(&data, ubyte.sizeof, "u8"); } else static if (is(T == ushort)) { long l = void; - if (unformat(v, l) == false) { - ddhxMsgLow("Could not parse number"); - return; - } - if (l < short.min || l > ushort.max) { - ddhxMsgLow("Integer too large for a u16 value"); - return; - } - short data = cast(short)l; - if (invert) - data = bswap16(data); - searchInternal(&data, short.sizeof, "u16"); + if (unformat(v, l) == false) + return ddhxError(DdhxError.unparsable); + if (l < short.min || l > ushort.max) + return ddhxError(DdhxError.overflow); + ushort data = cast(ushort)l; +// if (invert) +// data = bswap16(data); + return search(&data, ushort.sizeof, "u16"); } else static if (is(T == uint)) { long l = void; - if (unformat(v, l) == false) { - ddhxMsgLow("Could not parse number"); - return; - } - if (l < int.min || l > uint.max) { - ddhxMsgLow("Integer too large for a u16 value"); - return; - } - int data = cast(int)l; - if (invert) - data = bswap(data); - searchInternal(&data, int.sizeof, "u32"); + if (unformat(v, l) == false) + return ddhxError(DdhxError.unparsable); + if (l < int.min || l > uint.max) + return ddhxError(DdhxError.overflow); + uint data = cast(uint)l; +// if (invert) +// data = bswap(data); + return search(&data, uint.sizeof, "u32"); } else static if (is(T == ulong)) { long l = void; - if (unformat(v, l) == false) { - ddhxMsgLow("Could not parse number"); - return; - } - if (invert) - l = bswap(l); - searchInternal(&l, long.sizeof, "u64"); + if (unformat(v, l) == false) + return ddhxError(DdhxError.unparsable); +// if (invert) +// l = bswap(l); + return search(&l, ulong.sizeof, "u64"); } else static if (is(T == ubyte[])) { - searchInternal(v.ptr, v.length, "u8[]"); + return search(v.ptr, v.length, "u8[]"); } else static if (is(T == string)) { - searchInternal(cast(void*)v.ptr, v.length, "utf-8 string"); + return search(cast(void*)v.ptr, v.length, "utf-8 string"); } else static if (is(T == wstring)) { wstring ws; transcode(v, ws); - searchInternal(cast(void*)ws.ptr, ws.length, "utf-16 string"); + return search(cast(void*)ws.ptr, ws.length, "utf-16 string"); } else static if (is(T == dstring)) { dstring ds; transcode(v, ds); - searchInternal(cast(void*)ds.ptr, ds.length, "utf-32 string"); + return search(cast(void*)ds.ptr, ds.length, "utf-32 string"); } } -//TODO: Consider converting this to a ubyte[] -// Calling memcmp may be inlined with this -private void searchInternal(void *data, size_t len, const(char) *type) { - import core.stdc.string : memcmp; +//TODO: bool save = true +private int search(void *data, size_t len, string type) { + import ddhx.terminal : conheight; + debug import std.conv : text; - if (len == 0) { - ddhxMsgLow("Empty input, cancelled"); - return; - } + debug assert(len, "len="~len.text); ddhxMsgLow(" Searching %s...", type); + long pos = void; + const int e = searchInternal(data, len, pos); + if (e == 0) { + if (pos + input.bufferSize > input.size) + pos = input.size - input.bufferSize; + input.seek(pos); + globals.buffer = input.read(); + ddhxDraw(); + ddhxMsgLow(" Found at 0x%x", pos); + } + return e; +} + +private int searchInternal(void *data, size_t len, out long pos) { + enum BUFFER_SIZE = 16 * 1024; + import core.stdc.string : memcmp; - //TODO: Redo search - /*enum CHUNK_SIZE = 64 * 1024; - const ubyte s8 = (cast(ubyte*)data)[0]; - const ulong flimit = globals.fileSize; /// file size - const ulong dlimit = flimit - len; /// data limit - const ulong climit = flimit - CHUNK_SIZE; /// chunk limit - long pos = globals.position + 1; + ubyte *ptr = cast(ubyte*)data; + const ubyte mark = ptr[0]; + const bool byteSearch = len == 1; + ubyte[] inputBuffer = new ubyte[BUFFER_SIZE]; + ubyte[] dataBuffer; - // per chunk - while (pos < climit) { - const(ubyte)[] chunk = cast(ubyte[])globals.mmHandle[pos..pos+CHUNK_SIZE]; - foreach (size_t o, ubyte b; chunk) { - // first byte does not correspond - if (b != s8) continue; + if (byteSearch == false) + dataBuffer = new ubyte[len]; + + ubyte[] in_ = void; + long oldpos = input.position, p = oldpos + 1; + long o = void; + input.seek(p); + do { + in_ = input.readBuffer(inputBuffer); + + o = p; + for (size_t i_; i_ < in_.length; ++i_, ++p) { + if (in_[i_] != mark) continue; - // compare data - const long npos = pos + o; - const(ubyte)[] d = cast(ubyte[])globals.mmHandle[npos .. npos + len]; - if (memcmp(d.ptr, data, len) == 0) { - ddhxSeek(npos); - return; + if (byteSearch) { + pos = p; + return 0; + } + + // in buffer? + if (i_ + len < in_.length) { // in-buffer check + //if (ptr[0..len] == in_[i_..i_+len]) { + if (memcmp(in_.ptr + i_, ptr, len) == 0) { + pos = p; + return 0; + } + } else { // out-buffer check + input.seek(p); + input.readBuffer(dataBuffer); + //if (ptr[0..len] == dataBuffer[0..len]) { + if (memcmp(dataBuffer.ptr, ptr, len) == 0) { + pos = p; + return 0; + } + input.seek(o); } } - pos += CHUNK_SIZE; - } + } while (in_.length == BUFFER_SIZE); - // rest of data - const(ubyte)[] chunk = cast(ubyte[])globals.mmHandle[pos..$]; - foreach (size_t o, ubyte b; chunk) { - // first byte does not correspond - if (b != s8) continue; - - // if data still fits within file - if (pos + o >= dlimit) break; - - // compare data - const long npos = pos + o; - const(ubyte)[] d = cast(ubyte[])globals.mmHandle[npos .. npos + len]; - if (memcmp(d.ptr, data, len) == 0) { - ddhxSeek(npos); - return; - } - }*/ - - // not found - ddhxMsgLow("Not found (%s)", type); + input.seek(oldpos); + return ddhxError(DdhxError.notFound); } diff --git a/src/ddhx/utils.d b/src/ddhx/utils.d index f35160a..f4cfea2 100644 --- a/src/ddhx/utils.d +++ b/src/ddhx/utils.d @@ -27,6 +27,12 @@ return true; } +/// +@system unittest { + long l = void; + assert(unformat("0xffff", l)); + assert(cast(ushort)l == 0xffff); +} /** * Converts a string HEX number to a long number, without the prefix.