├── .github └── workflows │ ├── commit-if-modified.sh │ ├── copyright-year.sh │ ├── isaacs-makework.yml │ └── package-json-repo.js ├── LICENSE ├── README.md ├── hexedit.js └── package.json /.github/workflows/commit-if-modified.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | git config --global user.email "$1" 3 | shift 4 | git config --global user.name "$1" 5 | shift 6 | message="$1" 7 | shift 8 | if [ $(git status --porcelain "$@" | egrep '^ M' | wc -l) -gt 0 ]; then 9 | git add "$@" 10 | git commit -m "$message" 11 | git push || git pull --rebase 12 | git push 13 | fi 14 | -------------------------------------------------------------------------------- /.github/workflows/copyright-year.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | dir=${1:-$PWD} 3 | dates=($(git log --date=format:%Y --pretty=format:'%ad' --reverse | sort | uniq)) 4 | if [ "${#dates[@]}" -eq 1 ]; then 5 | datestr="${dates}" 6 | else 7 | datestr="${dates}-${dates[${#dates[@]}-1]}" 8 | fi 9 | 10 | stripDate='s/^((.*)Copyright\b(.*?))((?:,\s*)?(([0-9]{4}\s*-\s*[0-9]{4})|(([0-9]{4},\s*)*[0-9]{4})))(?:,)?\s*(.*)\n$/$1$9\n/g' 11 | addDate='s/^.*Copyright(?:\s*\(c\))? /Copyright \(c\) '$datestr' /g' 12 | for l in $dir/LICENSE*; do 13 | perl -pi -e "$stripDate" $l 14 | perl -pi -e "$addDate" $l 15 | done 16 | -------------------------------------------------------------------------------- /.github/workflows/isaacs-makework.yml: -------------------------------------------------------------------------------- 1 | name: "various tidying up tasks to silence nagging" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | makework: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Use Node.js 17 | uses: actions/setup-node@v2.1.4 18 | with: 19 | node-version: 16.x 20 | - name: put repo in package.json 21 | run: node .github/workflows/package-json-repo.js 22 | - name: check in package.json if modified 23 | run: | 24 | bash -x .github/workflows/commit-if-modified.sh \ 25 | "package-json-repo-bot@example.com" \ 26 | "package.json Repo Bot" \ 27 | "chore: add repo to package.json" \ 28 | package.json package-lock.json 29 | - name: put all dates in license copyright line 30 | run: bash .github/workflows/copyright-year.sh 31 | - name: check in licenses if modified 32 | run: | 33 | bash .github/workflows/commit-if-modified.sh \ 34 | "license-year-bot@example.com" \ 35 | "License Year Bot" \ 36 | "chore: add copyright year to license" \ 37 | LICENSE* 38 | -------------------------------------------------------------------------------- /.github/workflows/package-json-repo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const pf = require.resolve(`${process.cwd()}/package.json`) 4 | const pj = require(pf) 5 | 6 | if (!pj.repository && process.env.GITHUB_REPOSITORY) { 7 | const fs = require('fs') 8 | const server = process.env.GITHUB_SERVER_URL || 'https://github.com' 9 | const repo = `${server}/${process.env.GITHUB_REPOSITORY}` 10 | pj.repository = repo 11 | const json = fs.readFileSync(pf, 'utf8') 12 | const match = json.match(/^\s*\{[\r\n]+([ \t]*)"/) 13 | const indent = match[1] 14 | const output = JSON.stringify(pj, null, indent || 2) + '\n' 15 | fs.writeFileSync(pf, output) 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) 2011-2022 Isaac Z. Schlueter and Contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a hexadecimal editor. Here's how it works: 2 | 3 | 1. You type `hexedit blah.bin` 4 | 2. It dumps out the contents of `blah.bin` in hex to a temporary text file. 5 | 3. **Every time you save that file**, it picks up the change and writes 6 | the alteration back to `blah.bin`. 7 | 4. When the editor exits, it writes the change back to `blah.bin`. 8 | 9 | Each line in the editor is something like this: 10 | 11 | # offset: data # ASCII value. 12 | 00000000: 5468 6973 2069 7320 6120 6865 7861 6465 # This.is.a.hexade 13 | 00000010: 6369 6d61 6c20 6564 6974 6f72 2e20 2048 # cimal.editor...H 14 | 00000020: 6572 6527 7320 686f 7720 6974 2077 6f72 # ere's.how.it.wor 15 | 00000030: 6b73 3a0a 0a31 2e20 596f 7520 7479 7065 # ks:..1..You.type 16 | 17 | The annotations and whitespace are just for your benefit, and are 18 | ignored by the program. Everything before the first `:`, or after the 19 | first `#` is removed. Whitespace is stripped. If there are any invalid 20 | hex characters after this transformation, or if the result is an odd 21 | number of hex digits, then an error is thrown. 22 | 23 | You need to set a blocking editor as your `EDITOR` environment 24 | variable. `vim` works, or if you're on a Mac and use TextMate, you can 25 | use `mate_wait`. Anything that works for commit messages should be 26 | fine. 27 | 28 | ## Installing 29 | 30 | ``` 31 | npm install hexedit -g 32 | ``` 33 | 34 | There is no module to `require()`. It's strictly a command-line 35 | utility. 36 | 37 | ## Options 38 | 39 | Hexedit takes all the same options that `hexy` takes, and they do the 40 | same things. 41 | 42 | Usage: hexedit [options] 43 | Options are identical to hexy's: 44 | --width [(16)] how many bytes per line 45 | --numbering [(hex_bytes)|none] prefix current byte count 46 | --format [(fours)|twos|none] how many nibbles per group 47 | --caps [(lower)|upper] case of hex chars 48 | --annotate [(ascii)|none] provide ascii annotation 49 | --prefix [("")|] printed in front of each line 50 | --indent [(0)|] number of spaces to indent output 51 | --help|-h display this message 52 | -------------------------------------------------------------------------------- /hexedit.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var editor = process.env.EDITOR 4 | || (process.platform === "win32" ? "notepad" : "vim") 5 | 6 | var child_process = require("child_process") 7 | var fs = require("fs") 8 | var path = require("path") 9 | 10 | 11 | var nopt = require("nopt") 12 | var opts = nopt({ width: Number 13 | , numbering: ["hex_bytes", "none"] 14 | , format: ["fours", "twos", "none"] 15 | , caps: ["lower", "upper"] 16 | , annotate: ["ascii", "none"] 17 | , prefix: String 18 | , indent: Number 19 | , help: Boolean }, { h: '--help' }) 20 | 21 | var defaults = { width: 16 22 | , numbering: "hex_bytes" 23 | , format: "fours" 24 | , caps: "lower" 25 | , annotate: "ascii" 26 | , prefix: "" 27 | , indent: 0 } 28 | 29 | if (opts.argv.remain.length !== 1 || opts.help) { 30 | console.error("Usage: hexedit [options] ") 31 | console.error("Options are identical to hexy's:") 32 | console.error(' --width [(16)] how many bytes per line') 33 | console.error(' --numbering [(hex_bytes)|none] prefix current byte count') 34 | console.error(' --format [(fours)|twos|none] how many nibbles per group') 35 | console.error(' --caps [(lower)|upper] case of hex chars') 36 | console.error(' --annotate [(ascii)|none] provide ascii annotation') 37 | console.error(' --prefix [("")|] printed in front of each line') 38 | console.error(' --indent [(0)|] number of spaces to indent output') 39 | console.error(' --help|-h display this message') 40 | process.exit(opts.help ? 0 : 1) 41 | } 42 | 43 | var file = path.resolve(opts.argv.remain[0]) 44 | 45 | var tmp = path.resolve( 46 | path.dirname(file), ".hexedit-" + path.basename(file) + ".hex" 47 | ) 48 | 49 | // Bytes start at: 50 | // indent + prefix + (numbering ? 10 : 0) 51 | // 52 | // Bytes go for: 53 | // 2 * width + (spaces) 54 | // spaces = format === fours ? width/2 - 1 : format == twos ? width/4 -1 : 0 55 | 56 | opts.__proto__ = defaults 57 | var n = opts.prefix.length 58 | + opts.indent 59 | 60 | var header = "# " 61 | if (opts.numbering !== "none") { 62 | header += new Array(n + 1).join(" ") + "offset: " 63 | n += 10 64 | } 65 | var width = opts.width 66 | var spaces = opts.format === "fours" ? Math.ceil(width / 2) - 1 67 | : opts.format === "twos" ? width + 1 68 | : 0 69 | 70 | var b = 2 * opts.width + spaces 71 | n += b + 1 72 | header += "data" + (new Array(b - 1).join(" ")) 73 | 74 | if (opts.annotate !== "none") { 75 | header += "# ascii" 76 | } 77 | 78 | var hexy = require("hexy").hexy 79 | var buf = new Buffer( 80 | [ header ].concat( 81 | // hexy(fs.readFileSync(file), { width: 256, format: "none" }) 82 | hexy(fs.readFileSync(file), opts) 83 | .split("\n") 84 | .map(function (s) { 85 | if (!s.trim()) return s.trim() 86 | return s.slice(0, n + 1) + "#" + s.slice(n) 87 | }) 88 | ).join("\n") 89 | ) 90 | 91 | fs.writeFileSync(tmp, buf) 92 | fs.watch(tmp, update) 93 | 94 | // if it takes less than 100ms to exit, then something is very wrong. 95 | var start = Date.now() 96 | child_process.spawn(editor, [tmp], { customFds: [ 0, 1, 2 ] }) 97 | .on("exit", function (c) { 98 | var end = Date.now() 99 | if (end - start < 100) { 100 | console.error("Editor exited immediately.") 101 | return update(true, 1) 102 | } 103 | 104 | c = c || 0 105 | console.error("editor exited with "+c) 106 | update(true, c) 107 | }) 108 | 109 | var ut 110 | function update (exit, code) { 111 | if (ut) clearTimeout(ut) 112 | ut = setTimeout(function () { 113 | clearTimeout(ut) 114 | ut = null 115 | var c = fs.readFileSync(tmp) 116 | c = c.toString("utf8") 117 | c = c.split("\n").map(function (c) { 118 | return c.replace(/#.*$/g, "") 119 | .replace(/^[^:]+:/, "") 120 | .replace(/[\n\r\t\s ]/g, "") 121 | .trim() 122 | }) 123 | c = c.join("") 124 | c = c.trim() 125 | 126 | // check the bytes 127 | var bytes 128 | try { 129 | bytes = new Buffer(c, "hex") 130 | } catch (ex) { 131 | console.error(ex.message) 132 | if (exit === true && typeof code === "number") { 133 | process.exit(code || 1) 134 | } 135 | return 136 | } 137 | 138 | fs.writeFileSync(file, bytes) 139 | if (true === exit && typeof code === "number") { 140 | console.error("exiting") 141 | fs.unlinkSync(tmp) 142 | process.exit(code) 143 | } 144 | }, 100) 145 | } 146 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Isaac Z. Schlueter (http://blog.izs.me/)", 3 | "name": "hexedit", 4 | "description": "Hexadecimal Editor", 5 | "version": "1.0.3", 6 | "bin": "./hexedit.js", 7 | "repository": "https://github.com/isaacs/node-hexedit", 8 | "dependencies": { 9 | "hexy": "~0.2.1", 10 | "nopt": "~1.0.10" 11 | }, 12 | "license": "ISC", 13 | "files": ["hexedit.js"] 14 | } 15 | --------------------------------------------------------------------------------