├── .gitignore ├── src ├── hex2dec.d.ts ├── polycrc.d.ts └── BinaryVdf.ts ├── tsconfig.json ├── package.json ├── .eslintrc ├── LICENSE.md ├── README.md └── crc_algorithms.py /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | .vscode/ -------------------------------------------------------------------------------- /src/hex2dec.d.ts: -------------------------------------------------------------------------------- 1 | /** Declaration file generated by dts-gen */ 2 | 3 | declare namespace hex2dec { 4 | export function decToHex(decStr: string, opts: { prefix?: boolean }): string; 5 | 6 | export function hexToDec(hexStr: string): string; 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es6", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "outDir": "out", 9 | "declaration": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true 12 | }, 13 | "include": [ 14 | "./src/**/*" 15 | ], 16 | "lib": ["es2015"] 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "steam-binary-vdf", 3 | "version": "0.1.0", 4 | "description": "A module to read and write the binary vdf file format used by steam. For example: shortcuts.vdf. This module also provides Steam shortcut url calculation.", 5 | "keywords": [ 6 | "steam", "vdf" 7 | ], 8 | "homepage": "https://github.com/Corecii/steam-binary-vdf-ts", 9 | "bugs": "https://github.com/Corecii/steam-binary-vdf-ts/issues", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/Corecii/steam-binary-vdf-ts.git" 13 | }, 14 | "main": "./out/BinaryVdf.js", 15 | "types": "./out/BinaryVdf.d.ts", 16 | "files": [ 17 | "out/**/*" 18 | ], 19 | "scripts": { 20 | "build": "tsc", 21 | "watch": "tsc -w", 22 | "prepublishOnly": "tsc" 23 | }, 24 | "author": "corecii@corecii.com", 25 | "license": "MIT", 26 | "devDependencies": { 27 | "@types/fs-extra": "^8.0.1", 28 | "@typescript-eslint/eslint-plugin": "^3.1.0", 29 | "@typescript-eslint/parser": "^3.1.0", 30 | "eslint": "^7.2.0", 31 | "eslint-config-prettier": "^6.11.0", 32 | "eslint-plugin-prettier": "^3.1.3", 33 | "prettier": "^2.0.5", 34 | "typescript": "^3.7.2" 35 | }, 36 | "dependencies": { 37 | "@types/buffer-reader": "^0.1.0", 38 | "@types/node-int64": "^0.4.29", 39 | "buffer-reader": "^0.1.0", 40 | "hex2dec": "^1.1.2", 41 | "node-int64": "^0.4.0", 42 | "polycrc": "^0.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2018, 5 | "sourceType": "module", 6 | "project": "./tsconfig.json" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint", 10 | "@typescript-eslint/eslint-plugin", 11 | "prettier" 12 | ], 13 | "extends": [ 14 | "eslint:recommended", 15 | "plugin:@typescript-eslint/eslint-recommended", 16 | "plugin:@typescript-eslint/recommended", 17 | "prettier/@typescript-eslint", 18 | "plugin:prettier/recommended", 19 | ], 20 | "rules": { 21 | "prettier/prettier": [ 22 | "warn", 23 | { 24 | "semi": true, 25 | "trailingComma": "all", 26 | "singleQuote": false, 27 | "printWidth": 120, 28 | "tabWidth": 4, 29 | "useTabs": true 30 | } 31 | ], 32 | "@typescript-eslint/array-type": [ 33 | "warn", 34 | { 35 | "default": "generic", 36 | "readonly": "generic" 37 | } 38 | ], 39 | "@typescript-eslint/no-unused-vars": "warn", 40 | "@typescript-eslint/explicit-function-return-type": "off", 41 | "@typescript-eslint/no-namespace": "off", 42 | "@typescript-eslint/no-non-null-assertion": "warn", 43 | "@typescript-eslint/no-empty-function": "warn", 44 | "prefer-const": "warn", 45 | "no-undef-init": "error", 46 | "no-constant-condition": "off", 47 | } 48 | } -------------------------------------------------------------------------------- /src/polycrc.d.ts: -------------------------------------------------------------------------------- 1 | /** Declaration file generated by dts-gen */ 2 | declare namespace polycrc { 3 | export class CRC { 4 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 5 | constructor(width: number, poly: number, xor_in: number, xor_out: number, reflect: boolean); 6 | 7 | calculate(buffer: Buffer | string | number): number; 8 | 9 | calculate_no_table(buffer: Buffer | string | number): number; 10 | 11 | gen_table(): Int32Array; 12 | 13 | print_table(): string; 14 | 15 | crc_shift: number; 16 | mask: number; 17 | msb_mask: number; 18 | poly: number; 19 | reflect: boolean; 20 | reflected_xor_in: number; 21 | shifted_xor_in: number; 22 | table: Int32Array; 23 | width: number; 24 | xor_in: number; 25 | xor_out: number; 26 | } 27 | 28 | export const models: { 29 | crc1: CRC; 30 | crc10: CRC; 31 | crc16: CRC; 32 | crc24: CRC; 33 | crc32: CRC; 34 | crc32c: CRC; 35 | crc6: CRC; 36 | crc8: CRC; 37 | }; 38 | 39 | export function crc( 40 | width: number, 41 | poly: number, 42 | xor_in: number, 43 | xor_out: number, 44 | reflect: boolean, 45 | ): (buffer: Buffer | string | number) => number; 46 | 47 | export function crc1(buffer: Buffer | string | number): number; 48 | 49 | export function crc10(buffer: Buffer | string | number): number; 50 | 51 | export function crc16(buffer: Buffer | string | number): number; 52 | 53 | export function crc24(buffer: Buffer | string | number): number; 54 | 55 | export function crc32(buffer: Buffer | string | number): number; 56 | 57 | export function crc32c(buffer: Buffer | string | number): number; 58 | 59 | export function crc6(buffer: Buffer | string | number): number; 60 | 61 | export function crc8(buffer: Buffer | string | number): number; 62 | } 63 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## Steam Binary VDF 2 | 3 | > Copyright (c) 2020 Corecii Cyr 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ## getShortcutHash in BinaryVdf.ts 24 | 25 | getShortcutHash uses code adapted from [Scott Rice's ICE 26 | program](https://github.com/scottrice/Ice) which is under the following license: 27 | 28 | > Copyright (c) 2012, 2013 Scott Rice 29 | All rights reserved. 30 | > 31 | > Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | > 38 | > The above copyright notice and this permission notice shall be included in 39 | all copies or substantial portions of the Software. 40 | > 41 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 47 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/BinaryVdf.ts: -------------------------------------------------------------------------------- 1 | import BufferReader from "buffer-reader"; 2 | import * as polycrc from "polycrc"; 3 | import Int64 from "node-int64"; 4 | import { hexToDec } from "hex2dec"; 5 | 6 | export type VdfMap = { 7 | [name: string]: VdfMap | string | number; 8 | }; 9 | 10 | const enum VdfMapItemType { 11 | Map = 0x00, 12 | String = 0x01, 13 | Number = 0x02, 14 | MapEnd = 0x08, 15 | } 16 | 17 | type VdfMapItem = 18 | | { 19 | type: VdfMapItemType.MapEnd; 20 | } 21 | | { 22 | type: VdfMapItemType.String; 23 | name: string; 24 | value: string; 25 | } 26 | | { 27 | type: VdfMapItemType.Number; 28 | name: string; 29 | value: number; 30 | } 31 | | { 32 | type: VdfMapItemType.Map; 33 | name: string; 34 | value: VdfMap; 35 | }; 36 | 37 | function nextMapItem(buffer: BufferReader): VdfMapItem { 38 | const typeByte = buffer.nextUInt8(); 39 | if (typeByte === VdfMapItemType.MapEnd) { 40 | return { 41 | type: typeByte, 42 | }; 43 | } 44 | 45 | const name = buffer.nextStringZero(); 46 | let value; 47 | switch (typeByte) { 48 | case VdfMapItemType.Map: { 49 | value = nextMap(buffer); 50 | break; 51 | } 52 | case VdfMapItemType.String: { 53 | value = buffer.nextStringZero("utf-8"); 54 | break; 55 | } 56 | case VdfMapItemType.Number: { 57 | value = buffer.nextUInt32LE(); 58 | break; 59 | } 60 | } 61 | 62 | return { 63 | type: typeByte, 64 | name: name, 65 | value: value, 66 | }; 67 | } 68 | 69 | function nextMap(buffer: BufferReader): VdfMap { 70 | const contents = {}; 71 | while (true) { 72 | const mapItem = nextMapItem(buffer); 73 | if (mapItem.type === VdfMapItemType.MapEnd) { 74 | break; 75 | } 76 | contents[mapItem.name] = mapItem.value; 77 | } 78 | return contents; 79 | } 80 | 81 | export function readVdf(buffer: Buffer, offset?: number): VdfMap { 82 | const reader = new BufferReader(buffer); 83 | if (offset) { 84 | reader.seek(offset); 85 | } 86 | return nextMap(reader); 87 | } 88 | 89 | function addByte(value: number, contents: Array) { 90 | const typeBuffer = Buffer.allocUnsafe(1); 91 | typeBuffer.writeUInt8(value); 92 | contents.push(typeBuffer); 93 | } 94 | 95 | function addNumber(value: number, contents: Array) { 96 | const valueBuffer = Buffer.allocUnsafe(4); 97 | valueBuffer.writeUInt32LE(value); 98 | contents.push(valueBuffer); 99 | } 100 | 101 | function addString(value: string, contents: Array) { 102 | if (value.indexOf("\0") !== -1) { 103 | throw new Error('Strings in VDF files cannot have null chars ("\\0")'); 104 | } 105 | contents.push(Buffer.from(value, "utf-8")); 106 | addByte(0, contents); 107 | } 108 | 109 | function addMap(map: VdfMap, contents: Array) { 110 | for (const key of Object.keys(map)) { 111 | const value = map[key]; 112 | if (typeof value === "number") { 113 | addByte(VdfMapItemType.Number, contents); 114 | addString(key, contents); 115 | addNumber(value, contents); 116 | } else if (typeof value === "string") { 117 | addByte(VdfMapItemType.String, contents); 118 | addString(key, contents); 119 | addString(value, contents); 120 | } else if (typeof value === "object") { 121 | addByte(VdfMapItemType.Map, contents); 122 | addString(key, contents); 123 | addMap(value, contents); 124 | } else { 125 | throw new Error( 126 | `Type at ${key} (${typeof value}) is not allowed in VDF files. VDF files can only contain numbers, strings, or objects`, 127 | ); 128 | } 129 | } 130 | const endBuffer = Buffer.allocUnsafe(1); 131 | endBuffer.writeUInt8(VdfMapItemType.MapEnd); 132 | contents.push(endBuffer); 133 | } 134 | 135 | export function writeVdf(map: VdfMap): Buffer { 136 | const contents: Array = []; 137 | addMap(map, contents); 138 | return Buffer.concat(contents); 139 | } 140 | 141 | let shortcutCrc: ((buffer: Buffer | string | number) => number) | undefined; 142 | export function getShortcutHash(input: string): string { 143 | shortcutCrc = shortcutCrc || polycrc.crc(32, 0x04c11db7, 0xffffffff, 0xffffffff, true); 144 | const full64 = new Int64(shortcutCrc(input) | 0x80000000, 0x02000000); 145 | return hexToDec(full64.toOctetString()); 146 | } 147 | 148 | export function getShortcutUrl(appName: string, exe: string): string { 149 | return "steam://rungameid/" + getShortcutHash(exe + appName); 150 | } 151 | 152 | /* 153 | getShortcutHash uses code adapted from [Scott Rice's ICE 154 | program](https://github.com/scottrice/Ice) which is under the following license: 155 | 156 | Copyright (c) 2012, 2013 Scott Rice 157 | All rights reserved. 158 | 159 | Permission is hereby granted, free of charge, to any person obtaining a copy 160 | of this software and associated documentation files (the "Software"), to deal 161 | in the Software without restriction, including without limitation the rights 162 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 163 | copies of the Software, and to permit persons to whom the Software is 164 | furnished to do so, subject to the following conditions: 165 | 166 | The above copyright notice and this permission notice shall be included in 167 | all copies or substantial portions of the Software. 168 | 169 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 170 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 171 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 172 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 173 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 174 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 175 | THE SOFTWARE. 176 | */ 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `npm i --save steam-binary-vdf` 2 | 3 | # Steam Binary VDF 4 | 5 | `steam-binary-vdf` is a module for reading and writing the binary vdf file format used in files like `shortcuts.vdf`. This module also provides a utility function for calculating the `steam://rungameid/` url for a shortcut. 6 | 7 | ## Exports 8 | 9 | ```ts 10 | readVdf(buffer: Buffer, offset?: number): Object 11 | ``` 12 | Reads a vdf file from a buffer and returns an object with its contents. 13 | This returns a "plain" object of `[key: string]: value` with the values from the vdf file. 14 | 15 | ```ts 16 | writeVdf(map: Object): Buffer 17 | ``` 18 | Writes an object to a new buffer then returns the buffer. 19 | 20 | VDF files values only accept: 21 | * **unsigned 32-bit numbers**: `writeVdf` will error if a number is outside of the range `0 <= value <= 4294967295`. 22 | * **strings without null chars `\0`**: `writeVdf` will error if a string contains null chars. Remove, replace, or truncate your strings first. VDF files use null-terminated strings internally. 23 | * **Objects with string keys and accepted values**: `writeVdf` will error if a value does not match an accepted type. 24 | 25 | ```ts 26 | getShortcutHash(input: string): string 27 | ``` 28 | Returns the "hash" used by steam to represent non-steam shortcuts 29 | in the `steam://rungameid/` format. This uses code adapted from [Scott Rice's ICE program](https://github.com/scottrice/Ice). 30 | 31 | ```ts 32 | getShortcutUrl(appName: string, exe: string): string 33 | ``` 34 | Returns the shortcut url for a shortcut with the given name and target. The name is the `AppName` field and the target is the `exe` field from the shortcut entry in the shortcuts file. 35 | 36 | This just returns `"steam://rungameid/" + getShortcutHash(exe + appName)` 37 | 38 | ## Shortcuts.vdf example 39 | 40 | ```ts 41 | import { readVdf, writeVdf } from "steam-binary-vdf"; 42 | import fs from "fs-extra"; 43 | 44 | // read the vdf 45 | const inBuffer = await fs.readFile("C:\\Program Files (x86)\\Steam\\userdata\\USER_ID\\config\\shortcuts.vdf") 46 | 47 | const shortcuts = readVdf(inBuffer); 48 | console.log(shortcuts); // output below; 49 | 50 | // add to the vdf 51 | shortcuts.shortcuts['2'] = { 52 | AppName: 'Game 3', 53 | exe: 'D:\\Games\\Game.exe' 54 | }; 55 | 56 | const outBuffer = writeVdf(shortcuts); 57 | 58 | await fs.writeFile("C:\\Program Files (x86)\\Steam\\userdata\\USER_ID\\config\\shortcuts.vdf", outBuffer); 59 | ``` 60 | 61 | This will produce something like... 62 | 63 | ```js 64 | { 65 | shortcuts: { 66 | '0': { 67 | AppName: 'Game 1', 68 | exe: '"C:\\Program Files\\Game 1\\Game.exe"', 69 | StartDir: '"C:\\Program Files\\Game 1"', 70 | icon: '', 71 | ShortcutPath: '', 72 | LaunchOptions: '', 73 | IsHidden: 0, 74 | AllowDesktopConfig: 1, 75 | AllowOverlay: 1, 76 | openvr: 0, 77 | Devkit: 0, 78 | DevkitGameID: '', 79 | LastPlayTime: 1527542942, 80 | tags: {'0': 'some tag', '1': 'another tag'} 81 | }, 82 | '1': { 83 | AppName: 'Another Game', 84 | exe: '"C:\\Program Files\\Some Game 2\\AnyExe.exe"', 85 | StartDir: '"C:\\Any Location"', 86 | icon: '', 87 | ShortcutPath: '', 88 | LaunchOptions: '', 89 | IsHidden: 0, 90 | AllowDesktopConfig: 1, 91 | AllowOverlay: 1, 92 | openvr: 0, 93 | Devkit: 0, 94 | DevkitGameID: '', 95 | LastPlayTime: 1525830068, 96 | tags: {} 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | Notable things about `shortcuts.vdf`: 103 | 104 | * The root of VDF files are maps of string keys to values. `shortcuts.vdf` puts all of the shortcuts in the `shortcuts` value under the root. The vdf file *can* include more values under the root but typically does not. You can set and save other values under the root and Steam will treat the file like normal after a restart, but won't necessarily keep the extra data. 105 | * `shortcuts` is a *map* with numbers as *string keys*. Using a proper key here doesn't actually matter: steam will fix all keys to a number-as-a-string on startup. 106 | * `tags` is map-as-a-list like `shortcuts`, but I couldn't get steam to use it so I couldn't test its behavior. 107 | * When steam starts, it reads and sanitizes `shortcuts.vdf`. This means: 108 | * Any non-object values under `shortcuts` get discarded 109 | * Any object values under `shortcuts` get converted into shortcut definitions. Unknown keys in the object are removed and any non-existent shortcut definition keys are added with their default values. 110 | * Keys under `shortcuts` get converted to numbers-as-strings. For example, setting `shortcuts.a = {AppName: 'Test'}` in the above example would become `shortcuts['2']` or `['3']` depending on the contents. 111 | * `LastPlayTime` is a timestamp 112 | * Booleans are represented as numbers where 0 is false and 1 is true. 113 | * If `shortcuts.vdf` does not follow the binary vdf format, it is cleared and reset to an empty shortcuts vdf. 114 | 115 | The default values for a shortcut definition are: 116 | ```js 117 | { 118 | AppName: '', 119 | exe: '', 120 | StartDir: '', 121 | icon: '', 122 | ShortcutPath: '', 123 | LaunchOptions: '', 124 | IsHidden: 0, 125 | AllowDesktopConfig: 1, 126 | AllowOverlay: 1, 127 | openvr: 0, 128 | Devkit: 0, 129 | DevkitGameID: '', 130 | LastPlayTime: 0, 131 | tags: {} 132 | } 133 | ``` 134 | 135 | # Binary VDF Format 136 | 137 | The binary vdf format is built around a few structures: 138 | * Null-terminated strings (here on called `String`) 139 | * 32-bit little-endian integers (here on called `Integer`) 140 | * Map (here on called `Map`) 141 | * 1-byte object type (here on called `MapItemType`) 142 | * `0x00` represents a Map 143 | * `0x01` represents a String 144 | * `0x02` represents a Number 145 | * `0x08` represents the end of a map 146 | 147 | `Map` is structured as reptitions of any of the following: 148 | * `MapItemType(0x01)` `String(name)` `String(value)` 149 | * `MapItemType(0x02)` `String(name)` `Integer(value)` 150 | * `MapItemType(0x00)` `String(name)` `Map(value)` 151 | * `MapItemType(0x08)` 152 | 153 | When a type of `0x08` is encountered, map reading stops. 154 | 155 | The root of a binary vdf file is a `Map`. 156 | 157 | `steam-binary-vdf` reads `Integers` as unsigned integers. I haven't seen enough user-editable integers in vdf files to test if this is a correct representation of the data. 158 | 159 | `steam-binary-vdf` reads `Strings` as utf-8. The vdf format likely accepts any sequence of bytes for a string as long as it doesn't contain a null character `\0`. -------------------------------------------------------------------------------- /crc_algorithms.py: -------------------------------------------------------------------------------- 1 | # pycrc -- parameterisable CRC calculation utility and C source code generator 2 | # 3 | # Copyright (c) 2006-2013 Thomas Pircher 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to 7 | # deal in the Software without restriction, including without limitation the 8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | # sell copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | # IN THE SOFTWARE. 22 | 23 | 24 | """ 25 | CRC algorithms implemented in Python. 26 | If you want to study the Python implementation of the CRC routines, then this 27 | is a good place to start from. 28 | 29 | The algorithms Bit by Bit, Bit by Bit Fast and Table-Driven are implemented. 30 | 31 | This module can also be used as a library from within Python. 32 | 33 | Examples 34 | ======== 35 | 36 | This is an example use of the different algorithms: 37 | 38 | >>> from crc_algorithms import Crc 39 | >>> 40 | >>> crc = Crc(width = 16, poly = 0x8005, 41 | ... reflect_in = True, xor_in = 0x0000, 42 | ... reflect_out = True, xor_out = 0x0000) 43 | >>> print("0x%x" % crc.bit_by_bit("123456789")) 44 | >>> print("0x%x" % crc.bit_by_bit_fast("123456789")) 45 | >>> print("0x%x" % crc.table_driven("123456789")) 46 | """ 47 | 48 | # Class Crc 49 | ############################################################################### 50 | class Crc(object): 51 | """ 52 | A base class for CRC routines. 53 | """ 54 | 55 | # Class constructor 56 | ############################################################################### 57 | def __init__(self, width, poly, reflect_in, xor_in, reflect_out, xor_out, table_idx_width = None): 58 | """The Crc constructor. 59 | 60 | The parameters are as follows: 61 | width 62 | poly 63 | reflect_in 64 | xor_in 65 | reflect_out 66 | xor_out 67 | """ 68 | self.Width = width 69 | self.Poly = poly 70 | self.ReflectIn = reflect_in 71 | self.XorIn = xor_in 72 | self.ReflectOut = reflect_out 73 | self.XorOut = xor_out 74 | self.TableIdxWidth = table_idx_width 75 | 76 | self.MSB_Mask = 0x1 << (self.Width - 1) 77 | self.Mask = ((self.MSB_Mask - 1) << 1) | 1 78 | if self.TableIdxWidth != None: 79 | self.TableWidth = 1 << self.TableIdxWidth 80 | else: 81 | self.TableIdxWidth = 8 82 | self.TableWidth = 1 << self.TableIdxWidth 83 | 84 | self.DirectInit = self.XorIn 85 | self.NonDirectInit = self.__get_nondirect_init(self.XorIn) 86 | if self.Width < 8: 87 | self.CrcShift = 8 - self.Width 88 | else: 89 | self.CrcShift = 0 90 | 91 | 92 | # function __get_nondirect_init 93 | ############################################################################### 94 | def __get_nondirect_init(self, init): 95 | """ 96 | return the non-direct init if the direct algorithm has been selected. 97 | """ 98 | crc = init 99 | for i in range(self.Width): 100 | bit = crc & 0x01 101 | if bit: 102 | crc^= self.Poly 103 | crc >>= 1 104 | if bit: 105 | crc |= self.MSB_Mask 106 | return crc & self.Mask 107 | 108 | 109 | # function reflect 110 | ############################################################################### 111 | def reflect(self, data, width): 112 | """ 113 | reflect a data word, i.e. reverts the bit order. 114 | """ 115 | x = data & 0x01 116 | for i in range(width - 1): 117 | data >>= 1 118 | x = (x << 1) | (data & 0x01) 119 | return x 120 | 121 | 122 | # function bit_by_bit 123 | ############################################################################### 124 | def bit_by_bit(self, in_str): 125 | """ 126 | Classic simple and slow CRC implementation. This function iterates bit 127 | by bit over the augmented input message and returns the calculated CRC 128 | value at the end. 129 | """ 130 | register = self.NonDirectInit 131 | for c in in_str: 132 | octet = ord(c) 133 | if self.ReflectIn: 134 | octet = self.reflect(octet, 8) 135 | for i in range(8): 136 | topbit = register & self.MSB_Mask 137 | register = ((register << 1) & self.Mask) | ((octet >> (7 - i)) & 0x01) 138 | if topbit: 139 | register ^= self.Poly 140 | 141 | for i in range(self.Width): 142 | topbit = register & self.MSB_Mask 143 | register = ((register << 1) & self.Mask) 144 | if topbit: 145 | register ^= self.Poly 146 | 147 | if self.ReflectOut: 148 | register = self.reflect(register, self.Width) 149 | return register ^ self.XorOut 150 | 151 | 152 | # function bit_by_bit_fast 153 | ############################################################################### 154 | def bit_by_bit_fast(self, in_str): 155 | """ 156 | This is a slightly modified version of the bit-by-bit algorithm: it 157 | does not need to loop over the augmented bits, i.e. the Width 0-bits 158 | wich are appended to the input message in the bit-by-bit algorithm. 159 | """ 160 | register = self.DirectInit 161 | for c in in_str: 162 | octet = ord(c) 163 | if self.ReflectIn: 164 | octet = self.reflect(octet, 8) 165 | for i in range(8): 166 | topbit = register & self.MSB_Mask 167 | if octet & (0x80 >> i): 168 | topbit ^= self.MSB_Mask 169 | register <<= 1 170 | if topbit: 171 | register ^= self.Poly 172 | register &= self.Mask 173 | if self.ReflectOut: 174 | register = self.reflect(register, self.Width) 175 | return register ^ self.XorOut 176 | 177 | 178 | # function gen_table 179 | ############################################################################### 180 | def gen_table(self): 181 | """ 182 | This function generates the CRC table used for the table_driven CRC 183 | algorithm. The Python version cannot handle tables of an index width 184 | other than 8. See the generated C code for tables with different sizes 185 | instead. 186 | """ 187 | table_length = 1 << self.TableIdxWidth 188 | tbl = [0] * table_length 189 | for i in range(table_length): 190 | register = i 191 | if self.ReflectIn: 192 | register = self.reflect(register, self.TableIdxWidth) 193 | register = register << (self.Width - self.TableIdxWidth + self.CrcShift) 194 | for j in range(self.TableIdxWidth): 195 | if register & (self.MSB_Mask << self.CrcShift) != 0: 196 | register = (register << 1) ^ (self.Poly << self.CrcShift) 197 | else: 198 | register = (register << 1) 199 | if self.ReflectIn: 200 | register = self.reflect(register >> self.CrcShift, self.Width) << self.CrcShift 201 | tbl[i] = register & (self.Mask << self.CrcShift) 202 | return tbl 203 | 204 | 205 | # function table_driven 206 | ############################################################################### 207 | def table_driven(self, in_str): 208 | """ 209 | The Standard table_driven CRC algorithm. 210 | """ 211 | tbl = self.gen_table() 212 | 213 | register = self.DirectInit << self.CrcShift 214 | if not self.ReflectIn: 215 | for c in in_str: 216 | tblidx = ((register >> (self.Width - self.TableIdxWidth + self.CrcShift)) ^ ord(c)) & 0xff 217 | register = ((register << (self.TableIdxWidth - self.CrcShift)) ^ tbl[tblidx]) & (self.Mask << self.CrcShift) 218 | register = register >> self.CrcShift 219 | else: 220 | register = self.reflect(register, self.Width + self.CrcShift) << self.CrcShift 221 | for c in in_str: 222 | tblidx = ((register >> self.CrcShift) ^ ord(c)) & 0xff 223 | register = ((register >> self.TableIdxWidth) ^ tbl[tblidx]) & (self.Mask << self.CrcShift) 224 | register = self.reflect(register, self.Width + self.CrcShift) & self.Mask 225 | 226 | if self.ReflectOut: 227 | register = self.reflect(register, self.Width) 228 | return register ^ self.XorOut 229 | 230 | --------------------------------------------------------------------------------