├── tempCodeRunnerFile.nim ├── libraries └── supersnappy │ ├── LICENSE │ ├── supersnappy │ └── common.nim │ └── supersnappy.nim ├── README.md ├── versions ├── v0.nim ├── v3.nim ├── v2.nim ├── v4.nim ├── v1.nim ├── v5.nim └── v6.nim ├── save_monger.nim └── common.nim /tempCodeRunnerFile.nim: -------------------------------------------------------------------------------- 1 | save_version -------------------------------------------------------------------------------- /libraries/supersnappy/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Ryan Oldenburg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Save Monger 2 | This is the production code that is used to load and save game state in for [Turing Complete](https://store.steampowered.com/app/1444480/Turing_Complete/). 3 | 4 | It exposes 3 functions: 5 | 6 | ### file_get_bytes(filepath: string): seq[uint8] 7 | 8 | This function takes a file name, reads the file and returns its content as an array of bytes. 9 | 10 | ### parse_state(input: seq[uint8], headers_only = false): parse_result 11 | 12 | You can takes an array of bytes and returns a "parse result" struct. Print the struct or check the source code to see which fields it contains. 13 | 14 | ### state_to_binary(save_id: int, components: seq[parse_component], wires: seq[parse_wire], ...): seq[uint8] 15 | 16 | This will serialize game state to an array of bytes. You will probably want to read the source code for the exact definitions of the component and wire structs. 17 | 18 | # License 19 | This code is [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/), however the included library SuperSnappy is MIT license. 20 | 21 | # Unofficial Rust port 22 | https://crates.io/crates/tc_save_monger (Credit: danielrab) 23 | -------------------------------------------------------------------------------- /versions/v0.nim: -------------------------------------------------------------------------------- 1 | import ../common 2 | 3 | func get_point(bytes: seq[uint8], i: var int): point = 4 | return point( 5 | x: get_i8(bytes, i).int16, 6 | y: get_i8(bytes, i).int16 7 | ) 8 | 9 | func get_seq_point(bytes: seq[uint8], i: var int): seq[point] = 10 | let len = get_int(bytes, i) 11 | for j in 0..len - 1: 12 | result.add(get_point(bytes, i)) 13 | 14 | func get_string(bytes: seq[uint8], i: var int): string = 15 | let len = get_int(bytes, i) 16 | for j in 0..len - 1: 17 | result.add(chr(get_u8(bytes, i))) 18 | 19 | func get_seq_i64(bytes: seq[uint8], i: var int): seq[int] = 20 | let len = get_int(bytes, i) 21 | for j in 0..len - 1: 22 | result.add(get_int(bytes, i)) 23 | 24 | func get_component(bytes: seq[uint8], i: var int): parse_component = 25 | try: # Only fails for obsolete components (deleted enum values) 26 | var kind = component_kind(get_u16(bytes, i).int) 27 | let index = [DELETED_12, DELETED_13, DELETED_14, DELETED_15, DELETED_16].find(kind) 28 | if index != -1: 29 | kind = [Bidirectional1, Bidirectional8, Bidirectional16, Bidirectional32, Bidirectional64][index] 30 | result = parse_component(kind: kind) 31 | except: discard 32 | result.position = get_point(bytes, i) 33 | result.rotation = get_u8(bytes, i) 34 | result.permanent_id = get_u32(bytes, i).int 35 | result.custom_string = get_string(bytes, i) 36 | if result.kind in [Program8_1, DELETED_6, DELETED_7, Program8_4, Program]: 37 | discard get_string(bytes, i) 38 | elif result.kind == Custom: 39 | result.custom_id = get_int(bytes, i) 40 | 41 | func get_components(bytes: seq[uint8], i: var int): seq[parse_component] = 42 | let len = get_int(bytes, i) 43 | for j in 0..len - 1: 44 | let comp = get_component(bytes, i) 45 | if comp.kind == Error or comp.kind in DELETED_KINDS: continue 46 | result.add(comp) 47 | 48 | func get_wire(bytes: seq[uint8], i: var int): parse_wire = 49 | discard get_u32(bytes, i).int # Used to be permanent id 50 | result.kind = wire_kind(get_u8(bytes, i)) 51 | result.color = get_u8(bytes, i) 52 | result.comment = get_string(bytes, i) 53 | result.path = get_seq_point(bytes, i) 54 | 55 | func get_wires(bytes: seq[uint8], i: var int): seq[parse_wire] = 56 | let len = get_int(bytes, i) 57 | for j in 0..len - 1: 58 | result.add(get_wire(bytes, i)) 59 | 60 | proc parse*(bytes: seq[uint8], headers_only: bool, solution: bool, parse_result: var parse_result) = 61 | var i = 1 # 0th byte is version 62 | 63 | parse_result.save_id = get_int(bytes, i) 64 | parse_result.gate = get_u32(bytes, i).int 65 | parse_result.delay = get_u32(bytes, i).int 66 | parse_result.menu_visible = get_bool(bytes, i) 67 | parse_result.clock_speed = get_u32(bytes, i) 68 | discard get_u8(bytes, i) # Used to be nesting level 69 | parse_result.dependencies = get_seq_i64(bytes, i) 70 | parse_result.description = get_string(bytes, i) 71 | 72 | if not headers_only: 73 | parse_result.components = get_components(bytes, i) 74 | parse_result.wires = get_wires(bytes, i) -------------------------------------------------------------------------------- /libraries/supersnappy/supersnappy/common.nim: -------------------------------------------------------------------------------- 1 | when defined(release): 2 | {.push checks: off.} 3 | 4 | template read32*(s: string, pos: uint): uint32 = 5 | when nimvm: 6 | (s[pos + 0].uint32 shl 0) or 7 | (s[pos + 1].uint32 shl 8) or 8 | (s[pos + 2].uint32 shl 16) or 9 | (s[pos + 3].uint32 shl 24) 10 | else: 11 | cast[ptr uint32](s[pos].unsafeAddr)[] 12 | 13 | template read64*(s: string, pos: uint): uint64 = 14 | when nimvm: 15 | (s[pos + 0].uint64 shl 0) or 16 | (s[pos + 1].uint64 shl 8) or 17 | (s[pos + 2].uint64 shl 16) or 18 | (s[pos + 3].uint64 shl 24) or 19 | (s[pos + 4].uint64 shl 32) or 20 | (s[pos + 5].uint64 shl 40) or 21 | (s[pos + 6].uint64 shl 48) or 22 | (s[pos + 7].uint64 shl 56) 23 | else: 24 | cast[ptr uint64](s[pos].unsafeAddr)[] 25 | 26 | template copy64*(dst: var string, src: string, op, ip: uint) = 27 | when nimvm: 28 | for i in 0.uint .. 7: 29 | dst[op + i] = src[ip + i] 30 | else: 31 | cast[ptr uint64](dst[op].addr)[] = read64(src, ip) 32 | 33 | template copyMem*(dst: var string, src: string, op, ip, len: uint) = 34 | when nimvm: 35 | for i in 0.uint ..< len: 36 | dst[op + i] = src[ip + i] 37 | else: 38 | copyMem(dst[op].addr, src[ip].unsafeAddr, len) 39 | 40 | func varint*(value: uint32): string = 41 | if value < 1 shl 7: 42 | result.setLen(1) 43 | result[0] = value.char 44 | elif value < 1 shl 14: 45 | result.setLen(2) 46 | result[0] = ((value or 128) and 255).char 47 | result[1] = ((value shr 7) and 255).char 48 | elif value < 1 shl 21: 49 | result.setLen(3) 50 | result[0] = ((value or 128) and 255).char 51 | result[1] = (((value shr 7) or 128) and 255).char 52 | result[2] = ((value shr 14) and 255).char 53 | elif value < 1 shl 28: 54 | result.setLen(4) 55 | result[0] = ((value or 128) and 255).char 56 | result[1] = (((value shr 7) or 128) and 255).char 57 | result[2] = (((value shr 14) or 128) and 255).char 58 | result[3] = ((value shr 21) and 255).char 59 | else: 60 | result.setLen(5) 61 | result[0] = ((value or 128) and 255).char 62 | result[1] = (((value shr 7) or 128) and 255).char 63 | result[2] = (((value shr 14) or 128) and 255).char 64 | result[3] = (((value shr 21) or 128) and 255).char 65 | result[4] = ((value shr 28) and 255).char 66 | 67 | func varint*(buf: string): (uint32, int) = 68 | if buf.len == 0: 69 | return 70 | 71 | var b = cast[uint8](buf[0]) 72 | result[0] = b and 127 73 | result[1] = 1 74 | if b < 128: 75 | return 76 | if buf.len == 1: 77 | return (0.uint32, 0) 78 | b = cast[uint8](buf[1]) 79 | result[0] = result[0] or ((b and 127).uint32 shl 7) 80 | result[1] = 2 81 | if b < 128: 82 | return 83 | if buf.len == 2: 84 | return (0.uint32, 0) 85 | b = cast[uint8](buf[2]) 86 | result[0] = result[0] or ((b and 127).uint32 shl 14) 87 | result[1] = 3 88 | if b < 128: 89 | return 90 | if buf.len == 3: 91 | return (0.uint32, 0) 92 | b = cast[uint8](buf[3]) 93 | result[0] = result[0] or ((b and 127).uint32 shl 21) 94 | result[1] = 4 95 | if b < 128: 96 | return 97 | if buf.len == 4: 98 | return (0.uint32, 0) 99 | b = cast[uint8](buf[4]) 100 | result[0] = result[0] or ((b and 127).uint32 shl 28) 101 | result[1] = 5 102 | if b < 16: 103 | return 104 | return (0.uint32, 0) 105 | 106 | when defined(release): 107 | {.pop.} 108 | -------------------------------------------------------------------------------- /versions/v3.nim: -------------------------------------------------------------------------------- 1 | import ../libraries/supersnappy/supersnappy 2 | import ../common 3 | 4 | const TELEPORT_WIRE = 0b0010_0000'u8 5 | const DIRECTIONS = [ 6 | point(x: 1, y: 0), 7 | point(x: 1, y: 1), 8 | point(x: 0, y: 1), 9 | point(x: -1, y: 1), 10 | point(x: -1, y: 0), 11 | point(x: -1, y: -1), 12 | point(x: 0, y: -1), 13 | point(x: 1, y: -1), 14 | ] 15 | 16 | func get_component(input: seq[uint8], i: var int, solution = false): parse_component = 17 | try: # Only fails for obsolete components (deleted enum values) 18 | var kind = component_kind(get_u16(input, i).int) 19 | let index = [DELETED_12, DELETED_13, DELETED_14, DELETED_15, DELETED_16].find(kind) 20 | if index != -1: 21 | kind = [Bidirectional1, Bidirectional8, Bidirectional16, Bidirectional32, Bidirectional64][index] 22 | result = parse_component(kind: kind) 23 | except: discard 24 | if solution and result.kind == Rom: 25 | result.kind = SolutionRom 26 | result.position = get_point(input, i) 27 | result.rotation = get_u8(input, i) 28 | result.permanent_id = get_int(input, i) 29 | result.custom_string = get_string(input, i) 30 | result.setting_1 = get_u64(input, i) 31 | result.setting_2 = get_u64(input, i) 32 | 33 | if result.kind == Custom: 34 | result.custom_id = get_int(input, i) 35 | result.custom_displacement = get_point(input, i) 36 | 37 | elif result.kind in [Program8_1, Program8_4, Program]: 38 | let len = get_u16(input, i) 39 | var j = 0'u16 40 | while j < len: 41 | let key = get_int(input, i) 42 | result.selected_programs[key] = get_string(input, i) 43 | j += 1 44 | 45 | func get_components(input: seq[uint8], i: var int, solution = false): seq[parse_component] = 46 | let len = get_int(input, i) 47 | for j in 0..len - 1: 48 | let comp = get_component(input, i, solution) 49 | if comp.kind == Error or comp.kind in DELETED_KINDS: continue 50 | result.add(comp) 51 | 52 | func get_wire(input: seq[uint8], i: var int): parse_wire = 53 | result.kind = wire_kind(get_u8(input, i)) 54 | result.color = get_u8(input, i) 55 | result.comment = get_string(input, i) 56 | 57 | #[ 58 | Wire paths encoding rules: 59 | 1. The wire starts with a point: (x: int16, y: int16). 60 | 2. After this follow 1 or more segments (3 bit direction, 5 bit length) 61 | 3. We end once a 0 length segment is encountered (0 byte) 62 | ]# 63 | 64 | result.path.add(get_point(input, i)) 65 | 66 | var segment = get_u8(input, i) 67 | 68 | # This is a special case to support players who want to generate disconnected wires 69 | if segment == TELEPORT_WIRE: 70 | result.path.add(get_point(input, i)) 71 | return 72 | 73 | var length_left = (segment and 0b0001_1111).int 74 | while length_left != 0: 75 | let direction = DIRECTIONS[segment shr 5] 76 | 77 | while length_left > 0: 78 | result.path.add(result.path[^1] + direction) 79 | length_left -= 1 80 | 81 | segment = get_u8(input, i) 82 | length_left = (segment and 0b0001_1111).int 83 | 84 | func get_wires(input: seq[uint8], i: var int): seq[parse_wire] = 85 | let len = get_int(input, i) 86 | for j in 0..len - 1: 87 | result.add(get_wire(input, i)) 88 | 89 | proc parse*(compressed: seq[uint8], headers_only: bool, solution: bool, parse_result: var parse_result) = 90 | var bytes = uncompress(compressed[1..^1]) 91 | var i = 0 92 | 93 | parse_result.save_id = get_int(bytes, i) 94 | parse_result.gate = get_int(bytes, i) 95 | parse_result.delay = get_int(bytes, i) 96 | parse_result.menu_visible = get_bool(bytes, i) 97 | parse_result.clock_speed = get_u32(bytes, i) 98 | parse_result.dependencies = get_seq_int(bytes, i) 99 | parse_result.description = get_string(bytes, i) 100 | parse_result.camera_position = get_point(bytes, i) 101 | parse_result.synced = get_sync_state(bytes, i) 102 | parse_result.campaign_bound = get_bool(bytes, i) 103 | discard get_bool(bytes, i) # Eventually used for architecture score 104 | parse_result.player_data = get_seq_u8(bytes, i) 105 | 106 | if not headers_only: 107 | parse_result.components = get_components(bytes, i, solution) 108 | parse_result.wires = get_wires(bytes, i) -------------------------------------------------------------------------------- /versions/v2.nim: -------------------------------------------------------------------------------- 1 | import random 2 | import ../libraries/supersnappy/supersnappy 3 | import ../common 4 | 5 | const TELEPORT_WIRE = 0b0010_0000'u8 6 | const DIRECTIONS = [ 7 | point(x: 1, y: 0), 8 | point(x: 1, y: 1), 9 | point(x: 0, y: 1), 10 | point(x: -1, y: 1), 11 | point(x: -1, y: 0), 12 | point(x: -1, y: -1), 13 | point(x: 0, y: -1), 14 | point(x: 1, y: -1), 15 | ] 16 | 17 | func get_component(input: seq[uint8], i: var int, solution = false): parse_component = 18 | try: # Only fails for obsolete components (deleted enum values) 19 | var kind = component_kind(get_u16(input, i).int) 20 | let index = [DELETED_12, DELETED_13, DELETED_14, DELETED_15, DELETED_16].find(kind) 21 | if index != -1: 22 | kind = [Bidirectional1, Bidirectional8, Bidirectional16, Bidirectional32, Bidirectional64][index] 23 | result = parse_component(kind: kind) 24 | except: discard 25 | if solution and result.kind == Rom: 26 | result.kind = SolutionRom 27 | result.position = get_point(input, i) 28 | result.rotation = get_u8(input, i) 29 | result.permanent_id = get_int(input, i) 30 | result.custom_string = get_string(input, i) 31 | result.setting_1 = get_u64(input, i) 32 | result.setting_2 = get_u64(input, i) 33 | 34 | if result.kind == Custom: 35 | result.custom_id = get_int(input, i) 36 | result.custom_displacement = get_point(input, i) 37 | 38 | elif result.kind in [Program8_1, Program8_4, Program]: 39 | let len = get_u16(input, i) 40 | var j = 0'u16 41 | while j < len: 42 | let key = get_int(input, i) 43 | result.selected_programs[key] = get_string(input, i) 44 | j += 1 45 | 46 | func get_components(input: seq[uint8], i: var int, solution = false): seq[parse_component] = 47 | let len = get_int(input, i) 48 | for j in 0..len - 1: 49 | let comp = get_component(input, i, solution) 50 | if comp.kind == Error or comp.kind in DELETED_KINDS: continue 51 | result.add(comp) 52 | 53 | func get_wire(input: seq[uint8], i: var int): parse_wire = 54 | result.kind = wire_kind(get_u8(input, i)) 55 | result.color = get_u8(input, i) 56 | result.comment = get_string(input, i) 57 | 58 | #[ 59 | Wire paths encoding rules: 60 | 1. The wire starts with a point: (x: int16, y: int16). 61 | 2. After this follow 1 or more segments (3 bit direction, 5 bit length) 62 | 3. We end once a 0 length segment is encountered (0 byte) 63 | ]# 64 | 65 | result.path.add(get_point(input, i)) 66 | 67 | var segment = get_u8(input, i) 68 | 69 | # This is a special case to support players who want to generate disconnected wires 70 | if segment == TELEPORT_WIRE: 71 | result.path.add(get_point(input, i)) 72 | return 73 | 74 | var length_left = (segment and 0b0001_1111).int 75 | while length_left != 0: 76 | let direction = DIRECTIONS[segment shr 5] 77 | 78 | while length_left > 0: 79 | result.path.add(result.path[^1] + direction) 80 | length_left -= 1 81 | 82 | segment = get_u8(input, i) 83 | length_left = (segment and 0b0001_1111).int 84 | 85 | func get_wires(input: seq[uint8], i: var int): seq[parse_wire] = 86 | let len = get_int(input, i) 87 | for j in 0..len - 1: 88 | result.add(get_wire(input, i)) 89 | 90 | proc parse*(compressed: seq[uint8], headers_only: bool, solution: bool, parse_result: var parse_result) = 91 | var bytes = uncompress(compressed[1..^1]) 92 | var i = 0 93 | 94 | parse_result.save_id = rand(int.high) # This version was live for 2 hours only, because this field was missing, which causes custom components to crash 95 | parse_result.gate = get_int(bytes, i) 96 | parse_result.delay = get_int(bytes, i) 97 | parse_result.menu_visible = get_bool(bytes, i) 98 | parse_result.clock_speed = get_u32(bytes, i) 99 | parse_result.dependencies = get_seq_int(bytes, i) 100 | parse_result.description = get_string(bytes, i) 101 | parse_result.camera_position = get_point(bytes, i) 102 | discard get_bool(bytes, i) 103 | discard get_seq_u8(bytes, i) 104 | parse_result.player_data = get_seq_u8(bytes, i) 105 | 106 | if not headers_only: 107 | parse_result.components = get_components(bytes, i, solution) 108 | parse_result.wires = get_wires(bytes, i) -------------------------------------------------------------------------------- /versions/v4.nim: -------------------------------------------------------------------------------- 1 | import ../libraries/supersnappy/supersnappy 2 | import ../common 3 | 4 | const TELEPORT_WIRE = 0b0010_0000'u8 5 | const DIRECTIONS = [ 6 | point(x: 1, y: 0), 7 | point(x: 1, y: 1), 8 | point(x: 0, y: 1), 9 | point(x: -1, y: 1), 10 | point(x: -1, y: 0), 11 | point(x: -1, y: -1), 12 | point(x: 0, y: -1), 13 | point(x: 1, y: -1), 14 | ] 15 | 16 | func get_component(input: seq[uint8], i: var int, solution = false): parse_component = 17 | try: # Only fails for obsolete components (deleted enum values) 18 | var kind = component_kind(get_u16(input, i).int) 19 | let index = [DELETED_12, DELETED_13, DELETED_14, DELETED_15, DELETED_16].find(kind) 20 | if index != -1: 21 | kind = [Bidirectional1, Bidirectional8, Bidirectional16, Bidirectional32, Bidirectional64][index] 22 | result = parse_component(kind: kind) 23 | except: discard 24 | if solution and result.kind == Rom: 25 | result.kind = SolutionRom 26 | result.position = get_point(input, i) 27 | result.rotation = get_u8(input, i) 28 | result.permanent_id = get_int(input, i) 29 | result.custom_string = get_string(input, i) 30 | result.setting_1 = get_u64(input, i) 31 | result.setting_2 = get_u64(input, i) 32 | 33 | if result.kind == Custom: 34 | result.custom_id = get_int(input, i) 35 | result.custom_displacement = get_point(input, i) 36 | 37 | elif result.kind in [Program8_1, Program8_4, Program]: 38 | let len = get_u16(input, i) 39 | var j = 0'u16 40 | while j < len: 41 | let key = get_int(input, i) 42 | result.selected_programs[key] = get_string(input, i) 43 | j += 1 44 | 45 | func get_components(input: seq[uint8], i: var int, solution = false): seq[parse_component] = 46 | let len = get_int(input, i) 47 | for j in 0..len - 1: 48 | let comp = get_component(input, i, solution) 49 | if comp.kind == Error or comp.kind in DELETED_KINDS: continue 50 | result.add(comp) 51 | 52 | func get_wire(input: seq[uint8], i: var int): parse_wire = 53 | result.kind = wire_kind(get_u8(input, i)) 54 | result.color = get_u8(input, i) 55 | result.comment = get_string(input, i) 56 | 57 | #[ 58 | Wire paths encoding rules: 59 | 1. The wire starts with a point: (x: int16, y: int16). 60 | 2. After this follow 1 or more segments (3 bit direction, 5 bit length) 61 | 3. We end once a 0 length segment is encountered (0 byte) 62 | ]# 63 | 64 | result.path.add(get_point(input, i)) 65 | 66 | var segment = get_u8(input, i) 67 | 68 | # This is a special case to support players who want to generate disconnected wires 69 | if segment == TELEPORT_WIRE: 70 | result.path.add(get_point(input, i)) 71 | return 72 | 73 | var length_left = (segment and 0b0001_1111).int 74 | while length_left != 0: 75 | let direction = DIRECTIONS[segment shr 5] 76 | 77 | while length_left > 0: 78 | result.path.add(result.path[^1] + direction) 79 | length_left -= 1 80 | 81 | segment = get_u8(input, i) 82 | length_left = (segment and 0b0001_1111).int 83 | 84 | func get_wires(input: seq[uint8], i: var int): seq[parse_wire] = 85 | let len = get_int(input, i) 86 | for j in 0..len - 1: 87 | result.add(get_wire(input, i)) 88 | 89 | proc parse*(compressed: seq[uint8], headers_only: bool, solution: bool, parse_result: var parse_result) = 90 | var bytes = uncompress(compressed[1..^1]) 91 | var i = 0 92 | 93 | parse_result.save_id = get_int(bytes, i) 94 | parse_result.hub_id = get_u32(bytes, i) 95 | parse_result.gate = get_int(bytes, i) 96 | parse_result.delay = get_int(bytes, i) 97 | parse_result.menu_visible = get_bool(bytes, i) 98 | parse_result.clock_speed = get_u32(bytes, i) 99 | parse_result.dependencies = get_seq_int(bytes, i) 100 | parse_result.description = get_string(bytes, i) 101 | parse_result.camera_position = get_point(bytes, i) 102 | parse_result.synced = get_sync_state(bytes, i) 103 | parse_result.campaign_bound = get_bool(bytes, i) 104 | discard get_u16(bytes, i) # Eventually used for architecture score 105 | parse_result.player_data = get_seq_u8(bytes, i) 106 | 107 | if not headers_only: 108 | parse_result.components = get_components(bytes, i, solution) 109 | parse_result.wires = get_wires(bytes, i) 110 | -------------------------------------------------------------------------------- /versions/v1.nim: -------------------------------------------------------------------------------- 1 | import ../common 2 | 3 | const TELEPORT_WIRE = 0b0010_0000'u8 4 | const DIRECTIONS = [ 5 | point(x: 1, y: 0), 6 | point(x: 1, y: 1), 7 | point(x: 0, y: 1), 8 | point(x: -1, y: 1), 9 | point(x: -1, y: 0), 10 | point(x: -1, y: -1), 11 | point(x: 0, y: -1), 12 | point(x: 1, y: -1), 13 | ] 14 | 15 | func get_seq_i64(input: seq[uint8], i: var int): seq[int] = 16 | let len = get_int(input, i) 17 | for j in 0..len - 1: 18 | result.add(get_int(input, i)) 19 | 20 | func get_string(input: seq[uint8], i: var int): string = 21 | let len = get_int(input, i) 22 | for j in 0..len - 1: 23 | result.add(chr(get_u8(input, i))) 24 | 25 | func get_point(input: seq[uint8], i: var int): point = 26 | return point( 27 | x: get_i16(input, i), 28 | y: get_i16(input, i) 29 | ) 30 | 31 | func get_component(input: seq[uint8], i: var int, solution = false): parse_component = 32 | try: # Only fails for obsolete components (deleted enum values) 33 | var kind = component_kind(get_u16(input, i).int) 34 | let index = [DELETED_12, DELETED_13, DELETED_14, DELETED_15, DELETED_16].find(kind) 35 | if index != -1: 36 | kind = [Bidirectional1, Bidirectional8, Bidirectional16, Bidirectional32, Bidirectional64][index] 37 | result = parse_component(kind: kind) 38 | except: discard 39 | if solution and result.kind == Rom: 40 | result.kind = SolutionRom 41 | result.position = get_point(input, i) 42 | result.rotation = get_u8(input, i) 43 | result.permanent_id = get_int(input, i) 44 | result.custom_string = get_string(input, i) 45 | if result.kind in [Program8_1, DELETED_6, DELETED_7, Program8_4, Program]: 46 | discard get_string(input, i) 47 | elif result.kind == Custom: 48 | result.custom_id = get_int(input, i) 49 | 50 | func get_components(input: seq[uint8], i: var int, solution = false): seq[parse_component] = 51 | let len = get_int(input, i) 52 | for j in 0..len - 1: 53 | let comp = get_component(input, i, solution) 54 | if comp.kind == Error or comp.kind in DELETED_KINDS: continue 55 | result.add(comp) 56 | 57 | func get_wire(input: seq[uint8], i: var int): parse_wire = 58 | discard get_int(input, i) # used to be permanent_id 59 | result.kind = wire_kind(get_u8(input, i)) 60 | result.color = get_u8(input, i) 61 | result.comment = get_string(input, i) 62 | 63 | #[ 64 | Wire paths encoding rules: 65 | 1. The wire starts with a point: (x: int16, y: int16). 66 | 2. After this follow 1 or more segments (3 bit direction, 5 bit length) 67 | 3. We end once a 0 length segment is encountered (0 byte) 68 | ]# 69 | 70 | result.path.add(get_point(input, i)) 71 | 72 | var segment = get_u8(input, i) 73 | 74 | # This is a special case to support players who want to generate disconnected wires 75 | if segment == TELEPORT_WIRE: 76 | result.path.add(get_point(input, i)) 77 | return 78 | 79 | var length_left = (segment and 0b0001_1111).int 80 | while length_left != 0: 81 | let direction = DIRECTIONS[segment shr 5] 82 | 83 | while length_left > 0: 84 | result.path.add(result.path[^1] + direction) 85 | length_left -= 1 86 | 87 | segment = get_u8(input, i) 88 | length_left = (segment and 0b0001_1111).int 89 | 90 | func get_wires(input: seq[uint8], i: var int): seq[parse_wire] = 91 | let len = get_int(input, i) 92 | for j in 0..len - 1: 93 | result.add(get_wire(input, i)) 94 | 95 | proc parse*(bytes: seq[uint8], headers_only: bool, solution: bool, parse_result: var parse_result) = 96 | var i = 1 # 0th byte is version 97 | 98 | parse_result.save_id = get_int(bytes, i) 99 | parse_result.gate = get_u32(bytes, i).int 100 | parse_result.delay = get_u32(bytes, i).int 101 | parse_result.menu_visible = get_bool(bytes, i) 102 | parse_result.clock_speed = get_u32(bytes, i) 103 | discard get_u8(bytes, i) # Used to be nesting level 104 | parse_result.dependencies = get_seq_i64(bytes, i) 105 | parse_result.description = get_string(bytes, i) 106 | discard get_bool(bytes, i) 107 | parse_result.camera_position = get_point(bytes, i) 108 | discard get_bool(bytes, i) 109 | 110 | if not headers_only: 111 | parse_result.components = get_components(bytes, i, solution) 112 | parse_result.wires = get_wires(bytes, i) -------------------------------------------------------------------------------- /versions/v5.nim: -------------------------------------------------------------------------------- 1 | import ../libraries/supersnappy/supersnappy 2 | import ../common 3 | 4 | const TELEPORT_WIRE = 0b0010_0000'u8 5 | const DIRECTIONS = [ 6 | point(x: 1, y: 0), 7 | point(x: 1, y: 1), 8 | point(x: 0, y: 1), 9 | point(x: -1, y: 1), 10 | point(x: -1, y: 0), 11 | point(x: -1, y: -1), 12 | point(x: 0, y: -1), 13 | point(x: 1, y: -1), 14 | ] 15 | 16 | func get_component(input: seq[uint8], i: var int, solution = false): parse_component = 17 | try: # Only fails for obsolete components (deleted enum values) 18 | var kind = component_kind(get_u16(input, i).int) 19 | let index = [DELETED_12, DELETED_13, DELETED_14, DELETED_15, DELETED_16].find(kind) 20 | if index != -1: 21 | kind = [Bidirectional1, Bidirectional8, Bidirectional16, Bidirectional32, Bidirectional64][index] 22 | result = parse_component(kind: kind) 23 | except: discard 24 | if solution and result.kind == Rom: 25 | result.kind = SolutionRom 26 | result.position = get_point(input, i) 27 | result.rotation = get_u8(input, i) 28 | result.permanent_id = get_int(input, i) 29 | result.custom_string = get_string(input, i) 30 | result.setting_1 = get_u64(input, i) 31 | result.setting_2 = get_u64(input, i) 32 | 33 | if result.kind == Custom: 34 | result.custom_id = get_int(input, i) 35 | result.custom_displacement = get_point(input, i) 36 | 37 | elif result.kind in [Program8_1, Program8_4, Program]: 38 | let len = get_u16(input, i) 39 | var j = 0'u16 40 | while j < len: 41 | let key = get_int(input, i) 42 | result.selected_programs[key] = get_string(input, i) 43 | j += 1 44 | 45 | func get_components(input: seq[uint8], i: var int, solution = false): seq[parse_component] = 46 | let len = get_int(input, i) 47 | for j in 0..len - 1: 48 | let comp = get_component(input, i, solution) 49 | if comp.kind == Error or comp.kind in DELETED_KINDS: continue 50 | result.add(comp) 51 | 52 | func get_wire(input: seq[uint8], i: var int): parse_wire = 53 | result.kind = wire_kind(get_u8(input, i)) 54 | result.color = get_u8(input, i) 55 | result.comment = get_string(input, i) 56 | 57 | #[ 58 | Wire paths encoding rules: 59 | 1. The wire starts with a point: (x: int16, y: int16). 60 | 2. After this follow 1 or more segments (3 bit direction, 5 bit length) 61 | 3. We end once a 0 length segment is encountered (0 byte) 62 | ]# 63 | 64 | result.path.add(get_point(input, i)) 65 | 66 | var segment = get_u8(input, i) 67 | 68 | # This is a special case to support players who want to generate disconnected wires 69 | if segment == TELEPORT_WIRE: 70 | result.path.add(get_point(input, i)) 71 | return 72 | 73 | var length_left = (segment and 0b0001_1111).int 74 | while length_left != 0: 75 | let direction = DIRECTIONS[segment shr 5] 76 | 77 | while length_left > 0: 78 | result.path.add(result.path[^1] + direction) 79 | length_left -= 1 80 | 81 | segment = get_u8(input, i) 82 | length_left = (segment and 0b0001_1111).int 83 | 84 | func get_wires(input: seq[uint8], i: var int): seq[parse_wire] = 85 | let len = get_int(input, i) 86 | for j in 0..len - 1: 87 | result.add(get_wire(input, i)) 88 | 89 | proc parse*(compressed: seq[uint8], headers_only: bool, solution: bool, parse_result: var parse_result) = 90 | var bytes = uncompress(compressed[1..^1]) 91 | var i = 0 92 | 93 | parse_result.save_id = get_int(bytes, i) 94 | parse_result.hub_id = get_u32(bytes, i) 95 | parse_result.gate = get_int(bytes, i) 96 | parse_result.delay = get_int(bytes, i) 97 | parse_result.menu_visible = get_bool(bytes, i) 98 | parse_result.clock_speed = get_u32(bytes, i) 99 | parse_result.dependencies = get_seq_int(bytes, i) 100 | parse_result.description = get_string(bytes, i) 101 | parse_result.camera_position = get_point(bytes, i) 102 | parse_result.synced = get_sync_state(bytes, i) 103 | parse_result.campaign_bound = get_bool(bytes, i) 104 | discard get_u16(bytes, i) # Eventually used for architecture score 105 | parse_result.player_data = get_seq_u8(bytes, i) 106 | parse_result.hub_description = get_string(bytes, i) 107 | 108 | if not headers_only: 109 | parse_result.components = get_components(bytes, i, solution) 110 | parse_result.wires = get_wires(bytes, i) 111 | -------------------------------------------------------------------------------- /versions/v6.nim: -------------------------------------------------------------------------------- 1 | import ../libraries/supersnappy/supersnappy 2 | import ../common 3 | 4 | const TELEPORT_WIRE = 0b0010_0000'u8 5 | const DIRECTIONS = [ 6 | point(x: 1, y: 0), 7 | point(x: 1, y: 1), 8 | point(x: 0, y: 1), 9 | point(x: -1, y: 1), 10 | point(x: -1, y: 0), 11 | point(x: -1, y: -1), 12 | point(x: 0, y: -1), 13 | point(x: 1, y: -1), 14 | ] 15 | 16 | func get_component(input: seq[uint8], i: var int, solution = false): parse_component = 17 | try: # Only fails for obsolete components (deleted enum values) 18 | var kind = component_kind(get_u16(input, i).int) 19 | let index = [DELETED_12, DELETED_13, DELETED_14, DELETED_15, DELETED_16].find(kind) 20 | if index != -1: 21 | kind = [Bidirectional1, Bidirectional8, Bidirectional16, Bidirectional32, Bidirectional64][index] 22 | result = parse_component(kind: kind) 23 | #result = parse_component(kind: component_kind(get_u16(input, i).int)) # For v7 and beyond 24 | except: discard 25 | if solution and result.kind == Rom: 26 | result.kind = SolutionRom 27 | result.position = get_point(input, i) 28 | result.rotation = get_u8(input, i) 29 | result.permanent_id = get_int(input, i) 30 | result.custom_string = get_string(input, i) 31 | result.setting_1 = get_u64(input, i) 32 | result.setting_2 = get_u64(input, i) 33 | result.ui_order = get_i16(input, i) 34 | 35 | if result.kind == Custom: 36 | result.custom_id = get_int(input, i) 37 | result.custom_displacement = get_point(input, i) 38 | 39 | elif result.kind in [Program8_1, Program8_4, Program]: 40 | let len = get_u16(input, i) 41 | var j = 0'u16 42 | while j < len: 43 | let key = get_int(input, i) 44 | result.selected_programs[key] = get_string(input, i) 45 | j += 1 46 | 47 | func get_components(input: seq[uint8], i: var int, solution = false): seq[parse_component] = 48 | let len = get_int(input, i) 49 | for j in 0..len - 1: 50 | let comp = get_component(input, i, solution) 51 | if comp.kind == Error or comp.kind in DELETED_KINDS: continue 52 | result.add(comp) 53 | 54 | func get_wire(input: seq[uint8], i: var int): parse_wire = 55 | result.kind = wire_kind(get_u8(input, i)) 56 | result.color = get_u8(input, i) 57 | result.comment = get_string(input, i) 58 | 59 | #[ 60 | Wire paths encoding rules: 61 | 1. The wire starts with a point: (x: int16, y: int16). 62 | 2. After this follow 1 or more segments (3 bit direction, 5 bit length) 63 | 3. We end once a 0 length segment is encountered (0 byte) 64 | ]# 65 | 66 | result.path.add(get_point(input, i)) 67 | 68 | var segment = get_u8(input, i) 69 | 70 | # This is a special case to support players who want to generate disconnected wires 71 | if segment == TELEPORT_WIRE: 72 | result.path.add(get_point(input, i)) 73 | return 74 | 75 | var length_left = (segment and 0b0001_1111).int 76 | while length_left != 0: 77 | let direction = DIRECTIONS[segment shr 5] 78 | 79 | while length_left > 0: 80 | result.path.add(result.path[^1] + direction) 81 | length_left -= 1 82 | 83 | segment = get_u8(input, i) 84 | length_left = (segment and 0b0001_1111).int 85 | 86 | func get_wires(input: seq[uint8], i: var int): seq[parse_wire] = 87 | let len = get_int(input, i) 88 | for j in 0..len - 1: 89 | result.add(get_wire(input, i)) 90 | 91 | proc parse*(compressed: seq[uint8], headers_only: bool, solution: bool, parse_result: var parse_result) = 92 | var bytes = uncompress(compressed[1..^1]) 93 | var i = 0 94 | 95 | parse_result.save_id = get_int(bytes, i) 96 | parse_result.hub_id = get_u32(bytes, i) 97 | parse_result.gate = get_int(bytes, i) 98 | parse_result.delay = get_int(bytes, i) 99 | parse_result.menu_visible = get_bool(bytes, i) 100 | parse_result.clock_speed = get_u32(bytes, i) 101 | parse_result.dependencies = get_seq_int(bytes, i) 102 | parse_result.description = get_string(bytes, i) 103 | parse_result.camera_position = get_point(bytes, i) 104 | parse_result.synced = get_sync_state(bytes, i) 105 | parse_result.campaign_bound = get_bool(bytes, i) 106 | discard get_u16(bytes, i) # Eventually used for architecture score 107 | parse_result.player_data = get_seq_u8(bytes, i) 108 | parse_result.hub_description = get_string(bytes, i) 109 | 110 | if not headers_only: 111 | parse_result.components = get_components(bytes, i, solution) 112 | parse_result.wires = get_wires(bytes, i) 113 | -------------------------------------------------------------------------------- /save_monger.nim: -------------------------------------------------------------------------------- 1 | import os 2 | import libraries/supersnappy/supersnappy 3 | import common, versions/[v0,v1,v2,v3,v4,v5,v6] 4 | export common 5 | const LATES_VERSION* = 6'u8 6 | 7 | proc file_get_bytes*(file_name: string): seq[uint8] = 8 | 9 | if not fileExists(file_name): 10 | return 11 | 12 | var file: File 13 | 14 | # Since the game loads files from 2 different threads, we can't assume the file isn't locked (on Windows) 15 | while not file.open(file_name): 16 | sleep(1) 17 | defer: file.close() 18 | 19 | let len = getFileSize(file) 20 | var buffer = newSeq[uint8](len) 21 | if len == 0: return buffer 22 | 23 | discard file.readBytes(buffer, 0, len) 24 | return buffer 25 | 26 | proc parse_state*(input: seq[uint8], headers_only = false, solution = false): parse_result = 27 | 28 | # Versions modify the result object instead of returning a new one. 29 | # This is so that defaults can be set here for values old versions may not parse 30 | 31 | result.gate = 99999 32 | result.delay = 99999 33 | result.clock_speed = 100000.uint32 34 | result.menu_visible = true 35 | 36 | if input.len == 0: return 37 | 38 | result.version = input[0] 39 | 40 | case result.version: 41 | of 0: v0.parse(input, headers_only, solution, result) 42 | of 1: v1.parse(input, headers_only, solution, result) 43 | of 2: v2.parse(input, headers_only, solution, result) 44 | of 3: v3.parse(input, headers_only, solution, result) 45 | of 4: v4.parse(input, headers_only, solution, result) 46 | of 5: v5.parse(input, headers_only, solution, result) 47 | of 6: v6.parse(input, headers_only, solution, result) 48 | else: discard 49 | 50 | proc add_component(arr: var seq[uint8], component: parse_component) = 51 | arr.add_component_kind(component.kind) 52 | arr.add_point(component.position) 53 | arr.add_u8(component.rotation) 54 | arr.add_int(component.permanent_id) 55 | arr.add_string(component.custom_string) 56 | arr.add_u64(component.setting_1) 57 | arr.add_u64(component.setting_2) 58 | arr.add_i16(component.ui_order) 59 | 60 | if component.kind == Custom: 61 | arr.add_int(component.custom_id) 62 | arr.add_point(component.custom_displacement) 63 | elif component.kind in [Program8_1, Program8_4, Program]: 64 | arr.add_u16(component.selected_programs.len.uint16) 65 | for level, program in component.selected_programs: 66 | arr.add_int(level) 67 | arr.add_string(program) 68 | 69 | proc add_wire(arr: var seq[uint8], wire: parse_wire) = 70 | arr.add_wire_kind(wire.kind) 71 | arr.add_u8(wire.color) 72 | arr.add_string(wire.comment) 73 | arr.add_path(wire.path) 74 | 75 | proc state_to_binary*(save_id: int, 76 | components: seq[parse_component], 77 | wires: seq[parse_wire], 78 | gate: int, 79 | delay: int, 80 | menu_visible: bool, 81 | clock_speed: uint32, 82 | description: string, 83 | camera_position: point, 84 | hub_id: uint32, 85 | hub_description: string, 86 | synced = unsynced, 87 | campaign_bound = false, 88 | player_data = newSeq[uint8]()): seq[uint8] = 89 | 90 | var dependencies: seq[int] 91 | var components_to_save: seq[int] 92 | 93 | for id, component in components: 94 | if component.kind == Custom and component.custom_id notin dependencies: 95 | dependencies.add(component.custom_id) 96 | if component.kind in LATE_KINDS: continue 97 | if component.kind == WireCluster: continue 98 | components_to_save.add(id) 99 | 100 | result.add_int(save_id) 101 | result.add_u32(hub_id) 102 | result.add_int(gate) 103 | result.add_int(delay) 104 | result.add_bool(menu_visible) 105 | result.add_u32(clock_speed) 106 | result.add_seq_int(dependencies) 107 | result.add_string(description) 108 | result.add_point(camera_position) 109 | result.add_sync_state(synced) 110 | result.add_bool(campaign_bound) 111 | result.add_u16(0'u16) # Eventually used for architecture score 112 | result.add_seq_u8(player_data) 113 | result.add_string(hub_description) 114 | 115 | result.add_int(components_to_save.len) 116 | for id in components_to_save: 117 | result.add_component(components[id]) 118 | 119 | result.add_int(wires.len) 120 | for id, wire in wires: 121 | result.add_wire(wire) 122 | 123 | return LATES_VERSION & compress(result) 124 | -------------------------------------------------------------------------------- /libraries/supersnappy/supersnappy.nim: -------------------------------------------------------------------------------- 1 | # This implementation has been heavily influenced by snappy-c 2 | # See the snappy-c repo at https://github.com/andikleen/snappy-c for 3 | # extensive comments explaining the implementation. 4 | 5 | import bitops, supersnappy/common 6 | 7 | const 8 | uncompressLookup = [ 9 | 0x0001.uint16, 0x0804, 0x1001, 0x2001, 0x0002, 0x0805, 0x1002, 0x2002, 10 | 0x0003, 0x0806, 0x1003, 0x2003, 0x0004, 0x0807, 0x1004, 0x2004, 11 | 0x0005, 0x0808, 0x1005, 0x2005, 0x0006, 0x0809, 0x1006, 0x2006, 12 | 0x0007, 0x080a, 0x1007, 0x2007, 0x0008, 0x080b, 0x1008, 0x2008, 13 | 0x0009, 0x0904, 0x1009, 0x2009, 0x000a, 0x0905, 0x100a, 0x200a, 14 | 0x000b, 0x0906, 0x100b, 0x200b, 0x000c, 0x0907, 0x100c, 0x200c, 15 | 0x000d, 0x0908, 0x100d, 0x200d, 0x000e, 0x0909, 0x100e, 0x200e, 16 | 0x000f, 0x090a, 0x100f, 0x200f, 0x0010, 0x090b, 0x1010, 0x2010, 17 | 0x0011, 0x0a04, 0x1011, 0x2011, 0x0012, 0x0a05, 0x1012, 0x2012, 18 | 0x0013, 0x0a06, 0x1013, 0x2013, 0x0014, 0x0a07, 0x1014, 0x2014, 19 | 0x0015, 0x0a08, 0x1015, 0x2015, 0x0016, 0x0a09, 0x1016, 0x2016, 20 | 0x0017, 0x0a0a, 0x1017, 0x2017, 0x0018, 0x0a0b, 0x1018, 0x2018, 21 | 0x0019, 0x0b04, 0x1019, 0x2019, 0x001a, 0x0b05, 0x101a, 0x201a, 22 | 0x001b, 0x0b06, 0x101b, 0x201b, 0x001c, 0x0b07, 0x101c, 0x201c, 23 | 0x001d, 0x0b08, 0x101d, 0x201d, 0x001e, 0x0b09, 0x101e, 0x201e, 24 | 0x001f, 0x0b0a, 0x101f, 0x201f, 0x0020, 0x0b0b, 0x1020, 0x2020, 25 | 0x0021, 0x0c04, 0x1021, 0x2021, 0x0022, 0x0c05, 0x1022, 0x2022, 26 | 0x0023, 0x0c06, 0x1023, 0x2023, 0x0024, 0x0c07, 0x1024, 0x2024, 27 | 0x0025, 0x0c08, 0x1025, 0x2025, 0x0026, 0x0c09, 0x1026, 0x2026, 28 | 0x0027, 0x0c0a, 0x1027, 0x2027, 0x0028, 0x0c0b, 0x1028, 0x2028, 29 | 0x0029, 0x0d04, 0x1029, 0x2029, 0x002a, 0x0d05, 0x102a, 0x202a, 30 | 0x002b, 0x0d06, 0x102b, 0x202b, 0x002c, 0x0d07, 0x102c, 0x202c, 31 | 0x002d, 0x0d08, 0x102d, 0x202d, 0x002e, 0x0d09, 0x102e, 0x202e, 32 | 0x002f, 0x0d0a, 0x102f, 0x202f, 0x0030, 0x0d0b, 0x1030, 0x2030, 33 | 0x0031, 0x0e04, 0x1031, 0x2031, 0x0032, 0x0e05, 0x1032, 0x2032, 34 | 0x0033, 0x0e06, 0x1033, 0x2033, 0x0034, 0x0e07, 0x1034, 0x2034, 35 | 0x0035, 0x0e08, 0x1035, 0x2035, 0x0036, 0x0e09, 0x1036, 0x2036, 36 | 0x0037, 0x0e0a, 0x1037, 0x2037, 0x0038, 0x0e0b, 0x1038, 0x2038, 37 | 0x0039, 0x0f04, 0x1039, 0x2039, 0x003a, 0x0f05, 0x103a, 0x203a, 38 | 0x003b, 0x0f06, 0x103b, 0x203b, 0x003c, 0x0f07, 0x103c, 0x203c, 39 | 0x0801, 0x0f08, 0x103d, 0x203d, 0x1001, 0x0f09, 0x103e, 0x203e, 40 | 0x1801, 0x0f0a, 0x103f, 0x203f, 0x2001, 0x0f0b, 0x1040, 0x2040 41 | ] 42 | lenWordMask = [0.uint32, 0xff, 0xffff, 0xffffff, 0xffffffff.uint32] 43 | maxBlockSize = 1 shl 16 44 | maxCompressTableSize = 1 shl 14 45 | 46 | type 47 | SnappyError* = object of ValueError ## Raised if an operation fails. 48 | 49 | when defined(release): 50 | {.push checks: off.} 51 | 52 | template failUncompress() = 53 | raise newException( 54 | SnappyError, "Invalid buffer, unable to uncompress" 55 | ) 56 | 57 | template failCompress() = 58 | raise newException( 59 | SnappyError, "Unable to compress buffer" 60 | ) 61 | 62 | func uncompress*(dst: var string, src: string) {.raises: [SnappyError].} = 63 | ## Uncompresses src into dst. This resizes dst as needed and starts writing 64 | ## at dst index 0. 65 | 66 | let (uncompressedLen, bytesRead) = varint(src) 67 | if bytesRead <= 0: 68 | failUncompress() 69 | 70 | dst.setLen(uncompressedLen) 71 | 72 | let 73 | srcLen = src.len.uint 74 | dstLen = dst.len.uint 75 | var 76 | ip = bytesRead.uint 77 | op = 0.uint 78 | while ip < srcLen: 79 | if (cast[uint8](src[ip]) and 3) == 0: # LITERAL 80 | var len = src[ip].uint shr 2 + 1 81 | inc ip 82 | 83 | if len <= 16 and srcLen > ip + 16 and dstLen > op + 16: 84 | copy64(dst, src, op + 0, ip + 0) 85 | copy64(dst, src, op + 8, ip + 8) 86 | else: 87 | if len >= 61.uint: 88 | let bytes = len - 60 89 | len = (read32(src, ip) and lenWordMask[bytes]) + 1 90 | ip += bytes 91 | 92 | if len <= 0 or ip + len > srcLen or op + len > dstLen: 93 | failUncompress() 94 | 95 | copyMem(dst, src, op, ip, len) 96 | 97 | ip += len 98 | op += len 99 | else: # COPY 100 | if ip + 1 >= srcLen: 101 | failUncompress() 102 | 103 | let 104 | entry = uncompressLookup[cast[uint8](src[ip])].uint 105 | trailer = read32(src, ip + 1) and lenWordMask[entry shr 11] 106 | len = (entry and 255) 107 | offset = (entry and 0x700) + trailer 108 | 109 | ip += (entry shr 11) + 1 110 | 111 | if dstLen - op < len or op <= offset - 1: # Catches offset == 0 112 | failUncompress() 113 | 114 | if len <= 16 and offset >= 8.uint and dstLen > op + 16: 115 | copy64(dst, dst, op, op - offset) 116 | copy64(dst, dst, op + 8, op - offset + 8) 117 | op += len 118 | elif dstLen - op >= len + 10: 119 | var 120 | src = op - offset 121 | pos = op 122 | remaining = len.int 123 | while pos - src < 8: 124 | copy64(dst, dst, pos, src) 125 | remaining -= (pos - src).int 126 | pos += pos - src 127 | while remaining > 0: 128 | copy64(dst, dst, pos, src) 129 | src += 8 130 | pos += 8 131 | remaining -= 8 132 | op += len 133 | else: 134 | for i in op ..< op + len: 135 | dst[op] = dst[op - offset] 136 | inc op 137 | 138 | if op != dstLen: 139 | failUncompress() 140 | 141 | func uncompress*(src: string): string {.inline.} = 142 | ## Uncompresses src and returns the uncompressed data. 143 | uncompress(result, src) 144 | 145 | func uncompress*(src: seq[uint8]): seq[uint8] {.inline.} = 146 | ## Uncompresses src and returns the uncompressed data. 147 | cast[seq[uint8]](uncompress(cast[string](src))) 148 | 149 | func emitLiteral( 150 | dst: var string, 151 | src: string, 152 | op: var uint, 153 | ip, len: uint, 154 | fastPath: bool 155 | ) = 156 | var n = len - 1 157 | if n < 60: 158 | dst[op] = (n shl 2).char 159 | inc op 160 | if fastPath and len <= 16: 161 | copy64(dst, src, op + 0, ip + 0) 162 | copy64(dst, src, op + 8, ip + 8) 163 | op += len 164 | return 165 | else: 166 | var 167 | base = op 168 | count: uint 169 | inc op 170 | while n > 0.uint: 171 | dst[op] = (n and 255).char 172 | n = n shr 8 173 | inc op 174 | inc count 175 | dst[base] = ((59.uint + count) shl 2).char 176 | 177 | copyMem(dst, src, op, ip, len) 178 | op += len 179 | 180 | func findMatchLength(src: string, s1, s2, limit: uint): uint {.inline.} = 181 | var 182 | s1 = s1 183 | s2 = s2 184 | while s2 <= limit - 8: 185 | let x = read64(src, s2) xor read64(src, s1 + result) 186 | if x != 0: 187 | let matchingBits = countTrailingZeroBits(x).uint 188 | result += (matchingBits shr 3) 189 | return 190 | s2 += 8 191 | result += 8 192 | while s2 < limit: 193 | if src[s2] != src[s1 + result]: 194 | return 195 | inc s2 196 | inc result 197 | 198 | func emitCopy64Max(dst: var string, op: var uint, offset, len: uint) = 199 | if len < 12 and offset < 2048: 200 | dst[op] = (1.uint + (((len - 4.uint) shl 2) + ((offset shr 8) shl 5))).char 201 | inc op 202 | dst[op] = (offset and 255).char 203 | inc op 204 | else: 205 | dst[op] = (2.uint + ((len - 1.uint) shl 2)).char 206 | inc op 207 | when nimvm: 208 | let tmp = (offset and 0xffff).uint16 209 | dst[op + 0] = ((tmp shl 0) and 255).char 210 | dst[op + 1] = ((tmp shr 8) and 255).char 211 | else: 212 | cast[ptr uint16](dst[op].addr)[] = offset.uint16 213 | op += 2 214 | 215 | func emitCopy(dst: var string, op: var uint, offset, len: uint) = 216 | var len = len 217 | while len >= 68.uint: 218 | emitCopy64Max(dst, op, offset, 64) 219 | len -= 64 220 | 221 | if len > 64.uint: 222 | emitCopy64Max(dst, op, offset, 60) 223 | len -= 60 224 | 225 | emitCopy64Max(dst, op, offset, len) 226 | 227 | func compressFragment( 228 | dst: var string, 229 | src: string, 230 | op: var uint, 231 | start: uint, 232 | len: uint, 233 | compressTable: var seq[uint16] 234 | ) = 235 | let ipEnd = start + len 236 | var 237 | ip = start 238 | nextEmit = ip 239 | tableSize = 256.uint 240 | shift = 24.uint 241 | 242 | while tableSize < maxCompressTableSize and tableSize < len: 243 | tableSize = tableSize shl 1 244 | dec shift 245 | 246 | when nimvm: 247 | for i in 0 ..< tableSize: 248 | compressTable[i] = 0 249 | else: 250 | zeroMem(compressTable[0].addr, tableSize * sizeof(uint16).uint) 251 | 252 | template hash(v: uint32): uint32 = 253 | (v * 0x1e35a7bd) shr shift 254 | 255 | template uint32AtOffset(v: uint64, shift: int): uint32 = 256 | ((v shr shift) and 0xffffffff.uint32).uint32 257 | 258 | template emitRemainder() = 259 | if nextEmit < ipEnd: 260 | emitLiteral(dst, src, op, nextEmit, ipEnd - nextEmit, false) 261 | 262 | if len >= 15.uint: 263 | let ipLimit = start + len - 15 264 | inc ip 265 | 266 | var nextHash = hash(read32(src, ip)) 267 | while true: 268 | var 269 | skipBytes = 32.uint 270 | nextIp = ip 271 | candidate: uint 272 | while true: 273 | ip = nextIp 274 | var 275 | h = nextHash 276 | bytesBetweenHashLookups = skipBytes shr 5 277 | inc skipBytes 278 | nextIp = ip + bytesBetweenHashLookups 279 | if nextIp > ipLimit: 280 | emitRemainder() 281 | return 282 | nextHash = hash(read32(src, nextIp)) 283 | candidate = start + compressTable[h] 284 | compressTable[h] = (ip - start).uint16 285 | 286 | if read32(src, ip) == read32(src, candidate): 287 | break 288 | 289 | emitLiteral(dst, src, op, nextEmit, ip - nextEmit, true) 290 | 291 | var 292 | inputBytes: uint64 293 | candidateBytes: uint32 294 | while true: 295 | let 296 | matched = 4.uint + findMatchLength(src, candidate + 4, ip + 4, ipEnd) 297 | offset = ip - candidate 298 | ip += matched 299 | emitCopy(dst, op, offset, matched) 300 | 301 | let insertTail = ip - 1 302 | nextEmit = ip 303 | if ip >= ipLimit: 304 | emitRemainder() 305 | return 306 | inputBytes = read64(src, insertTail) 307 | let 308 | prevHash = hash(uint32AtOffset(inputBytes, 0)) 309 | curHash = hash(uint32AtOffset(inputBytes, 8)) 310 | compressTable[prevHash] = (ip - start - 1).uint16 311 | candidate = start + compressTable[curHash] 312 | candidateBytes = read32(src, candidate) 313 | compressTable[curHash] = (ip - start).uint16 314 | 315 | if uint32AtOffset(inputBytes, 8) != candidateBytes: 316 | break 317 | 318 | nextHash = hash(uint32AtOffset(inputBytes, 16)) 319 | inc ip 320 | 321 | emitRemainder() 322 | 323 | func compress*(dst: var string, src: string) {.raises: [SnappyError].} = 324 | ## Compresses src into dst. This resizes dst as needed and starts writing 325 | ## at dst index 0. 326 | 327 | when sizeof(int) > 4: 328 | # Ensure varint32 prefix will work. 329 | if src.len > uint32.high.int: 330 | failCompress() 331 | 332 | dst.setLen(32 + src.len + (src.len div 6)) # Worst-case compressed length 333 | 334 | let varintBytes = varint(src.len.uint32) 335 | for i in 0 ..< varintBytes.len: 336 | dst[i] = varintBytes[i] 337 | 338 | var 339 | ip = 0.uint 340 | op = varintBytes.len.uint 341 | compressTable = newSeq[uint16](maxCompressTableSize) 342 | while ip < src.len.uint: 343 | let 344 | fragmentSize = src.len.uint - ip 345 | bytesToRead = min(fragmentSize, maxBlockSize) 346 | if bytesToRead <= 0: 347 | failCompress() 348 | 349 | compressFragment(dst, src, op, ip, bytesToRead, compressTable) 350 | ip += bytesToRead 351 | 352 | dst.setLen(op) 353 | 354 | func compress*(src: string): string {.inline.} = 355 | ## Compresses src and returns the compressed data. 356 | compress(result, src) 357 | 358 | func compress*(src: seq[uint8]): seq[uint8] {.inline.} = 359 | ## Compresses src and returns the compressed data. 360 | cast[seq[uint8]](compress(cast[string](src))) 361 | 362 | when defined(release): 363 | {.pop.} 364 | -------------------------------------------------------------------------------- /common.nim: -------------------------------------------------------------------------------- 1 | import tables 2 | export tables 3 | 4 | const TELEPORT_WIRE = 0b0010_0000'u8 5 | 6 | type component_kind* = enum 7 | Error = 0 8 | Off = 1 9 | On = 2 10 | Buffer1 = 3 11 | Not = 4 12 | And = 5 13 | And3 = 6 14 | Nand = 7 15 | Or = 8 16 | Or3 = 9 17 | Nor = 10 18 | Xor = 11 19 | Xnor = 12 20 | Counter8 = 13 21 | VirtualCounter8 = 14 22 | Counter64 = 15 23 | VirtualCounter64 = 16 24 | Ram8 = 17 25 | VirtualRam8 = 18 26 | DELETED_0 = 19 27 | DELETED_1 = 20 28 | DELETED_17 = 21 29 | DELETED_18 = 22 30 | Register8 = 23 31 | VirtualRegister8 = 24 32 | Register8Red = 25 33 | VirtualRegister8Red = 26 34 | Register8RedPlus = 27 35 | VirtualRegister8RedPlus = 28 36 | Register64 = 29 37 | VirtualRegister64 = 30 38 | Switch8 = 31 39 | Mux8 = 32 40 | Decoder1 = 33 41 | Decoder3 = 34 42 | Constant8 = 35 43 | Not8 = 36 44 | Or8 = 37 45 | And8 = 38 46 | Xor8 = 39 47 | Equal8 = 40 48 | DELETED_2 = 41 49 | DELETED_3 = 42 50 | Neg8 = 43 51 | Add8 = 44 52 | Mul8 = 45 53 | Splitter8 = 46 54 | Maker8 = 47 55 | Splitter64 = 48 56 | Maker64 = 49 57 | FullAdder = 50 58 | BitMemory = 51 59 | VirtualBitMemory = 52 60 | DELETED_10 = 53 61 | Decoder2 = 54 62 | Timing = 55 63 | NoteSound = 56 64 | DELETED_4 = 57 65 | DELETED_5 = 58 66 | Keyboard = 59 67 | FileLoader = 60 68 | Halt = 61 69 | WireCluster = 62 70 | LevelScreen = 63 71 | Program8_1 = 64 72 | Program8_1Red = 65 73 | DELETED_6 = 66 74 | DELETED_7 = 67 75 | Program8_4 = 68 76 | LevelGate = 69 77 | Input1 = 70 78 | LevelInput2Pin = 71 79 | LevelInput3Pin = 72 80 | LevelInput4Pin = 73 81 | LevelInputConditions = 74 82 | Input8 = 75 83 | Input64 = 76 84 | LevelInputCode = 77 85 | LevelInputArch = 78 86 | Output1 = 79 87 | LevelOutput1Sum = 80 88 | LevelOutput1Car = 81 89 | DELETED_8 = 82 90 | DELETED_9 = 83 91 | LevelOutput2Pin = 84 92 | LevelOutput3Pin = 85 93 | LevelOutput4Pin = 86 94 | Output8 = 87 95 | Output64 = 88 96 | LevelOutputArch = 89 97 | LevelOutputCounter = 90 98 | DELETED_11 = 91 99 | Custom = 92 100 | VirtualCustom = 93 101 | Program = 94 102 | DelayLine1 = 95 103 | VirtualDelayLine1 = 96 104 | Console = 97 105 | Shl8 = 98 106 | Shr8 = 99 107 | 108 | Constant64 = 100 109 | Not64 = 101 110 | Or64 = 102 111 | And64 = 103 112 | Xor64 = 104 113 | Neg64 = 105 114 | Add64 = 106 115 | Mul64 = 107 116 | Equal64 = 108 117 | LessU64 = 109 118 | LessI64 = 110 119 | Shl64 = 111 120 | Shr64 = 112 121 | Mux64 = 113 122 | Switch64 = 114 123 | 124 | ProbeMemoryBit = 115 125 | ProbeMemoryWord = 116 126 | 127 | AndOrLatch = 117 128 | NandNandLatch = 118 129 | NorNorLatch = 119 130 | 131 | LessU8 = 120 132 | LessI8 = 121 133 | 134 | DotMatrixDisplay = 122 135 | SegmentDisplay = 123 136 | 137 | Input16 = 124 138 | Input32 = 125 139 | 140 | Output16 = 126 141 | Output32 = 127 142 | 143 | DELETED_12 = 128 144 | DELETED_13 = 129 145 | DELETED_14 = 130 146 | DELETED_15 = 131 147 | DELETED_16 = 132 148 | 149 | Buffer8 = 133 150 | Buffer16 = 134 151 | Buffer32 = 135 152 | Buffer64 = 136 153 | 154 | ProbeWireBit = 137 155 | ProbeWireWord = 138 156 | 157 | Switch1 = 139 158 | 159 | Output1z = 140 160 | Output8z = 141 161 | Output16z = 142 162 | Output32z = 143 163 | Output64z = 144 164 | 165 | Constant16 = 145 166 | Not16 = 146 167 | Or16 = 147 168 | And16 = 148 169 | Xor16 = 149 170 | Neg16 = 150 171 | Add16 = 151 172 | Mul16 = 152 173 | Equal16 = 153 174 | LessU16 = 154 175 | LessI16 = 155 176 | Shl16 = 156 177 | Shr16 = 157 178 | Mux16 = 158 179 | Switch16 = 159 180 | Splitter16 = 160 181 | Maker16 = 161 182 | Register16 = 162 183 | VirtualRegister16 = 163 184 | Counter16 = 164 185 | VirtualCounter16 = 165 186 | 187 | Constant32 = 166 188 | Not32 = 167 189 | Or32 = 168 190 | And32 = 169 191 | Xor32 = 170 192 | Neg32 = 171 193 | Add32 = 172 194 | Mul32 = 173 195 | Equal32 = 174 196 | LessU32 = 175 197 | LessI32 = 176 198 | Shl32 = 177 199 | Shr32 = 178 200 | Mux32 = 179 201 | Switch32 = 180 202 | Splitter32 = 181 203 | Maker32 = 182 204 | Register32 = 183 205 | VirtualRegister32 = 184 206 | Counter32 = 185 207 | VirtualCounter32 = 186 208 | 209 | LevelOutput8z = 187 210 | 211 | Nand8 = 188 212 | Nor8 = 189 213 | Xnor8 = 190 214 | Nand16 = 191 215 | Nor16 = 192 216 | Xnor16 = 193 217 | Nand32 = 194 218 | Nor32 = 195 219 | Xnor32 = 196 220 | Nand64 = 197 221 | Nor64 = 198 222 | Xnor64 = 199 223 | 224 | Ram = 200 225 | VirtualRam = 201 226 | RamLatency = 202 227 | VirtualRamLatency = 203 228 | 229 | RamFast = 204 230 | VirtualRamFast = 205 231 | Rom = 206 232 | VirtualRom = 207 233 | SolutionRom = 208 234 | VirtualSolutionRom = 209 235 | 236 | DelayLine8 = 210 237 | VirtualDelayLine8 = 211 238 | DelayLine16 = 212 239 | VirtualDelayLine16 = 213 240 | DelayLine32 = 214 241 | VirtualDelayLine32 = 215 242 | DelayLine64 = 216 243 | VirtualDelayLine64 = 217 244 | 245 | RamDualLoad = 218 246 | VirtualRamDualLoad = 219 247 | 248 | Hdd = 220 249 | VirtualHdd = 221 250 | 251 | Network = 222 252 | 253 | Rol8 = 223 254 | Rol16 = 224 255 | Rol32 = 225 256 | Rol64 = 226 257 | Ror8 = 227 258 | Ror16 = 228 259 | Ror32 = 229 260 | Ror64 = 230 261 | 262 | IndexerBit = 231 263 | IndexerByte = 232 264 | 265 | DivMod8 = 233 266 | DivMod16 = 234 267 | DivMod32 = 235 268 | DivMod64 = 236 269 | 270 | SpriteDisplay = 237 271 | ConfigDelay = 238 272 | 273 | Clock = 239 274 | 275 | LevelInput1 = 240 276 | LevelInput8 = 241 277 | LevelOutput1 = 242 278 | LevelOutput8 = 243 279 | 280 | Ashr8 = 244 281 | Ashr16 = 245 282 | Ashr32 = 246 283 | Ashr64 = 247 284 | 285 | Bidirectional1 = 248 286 | VirtualBidirectional1 = 249 287 | Bidirectional8 = 250 288 | VirtualBidirectional8 = 251 289 | Bidirectional16 = 252 290 | VirtualBidirectional16 = 253 291 | Bidirectional32 = 254 292 | VirtualBidirectional32 = 255 293 | Bidirectional64 = 256 294 | VirtualBidirectional64 = 257 295 | 296 | const EARLY_KINDS* = {LevelInput1, LevelInput2Pin, LevelInput3Pin, LevelInput4Pin, LevelInputConditions, LevelInput8, Input64, LevelInputCode, LevelOutput1, LevelOutput1Sum, LevelOutput1Car, LevelOutput2Pin, LevelOutput3Pin, LevelOutput4Pin, LevelOutput8, LevelOutputArch, LevelOutputCounter, LevelOutput8z, DelayLine1, DelayLine16, BitMemory, Ram8, Hdd, Register8, Counter32, Counter16, Register16, DelayLine8, Custom, SolutionRom, RamFast, Counter64, Rom, Register32, Ram, Register8RedPlus, DelayLine64, Register64, DelayLine32, Counter8, Register8Red, RamLatency, RamDualLoad, DelayLine1, DelayLine16, BitMemory, Ram8, Hdd, Register8, Counter32, Counter16, Register16, DelayLine8, Custom, SolutionRom, RamFast, Counter64, Rom, Register32, Ram, Register8RedPlus, DelayLine64, Register64, DelayLine32, Counter8, Register8Red, RamLatency, RamDualLoad, Bidirectional1, Bidirectional8, Bidirectional16, Bidirectional32, Bidirectional64} 297 | const LATE_KINDS* = {VirtualCounter8, VirtualCounter64, VirtualRam8, VirtualRegister8, VirtualRegister8Red, VirtualRegister8RedPlus, VirtualRegister64, VirtualBitMemory, VirtualCustom, VirtualDelayLine1, VirtualRegister16, VirtualCounter16, VirtualRegister32, VirtualCounter32, VirtualRam, VirtualRamLatency, VirtualRamFast, VirtualRom, VirtualSolutionRom, VirtualDelayLine8, VirtualDelayLine16, VirtualDelayLine32, VirtualDelayLine64, VirtualRamDualLoad, VirtualHdd, VirtualBidirectional1, VirtualBidirectional8, VirtualBidirectional16, VirtualBidirectional32, VirtualBidirectional64} 298 | const CUSTOM_INPUTS* = {Input1, Input8, Input16, Input32, Input64} 299 | const CUSTOM_OUTPUTS* = {Output1, Output8, Output16, Output32, Output64} 300 | const CUSTOM_TRISTATE_OUTPUTS* = {Output1z, Output8z, Output16z, Output32z, Output64z} 301 | const CUSTOM_BIDIRECTIONAL* = {Bidirectional1, Bidirectional8, Bidirectional16, Bidirectional32, Bidirectional64} 302 | const LEVEL_INPUTS* = {LevelInput1, LevelInput2Pin, LevelInput3Pin, LevelInput4Pin, LevelInput8, LevelInputArch, LevelInputCode, LevelInputConditions} 303 | const LEVEL_OUTPUTS* = {LevelOutput1, LevelOutput1Car, LevelOutput1Sum, LevelOutput2Pin, LevelOutput3Pin, LevelOutput4Pin, LevelOutput8, LevelOutput8z, LevelOutputArch, LevelOutputCounter} 304 | const LATCHES* = {AndOrLatch, NandNandLatch, NorNorLatch} 305 | const DELETED_KINDS* = {DELETED_0, DELETED_1, DELETED_2, DELETED_3, DELETED_4, DELETED_5, DELETED_6, DELETED_7, DELETED_9, DELETED_9, DELETED_10, DELETED_11, DELETED_12, DELETED_13, DELETED_14, DELETED_15, DELETED_16, DELETED_17, DELETED_18} 306 | 307 | type point* = object 308 | x*: int16 309 | y*: int16 310 | 311 | const DIRECTIONS = [ 312 | point(x: 1, y: 0), 313 | point(x: 1, y: 1), 314 | point(x: 0, y: 1), 315 | point(x: -1, y: 1), 316 | point(x: -1, y: 0), 317 | point(x: -1, y: -1), 318 | point(x: 0, y: -1), 319 | point(x: 1, y: -1), 320 | ] 321 | 322 | type wire_kind* = enum 323 | wk_1 324 | wk_8 325 | wk_16 326 | wk_32 327 | wk_64 328 | 329 | type sync_state* = enum 330 | unsynced 331 | synced 332 | changed_after_sync 333 | 334 | type parse_component* = object 335 | kind*: component_kind 336 | position*: point 337 | custom_displacement*: point 338 | rotation*: uint8 339 | real_offset*: int8 340 | permanent_id*: int 341 | custom_string*: string 342 | custom_id*: int 343 | setting_1*: uint64 344 | setting_2*: uint64 345 | selected_programs*: Table[int, string] 346 | ui_order*: int16 347 | 348 | type parse_wire* = object 349 | path*: seq[point] 350 | kind*: wire_kind 351 | color*: uint8 352 | comment*: string 353 | 354 | type parse_result* = object 355 | version*: uint8 356 | components*: seq[parse_component] 357 | wires*: seq[parse_wire] 358 | save_id*: int # Unique id for each architectures and custom components. For levels it serves as a check against outdated versions 359 | hub_id*: uint32 360 | hub_description*: string 361 | gate*: int 362 | delay*: int 363 | menu_visible*: bool 364 | clock_speed*: uint32 365 | dependencies*: seq[int] 366 | description*: string 367 | camera_position*: point 368 | player_data*: seq[uint8] 369 | synced*: sync_state 370 | campaign_bound*: bool 371 | 372 | # Unfortunately casting to uints is necessary to avoid crashing on under / overflows 373 | proc `+`*(a: point, b: point): point = 374 | return point( 375 | x: cast[int16](cast[uint16](a.x) + cast[uint16](b.x)), 376 | y: cast[int16](cast[uint16](a.y) + cast[uint16](b.y)) 377 | ) 378 | 379 | proc `-`*(a: point, b: point): point = 380 | return point( 381 | x: cast[int16](cast[uint16](a.x) - cast[uint16](b.x)), 382 | y: cast[int16](cast[uint16](a.y) - cast[uint16](b.y)) 383 | ) 384 | 385 | proc `*`*(a: point, b: int16): point = 386 | return point( 387 | x: cast[int16](cast[uint16](a.x) * cast[uint16](b)), 388 | y: cast[int16](cast[uint16](a.y) * cast[uint16](b)) 389 | ) 390 | 391 | proc get_bool*(input: seq[uint8], i: var int): bool = 392 | result = input[i] != 0 393 | i += 1 394 | 395 | proc get_int*(input: seq[uint8], i: var int): int = 396 | result = input[i+0].int shl 0 + 397 | input[i+1].int shl 8 + 398 | input[i+2].int shl 16 + 399 | input[i+3].int shl 24 + 400 | input[i+4].int shl 32 + 401 | input[i+5].int shl 40 + 402 | input[i+6].int shl 48 + 403 | input[i+7].int shl 56 404 | i += 8 405 | 406 | proc get_u64*(input: seq[uint8], i: var int): uint64 = 407 | result = cast[uint64](get_int(input, i)) 408 | 409 | proc get_u32*(input: seq[uint8], i: var int): uint32 = 410 | result = input[i+0].uint32 shl 0 + 411 | input[i+1].uint32 shl 8 + 412 | input[i+2].uint32 shl 16 + 413 | input[i+3].uint32 shl 24 414 | i += 4 415 | 416 | proc get_u16*(input: seq[uint8], i: var int): uint16 = 417 | result = input[i+0].uint16 shl 0 + 418 | input[i+1].uint16 shl 8 419 | i += 2 420 | 421 | proc get_i16*(input: seq[uint8], i: var int): int16 = 422 | result = cast[int16](get_u16(input, i)) 423 | 424 | proc get_u8*(input: seq[uint8], i: var int): uint8 = 425 | result = input[i] 426 | i += 1 427 | 428 | proc get_i8*(input: seq[uint8], i: var int): int8 = 429 | result = cast[int8](input[i]) 430 | i += 1 431 | 432 | proc get_sync_state*(input: seq[uint8], i: var int): sync_state = 433 | result = sync_state(get_u8(input, i)) 434 | 435 | proc get_point*(input: seq[uint8], i: var int): point = 436 | return point( 437 | x: get_i16(input, i), 438 | y: get_i16(input, i) 439 | ) 440 | 441 | proc get_seq_u8*(input: seq[uint8], i: var int, bits32 = false): seq[uint8] = 442 | var len = 0 443 | if bits32: 444 | len = get_u32(input, i).int 445 | else: 446 | len = get_u16(input, i).int 447 | var j = 0 448 | while j < len: 449 | result.add(get_u8(input, i)) 450 | j += 1 451 | 452 | proc get_seq_int*(input: seq[uint8], i: var int): seq[int] = 453 | let len = get_u16(input, i) 454 | var j = 0'u16 455 | while j < len: 456 | result.add(get_int(input, i)) 457 | j += 1 458 | 459 | proc get_string*(input: seq[uint8], i: var int): string = 460 | let len = get_u16(input, i) 461 | var j = 0'u16 462 | while j < len: 463 | result.add(chr(get_u8(input, i))) 464 | j += 1 465 | 466 | proc add_bool*(arr: var seq[uint8], input: bool) = 467 | arr.add(input.uint8) 468 | 469 | proc add_int*(arr: var seq[uint8], input: int) = 470 | arr.add(cast[uint8]((input shr 0) and 0xff)) 471 | arr.add(cast[uint8]((input shr 8) and 0xff)) 472 | arr.add(cast[uint8]((input shr 16) and 0xff)) 473 | arr.add(cast[uint8]((input shr 24) and 0xff)) 474 | arr.add(cast[uint8]((input shr 32) and 0xff)) 475 | arr.add(cast[uint8]((input shr 40) and 0xff)) 476 | arr.add(cast[uint8]((input shr 48) and 0xff)) 477 | arr.add(cast[uint8]((input shr 56) and 0xff)) 478 | 479 | proc add_u64*(arr: var seq[uint8], input: uint64) = 480 | add_int(arr, cast[int](input)) 481 | 482 | proc add_u32*(arr: var seq[uint8], input: uint32) = 483 | arr.add(cast[uint8]((input shr 0) and 0xff)) 484 | arr.add(cast[uint8]((input shr 8) and 0xff)) 485 | arr.add(cast[uint8]((input shr 16) and 0xff)) 486 | arr.add(cast[uint8]((input shr 24) and 0xff)) 487 | 488 | proc add_u16*(arr: var seq[uint8], input: uint16) = 489 | arr.add(cast[uint8]((input shr 0) and 0xff)) 490 | arr.add(cast[uint8]((input shr 8) and 0xff)) 491 | 492 | proc add_i16*(arr: var seq[uint8], input: int16) = 493 | arr.add(cast[uint8]((input shr 0) and 0xff)) 494 | arr.add(cast[uint8]((input shr 8) and 0xff)) 495 | 496 | proc add_u8*(arr: var seq[uint8], input: uint8) = 497 | arr.add(input) 498 | 499 | proc add_i8*(arr: var seq[uint8], input: int8) = 500 | arr.add(cast[uint8](input)) 501 | 502 | proc add_component_kind*(arr: var seq[uint8], input: component_kind) = 503 | arr.add_u16(ord(input).uint16) 504 | 505 | proc add_wire_kind*(arr: var seq[uint8], input: wire_kind) = 506 | arr.add(ord(input).uint8) 507 | 508 | proc add_sync_state*(arr: var seq[uint8], input: sync_state) = 509 | arr.add(ord(input).uint8) 510 | 511 | proc add_point*(arr: var seq[uint8], input: point) = 512 | arr.add_i16(input.x) 513 | arr.add_i16(input.y) 514 | 515 | proc add_seq_u8*(arr: var seq[uint8], input: seq[uint8], bits32 = false) = 516 | if bits32: 517 | arr.add_u32(input.len.uint32) 518 | else: 519 | arr.add_u16(input.len.uint16) 520 | for i in input: 521 | arr.add_u8(i) 522 | 523 | proc add_seq_int*(arr: var seq[uint8], input: seq[int]) = 524 | arr.add_u16(input.len.uint16) 525 | for i in input: 526 | arr.add_int(i) 527 | 528 | proc add_seq_u64*(arr: var seq[uint8], input: seq[uint64]) = 529 | arr.add_u16(input.len.uint16) 530 | for i in input: 531 | arr.add_u64(i) 532 | 533 | proc add_string*(arr: var seq[uint8], input: string) = 534 | arr.add_u16(input.len.uint16) 535 | for c in input: 536 | arr.add_u8(ord(c).uint8) 537 | 538 | proc add_table_int_string*(arr: var seq[uint8], input: Table[int, string]) = 539 | arr.add_int(input.len) 540 | for key, value in input: 541 | arr.add_int(key) 542 | arr.add_string(value) 543 | 544 | proc add_path*(arr: var seq[uint8], path: seq[point]) = 545 | #[ 546 | Wire paths encoding rules: 547 | 1. The wire starts with a point: (x: int16, y: int16). 548 | 2. After this follow 1 or more segments (3 bit direction, 5 bit length) 549 | 3. We end once a 0 length segment is encountered (0 byte) 550 | ]# 551 | 552 | arr.add_point(path[0]) 553 | 554 | var offset = 0 555 | 556 | while offset < path.high: 557 | let original_direction = DIRECTIONS.find(path[offset + 1] - path[offset]) 558 | 559 | if original_direction == -1: 560 | if path.len == 2: 561 | # Special case for players who want to generate "teleport" wires 562 | arr.add_u8(TELEPORT_WIRE) 563 | arr.add_point(path[1]) 564 | return 565 | break 566 | 567 | # We have 5 bits to save the length, so max length is 31 568 | let max_length = min(path.high - offset, 0b0001_1111) 569 | var length = 1 570 | var direction = original_direction 571 | 572 | while length < max_length: 573 | direction = DIRECTIONS.find(path[offset + length + 1] - path[offset + length]) 574 | if direction != original_direction: break 575 | length += 1 576 | 577 | let segment = ((original_direction shl 5) or length).uint8 578 | arr.add_u8(segment) 579 | 580 | offset += length 581 | 582 | # Length 0 segment, ends the wire 583 | arr.add_u8(0.uint8) 584 | --------------------------------------------------------------------------------