├── README.md └── glitch_tool.py /README.md: -------------------------------------------------------------------------------- 1 | # glitch-tool 2 | 3 | glitch-tool is a simple Python script for messing with files in a few different ways. This tool was created for making glitch art, more specifically doing databending. You can read more about my results in [this blog post](https://tobloef.com/blog/glitch-art/). This tool was mostly created for this one-time use and therefore the code quality isn't great. 4 | 5 | ## Usage 6 | ``` 7 | usage: glitch_tool.py [-h] [-i INFILE] [-m MODE] [-o OUTDIR] [-s SEED] 8 | [-a AMOUNT] [-c CHANGES] [-b BYTES] [-r REPEAT_WIDTH] 9 | [-q] [--output-iterations OUTPUT_ITERATIONS] 10 | 11 | Required arguments: 12 | -i, --infile Input file 13 | -m, --mode File change mode 14 | -o, --outdir Output folder 15 | Optional arguments: 16 | -s, --seed Seed to use for random 17 | -a, --amount Amount of new files to create 18 | -c, --changes Amount of random changes. Can be in a range, like 1-10. 19 | -b, --bytes Amount of bytes to change each change. Can be in a range, like 1-10. 20 | -r, --repeat-width Amount of bytes to repeat. Can be in a range, like 1-10. 21 | -q, --quiet Surpress logging 22 | --output-iterations How many changes between outputs 23 | ``` 24 | 25 | ### Modes 26 | The valid modes are: 27 | 28 | * `change` - Change bytes in chunk to random values. 29 | * `reverse` - Reverse order of bytes in chunk. 30 | * `repeat` - Repeat first X bytes (specfied with `--repeat-width`) of chunk throughout the chunk. 31 | * `remove` - Remove the chunk entirely. 32 | * `zero` - Make the chunk all zeroes. 33 | * `insert` - Insert random chunk of data at a random point. 34 | * `replace` - Replace chunk with a chunk of random data. 35 | * `move` - Remove a chunk from one position to another. 36 | -------------------------------------------------------------------------------- /glitch_tool.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import copy 3 | import math 4 | from os import path 5 | import platform 6 | import random 7 | import re 8 | import sys 9 | 10 | def writeFile(fileByteList, fileNum, iteration, bytesTochange, seed): 11 | filename, extension = path.splitext(args.infile) 12 | filename = filename.split("\\")[-1] if platform.system == "Windows" else filename.split("/")[-1] 13 | outPath = f"{args.outdir}{filename}_m={args.mode}_b={bytesTochange}_s={seed}_n={fileNum}_i={iteration}{extension}" 14 | if (not args.quiet): 15 | print("Writing file to " + outPath) 16 | open(outPath, "wb").write(bytes(fileByteList)) 17 | 18 | def messWithFile(originalByteList, iterations, bytesToChange, repeatWidth, fileNum): 19 | newByteList = copy.copy(originalByteList) 20 | iteration = 1 21 | seed = args.seed or random.randrange(sys.maxsize) 22 | random.seed(seed) 23 | for i in range(iterations): 24 | iteration = i+1 25 | if (args.mode == "repeat"): 26 | newByteList = repeatBytes(newByteList, bytesToChange, repeatWidth) 27 | else: 28 | newByteList = transforms[args.mode](newByteList, bytesToChange) 29 | if (args.output_iterations > 0 and iteration%args.output_iterations == 0): 30 | writeFile(newByteList, fileNum, iteration, bytesToChange, seed) 31 | writeFile(newByteList, fileNum, iteration, bytesToChange, seed) 32 | 33 | # Transforms 34 | 35 | def changeBytes(byteList, bytesToChange): 36 | pos = random.randint(0, len(byteList) - bytesToChange) 37 | chunk = [random.randint(0, 255) for i in range(bytesToChange)] 38 | byteList[pos:pos+bytesToChange] = chunk 39 | return byteList 40 | 41 | def reverseBytes(byteList, bytesToChange): 42 | pos = random.randint(0, len(byteList) - bytesToChange) 43 | chunk = byteList[pos:pos+bytesToChange][::-1] 44 | byteList[pos:pos+bytesToChange] = chunk 45 | return byteList 46 | 47 | def repeatBytes(byteList, bytesToChange, repeatWidth): 48 | pos = random.randint(0, len(byteList) - bytesToChange) 49 | chunk = [] 50 | for i in range(math.ceil(bytesToChange/repeatWidth)): 51 | chunk.extend(byteList[pos:pos+repeatWidth]) 52 | byteList[pos:pos+bytesToChange] = chunk[:bytesToChange] 53 | return byteList 54 | 55 | def removeBytes(byteList, bytesToChange): 56 | pos = random.randint(0, len(byteList) - bytesToChange) 57 | byteList[pos:pos+bytesToChange] = [] 58 | return byteList 59 | 60 | def zeroBytes(byteList, bytesToChange): 61 | pos = random.randint(0, len(byteList) - bytesToChange) 62 | byteList[pos:pos+bytesToChange] = [0] * bytesToChange 63 | return byteList 64 | 65 | def insertBytes(byteList, bytesToChange): 66 | pos = random.randint(0, len(byteList)) 67 | chunk = [random.randint(0, 255) for i in range(bytesToChange)] 68 | byteList[pos:pos] = chunk 69 | return byteList 70 | 71 | def replaceBytes(byteList, bytesToChange): 72 | pos = random.randint(0, len(byteList) - bytesToChange) 73 | chunk = byteList[pos:pos+bytesToChange] 74 | old = random.randint(0, 255) 75 | new = random.randint(0, 255) 76 | chunk = [new if b == old else b for b in chunk] 77 | byteList[pos:pos+bytesToChange] = chunk 78 | return byteList 79 | 80 | def moveBytes(byteList, bytesToChange): 81 | pos = random.randint(0, len(byteList) - bytesToChange) 82 | chunk = byteList[pos:pos+bytesToChange] 83 | byteList[pos:pos+bytesToChange] = [] 84 | newPos = random.randint(0, len(byteList)) 85 | byteList[newPos:newPos] = chunk 86 | return byteList 87 | 88 | def main(): 89 | # Do stuff to arguments 90 | if (not args.infile): 91 | print("Error: No input file specified") 92 | return False 93 | if (not path.isfile(args.infile)): 94 | print("Error: Input file not found") 95 | return False 96 | if (not args.mode): 97 | print("Error: No mode specified") 98 | return False 99 | if (not (args.mode in transforms)): 100 | print("Error: Invalid mode") 101 | return False 102 | minChanges = 1 103 | maxChanges = 1 104 | if (args.changes and re.match(r"[0-9]+-[0-9]+", args.changes)): 105 | parts = args.changes.split("-") 106 | minChanges = int(parts[0]) 107 | maxChanges = int(parts[1]) 108 | elif (args.changes): 109 | minChanges = int(args.changes) 110 | maxChanges = int(args.changes) 111 | minBytes = 1 112 | maxBytes = 1 113 | if (args.bytes and re.match(r"[0-9]+-[0-9]+", args.bytes)): 114 | parts = args.bytes.split("-") 115 | minBytes = int(parts[0]) 116 | maxBytes = int(parts[1]) 117 | elif (args.bytes): 118 | minBytes = int(args.bytes) 119 | maxBytes = int(args.bytes) 120 | minRepeating = 1 121 | maxRepeating = 1 122 | if (args.repeat_width and re.match(r"[0-9]+-[0-9]+", args.repeat_width)): 123 | parts = args.repeat_width.split("-") 124 | minRepeating = int(parts[0]) 125 | maxRepeating = int(parts[1]) 126 | elif (args.repeat_width): 127 | minRepeating = int(args.repeat_width) 128 | maxRepeating = int(args.repeat_width) 129 | # Let the glitching commense! 130 | originalByteList = list(open(args.infile, "rb").read()) 131 | for i in range(args.amount): 132 | iterations = random.randint(minChanges, maxChanges) 133 | bytesToChange = random.randint(minBytes, maxBytes) 134 | repeatWidth = random.randint(minRepeating, maxRepeating) 135 | messWithFile(originalByteList, iterations, bytesToChange, repeatWidth, i+1) 136 | if (not args.quiet): 137 | print("Finished writing files") 138 | 139 | 140 | # Constants 141 | transforms = { 142 | "change": changeBytes, 143 | "reverse": reverseBytes, 144 | "repeat": repeatBytes, 145 | "remove": removeBytes, 146 | "zero": zeroBytes, 147 | "insert": insertBytes, 148 | "replace": replaceBytes, 149 | "move": moveBytes 150 | } 151 | 152 | # Setup argparser 153 | parser = argparse.ArgumentParser(description="Do terrible things to data.") 154 | # Required arguments 155 | parser.add_argument("-i", "--infile", help="Input file") 156 | parser.add_argument("-m", "--mode", help="File change mode") 157 | # Optional arguments 158 | parser.add_argument("-o", "--outdir", default="./", help="Output folder") 159 | parser.add_argument("-s", "--seed", type=int, help="Seed to use for random") 160 | parser.add_argument("-a", "--amount", type=int, default=1, help="Amount of new files to create") 161 | parser.add_argument("-c", "--changes", help="Amount of random changes. Can be in a range, like '1-10'.") 162 | parser.add_argument("-b", "--bytes", help="Amount of bytes to change each change. Can be in a range, like '1-10'.") 163 | parser.add_argument("-r", "--repeat-width", help="Amount of bytes to repeat. Can be in a range, like '1-10'.") 164 | parser.add_argument("-q", "--quiet", default=False, action="store_true", help="Surpress logging") 165 | parser.add_argument("--output-iterations", type=int, default=0, help="How many iterations between outputs") 166 | args = parser.parse_args() 167 | 168 | main() 169 | --------------------------------------------------------------------------------