Andri Yadi
Published © MIT

AIoT: Sipeed MAix Board + Arduino on OSX

Maixduino was released to support Arduino development for Sipeed's MAix dev boards. But it's only for Linux and Windows, what about OSX?

BeginnerFull instructions provided1 hour4,233
AIoT: Sipeed MAix Board + Arduino on OSX

Things used in this project

Hardware components

Sipeed Maix Go
It should work for Sipeed Maix Go, Maix One Dock, and Maix Bit
×1

Software apps and online services

OSX
Arduino IDE
Arduino IDE

Story

Read more

Code

Boards Manager Definition File

JSON
{
    "packages": [
        {
            "name": "Maixduino", 
            "maintainer": "Sipeed",
            "websiteURL": "https://maixduino.sipeed.com", 
            "email": "support@sipeed.com",
            "help": {
                "online": "https://maixduino.sipeed.com"
            }, 
            "platforms": [
                {
                    "category": "K210",
                    "name": "Maixduino(k210)",
                    "version": "0.3.6",
                    "architecture": "k210",
                    "url": "http://dl.cdn.sipeed.com/Maixduino_core_v0.3.6_2.zip",
                    "checksum": "SHA-256:111f7a76e489be11a2ed658de9d6e63a3847bc63bef67646b48df03a1550265b",
                    "archiveFileName": "Maixduino_core_v0.3.6_2.zip",
                    "size": "1063542",
                    "boards": [
                        {"name": "Sipeed \"Maix One Dock\"/\"Maix Bit\" Board"},
                        {"name": "Sipeed Maix Go Board"}
                    ],
                    "toolsDependencies": [
                        {
                            "packager": "Maixduino",
                            "version": "8.2.0",
                            "name": "riscv64-unknown-elf-gcc"
                        },
                        {
                            "packager": "Maixduino",
                            "version": "1.0.0",
                            "name": "kflash"
                        }
                    ]
                }
            ],
            "tools": [
                {
                    "name": "riscv64-unknown-elf-gcc", 
                    "version": "8.2.0", 
                    "systems": 
                    [
                        {
                            "url": "http://dl.cdn.sipeed.com/win_toolchain_v8.2.0_20190213.zip",
                            "checksum": "SHA-256:9af21c336c8ccb405a10d715c480f29c5996d3932adf6955993ca4b10502b72b",
                            "host": "i686-mingw32",
                            "archiveFileName": "win_toolchain_v8.2.0_20190213.zip",
                            "size": "487337534"
                        },
                        {
                            "url": "http://dl.cdn.sipeed.com/linux_kendryte-toolchain-ubuntu-amd64-8.2.0-20190213.tar.gz",
                            "checksum": "SHA-256:aa2fcc76ff61261b3667a422d4f67dec19c4547474bff4ebadaa1258b87985da",
                            "host": "x86_64-linux-gnu",
                            "archiveFileName": "linux_kendryte-toolchain-ubuntu-amd64-8.2.0-20190213.tar.gz",
                            "size": "360536176"
                        },
                        {
                            "url": "https://github.com/kendryte/kendryte-gnu-toolchain/releases/download/v8.2.0-20190213/kendryte-toolchain-osx-mojave-8.2.0-20190213.tar.gz",
                            "checksum": "MD5:74caa57a8d3bf2307bd23bf009fffffb",
                            "host": "i386-apple-darwin",
                            "archiveFileName": "kendryte-toolchain-osx-mojave-8.2.0-20190213.tar.gz",
                            "size": "50919270"
                        }
                    ]
                },
                {
                    "name": "kflash", 
                    "version": "1.0.0", 
                    "systems": 
                    [
                        {
                            "url": "http://dl.cdn.sipeed.com/win_kflash_py_v1.0.0.zip",
                            "checksum": "SHA-256:e2c1f217bb62bd30fd7d5a43aa7931b1144098f6d13ff9ed2a0c428130aaeec1",
                            "host": "i686-mingw32",
                            "archiveFileName": "win_kflash_py_v1.0.0.zip",
                            "size": "5900033"
                        },
                        {
                            "url": "http://dl.cdn.sipeed.com/linux_kflash_v1.0.0.zip",
                            "checksum": "SHA-256:d409ef1945c53b148bbf43832b25901c58dc9e1a18f431bd6992fdcc8f363630",
                            "host": "x86_64-linux-gnu",
                            "archiveFileName": "linux_kflash_v1.0.0.zip",
                            "size": "63387"
                        },
                        {
                            "url": "https://github.com/kendryte/kflash.py/archive/master.zip",
                            "checksum": "MD5:afd678846158c4fe023dca1b8c6925fa",
                            "host": "i386-apple-darwin",
                            "archiveFileName": "kflash.py-master.zip",
                            "size": "51762"
                        }
                    ]
                }
            ]
        }
    ]
}

Modified platform.txt

INI
name = Maixduino
version = 0.2.3

# arch
arch              = k210
arch_for_c_cpp    = K210

# Compile variables
compiler.path={runtime.tools.riscv64-unknown-elf-gcc.path}/bin/
compiler.c.cmd=riscv64-unknown-elf-gcc
compiler.cpp.cmd=riscv64-unknown-elf-g++
compiler.ld.cmd=riscv64-unknown-elf-ld
compiler.ar.cmd=riscv64-unknown-elf-ar
compiler.objcopy.cmd=riscv64-unknown-elf-objcopy
compiler.elf2hex.cmd=riscv64-unknown-elf-objcopy
compiler.size.cmd=riscv64-unknown-elf-size

compiler.clib.path={runtime.tools.riscv64-unknown-elf-gcc.path}/include
compiler.sdk.path={runtime.platform.path}/cores/arduino/kendryte-standalone-sdk/lib
compiler.lib_hal_inc.path={runtime.platform.path}/cores/arduino/hal/include
compiler.cores.path={runtime.platform.path}/cores/arduino/
compiler.preproc.flags=-I{build.system.path}/include -I{compiler.cores.path} -I{compiler.lib_hal_inc.path} -I{compiler.sdk.path}/bsp/include -I{compiler.sdk.path}/drivers/include -I{compiler.sdk.path}/utils/include -I{compiler.sdk.path}/freertos/conf -I{compiler.sdk.path}/freertos/include -I{compiler.sdk.path}/freertos/portable -I{compiler.clib.path}

compiler.both.flags=-mcmodel=medany -mabi=lp64f -march=rv64imafc -fno-common -ffunction-sections -fdata-sections -fstrict-volatile-bitfields -fno-zero-initialized-in-bss -Os -ggdb -Wall -Werror=all -Wno-error=unused-function -Wno-error=unused-but-set-variable -Wno-error=unused-variable -Wno-error=deprecated-declarations -Wextra -Werror=frame-larger-than=65536 -Wno-unused-parameter -Wno-sign-compare -Wno-error=missing-braces -Wno-error=return-type -Wno-error=pointer-sign -Wno-missing-braces -Wno-strict-aliasing -Wno-implicit-fallthrough -Wno-missing-field-initializers -Wno-int-to-pointer-cast -Wno-error=comment -Wno-error=logical-not-parentheses -Wno-error=duplicate-decl-specifier -Wno-error=parentheses -lpthread

compiler.debug.flags=-DCONFIG_LOG_ENABLE -DCONFIG_LOG_LEVEL=LOG_INFO -DDEBUG=1 -D__riscv64

compiler.c.flags=-c {compiler.debug.flags} {compiler.both.flags} {compiler.preproc.flags} -std=gnu11 -Wno-pointer-to-int-cast -Wno-old-style-declaration -g -Wno-error=unused-variable -Wno-error=unused-function -Wno-error=unused-const-variable

compiler.cpp.flags=-c {compiler.debug.flags} {compiler.both.flags} -I{runtime.platform.path}/libraries/SPI/src {compiler.preproc.flags} -std=gnu++17 -g -Wno-error=unused-variable -Wno-error=unused-function -Wno-error=unused-const-variable

compiler.ld.flags=-mcmodel=medany -mabi=lp64f -march=rv64imafc -fno-common -ffunction-sections -fdata-sections -fstrict-volatile-bitfields -fno-zero-initialized-in-bss -Os -ggdb -nostartfiles -static -Wl,--gc-sections -Wl,-static -Wl,--whole-archive -Wl,--no-whole-archive -Wl,-EL -Wl,--no-relax -T {build.ldscript}

compiler.S.flags=-c {compiler.debug.flags} {compiler.both.flags} {compiler.preproc.flags} -g -x assembler-with-cpp -D __riscv64

compiler.ar.flags=rcs

compiler.objcopy.eep.flags=

compiler.elf2hex.flags=-R .rel.dyn

compiler.define=-DARDUINO=

compiler.c.extra_flags=-DF_CPU={build.f_cpu} -D{build.board} -D{arch_for_c_cpp} -DARCH={arch_for_c_cpp}
compiler.c.elf.extra_flags=
compiler.cpp.extra_flags=-DF_CPU={build.f_cpu} -D{build.board} -D{arch_for_c_cpp} -DARCH={arch_for_c_cpp}
compiler.S.extra_flags=-DF_CPU={build.f_cpu} -D{build.board}
compiler.ar.extra_flags=
compiler.elf2hex.extra_flags=

# Can be overridden in boards.txt
build.extra_flags=

# USB Flags
# ---------
build.usb_flags=
build.openocdcfg=

# Compile patterns
# ---------------------

## Compile S files
recipe.S.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.S.flags} -DARDUINO={runtime.ide.version} {compiler.S.extra_flags} {build.extra_flags} {includes} "{source_file}" -o "{object_file}"

## Compile c files
recipe.c.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.c.flags} -DARDUINO={runtime.ide.version} {compiler.c.extra_flags} {build.extra_flags} {includes} "{source_file}" -o "{object_file}"

## Compile c++ files
recipe.cpp.o.pattern="{compiler.path}{compiler.cpp.cmd}" {compiler.cpp.flags} -DARDUINO={runtime.ide.version} {compiler.cpp.extra_flags} {build.extra_flags} {includes} "{source_file}" -o "{object_file}"

## Create archives
recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compiler.ar.extra_flags} "{archive_file_path}" "{object_file}"

## Link gc-sections, archives, and objects
recipe.c.combine.pattern="{compiler.path}{compiler.cpp.cmd}" {compiler.ld.flags} {build.extra_flags} {object_files} -o "{build.path}/{build.project_name}.elf" -Wl,--start-group -lgcc -lm -lc -Wl,--end-group -Wl,--start-group "{archive_file_path}" -lgcc -lm -lc -Wl,--end-group

## Create binary
recipe.objcopy.eep.pattern="{compiler.path}{compiler.objcopy.cmd}" {compiler.objcopy.eep.flags} --output-format=binary "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.bin"

## Create hex
recipe.objcopy.hex.pattern="{compiler.path}{compiler.elf2hex.cmd}" {compiler.elf2hex.flags} -O srec "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.hex"

## Compute size
recipe.size.pattern="{compiler.path}{compiler.size.cmd}" -B "{build.path}/{build.project_name}.elf"
recipe.size.regex=\s*[0-9]+\s+[0-9]+\s+[0-9]+\s+([0-9]+).*

## Save hex
recipe.output.tmp_file={build.project_name}.bin
recipe.output.save_file={build.project_name}.{build.variant}.bin

# Uploader tools
# -------------------

tools.kflash.path={runtime.tools.kflash.path}/
tools.kflash.cmd=/usr/local/bin/python3 {runtime.tools.kflash.path}/kflash.py
tools.kflash.cmd.windows={runtime.tools.kflash.path}/kflash_py
tools.kflash.program.pattern={cmd} -n -p {serial.port} -b {build.burn_baudrate} -B {build.burn_tool_firmware} {build.path}/{build.project_name}.bin
tools.kflash.program.pattern.windows="{cmd}" -n -p {serial.port} -b {build.burn_baudrate} -B {build.burn_tool_firmware} {build.path}/{build.project_name}.bin

Modified kflash.py

Python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import time
import zlib
import copy
import struct
from enum import Enum
import binascii
import hashlib
import argparse
import math
import zipfile, tempfile
import json
import re
import os

BASH_TIPS = dict(NORMAL='\033[0m',BOLD='\033[1m',DIM='\033[2m',UNDERLINE='\033[4m',
                    DEFAULT='\033[0m', RED='\033[31m', YELLOW='\033[33m', GREEN='\033[32m',
                    BG_DEFAULT='\033[49m', BG_WHITE='\033[107m')

ERROR_MSG   = BASH_TIPS['RED']+BASH_TIPS['BOLD']+'[ERROR]'+BASH_TIPS['NORMAL']
WARN_MSG    = BASH_TIPS['YELLOW']+BASH_TIPS['BOLD']+'[WARN]'+BASH_TIPS['NORMAL']
INFO_MSG    = BASH_TIPS['GREEN']+BASH_TIPS['BOLD']+'[INFO]'+BASH_TIPS['NORMAL']

VID_LIST_FOR_AUTO_LOOKUP = "(1A86)|(0403)|(067B)|(10C4)|(C251)|(0403)"
#                            WCH    FTDI    PL     CL    DAP   OPENEC
timeout = 0.5

MAX_RETRY_TIMES = 10

class TimeoutError(Exception): pass

class ProgramFileFormat(Enum):
    FMT_BINARY = 0
    FMT_ELF = 1
    FMT_KFPKG = 2

try:
    import serial
    import serial.tools.list_ports
except ImportError:
    print(ERROR_MSG,'PySerial must be installed, run '+BASH_TIPS['GREEN']+'`pip3 install pyserial`',BASH_TIPS['DEFAULT'])
    sys.exit(1)

# AES is from: https://github.com/ricmoo/pyaes, Copyright by Richard Moore
class AES:
    '''Encapsulates the AES block cipher.
    You generally should not need this. Use the AESModeOfOperation classes
    below instead.'''
    @staticmethod
    def _compact_word(word):
        return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3]

    # Number of rounds by keysize
    number_of_rounds = {16: 10, 24: 12, 32: 14}

    # Round constant words
    rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ]

    # S-box and Inverse S-box (S is for Substitution)
    S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ]
    Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ]

    # Transformations for encryption
    T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ]
    T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ]
    T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ]
    T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ]

    # Transformations for decryption
    T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ]
    T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ]
    T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ]
    T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ]

    # Transformations for decryption key expansion
    U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ]
    U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ]
    U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ]
    U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ]

    def __init__(self, key):

        if len(key) not in (16, 24, 32):
            raise ValueError('Invalid key size')

        rounds = self.number_of_rounds[len(key)]

        # Encryption round keys
        self._Ke = [[0] * 4 for i in range(rounds + 1)]

        # Decryption round keys
        self._Kd = [[0] * 4 for i in range(rounds + 1)]

        round_key_count = (rounds + 1) * 4
        KC = len(key) // 4

        # Convert the key into ints
        tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in range(0, len(key), 4) ]

        # Copy values into round key arrays
        for i in range(0, KC):
            self._Ke[i // 4][i % 4] = tk[i]
            self._Kd[rounds - (i // 4)][i % 4] = tk[i]

        # Key expansion (fips-197 section 5.2)
        rconpointer = 0
        t = KC
        while t < round_key_count:

            tt = tk[KC - 1]
            tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^
                      (self.S[(tt >>  8) & 0xFF] << 16) ^
                      (self.S[ tt        & 0xFF] <<  8) ^
                       self.S[(tt >> 24) & 0xFF]        ^
                      (self.rcon[rconpointer] << 24))
            rconpointer += 1

            if KC != 8:
                for i in range(1, KC):
                    tk[i] ^= tk[i - 1]

            # Key expansion for 256-bit keys is "slightly different" (fips-197)
            else:
                for i in range(1, KC // 2):
                    tk[i] ^= tk[i - 1]
                tt = tk[KC // 2 - 1]

                tk[KC // 2] ^= (self.S[ tt        & 0xFF]        ^
                               (self.S[(tt >>  8) & 0xFF] <<  8) ^
                               (self.S[(tt >> 16) & 0xFF] << 16) ^
                               (self.S[(tt >> 24) & 0xFF] << 24))

                for i in range(KC // 2 + 1, KC):
                    tk[i] ^= tk[i - 1]

            # Copy values into round key arrays
            j = 0
            while j < KC and t < round_key_count:
                self._Ke[t // 4][t % 4] = tk[j]
                self._Kd[rounds - (t // 4)][t % 4] = tk[j]
                j += 1
                t += 1

        # Inverse-Cipher-ify the decryption round key (fips-197 section 5.3)
        for r in range(1, rounds):
            for j in range(0, 4):
                tt = self._Kd[r][j]
                self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^
                                  self.U2[(tt >> 16) & 0xFF] ^
                                  self.U3[(tt >>  8) & 0xFF] ^
                                  self.U4[ tt        & 0xFF])

    def encrypt(self, plaintext):
        'Encrypt a block of plain text using the AES block cipher.'

        if len(plaintext) != 16:
            raise ValueError('wrong block length')

        rounds = len(self._Ke) - 1
        (s1, s2, s3) = [1, 2, 3]
        a = [0, 0, 0, 0]

        # Convert plaintext to (ints ^ key)
        t = [(AES._compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in range(0, 4)]

        # Apply round transforms
        for r in range(1, rounds):
            for i in range(0, 4):
                a[i] = (self.T1[(t[ i          ] >> 24) & 0xFF] ^
                        self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^
                        self.T3[(t[(i + s2) % 4] >>  8) & 0xFF] ^
                        self.T4[ t[(i + s3) % 4]        & 0xFF] ^
                        self._Ke[r][i])
            t = copy.copy(a)

        # The last round is special
        result = [ ]
        for i in range(0, 4):
            tt = self._Ke[rounds][i]
            result.append((self.S[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
            result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
            result.append((self.S[(t[(i + s2) % 4] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
            result.append((self.S[ t[(i + s3) % 4]        & 0xFF] ^  tt       ) & 0xFF)

        return result

    def decrypt(self, ciphertext):
        'Decrypt a block of cipher text using the AES block cipher.'

        if len(ciphertext) != 16:
            raise ValueError('wrong block length')

        rounds = len(self._Kd) - 1
        (s1, s2, s3) = [3, 2, 1]
        a = [0, 0, 0, 0]

        # Convert ciphertext to (ints ^ key)
        t = [(AES._compact_word(ciphertext[4 * i:4 * i + 4]) ^ self._Kd[0][i]) for i in range(0, 4)]

        # Apply round transforms
        for r in range(1, rounds):
            for i in range(0, 4):
                a[i] = (self.T5[(t[ i          ] >> 24) & 0xFF] ^
                        self.T6[(t[(i + s1) % 4] >> 16) & 0xFF] ^
                        self.T7[(t[(i + s2) % 4] >>  8) & 0xFF] ^
                        self.T8[ t[(i + s3) % 4]        & 0xFF] ^
                        self._Kd[r][i])
            t = copy.copy(a)

        # The last round is special
        result = [ ]
        for i in range(0, 4):
            tt = self._Kd[rounds][i]
            result.append((self.Si[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
            result.append((self.Si[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
            result.append((self.Si[(t[(i + s2) % 4] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
            result.append((self.Si[ t[(i + s3) % 4]        & 0xFF] ^  tt       ) & 0xFF)

        return result

class AES_128_CBC:

    def __init__(self, key, iv = None):
        self._aes = AES(key)
        if iv is None:
            self._last_cipherblock = [ 0 ] * 16
        elif len(iv) != 16:
            raise ValueError('initialization vector must be 16 bytes')
        else:
            self._last_cipherblock = iv


    def encrypt(self, plaintext):
        if len(plaintext) != 16:
            raise ValueError('plaintext block must be 16 bytes')

        precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ]
        self._last_cipherblock = self._aes.encrypt(precipherblock)

        return b''.join(map(lambda x: x.to_bytes(1, 'little'), self._last_cipherblock))

    def decrypt(self, ciphertext):
        if len(ciphertext) != 16:
            raise ValueError('ciphertext block must be 16 bytes')

        cipherblock = ciphertext
        plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ]
        self._last_cipherblock = cipherblock

        return b''.join(map(lambda x: x.to_bytes(1, 'little'), plaintext))

ISP_PROG = '789cecbd7b5c1447f6385add333dcd20283ac0a08c093202ca1a1745c5a861076118c5c4353e20bad1800d22061f08684cc2ca30d38c232a9a06677470831841d98d6b243ac6d7401449cc63cd4331d968d00106141522306078dcaaee1e5e31bbbf7bbff773ffb89f2f7e6aaa4fd5a953a74e9d5375ead1edb8638f2f7d7807031818fc97ba28282875910f0c0a14a619700098acac11a97b1707a91558983a0c9bab9e8b85abc3b1087504a6542bb1487524a652abb079ea79d87cf57c2c4a1d852d502fc05e56bf8cbda27e055ba85e88fd59fde703ab530bf1a054b720b00480e6ef605882c1188625388c61582280310c4b84308661090163189688600cc31212c6302c7182310c4bc430866189338c6158320cc6302c7181310c4b5c610cc392e1308661c90818c360380cdba4aed823c53120c14ffd2cd98c77a5fa295a24980708c546eddbe813b4206ea467cd135f9f69d44f389e8d378ea28c381e37724c8de13d240fbc3b55ae68192e38f81e4e8dc334a77040edc041d6ea71d846e0e39d0a7c82d0339622aa756e18de34aad9b3754cc7735d4535c5b5a50d279a4e359f693dd771a96beeddc8baa8c6850f17b744b7ade85cd57def6e7dddfdc6470f7f69696f7bdad9d31de083b9058c1fe91630619c5bc0a4296e013e61a302c6bf3a2a6042dca8804929a3027cd4ee01e3f7b9074c38ec1e30e923f7009f0acf80f1df78064cb8eb1930a9c513961f0dcb8f86e547c3f2a361796f58de1b96f786e5bd61f9b1b0fc58587e2c2c3f16967f1e967f1e967f1e967f3ed5cf67da133fb769ced9ee38b58c008f7d36458f2e7c1cb4774aaa9bc7b4a2d8b971f7e2f0cd459be7a6de4b15c517c74726d42788b6146f89dc5abfd5796de9daa8a4fb49ce6f97be1df5cefd7786279f485eb8fed1fae17f3df1d785db1f6d2761db743e6a37721c3652374e3d92f4c546e97cd5a3483926d1c9d512723ce6ae1baf7627fd300f9d9fda83f4c73c75fe6a4f320093ea02d4527202e6a59ba0f6222762a37513d5a3c9406c8c2e503d86fc03e6adfb83da9b9c84c97493d432f2056cacee05f5587232f69c6eb2fa39f28fd8f3ba3faa9f9760980fea2700602fd538d78e6af06c1ad3fc5cab6f877f5769cd89da530d679ace355f6afdb4e36a57d4dd85758b1ba31fae6859d5b6ba734df7fdbb8fea7e696c7ff8b4a5a7ed896f10d8dff9c4d76ddafe6ebc4854ec5c3afcc4a8539e67c69c7bee525151717169e98913a74e9d3973eedca54b738f449644fd7de13f1797459b579c5f65b977a4bee4fedf1ffdf397b276f3d3f33d164eb7e6fee4d02da457f293bec0b008005ba6a299cfffd1912ff8080778dcd79b53f74e058fe0efa869665f004c59f80e646959b1be717357df5b8da716a5ce4dbb97264a284e885c53bf46b4b5786be45bf56f3927952645adbbbfcef99dd277a2debdffeef0f527d62fdcf068c3f0ed27b68ff239e5b378dc2fe3464d3d357571f02fc19ee3cf8c8ff66bf7f39c71664674487bc89809e726ac98f874e29859e766ad98fd74f673932e4d5af542cf0bcf855e0a5df5a79e3f39a41b3652549beaaf6831e820e7ba80eb0554c516e39fd373e9354cb6e7d7cb728dff32aa24895260acbac5c6b7aaf2542164676f86b4734f5ed5f7aaf4dc5b55b716b0395fa03800c6c605db4c46f88b20e317b48a92b5034269502019c5e63cd91a11940a16b9b5590c6e28c5e7a3a2584c198665654af301901075df1b0c8897c4ef297d09900a40a64470e49f944000708d03e7e8b7061833b4dfb7faddbb69bdaaacb20d577fcee15affe1c045743455b67fd18f512b21806a7b6a204126a3d395eabfe0ebfb8acd81a128965649212027ec5f227e200516b7aa64f7e5b0915c8df4b75c29fa1a4f912d1786c1ba4122d0b44b8d0008e69dfd6cac6ab694c4b896547d6e80a99acf3efbdc755e0135dbcb0b0b4e24312cc078d95bd7de7beb32a1325695e653634fe3f0e98b2f97a8fdbc7504a05acf006f9d08a895de3484a83a9cf12071f5be037f9bbcbfa5477e8c04f20f488029b3320942a06aeb3dfb5d8645708c14051367004dc80181dd3573dcdcb270dcd0f3787ecab9f4cfca213746a73e6ee41f4831c887e769e1ae251212e0e709b362b26e081fc9fd7c10aac9fb8d5f480880d3f7f7451a633c72accf553f93b37433e24aa0242067c7be4bb0ec5a8228ce763aada0d26e016fd20951df01a9af6d001cf5c9fb0ffc6dbd39d809969ae784da5348929d166ce401d5040067b3485a296a920b5b00d7ab352df7a00e11715c9f33ba9253059437e990ad7bd8c7df2d85c1f8c5f74bd4fea8662aed26ac15b629d29b86d0863ac078c25adf1bfdb733f929835ac04a94be01681192e8e7966d29fe4ddd31a5da74a56fb3a855eede02a469501f84c2799c7e839fea6ac73a74fdd64bfc53e18f6aa51fff5c5ef391729cd210c46ae39b95289d7d8e5db74f39917f2e4cca57bec03fd7ac3da09ce2c05f7b4839dd819f288b65b535d45e54c1eaed11b1bdf030a7c14ef6a21bdcd3307b216f4f870b283194dead2fe4c524ee6823d47094e7c2e2be642f504772292573b148bd924d9d612fe0a49cf9efa24d78dabd784ca984929ec8b7075c9bcf3f29be54234eb9b67f2b4b664bffc96e44f51fc921f71fe73872b61b7f50e59bb191be0df9aa406d1070a669ae3f81a33fc1971a951aa03e95835630371ef52c365242b4005113cf4b9740d90231083dcadb96a257c95d5a802986c08ec09889a10163b78ff02556ee16b5fa364fae9d5a231f7bd841fda7a284ac35139c2667ef53f2faf31511c1e73dc5fcacc3e81ed452ae9dd84806da02d23596ba7008f526b987836ee64d4e371d54333f7368254b43345063cbbf73e8310f573a7057a614d52c6c5ddc1cddb4a261556d76ad7f575447a97ef2cea95ac619b8498601dc60823d94f3204bdc541c2b8abf9736775bd136fced7bebe6be59f426a68c8334187be788d9baa78a733a2ab51614e94c5a128c2df0d75369262011c3c1d13c0c7c4b2fa4bd4562906560a4625ccd9cd78e0f3b2f7e110b3d78b76ba5796ccae20ec941111eddbaa279758364c57d4c9a0cf5dc6557a5f8cd7deb8a1aaed52cec5ad594dd545c1ba89dac3fa53f93b3faa0f7306f5fd3ce9de3a8f7c5a03e3612fa1ba2a47bdbe6be5df436fecebd37e726634a3fc41b94a6492b86e35b08c608019eafa21e6d1568029cc13d43966eb78a93b49097b41648a45a56da3fe4ca3d5a40717614ecb5090e891f670c42c8bb5f41e881f3fa038ae5e60fff4bf9ee18b988a392af5c6f61c89382d02bdcdc97b8318f92c227814ab551af3a6fb00169368422dbdf44a36715f8219783d724133cec7b551d50f0a9bc347a847ff635dac193e2ec6933a43b2cf48a09d61c7a95a3ae4b72504f4c1a48dd73cd60ead96b1cd4116d8674f39597b60f57228a2207a755f17ba125a3674d952ede40c3b8323fee50e479a91d9cdf7f06dcc835405a9aab57e3664bab793e659fca03a3471c462d1e3e9833db1bfdd44adee0a89d7efdb7d4ea5fe7a871b4d68c9862a623b959efc8edc389db524a0d0b6b359fe880612bdbde05510d824f48804649ff264d80180bd462ca36937c4227d004ccc74cd55240e71c7f7c389ea1af6045f9bcec5e35917016a82285fe79b297bd7552902555335e5f7e6d90679600af97e9c40a926ab3012897e1b2eb882aa6646c010051e5c6a6b9494549c86a057ef38124bd6dc4ab966d31d6025b57199907f6268e4c1c4932a208e06ba046df047b1f186d871343f3e493846099c5f820cff6950559aa9baba8c9b7018ded815a9eaf97f47d3ae713e64bf34f6b4c04011c50e19f789d9c752f961bb1cc31c59e261db969f68d129c934ac02c23f4af647f6ecbcd983ed7236cdfcadd4effcacad45479004115018ac81f724d3adb2fcc7e5250f19e878a5a5bfd9c34115a1d5992cb18fcc1394341f8b614df66ffa6a80651ebc2da738653dac9da407d964e50990f7b8f06840a8d499aaaa3e05312c5a7c13df273d863549a1dec8e595628081003710e6195b4d94770dcc63298dfbe98f97550968095a44f2750c77c784c16a6cfe9ace65bf4312f8563985f46e7576694f7d5bf3555cba0562821e79590f31bb99aab0940924d834f45e8391ddc13dddea3a9ba01d4fea85d82aa3a30978ddb80993efb90a189a9abc995b9e302ba63463f66aaecbdd49e765c1f7324b375684f2a45e078ee38a5f72da431e7c966c5c3dcb22a12cb98ae0e486f2344192115668d2a0f08e66b211f65b6004c30ff0330575c96d809d33e06abc5c1499d009bc848e70b0efc0b4f2bdac4d1962e63fdb87794719c165faec9480f4e3fee98230ed3f3f9b9c1886684e9bd1ad5f7d03eaf0086106245e437b982c85a1045cf15a1a756b05ab4720fe6cfcbe9838c8b02d8720d948c2f6ca306b6fd53366e83fd812c68a48ad2da001d23b6ece06b29bc9d743a2b332e75794a766b517371d3b586af6b390e13368fd9c773f2d2e1242fa7b8344627169c3330426df7a5bc714912911e572bfd792d54c8dd9d243a3136c3c0e8220047a172a3cccacf322f727a5832b280a29512c20d372e481fe0abf35e4607cae19e15adfed90682f5b53fed4f2db7d38950b3339f94a8829e240a83f87adee4752ad8cb89e3b14c1710165a8b9ea187d47dce2011fb7487e61d4ebc4b7a399d33eca28edf3d9c08c70a1c69f6c36aee19e51484b7fcec9ba857fad2479cec5d8713e54e01dd8e3ce8093abcbe612b2d1a6837be89feb46f36f479ba8e38915dbc6d7e7138d1cbe92e79ce80a8c84f3aca143a2559a0c7ea220d477d2ebf805606d0d775a1dcea700971d295f7d7ce49887257d6af67f18e7c22214fba5abdea7af8fcb312e2ba6ba599b346516ba9168b30914240d9ccbcf622cd2dd36981a09204936b09216f499d450977e118236a0aac9d5cc3a7d9b3127c1378c9fd0a3d1a5e1f05ed88af8230cabb0ea86db4aaa062dc317921013a634269e859ab289d0de823a93dd5704eeb369ba40486783455936156b7ba1ebdaa028ef5e5ae94671b3056c039d7a5d1cc88aebb76c75847dee84229275d132c2f8507eaa894cb38b3d50e2aac17ef325b44bd2fed3cb7dfc5ea620b23adebab7b46de83ed105d56ec14a17da6fdfb13338b819c348323223390c3708480310c47843086e18800c6301cc1610cc3110cc6301c01308601ceff6d6d2398fd34b8b19bd94f60ec2f7e63f77233b33f0070756467d932fd9feecfac908ecd0ddd6f4db9dc71c8bc3ca5b8a9543b992e82be5c762bdfc7c30d31ec7ca8bc5443ab2ed5cabc8a12429bf4aad0eb17e6057611c4e48ec0d6ccb5fb6c0edbc8f47ad9c9b10e2d273501c261748efab106fa0a8c9df43960931042672e37f3ba462ac424860001f50a09c489d077f06a05a8c4b6ad70e59b2b9fd0eaec4f073915f194cb47772f1d79f778c47fa2e9f3fd62e5cb4edc734d6ff126519a3f5d1f9f80d62cdc3cfc7329bd83e2d7912da53a9a14c051717d6f305da2c8f0c8ca0c1be95b2382de96dceb34908fae04f22002c8a7c130817096cf81b10f81cbc713f8b84835b46cda991f0fee19a4acc5b7cabab8a744fb9c1afea96b4e2df7a4eb051d731a10af787cbfd6cb2f183c58cb77e7b80a7f494ab3767e9bcfbfc4c33f71b0e02a072b7f7294e7e17ff3f9571c6de0fba37bce3cf5177c8d6c0d5437b7fab075cc69728a87bee0f8d6c50ca95d229fd9ba18f9d5f220e162beec63a7b544e451de7b2e7ce4942c8efcdce14b3fa455733e4b30cbd6a96d6a15b2164d35818d4e29eab8d6fc6d43765771ebd74d376b7fac59d5b5ba634debbae60d4d8773a6ee4433e98daa82f59555a8f7342f3a03893d0050a34837d8a3515426e98529d50bb00514183641fc7045f671e52a9adbc188dd41b912fe0471de393d2c7a1795fa176cc52ea75859b2fb22aab5c43f22e27401755d1b708786232987ff9e2cb6888ea28b0db21a3ec59ff222dcceef3ca82094d401678072b99cf2f1198d9a1067acd8143ccca428d31a14f29907c1e9aee385fd38a0a13b66ca8d178e69ce8981e6633da81077df2acea6524de0c7ec39b12e6fa9fda3b3a3e82c03155de88c68951ed4ef425a259f590ab675aa55f59b22d38ad344dbea9322d715af13bd79ef9db9ef16bd8b67dc5b1f4a8ba17e860c0399ea28758464c1b0f29566548be022aa2522a2586715bbf608a0bc9c62992da40f952572a3d61f7282cff33ebf265ce4927c5e5880bbee36b912b057c782fb86f357f204545b2b20fcce0b8f6292ada462e5e562d4126ed67dcea3ee2315f241580bb291426477dd319f58100fcc8207bdd82b9205bff6babeac7f79bed92579437620bfaf539e82b8284a946cb59b2589a493dcab16c89a84d71f19ce6b3f0e93d5445cb9afbbafa552bbc17d9d536c68f64d3a942f99b9edb588a2c4b1efbf76c52bf61cec1156da07835d0ff5495bddd572d13fdbc1a34f46a96e658b4bb243fab1be9cb5dbed8857bd59964cd59500d8f7efdcc6a9d443e0bed61fd6c673b9f9f588d93a834216fbda15d4fbaffedadf8b3ea2e5df4c34dfd7514ffe8efda83b5f558dcf847d877aee7c76358e1b4ab5d6f0c2f6bad39af350fee7f500535688f3cf9b9cbd414a19b5e027807a7a15dbcf107341e18f19175767df7178056fbe1e714f7b5ea70b7bfd8a2cf61eaaf93c5a67fa77f5af33a33a16b606ee3cb19311128bb1086241f75ff44dbef46f5798687d29f8045ac8712de87e91fa5e0cb4b13f583431628c7ae757a0914ec428dd56ec1abd98ce3a387f17365e22d60fbb6b199b92dd54d4505c8bd68d8b3bd0aa7255d3ea8635d0374887f3560d4ebd6dc79956b2d78c76dc9037d86ac03f52c92a30e5b3569245c9ec4a12ae1ead8f3eee0ad4f33d19c7909b9e376c8323885efc2b55631032d5d3803a00ad23bca552309c5c6800898c88eca53cbf0760a33e801aae13cae2bd92ddd7b6988a0cc647ee0f8c89ec6ea72dae50529d07d719cd02ec8b7d2aaacee6c454c703934e2718428d207b339494f863e0159f620a161914d4e24ae7ee103a40f0b11837a4a3f153f984c23f76f38aef8ee4317ebaeac4d8dc81acb64c94c7ae97e41f57ffc2c617aa9b99444fb0ebd1aefb05f5be0637ab43ebca97c2d119bb71f11bc40b309166e2289cdf15046583aba1ea2b90339be8105c252a3cb8945698b295dc6761448a650c59435aebea5b99eaf198495745be64714fa6e689806c6ddee382fb65642d98689ea19d6e316c811c58ce34b0b1f94c03c2f135c812199b14c86c8720ade6e70567f580ba57efe9aac102bca3031444001d89a451b6ff2a2b91b27a112645e567dfbce74e49a0acdd9adc1b8035f3917735c40e0f965881c00c69ac4e14956d490407ea5b4c22017ada175077276361bf74c3a40b0db37684d282693b306ab47034e1c76c990ec29cea9e32ed21803a2492213a8cea496f67ae14c96eaafd2794221250f17026192d90414bc1041fe700e6ed2bbd94ed2008d61d5474aba8af6c819c17ac4a45e52464d5da4cca9dea8e09def517cc3f6fd64eea50bb17f457f1a2be712936a33b2630871ad1e643a5d118659a3dcef02ea4b0eb1f6982121730ee7db6ef4ed836539ec95246fab2807a8ff4d498c558749e24de1d64dab87a546f6a20b617456db10396e329f6755c4ec03a98bac92e635367d9d72e2b95441bc0be48eaee5521139d084cd9d902ef6c4fe0ede909868b16ee9710a25eb7d59457a58850996efa2bca3cab012b79543eb4fa9a53935b0368048f524e7f1349d55d0526d119a202fa7f485fae0226ba0a52ac1765a0140f2ec50e53b69029d03786fa2282fa9254d9c1440760a6ecabe472b3db3a4a5409dcd7b41cb4be59dde3b44169768f774b6e3958b663bf82facb73024e9b656b83e12abb68ffdec7b2fbac443eb15d61e38f6c57189b177079ecf24066939ff4c45e28a3be708298056b906e15d4cbd6a23223cf4fd58ebe1826757fb33bc4baa7bec6f5e2be004e37eaeed49595ea6e5b88c40c72565ef7d68c5df9a735339ce1cc40ed6f7766eb284b7c45f2eeaf40ada2442ea0425506c7ba50139110bcb304acc8cbf028dbfa0108767eaa087ebb147cdbd7af356f400aa3dbe01a9e225ca0a7e2d2eb9b8f45a15e0a2612c2ceeb5458e04156a63ef668c9ba319cad9e4a34cba72dc0acd9b3ad192a6a438c8056c9a7c1910ff67741628b29188eef549d01404dfbcc26ecb35fafb197cac89cb04fde67e94db02fd25b34e61c20d1eeec0933d23915b9f28f7f054723cb743920cf96f7c0ba55f713430fc3fae6f80be9e7e18882f1e30694336ba517daffa1be843ceac0fd947b25bb63ad5111c0badbdc4310dde6b2aaa3e0504e30590957108754d4a32a50aaa336d5396bae12802669e2486e758ff5bead078ea55bd261debe6b037398f6ada0dbcc6cd1f54ab2455d54739548534600cd291a682c0426b0d098e612810b2ed1b8a08c1068ced20286a685f2935b81e02cdd2bff3806c8cfda31f9051b263f592d907f5c25909f4dc4a15728801ea7507e81ec4d37ef8d3b4c1e26e417aa7aac0f202fb5b6f6ac4ceac95161597625a0b6dfc019657baf5ad52da59836b87608d6b503c13c125a72150834c80189dd3607ebda802612a6916d907339a80656693b6c915a556791a6b3fee7746930eb8f4a78781a070bbcc7a6143564374dd60e9e974ed1a39b22e3eb6307cf3a7e8e7d02a674e754de77887d5a94a099a1ed85232ccee8d3316698b057d279b5977a1a22403399e457712f379b250aa05f53f8146051683e5b4c0fa59d95897676ad1bce77513149d3f971698fdb9b65ba3f60c644caa91d48d26dbda1f94e09847edcc3145399b811506eed4209310ca7ae1e17fe682ad3278571fab97537d3381ace29b3a117abed8da67d77329da341596312269f33ad97d7c7c7472dd4b524ccb8b1e04de363d9833bdae945e88e00bd93da27f2d54c13f696694d0ac1392d90bc7dad37ea20954d7a95895518256b77664858e7d79d9ec6b505c91291be57b25388c91e1b1f48c84c219550e5c48836e1dfeaa8eeab40165bd0208b2f6882f3b280b25609c3848c4e887bc51aad5ef1469bd75ae383325d3c863867ad62ba3d93e33c042b88a773ca1a1331f9c7d3b068c7faadf69bd3b2b505cdb2e482568aac47a72e38a427505a649be0b3a0a0431002fb4227141abb522c45d003e17be9df4916ea6a922b6a2bcb2f2dc4509b11ff886f06f15d53e52c2137e154461580fc3540fe9a2488eb8d886b580297c5caeecae26575b2b5b246637c5976525846249153a69b86519b6e8e294b7a0010af65743cc6d47b82bc3a636205114d73de042ff3ed8e96d4542399dd3e0f25d0ec956c84ad69675b03eb11c4a1d6d04221d7220d6c5141d7ab66c74ab3f0eb57cd94629db820d9b8d6d196be363caa123eb30debabc06fdbf0adfe05cbb3ea6f7946fd77fbebbf7ad70c5bd1dcdb0b3d562a70a71527ea118e84247a3b4d770c94d106342fd2bd4ce7e7bdd70eac2f433cb2bcd5578958deb65709bd62bdee7ac57bd5b1bc5155b88484fe334960ee549ed53d31cfe6f5a6f14111ed90556c3276da6bad57a357b2d743964f12f63a49e09f233e49a140d6e29e0661817b5b85b954e7ab5b6f71f05a737ebde59a7e6c59596323db370edfeec38b681d1aa89d4c1b6c68de264f896af9b3ac89dc7c80d2a17f70aa207cdf655979512c1e8fce8351a9c95a4dbe1f06f1d951b9703f359f00afaf2d35bcdee0c042ab810c6e9700ae071e9ad1dad3b16bbbb056d4cad849b752ed64ed54fd19bdfe0b444d1f8945f21afeeee0d21a8ff118dab1fc3a3b2abb28696e923f8d7696f8fd2162103f5cf9b7ba63d03a88f26e03dd5bbb792a0ece687e1f635e43b71995e2e8584739a4718a9e91cd4ae42859e49088cff30325127078b044b091fcbef6f7feb5a5da1d946f83088ea6a32be7ccd7db381d51600229ec5d2929a86098761df8e411d34e62fa47475505e5709d2644ebb48c6568c7bc88dd33df97437f312959fd15434690141cd91d7b37e065f91ca11049473e5e0818bbb91bb544960cd76bec4a510738f800bbf72221810f94f3046a870daea51d346a36b179762920aa648be4fe4280769365dfcb9231e5c55e76c7c74e3a4dffb763dd79d4accfc154fbac5e8b5ebbd26219a4331f907b1d12ca6c1928213277b084b6a5a0f347d4f7a72b50af0f944de6e364742f82db51bdf33b7b476c6e6609e2ee9095c31998537e62aa3219f0583f62f35f29ec6f6fa1f39cb581fa03552e6ff9a3d5ff1202642ce7a93bc1d4e4be3de319cf9278cd3424f149efaa3f77a49407c31411c77bf9cf8817b917ec0ba1d099ce6177d9b8b3eba37d4f460e17dce9e751711ded8f46d1680f1eed916a0c9c75480ca4208c417ba9c84aa0cf0190b4a8f7446e44a28424337fc8352ae5c63640bd5203ce2ea7a26aa0af33f89c60667c46b5e038e4677f88405e2804d48f22a7456b517bf7e51ca892458cdd3327f9c0e539afc138dee52d75249249778c261a5acbd83650a04857510976a71052e14390c1317640e4bcf0afeead8ccebedd2bb92dd7642031b4b7ea55833403e9a0c41022a05e23910fea8ecaefb3ce6b98942c2f1c8f5dfc66a82c7d7c902c3f31bbbc8545d2b0661cd61c986d1dd5d65ea0f8e46758336d03cb55549e4d88ca8f35333611ab8119cba658349fd09875a7b859f009015c73e427a763b3c52f86a95523ad05afbe7ec5bad2e7315af95fcb5e98cdf6b2aab06fef24c3527a60b6f8b8a2c5e2d8a78e6a18aa838a2aa459d8fcd397fb75119cf91d5df461b5e0affd1a96598b3003f5bfa7b735dafe9e2f3c39b8e7fbc7c4c95a512b5cc327a175bbbe97d7af78e44f1389da4486b477076b758a16a37fde94cc0cf7a942ea0d74f2c2f34f0dd503b5b26f37a908d1283658c7b4759511990a477a6621ecb3b16d783f66ecfb0288d91d5946d629acdfb53f45be35931da9cb5bcdad7d3d76b3def687c57971664453bd8ba975075a1bbf8ecc2b088fa267431e59ffab54f71e6fdfcb0965be99ca269e43771dfc6b03b527b4a226be7dd19ad3d057c916f64a13904f4a3433f6badee0ec76e0b5463093c0687d8a891afb99f09c41df28300b7b0fecd9bbee9cc1b8c1eb91db0309e4c9588f4e06e573aa9bb813423203ae209b6cfce96b09bd8b729c4bd634fad273e3efc5a2f155faa4b717ce1c8bbceab179f97735a785bd86b5107fc784c6dd7b4e1876ada3767a883567b3a1cfee81c9cf9e0565443dd89540ebf3ebce19bcd651639d4486047647b89e929a45d2359083909bf56cfca79bb5c647c6fbc43c8e3be4a9cbac56895387846c7e9e8ab70b25d52180db33a05565862ae096f04d2e5b72467b8d89940293410af690a5062325c9267bf7425ad01b78bccf4cccfbd0e2b6c1edb1d7fd9566c94d0318a9a2e2a09f539d08bc499dc05b24e557c581fb196e556cac14d12aef980045301c9265092b730d485265b61f773dda7b3fafd1fd51e5c557a1ad57c1f268552c41bb28895540528d526c70552c41bb286c8a1da66c85ab6206ae8a256817a5b1b243521d80799355dcaa18ae52bcd6a498ac0f6c3d2e7055ccf9087d736935acf79fe424c74c5158c0cf14d5eceefba4c133c5f414ffba13ec6ceedb287a38554bfd04d75ad570be2b14013462d38f191228c61e0b729ae1587d8c2a5a63fd33d1f58beaa54422a133579c78209731c3f5f4a708535603b52a7362217c16ca6a30e5fdd8a8f8d278d7386e371c591f3a998f6ad0e74cd6eaffc59069a4ac46d644ab64d71df684ac6929088223313f4fb4172504eafd1d9677197bc8cf993edbec2d26f6f4188e592b2d065b2ff4c24bdebdd084c604a71aa726381a39b19e48a2ecfa7b2afa32f26bfac61031f639c24314a42c0504a1335bb9d7782cdff21b8916935e0e8996ef1c285195f4b7fe1a57922d574abac372dcb8f4eec07255926797e3fbcfcd514af1d741fde736b814fa379c3054a3b607f8213dcef8198d469872aa06d7a033bff98d4813617d91d26a767de732ae701f09fdcf1eeb435bcfe45a6e7ccd6ce557fcbf4aa7b323c37c7e548ee067f9051c9ee20187a718c1c38d4509d29bbdbd12913f1c13599dff847cb92fe6cf1a795a614e8959861b9640de8f05c3381aa0ce711259fe841f57e6cba8965fd17d4d4cf98d7974cab5e6af9b02f527f45225e2cd6faf41c98e077bb2bb8a3a8a5bbf6db8597b4a3b83d71790b230fb1acd61d27b78ba1b0df3d971fe6fb05d51f069e7fd3d1267375c7056cbeea0e4c50ace884194ce4474c295ab78a2630dc0e8c57885d03a767eb714969788f5e7e808aefc3ab6bc7bac60a618ad77c192b619d74d3a2198db15da31a3959799af608618e6bc78dd44b7f64e6d9adc10d8cc6b82cfeab73edd8a6f2ddab27acda70981355fef88a6f5e379fe132462311e26b4be17f1ab84d8bb84f7f93512d83106153bceee3c6710949100d714288debf759c55f69eae1d8ad0a8e6ec3a9b76e82a3f3d40f0ad68fcd35b6a0d3e06abcce5c06730410c77b870804a7b7e165d570fd230202ea27274cf0190124a24d02c95bf300e5ee849e0982fce86f474654f72458febfa9e1f3dc03ffca9b4fab46179ed7b5e3b3212debbb377b8eced3db1cb5cc7ead0ddf3b7f36a4fc69f6f9ff43da9f9b57a6ac6810cc16e18b9b5f9f2b08cc86e3ea30505433dbf9be62b6d6a610a8ae0274cf6d616b745376edaa5aa461a7b49a59d9d8c8cb19d3345542a0cfd95755302f4c45edb389b64da3962ab0d9c288b08258d33021389a73f4b2a6f20a38501145671de0b00f5d66b1dfb7091902081ce90c9149406fd00a6312a5514a0581d2bd9d85e836d28368d3d1c2712aeb3edbd38c6968d40738c2ea9e6d5db0a9d79b6ced2dd2514b8440ad9a2db42bb69921e65e5b17a2bfcce2a8256356bab9ffd9ba40f19b722916b6e6ec7518edaca97cdafbb0b320769c0a1b7f3487cab2e1cf3e6f71709231cdba20b3a7c5329996cad91dad93797198ca2fdc63def17913179a48a29be9b18f500750dbbfc34c645b27baf746c576e02632ff29454bb1bcbb5e9fc21e241fe1b44850d9defbd2fb2d6688f72bc2b3aeeee881785d772d68268abafb75d7b71d375b450f7d1b7f6c865ea37e6a2d23c67089f3759c710682a244894e0a66d56a2a4970a0919b5b9c93eab745be5dfcb6e81d57f69edf229cd2231f78460d7e206bcd0c6d5176a359a3520bf03b721c135698e42e2d7877cca5f2af1bb29a24a220dc4113dd99d85627809812b80ec0ae6e839877710959885392bae1125d008b03e7b9b9126d3608bd43fd9b04081bade62489702508b143efa03d39385a0fa2cad21859e7c2d1f052202a0ca905d4121264c49c2b0fd58a54948a0488cb4e963f862c2403b3211de7a244265b0a66b074681523da2ba286dd70a1acc56e8c2893a05c6f8ca21e143b5b87118f50cba1cfd9cc108b70e8eb1f1867a68629310a2c13583d2b7f41b4d15c39b1907b42fcb6e023cd1c24055f59d87e843de2a79aa1b5aeb7ffb2c96992e3bee3dd5f5463cdfdf9d49b76d09f5bf8f32faa4be52f59b2ca5fb030a272915a651d7ba3b5680da3f304a1906f11e4dbea5ed9cae8fc59d813e8557e10334844096f8c62440a91f5f91bb5c723cf5ca1969280509d2b17ab24c3a0740a6c4e4eb1d46b0494d5753c982854c8f116e1242f7e443f99700cd22029e71bc3a97bc55062cd220a6b73817e076995b6d538da8b5a49abc4107711491137702835987fe34e05ac0ff3d344c2de26e8a792e91ee0e1416762d96eb9cb617c74e133b8396483753bc5ce37136834c07f9f33c5872f1e73523872670fc9cdfc28c8eb5adf4a26f60c718c504df282d481536c85f9b0ea125b2f0d258a6a85352aee9a354a21d46704e307566889f3b258f47ca9e1a5f74dd924a044d580263557edbdea7aa4d74a73fd1a5a74a6e986796ccaa5da396ee856e1e4d6a91d33baced57cda70b529b4f6097af382f509c88f32a243b553757f2cb1b960012340f388f45f6f857b586915ebe707da4fb033f9b1ea13288f7d3e41fe0303bb0b0b5494b11d10016c5ab1ed1f95e63e8c0fc9520c74471391af2bd4016dbda9018a9633ba7f06b4baa8d91a1eda5f577cf30079f4f2d2ca636c7cacee18ca61eb9c4c1663a0f309c79fae2230bb68cd0e8abb0b967096effb8ba805d07206506dfb55164199da85af2b4cd9f65ed6c3ffc3cd222aea3a60792ab115b1f161b2a88fcfe3642106d01e107743d825821a6d067400cbc50bf6bfa955a7cddd3147fbdb7598fc1b0618a7eb04f7765b2ae46599051be95f8b245caa4d1d0f4eba2af9d93a1af7c0f60513f9b8593502c9797cf0b236fc490038a9ceb53e2ce9e1769ef85bda4a2e0866663bf1f37b3b3652b2b563c43983a68a04be0d53b570d5cfcffd36b836f3afd59c25c109ed9cbfce7c7bda9649c913d6066d16356519273420ba123a0270bb5ae89659a00ec9ea891f38298d841e8ca8be9ef38ceaeaf93d823b5821877742f769368bc7e627344eddc195b8cadfe2cbfc3756883cb1c9fa529af3118fda39ffebeb26e4814d6d38a39dc5fb60e5b7e6aeb9c66329dbf9f2d59c0f36bf00fa60b3900f55dafedf7d309fef87fa60f3cf421f8c2d1fd8fedf7d30c5cedff3c10a77fcbe0f56fe65bf0f36f89e8a9895eb0cdda9ecc9d97d7278d7b1131afb88f5d8b8f636fc062fdd8157f3a01f0fd40fc5f3dbd247af71003deb6fe86deca3573f80dedddfd0ebc38bad1d40efce6fe8adefa3777700bd9f34770880f612444d4c4f28d8ad9aaca5f6f6087785112aef5be9c0fbbb3a60bad5064cd54a8ce96d234d372ac16ebde4ad0ecc7b8400301d1d58e89ed95233a076fc2c38a4a41752b4059fa12ba54f6573b556a6f866f31cfdd85f6be1f7ea85deab2e2928d170748ffe4fe83c6b8fd329a3e6b41380f637ef84a18c68039a4a02783f371d1044597e2556d67016a0bbd1d6cb753d92f027bd92f4e781245502188f99c0b5913a381cd74752c3abd10937a8c6e948cab31a988c3b30d3ad1d61a7f2471f33199de0b353d80d8b7a42a5c5aa1bde73265f7ed203ebb71e51d996da3135453ce74757f1d23a2e212280e3fe355ce7f2d2da9de38a64b5df86644561126f28214e52e940d2c3492a38d50a0e4169d9b1ee1c4255664b0448560c118423c9419f4a7c0967884d38114e119706c94db9c6213750d52f379f0a2c9cb96156e82b874a4d02a570c2408905c219f99a99047a0b69ff0c50465783e09b9fa1378d322b8177b6085895f53d03c68b23fdedf57b8d6fefc181ed9d9aff8a655c85a1ea0d345ecfa1da4b70349ef453b8fa3e37a254f2eff3004351ecabcbd0da15dda3949f2b792d2b535a8921798ecfcaf4abe4c7b9578c1436f2a3446e740d3508c6eb7a350155bda55adc3d4b8aed7555aedcc58db535bd66fb53dca4239dcec7b4e121ca1168f53afe5e2cbac35f16d38acf8d0fae2ec1cbec25b8b1065316d47263231a71e98ab0919369892853a45642cdbf8ef6170aa38b622544e653c41f8ab33233a6d32aea4109cef569b0b61250c4c74000356f641d92c1541ad1a389a9e86e2d77a7aac7774d5642a3852bcfaedb42d8717db263dd06293e2c018cd40f84401f3b345f16c6eef85520ae3296416a2aea71097026cc1019aee9c3e14c9133baca184e28b7998c97399c9529e9ca6cde5ec1bb703d24542b9d69ae158a282c12dd7e9408ac47254ec04d1213ad603cfda1250480027070ffa5fc82b99a63449724bd1d33ddd461f4cef3fb8b4159bd27368e9417dee87226f3c00c83c69fec32551b303ae7bca104dcc895fbd8bb087feb18d1af3b75982a84f8c3a27191bb96309e01a074bfbcf0ccf05de144c03e58fe8c8bd5a3aaa738967b2b484236e3a53a6a5d09f4a59b47642ca3d698f12c0fa98a5de71774aacfe51bc3cca22ed0ed8928ee244b0de9b9c6cb0e4940bf0ea71e9588fa778e16d62eae4194266bbb63a8b56621436692d87c6a4f1b5eaa0d71fec322460c04de849895eeb7868270763572500728a77cb412237989d98989d6b12f760f38a76953abfaf774f175a134da89ea66ef3d3f813e8c4447e2661170f1256718e43ed0ab37e8c0b9fde24408f992403e9e04d218f4a64dd5240fab99002e12919b10c9e6c47eb497460458c754f5102a4334d4796d7d20b3bf1a8490c06586c12cf28174d8fd3611988b56d30887c9befaa244e4233c635047a27be4f28fa157e85f415a0f5eed4125a0b72f9a61581aaef63ff06977485d6fffcd782433372155578223cfcb14b30c8c21cf1976922101c0e51cd4253a12f5f5b25cf4ee65bf946189fb2500f7801ae7b24d1d9aefd2a78b6cdb491f6199ce8623af06b55242dafe586a780fd2dd0517a32cce34894125a01691ce2fa688ba7c3bfc5ba39a1736a1b24f262a8290dc2479d560465e08e9037c0d11e1828924508f97ec17e2d87b120f7fa8417973b1896a7ff7cb1232d329354415949a280c72d4cbd55932e1145ba7911257ed489c5c8b1b8a12cc905e685e44f993adc2203d92c18e27256450eadef941729ff1d8bd4d73d38ad2f06df79226d3e3606f4e867dd9a2a2ee950cc7790db49ddea69e05ed8fd69f3364e942c88845b2bf749a6eef76f9775626825e67a197fb2053be076833759b1d901f7868aaec83a683dba6cf2d0e48092e9abee9cb5b86e6b1ee657db90908fe75595f7e3a82edaff6e5b36ffb3d7eb52f3f1fc13fe7f7e51f45b025df6c16452cfa6945c65653fe69d81efbf6cedcdbbbbffc312b934b37e5570274b2fbd0ec806f806d5b2b2d0ea80ea46cfdaa2faf0dc46dfda62f8fc0c2b6fed097e781615b13faf2fcb010b2ab37a12f773a843b7ae3faf295106eee8debcb5f06e19adea37df909102eef3dda979f0ee1ccdec37df9348441efe1befc7c04f7f8f5e51f4570975f7fab105c33cec2aeff1b4a7047ff06bc3fb07f11764124a29421453491ac765d459aced21992f3e5d5db2cfd82a82125ae75b2bd228b6135a5ba828706ea0682fa750341fdba81a07edd40d020dd60a9be5cfd21d7f383a8be3e88eaeb83a8be3e882a82388d59d697cbd7d297cf6b585f3ea751af72328d1ea251371b2f72e943342a7a9046450fd2a8e8411a153d48a3a2076954f4108d8aeed3a0be7c4e83e2faf2790deacbe734e8685f3ea72187fbf2390d3ddc97cf69985f5f3eaf617df9a711dc31ae2f9fd7b03ee971f67be0a203e6ecf7c0e9b09134f426d05e3eef737f3251c99d5d148ef5a5d17b076aa5e3cd83236606ed0d594b44c867f226fd7a6faba81a1b8eceaa6c20a42a00f8493372096b88ea16c8c81d6b3d9073c8d6f7eeee378e2f18705a84f67464d55f991d10897d65e97bc66f9bd158dea8a26c25fed03642d8b16f0b671b7d23df92dd7aa46d75a6af7265df21abe0520fe9919e31f6ad5d75e6fe14b6fdd61f58f9142cced80abd58d2fe3cd2bacedcaf720bbee5ade28d8bfa0a64253f2b2d0e18e9292cdb0c6bf939892bbfeaa21ee91b8df46d045bfe4e98d99183344e0935ae8e4db9a82f58853429846ce96de37ae30df6ed1e3bd90cede667be8757a1b4b041347fba339dcd43392124d6fb134b27437a832f01fb1743564e73d67fa79beddb39e1fdd4275d7e781ae15e98fb5bea173eed3c3d90fa85b903a95f884469280fd540606c89ab29171d79036bfec7d5574fa39adf55f5d79c5c45b3b85d91bfadb9ebeabe8b036bee8a1c5873d710ea4d57c7b2d4e72c18d0ae2fc41c2751615b512aa28c6a60b9bc76fb3ccb4df8a71e155b2af6b5411ac997bf2ae3d2504d9f4a2b18ae27e6a8fa712655659c473ab74f453521cf82d3b9c4e87e9d431ac6df2af22094dc931c172b6f23cf19e773dc432a7761d0a33f36f056cbc25ae821136dce7e5fe95514687361bf39341f5372e74985d9a5fa289a7baea14be9858e771d5cd87344363d565bdaf70e44a6b254ebebc0cf2a451e21872fe6cfbadec1fc069ff69b615b05c77540abca5449b67502d3f71180696d1510625ae8bdf403f0d20ec43521447c9f3673d855e08aaafc37d870ec1642ab98e7aac294a6ea126c778e35c7d6c1c92ae0af70bd50d9f7dee103c14421269c07e63968a09a109d9776607e881621c4cc182fc7230fd991a5a9649243f2a482933c1a697c6b38f93b4e1107be0d15de452807c28246b1b292e372119d43ed21fd31e55dc73bba9db245c82744ef55efcea1038297b501cc6fbd19f51fef6777a0d617bc71316777617f6a613b976aaa5681db03b07dda1ce924d63920bdf08923bd0aacb40cc0ff854d5f62aa0e018760fdb397b5e1d307d26b76e42782ca81f41e39d20de0ae99e047d4d81f1db5e880da22e653c10f28b52b1c69f9e8638ed4c26a2e15714a07a05b04af9a1d79353750de055527dbf3c716a037c2093f93d40320adf0863a01672536c77b190dbe32f3e3792d4afbe90db45ec9303bf67cb834c4511d9fa6b8e74823b16f2c8e3d1f961bd55d075cc3c12d0ef8670e867430bdc5d1f62a40e7a8ff8529f755f16dfa09f3e368dfb538acaeb20fbb7a0876f90f1cf685f04fccbfc5b60dc1aeb9e9c0f678066dfb106cf0bd037be2006cc7d3f1b20123c7b7fc9ee9b21de1e8aee1c0f783b332b9bc80a53bc24375441d3bc22d365507c0daf6fd4baf3244c2b55851fb622e1dadc02f7ceb04a9209c108873e0f26e284104a920d4f833ad427e1f98e91e6ebad98e3dec9185a3d1f3f35c19bb8e0a1be9aa74ec9f15b6b7204e7c903ec215d54ce39f2bb6a4f36fdf2fdfbde37ffc7523649dfcce02db42ddf473064265b472376d344f391b9fecf040e6f1e39c5afc39df1bfccd4f45972f3df0fe012fcb2089fb74506428a044b57d37b6ecfcbecc1f8b0cec7bcd2c44061519507994c2ddccf4af8daa614f1cf8b7a3140a0672c9fbe22f841ae848772b07a9fe106af0a24ab5d4f3d5401a81be66337e52689e53ece84a9eebe5f22061df5db6cc76b4d7124517c5e349dcdeb66f0d47a7ca1f71eb6b900de036b689e76ffc60fe8613bcbcc6971a7650c1d30bd19bc1249c2b6364755e9fcb1eca5ad0fbad5999b204e39b0575c646e39b2eb65d0f64713b5a685276372bd33dc12b499662bcef6ecb4ba4986260b419295802cf901aad3b28a86feccaddff53b56a864e22028a5daba983474196344ea966ca742578306187d4e980bb222b53d583b4862b21b2f497d8f710ed5cdd8d443b01e70c3bac68e52f3f4b02b40fc1967cbfaa4badfaca82ce2530d51f43ea5c3080248fa48ea49f3a51d1d21df3cf9036178471fcb1c30f2d6c173f74dcce029da3ebfa6e6afd8a15ca12f2adb44a56279fe987a9231c398aa748e6fe349ec4ed997925752fb58e0ae885b2a86bb4742ffdcacc9ec1f03575ffeab049f993c16fdc821e07c5d847a32bfbde209e89be623168e679c2edb1a13b6b4fc62b5af831fca9a374e6fdfe16d434f5b7a0fc316a81fe316a8170e0bd33eea6f143af242abf1620ae6f98fb6835199642c96be34b0ae2bb7bfbe4d2b4beb3ff56f0b3b851b4f47d79e36e3f078536c84112b5bf159725744b6575d4bc66b67d6c3b83503b07f374a4b6afc65aac503a83dd27bd8e4ef0b99d56fa1b1ecf87fd4a1aabb3aa5fbb9785ea4e64ffb3b8da45edcf9e793dfd3e3cff1ead4276209f64ef4078f2d2aa0ef6c40a3d17921d48370a5494573ba0fdd9b463d5edfc599d8abd67d48a818c685af97a383aab436745a774ff0cb0f79fd585e75bd1fead7cd2e95fd838f0c62f6c693476168a7e41677559994e2aea4031a003d8d492f666351c37bb63ee5ad0d713b97328df9ad47e191ee5f7431f175012e934e81b9e338c4c447b53d8bea258f44dac5be1eaf7f0785cba8cfd225cf6cce06537f0bcf059f9122981638cf132b6ef56f86797b9514b548b6ae1a56919f056f7ab83f30acbc5757cdf5303b0eef77d8b8e1bdf6abba3670c3a0f35aa2869bdf0a7706f9dbd17ed68cb03eaefa19bc0acfc8e54df63e30fc99a3e991e256b30d05fafcf271e7503ea7ba30fef307967e87962ba851bd95169de968a90d71bd834b9616a6dea266190633e47fbeb45897313efade1b467cdc79ce6d47d6ca861d79a3e19cbf8b3c8466e76eafb169d8d1d914bd0d8a9db1a0ac74d67228fe2c7cb7b07ea4b0d193145b14656ba707c36786d84ff54dde69529d9b54535dc6d99150de81dde55b5ab6b1c6fc196df0dcce1796b94b8cf76cc0fdf16198c147fee77dd508a4eeda23613a4d11aacd303778aca13b9cb8302302305fdf93176feeb01e03623cc742913e6e3813bd50b3c5ebe99e74fd3c3f8be3bca68b5b8d598d30b71f032e1415c7a017d21aa89369c47dfa77a73e77067ff82d50ebb2c2a36ad728c1887a5a7d97393ddfefc777fca0bb914bffd8e94c2f7b91462af2325f36f8612562fe2bf85f39e0ee3203221f4a03ce86d8c9f2fafa2560aa6eb09d4322e2d914da3524bc00e2b4366c291bf0d87b10b43e841f0b05ff115f9d18e3a0ff0fc557325abaea099f793aa8579b2d8b31b35217a824ab10b45f1c5b1836fe8e0c971fc68a2b9cdfb3915a8e469bb8cfa982d67bd5bdd8ea47bdcf2dbfb3da8745626fa3a01fa3201ba5f87ce70077e9d80b7d52b6876e5e99b117dea3d720c33c68f4f4bb4dc84761ba8f77691c2f1408751a35c449c8caa5e0dd6e980177549eb451197e9044e4febf3183abf843f4fdacdd7f12da78d257f46df2ef135e4516ed45e5e8eb653ee56244956b25caf7d678840237784b94cb7142b08371c47dad5f8b2340ad270be7f5c1d80bd42adebc21917a8432e0704d2bfc3f4610fe61916b0df2ffb4719b913f89a8a4ca1359a9061a0803a6abd99d71d5350ee7b5013e20c1851267e61239a7be1934bb0f8073cf4c0c2039a19620035f44935205ee1dbfdf750d83ffb2e13afa0f3e175f4b3bf64804e8bd95a366a2eea304df16e40a5ff046ee6e595cfb730f46e30999e5ccbdfcc7e5b73c695284a505f93d0ae02fe26f7b6a204c1e9dd84e6cc3040b51603f9c9bf03872eadc95f98cfef2aa567256aceec26f6527336586dedbf10aff899f9fe426f9753d82b4876c7cdcb53ae35705ff9f9baf6db9ae8ae151dab5a5737af695ad7b0c1f11ecf51c968475f271aaf21cbe5f4bb80a80a3d40abe4419d007d41a43e3e32a93849b4eededb73df297a077ff75ef2dcf545ebd13745ae19b8bed1ee431e57b06e3ce64ec9a8d29d79ab2fa452f7ab8113e54231342de2357eaf04da79b07627cef3cb04eb0230c12ce85db27d7afc8ff49ba1bb603fe20b73a42fc05e7475ddc4e4fcfd13037c6676bf9254263a2828d36583af21a7e3aa8c8961c219795e890bf38c36ca1d6a5ea2d186567cc8eaca744ff16b79ab0f0413a57868fe8a7c5942c1c6821a79d07cac20165ab3333b83be68dfe36aa6463b8375d977e80d0e3fe158c698380b3b47f48fbe8cc38f247796428b38f005cf7f5668be57dc9ced3439a75716c78de4fde36e61aea35449362a957f999b05abfe1a9a2f8bbbb07ddf17a86cb71495bdb0fd803535561884024767650afa5a02fa76021a81b9ef279cc8490df40119cb99e54a8164f9e780e9ecdc8e05066f3b0e98156d022a538c6e456da77677e2b46ab8f88e14dd6e66f4a3cbb105ea08bf6314217672ec801466617e2692ee65c4d7cb8347778232fd7145a38522c522460ae98a9bb7bb2ea888b056925d4cf50241c6d229997095ba3d382d0d63fefdabe0932a09aa47631311aae1c255d2e5bb189db41c53319e7f003fec29d3772aba3d3f51055f7d1ba3eaab45063f7497a72e66a7e8c4fe82b9270c593af57c62c14533aa45bce090d9f5b33eff731bc755b07b2bba4b55aecf4d378bbf64a45700f53589be92b0dd55551191047d6742f5aa05c162558a994b7fd52cf1d002ea2dbd5bf76875a464298197bda6c7bb63d44c63aefabde0ad7afc2a1dbc432a403e06f52e89051a18baa3974a2c81164a620cddd01b680826c9b0e0d77478f0561d5e964d0a182989cfd5515dd5a0d4202121b60d62b755c391aba117a6e411787eeef25d6521cd60259440e79fa865cdc3255bd380eb65b10abdb94109efbbb8aaa8e1f79d292f6720d9da8a6e2208aac148a1449a06b8b77aee49bba519b0b45821216bb6bb66526fd8811e9679eab45b45899e3a5184b353c65206e631b54b41c632d40f53d471bbbf954adcdb052b7787ed3e509e5ae80e7ee746a76727d494d1a0f26274769476850e51b6034819b7bbd32aaba7bd158b208a5665d799995a2dc022902c532c44c442772cd255f5b9655ccf38e13ecbc41f1f5aa6f770b91f5a127a680ba13af480d992067c75873ee5db3aecbe136cabe0be93af0ed914b305b6564969cf406945803846a2920222875096916d0a38d20aa84fdb9dbc69d4ab40a87fbf2cfa03807a3d785a2658b68b8132dc7d59cfd1259e0a21bf239ee248860c27430d92212385fd7d8bc4111616015b83938f98daf1a0e5a2af2eeca984d48224b3af6ef9b7b72ddb9e8e137e6e8983ad70cd54abacc3c9fb12bb3ba8b0b896f375604f715807f914784450226740448c2ef48b58dfe39a89e8a95549a7fd22d29f3e3423b9201960911511872ef6431f9e7eb6b53e81d6dabd5402adf5a34cc9b6ceedeac0c65dcc7268a923c4ac3653c390a5ee11de91dede2511233bc5a23c8e555ce97b636e1e670f1268a56579503aec5b5617af30862b40c2da685894b5ead9367af80bd646b5cfb6d16b0668a1cae0abdb30c90e29a0ea6e387356aa1cd76fa518b452da2261adf4e8002bad091d6aa5eb7f63a5615171ac95cee7ad3489b7d2f903641816f52c6da684ce20d567ccefe873460cc630378538b34c8877eedab62b98c8c383b5d9d05acfe065743ece40bb2fca3f9147b56901a1c2a5e7f6672cc5f606dbb5206e372e0d3448dc0d40bd1769d94ae64c5e586eb130b8b616b0d6b882746262dc01b103da32d9f1276a51b3b3c396d13719a8dcfb22a8dffafb22caf85f6c9974e26dd9bac8fe0bb4383dabc1eaa7422ad359c8d9b264dd0a20362fccf6e76c92b08f42d66e1f05b57894bd038b7891b5c9d01d872caffc324eb8cdf2ea9d87966dbf7016f88d39a167fd100b6439cc612d50c35ae081ff772c90a54bb3fce7420b343edb0211166b814ee43dc9ba40e077fabf58a090ac916c1dc35b205b879ab5401d6b81d9032d10d153ab7e38cf59202781bb65fd5a947efe4900b4b218490cb4328d64ab7d3b23aaf953636ec63eea6a3d201e1f7a805643631b616a1875ad1ea723b1806f3d254450f88ddcd187bcc9eaf2e01b25009dbe617efa7fd1aad44229dab15161cadb960ad543183296b5991d6b5ad7af24ee7045bab7efdb9cc3616f86ebdf979041e1b309bb22d8fd032069b36fef8ef94893b16bb147d8eebe779823d4aadbe6fe5d9b2504518338379399c07b793ad62d6dcc8563c408758069691b26214603491e946c7add086cefc84a09219eb945a856a90ba951621c5ad6977dfb092213b98cb7c5bdb366d325701e1f571e422a40672ea15ce881ded4da229cbfeb3d5242346f97a4b76d974895828c65c8ea7adc97ef56ee3e7019b599db05e1f6da10269a5f3305c1316d50d7ed23465bef4adb7e95404d199b5b06d3a47eac3f7faf943daf984d9428b2324b69f4a561c779847c38e7edd83ad03e59d848b572e00e51acd410c0fa538f765073d6efbb9c65e8db59f1c7467279558f7c1ba4e3d13ee1078fc6ee42f7c25cb597b4236bfbf76b32dc19e934500c7dab2c439db9afdc7d47b9f1f75139b43fe9aa554784eaa9fd5a9cc3d1f118da86b1bbf8bcf7b58364c018a6816b90322dcc32dc350f847e60df730dd4f2ebeff6c94d531b66d48a5af97dd3efb81a126b8c54d19ab96b3421bae7ef25e86d0c49fac8e7d89f33aa3294d41b75807febea09bf1ab895b10c9d38a1b326d7cf241e2120345f3eb3d24d708c9c4244cef6ac07c16b3f037208c967def0d14752d2ef81a6583565d91e79695590492405627238995775cec0ef273ddc65e5bf9ef01da65c09d7f2d14da53bf9fb970fd00cb5a2c1315fadaa9dbc935f7f5471bc977caff98404c402cdc7fae70fd5fc7625cadf47b4c5f16f7a5abfcc8cf016b7f60e17fae759e78bbb28b133d084687de0221fcb7831cebdcd5c10e11edf1d42190204f20fc6636d370525c229b33dff8069feae0529b95880f871d6fe8208ab716ba7c6ac7fde9a6de87ad52c9f26043f583425115334d3b56e2db9ea00b52d9aceda7fd73219fe7e6ee12cb2ffff1d619cf60ad09e4aa0965a4180d9c24c85639d0d2a32967a13c2be2f140c2c739d4065a85504e0dec713d5f697547c0f2ab042ac10bda9227a7842ebdbb893ee2f098867d506be41b5f9d33b1d7b95eea8e6feb72247a74c02d9fcd7436b9e76c750e2363055e9bdec8682d0d76f12b33715eb1c6de36baa7966bb62afa19a06eee70f2cd3fccc323e5583cb70361af0859162ef846b42778c7de0f8bf5c10955857440572296b737cb1f632fa5e1ad4d6584779f92725570533d1b7f5b1c86042803938e0cace65acee095d2cdee9922b73e9bbe6c17c02e1b3f82cbc3098cf8238b95b1b2848908f84bff35faff869be1cbd0dff8adca30dfcf48adc133ea7c8a5f0779bacf142dc9cc6829573ea2e24c81e5e582f6bbb902eebceca1cfe416fef4218de85e17b18fc8ff6f66e80e1ed92dedebfc270108645305c3ad6db3b09c661c949891b12e27d12366fdeb8799c33f84b8472ee32d54a9fa40d3e91c971a96b7d5ed9189f007975a4b389286d1644494af3498fdb9c069fd21236a76f4a7b365ef2c68d9b8033e0fed237ac8ddb109f0c6b640bf1a5a0666edc9ce0139fbe7ed32c1fbf545767101e979e9ae013f4965fd09419c96f4df2512e0aef836036d89c90f817bfa0a9f12b27f8a54ef4796900e6efe5b8b22cacf9bdec097e6b264ef2f98fd98842de649089e80c8d43173e3bdd115f7de53fe7cf50fce7fc451bb8f8353e5ecbc79bf838938f77f0b1918f0bf9f8241f9fe5e3cff8f83a1f370c819b87c01d7c0c3672b10f1f4fe0e3997cace0e3b543e04d7cfc161f1b87c08543e06343e09343e0b343e0723efe8c8f6f0d816b86c00d43e0e62170c710186c1a0c3b0d81dd86c05e43609f4d83e53961081c34049e39042e4fe6e9f1f1ad2170cd10b86108dc3c04ee180283f58361a721b01b0f7bf171d01078261f2bf8387608bc968f37f1f18e21f0de21b071085c38043e36043ec9c767f9f8fa10f8d610b86608dc30046e1e02770c81c186c1b0d310d86d08ecc5c33e8efe1d022b86c0f386c06bd2827c501cb9c8272d61fda68d9be3362725a4a2f4293e5cfe543e0ee6e3697c3c9d8f67f0710817a7f6d14b8ddb02876338f025a5c21139956d1dcce7e9c6f5e1c56d4e4c5f9fb0212df58f9b13d2d2376ff0d912979c9ee0c08f73e04ffd0dfe40c435713c7f713c7f713c7f713c7f710efefae82c79367f3c9d549e4e2a4f2795a793eaa033938f5fe4e329417c3c8593c74c473d4bfbe5ea131e979c9cb099cde7cba5f1e5d2b8726f276cde8832e6c56d8e7f616bd266c8214a029be37c7c50fa624e4471f1f19b135221cfa99bb8f4256971d49b3e9b36b2b31f8413f97455f2c6d571c9fd19697cfad2b59b13e2e207a407f1e9cf6216fe417560f3a13ab0716ad01fd76c42f50e12e21fd76c8e5b9f30800d90ca971b8c07401c5f5f64fa062a2d69e386ffac05109fa71337f5f7ca0d2e00d581c39fc6c7d3f978061f87f0ed98fa2cfe52412a5f3e952f9fca974fe5cba73acacfe4e317f9784a101f4fe1e4c5d349e3e9a4f174d2783ae35fe4ecf0653e4ee3e3023e769fc5c5577878ee6c2e6ee2e1b743fe73bc1a76f39b6c7f70725913979e9ce6b326218d5a3b004ede18173f303f350dba4d2c9c045520112a50d286d4b4cde9acc8d9f4f549a971bc8bd74f0cfe45fb4cf04b4e9f089dad593e0370500550bad4c62d0948a5e3d27cfc826626bf35d9273961c32ce4174d421a3d8b4d9c047b814b8b8f4b8b9bc5fb5fa92ca1597e53e227ad81d4d2d093f75f82d6f30ee090ba7e8f0fb661ff33469e55fba0fafb8507fed7bffa5fff6ab0bff3bffed560f8ffeffe151c2466074f59af1c301cfd393dcd67e31a9ff509eb376edee6eacc0d234b962f090f7bf9650e3f787dcc00fcd46da96f24bc05d7c4149a8fe37d566ff36157b57ec9f13e5b93d2d6a2b5245a860e1a8f9e51efb20da9e99be0e49e86c6a86da9881aa231cb272ee8253fb4c28d9bc2c753513c6e08c5ff877f222e120e4926060238fe3fac64c01ff97f0759f07f8cd9cacfa79bf9f84b3e9e31878b47bdc4c54d3cbc85cfcfe46168290083018741f03c94c79f4e020206110c240c4e308861708661180c2e10c71586e1308c80c10dec158c84f128585e026377187bc0e0099fa530783dbf178c86610c0cde30c860180bc373303c0f830f0ce360f085410ec37818fc60f08721008609304c842110863fc0300986ffab1dbb8f6da28c0338dea1c3461343fc431b63642c688a113a60624144c4811d0e99ac607913b6b5d8b1b2ce76b06e22022214182f226237df1011ab2822225644458451e6861315abc2c0b7597120be6145e4fcde7a63eb2f26868821c63dcba7dfbbe76eb75bbbb477eb8e1e30210d3dd10bbd918e6bd007d7c28cbee887ebd01fd763006ec040cef546ce7310bd8966d0c17408bfebcdb0201343710bb2300cb7b2cf7064e3368ce079c8a156b68de418a3703beb368cc618d6c7621cc6e30ee62660227291877cd8e160fb24dc09270a989b8c42b83005457077563f4696eaee82075e94303715d3500a1faf7019ca7137a6e31ecce854a9bb1733310bb3711fe6f03df7632ee6713c3fe6630116a2028bb0184ba07e3d80656995ba07b19ce587b0020f23804a8e558547f0281ec3e378022bf12456b1df53588da7b106cf301fc4b3780e6bf13c5ec03abc88f5ecf71236e0656cc42bcc6fc2ab08e1356cc6ebd88237f026dec256bc8d6d7807dbb103d5d8893076a106efa21675d88df7508ff7b1071fe0437cc439ecc5c788e0139ea14f3b57ea3e63791ff6a381d7e0003dc8fe9fe30b7c89aff0351af10da2f81687f01d9a701847f03d8ee207fc889ff0337ec131fc8a187ec371fc8e13f80327a1746e7e0318a1bd95e4c4d3355b5bb7aa0f9bb62bca76ecc1411cc1099cbf43512ec555e887a11883c9988d65588975d8821a4410450cfa6a4531c008332cb0c18954f487152e94c38f15588d0dd8867a34a009c7d5e3ed5494496145990623cb7a9861c1441463062a50852036e162f6afa67bd18863b892b93ee888211885d5d88c5a342006fd2e45b91c660cc53814a302abb019bb7108db6af81968840e97211d16d8518e6558833d588f504dfcfbaa915aab286918880c78e0473d8e425fc773ae6d4b6139a42dabdb57b2be116174dacdef46b3e0c43e34e1445d7cdffa36c7543b80fd5da84239fb2caf6bdd1e66ce501b97a151e7baa436dff9abb733dc9894a674498dff5199467ab98d354d73977a4b723da66c8f7bb2239f9beb5e693dfb9a0a1d45764f5989a37ba9db53e82dcecd77b44eb17b913dd7e52e62d15e687215e47153ef70784adc5e939b0b91a2124f598f7cdd10e6465887e7b47c3efec5f58691872becea729beb089f35d75bf86f9d5f09c7f672728ca93eabbb38db53e0f61494a8efcbc6141777f6599939d609993913060fcbb68e36a614fb321caedc32875d3da92cb68f9deacbf67a32ede353baa5746b3e5fe33e5e6374d59ab5bf75fd6a8d3aa7aeebdb48c7a40371d391a7b136b4aeabcb79daf6896d541de4ef36a943877318e76a23598c8e7f33cefb87437f96877f56cb75932e7e719874ea1ad1ac3ecc498e6f1f947c6abfe6b175ae767f394fbbdff1276effaf8f24ad17f6ae5b9b7ed194c6a68664dd0567f58cda47fb681feda37dfc9f4654fb5c6d694c543f3fb10651a3a859d4226a13758afa44fda201d1a06848342c1a118d8ac644f50b126b10358a9a452da23651a7a84fd42f1a100d8a8644c3a211d1a8684c54bf30b10651a3a859d4226a13758afa44fda201d1a06848342c1a118d8ac644f5158935881a45cda216519ba853d427ea170d88064543a261d18868543426aa5f945883a851d42c6a11b5893a457da27ed180685034241a168d88464563a2fac58935881a45cda216519ba853d427ea170d88064543a261d18868543426aa5f925883a851d42c6a11b5893a457da27ed180685034241a168d88464563a2faa58935881a45cda216519ba853d427ea170d88064543a261d18868cb5018ba3338cef4f1ced468f97f84a28d967f4b6c3d9934b3659f9987075c72bac7fd13bd7c3541'
ISP_PROG = binascii.unhexlify(ISP_PROG)
ISP_PROG = zlib.decompress(ISP_PROG)

def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 100, fill = '='):
    """
    Call in a loop to create terminal progress bar
    @params:
        iteration   - Required  : current iteration (Int)
        total       - Required  : total iterations (Int)
        prefix      - Optional  : prefix string (Str)
        suffix      - Optional  : suffix string (Str)
        decimals    - Optional  : positive number of decimals in percent complete (Int)
        length      - Optional  : character length of bar (Int)
        fill        - Optional  : bar fill character (Str)
    """
    percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
    filledLength = int(length * iteration // total)
    bar = fill * filledLength + '-' * (length - filledLength)
    print('\r%s |%s| %s%% %s' % (prefix, bar, percent, suffix), end = '\r')
    # Print New Line on Complete
    if iteration == total:
        print()

def slip_reader(port):
    partial_packet = None
    in_escape = False

    while True:
        waiting = port.inWaiting()
        read_bytes = port.read(1 if waiting == 0 else waiting)
        if read_bytes == b'':
            raise Exception("Timed out waiting for packet %s" % ("header" if partial_packet is None else "content"))
        for b in read_bytes:

            if type(b) is int:
                b = bytes([b])  # python 2/3 compat

            if partial_packet is None:  # waiting for packet header
                if b == b'\xc0':
                    partial_packet = b""
                else:
                    raise Exception('Invalid head of packet (%r)' % b)
            elif in_escape:  # part-way through escape sequence
                in_escape = False
                if b == b'\xdc':
                    partial_packet += b'\xc0'
                elif b == b'\xdd':
                    partial_packet += b'\xdb'
                else:
                    raise Exception('Invalid SLIP escape (%r%r)' % (b'\xdb', b))
            elif b == b'\xdb':  # start of escape sequence
                in_escape = True
            elif b == b'\xc0':  # end of packet
                yield partial_packet
                partial_packet = None
            else:  # normal byte in packet
                partial_packet += b


class ISPResponse:
    class ISPOperation(Enum):
        ISP_ECHO = 0xC1
        ISP_NOP = 0xC2
        ISP_MEMORY_WRITE = 0xC3
        ISP_MEMORY_READ = 0xC4
        ISP_MEMORY_BOOT = 0xC5
        ISP_DEBUG_INFO = 0xD1

    class ErrorCode(Enum):
        ISP_RET_DEFAULT = 0
        ISP_RET_OK = 0xE0
        ISP_RET_BAD_DATA_LEN = 0xE1
        ISP_RET_BAD_DATA_CHECKSUM = 0xE2
        ISP_RET_INVALID_COMMAND = 0xE3

    @staticmethod
    def parse(data):
        op = data[0]
        reason = data[1]
        text = ''
        try:
            if ISPResponse.ISPOperation(op) == ISPResponse.ISPOperation.ISP_DEBUG_INFO:
                text = data[2:].decode()
        except ValueError:
            print('Warning: recv unknown op', op)

        return (op, reason, text)


class FlashModeResponse:
    class Operation(Enum):
        ISP_DEBUG_INFO = 0xD1
        ISP_NOP = 0xD2
        ISP_FLASH_ERASE = 0xD3
        ISP_FLASH_WRITE = 0xD4
        ISP_REBOOT = 0xD5
        ISP_UARTHS_BAUDRATE_SET = 0xD6
        FLASHMODE_FLASH_INIT = 0xD7

    class ErrorCode(Enum):
        ISP_RET_DEFAULT = 0
        ISP_RET_OK = 0xE0
        ISP_RET_BAD_DATA_LEN = 0xE1
        ISP_RET_BAD_DATA_CHECKSUM = 0xE2
        ISP_RET_INVALID_COMMAND = 0xE3
        ISP_RET_BAD_INITIALIZATION = 0xE4

    @staticmethod
    def parse(data):
        op = data[0]
        reason = data[1]
        text = ''
        if FlashModeResponse.Operation(op) == FlashModeResponse.Operation.ISP_DEBUG_INFO:
            text = data[2:].decode()

        return (op, reason, text)


def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]


def get_terminal_size(fallback=(100, 24)):
    for i in range(0,3):
        try:
            columns, rows = os.get_terminal_size(i)
        except OSError:
            continue
        break
    else:  # set default if the loop completes which means all failed
        columns, rows = fallback
    return columns, rows

class MAIXLoader:
    def change_baudrate(self, baudrate):
        print(INFO_MSG,"Selected Baudrate: ", baudrate, BASH_TIPS['DEFAULT'])
        out = struct.pack('III', 0, 4, baudrate)
        crc32_checksum = struct.pack('I', binascii.crc32(out) & 0xFFFFFFFF)
        out = struct.pack('HH', 0xd6, 0x00) + crc32_checksum + out
        self.write(out)
        time.sleep(0.05)
        self._port.baudrate = baudrate

    def __init__(self, port='/dev/ttyUSB1', baudrate=115200):
        # configure the serial connections (the parameters differs on the device you are connecting to)
        self._port = serial.Serial(
            port=port,
            baudrate=baudrate,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
            timeout=0.1
        )
        print(INFO_MSG, "Default baudrate is", baudrate, ", later it may be changed to the value you set.",  BASH_TIPS['DEFAULT'])

        self._port.isOpen()
        self._slip_reader = slip_reader(self._port)

    """ Read a SLIP packet from the serial port """

    def read(self):
        return next(self._slip_reader)

    """ Write bytes to the serial port while performing SLIP escaping """

    def write(self, packet):
        buf = b'\xc0' \
              + (packet.replace(b'\xdb', b'\xdb\xdd').replace(b'\xc0', b'\xdb\xdc')) \
              + b'\xc0'
        #print('[WRITE]', binascii.hexlify(buf))
        return self._port.write(buf)

    def read_loop(self):
        #out = b''
        # while self._port.inWaiting() > 0:
        #     out += self._port.read(1)

        # print(out)
        while 1:
            sys.stdout.write('[RECV] raw data: ')
            sys.stdout.write(binascii.hexlify(self._port.read(1)).decode())
            sys.stdout.flush()

    def recv_one_return(self):
        timeout_init = time.time()
        data = b''
        # find start boarder
        #sys.stdout.write('[RECV one return] raw data: ')
        while 1:
            if time.time() - timeout_init > timeout:
                # print()
                # print(ERROR_MSG,'Response timeout',BASH_TIPS['DEFAULT'])
                raise TimeoutError
            c = self._port.read(1)
            #sys.stdout.write(binascii.hexlify(c).decode())
            sys.stdout.flush()
            if c == b'\xc0':
                break

        in_escape = False
        while 1:
            if time.time() - timeout_init > timeout:
                raise TimeoutError
            c = self._port.read(1)
            #sys.stdout.write(binascii.hexlify(c).decode())
            sys.stdout.flush()
            if c == b'\xc0':
                break

            elif in_escape:  # part-way through escape sequence
                in_escape = False
                if c == b'\xdc':
                    data += b'\xc0'
                elif c == b'\xdd':
                    data += b'\xdb'
                else:
                    raise Exception('Invalid SLIP escape (%r%r)' % (b'\xdb', c))
            elif c == b'\xdb':  # start of escape sequence
                in_escape = True

            data += c

        #sys.stdout.write('\n')
        return data

    # kd233 or open-ec or new cmsis-dap
    def reset_to_isp_kd233(self):
        self._port.setDTR (False)
        self._port.setRTS (False)
        time.sleep(0.1)
        #print('-- RESET to LOW, IO16 to HIGH --')
        # Pull reset down and keep 10ms
        self._port.setDTR (True)
        self._port.setRTS (False)
        time.sleep(0.1)
        #print('-- IO16 to LOW, RESET to HIGH --')
        # Pull IO16 to low and release reset
        self._port.setRTS (True)
        self._port.setDTR (False)
        time.sleep(0.1)
    def reset_to_boot_kd233(self):
        self._port.setDTR (False)
        self._port.setRTS (False)
        time.sleep(0.1)
        #print('-- RESET to LOW --')
        # Pull reset down and keep 10ms
        self._port.setDTR (True)
        self._port.setRTS (False)
        time.sleep(0.1)
        #print('-- RESET to HIGH, BOOT --')
        # Pull IO16 to low and release reset
        self._port.setRTS (False)
        self._port.setDTR (False)
        time.sleep(0.1)

    #dan dock
    def reset_to_isp_dan(self):
        self._port.setDTR (False)
        self._port.setRTS (False)
        time.sleep(0.1)
        #print('-- RESET to LOW, IO16 to HIGH --')
        # Pull reset down and keep 10ms
        self._port.setDTR (False)
        self._port.setRTS (True)
        time.sleep(0.1)
        #print('-- IO16 to LOW, RESET to HIGH --')
        # Pull IO16 to low and release reset
        self._port.setRTS (False)
        self._port.setDTR (True)
        time.sleep(0.1)
    def reset_to_boot_dan(self):
        self._port.setDTR (False)
        self._port.setRTS (False)
        time.sleep(0.1)
        #print('-- RESET to LOW --')
        # Pull reset down and keep 10ms
        self._port.setDTR (False)
        self._port.setRTS (True)
        time.sleep(0.1)
        #print('-- RESET to HIGH, BOOT --')
        # Pull IO16 to low and release reset
        self._port.setRTS (False)
        self._port.setDTR (False)
        time.sleep(0.1)

    # maix go for old cmsis-dap firmware
    def reset_to_isp_goD(self):
        self._port.setDTR (True)   ## output 0
        self._port.setRTS (True)
        time.sleep(0.01)
        #print('-- RESET to LOW --')
        # Pull reset down and keep 10ms
        self._port.setRTS (False)
        self._port.setDTR (True)
        time.sleep(0.01)
        #print('-- RESET to HIGH, BOOT --')
        # Pull IO16 to low and release reset
        self._port.setRTS (False)
        self._port.setDTR (True)
        time.sleep(0.01)
    def reset_to_boot_goD(self):
        self._port.setDTR (False)
        self._port.setRTS (False)
        time.sleep(0.01)
        #print('-- RESET to LOW --')
        # Pull reset down and keep 10ms
        self._port.setRTS (False)
        self._port.setDTR (True)
        time.sleep(0.01)
        #print('-- RESET to HIGH, BOOT --')
        # Pull IO16 to low and release reset
        self._port.setRTS (True)
        self._port.setDTR (True)
        time.sleep(0.01)

    # maix go for openec or new cmsis-dap  firmware
    def reset_to_boot_maixgo(self):
        self._port.setDTR (False)
        self._port.setRTS (False)
        time.sleep(0.01)
        #print('-- RESET to LOW --')
        # Pull reset down and keep 10ms
        self._port.setRTS (False)
        self._port.setDTR (True)
        time.sleep(0.01)
        #print('-- RESET to HIGH, BOOT --')
        # Pull IO16 to low and release reset
        self._port.setRTS (False)
        self._port.setDTR (False)
        time.sleep(0.01)

    def greeting(self):
        self._port.write(b'\xc0\xc2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0')
        op, reason, text = ISPResponse.parse(self.recv_one_return())

        #print('MAIX return op:', ISPResponse.ISPOperation(op).name, 'reason:', ISPResponse.ErrorCode(reason).name)


    def flash_greeting(self):
        retry_count = 0
        while 1:
            self._port.write(b'\xc0\xd2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0')
            retry_count = retry_count + 1
            try:
                op, reason, text = FlashModeResponse.parse(self.recv_one_return())
            except IndexError:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                print(WARN_MSG,"Index Error, retrying...",BASH_TIPS['DEFAULT'])
                time.sleep(0.1)
                continue
            except TimeoutError:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                print(WARN_MSG,"Timeout Error, retrying...",BASH_TIPS['DEFAULT'])
                time.sleep(0.1)
                continue
            except:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                print(WARN_MSG,"Unexcepted Error, retrying...",BASH_TIPS['DEFAULT'])
                time.sleep(0.1)
                continue
            # print('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:',
            #      FlashModeResponse.ErrorCode(reason).name)
            if FlashModeResponse.Operation(op) == FlashModeResponse.Operation.ISP_NOP and FlashModeResponse.ErrorCode(reason) == FlashModeResponse.ErrorCode.ISP_RET_OK:
                print(INFO_MSG,"Boot to Flashmode Successfully",BASH_TIPS['DEFAULT'])
                self._port.flushInput()
                self._port.flushOutput()
                break
            else:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                print(WARN_MSG,"Unexcepted Return recevied, retrying...",BASH_TIPS['DEFAULT'])
                time.sleep(0.1)
                continue

    def boot(self, address=0x80000000):
        print(INFO_MSG,"Booting From " + hex(address),BASH_TIPS['DEFAULT'])

        out = struct.pack('II', address, 0)

        crc32_checksum = struct.pack('I', binascii.crc32(out) & 0xFFFFFFFF)

        out = struct.pack('HH', 0xc5, 0x00) + crc32_checksum + out  # op: ISP_MEMORY_WRITE: 0xc3
        self.write(out)

    def recv_debug(self):
        op, reason, text = ISPResponse.parse(self.recv_one_return())
        #print('[RECV] op:', ISPResponse.ISPOperation(op).name, 'reason:', ISPResponse.ErrorCode(reason).name)
        if text:
            print('-' * 30)
            print(text)
            print('-' * 30)
        if ISPResponse.ErrorCode(reason) not in (ISPResponse.ErrorCode.ISP_RET_DEFAULT, ISPResponse.ErrorCode.ISP_RET_OK):
            print('Failed, retry, errcode=', hex(reason))
            return False
        return True

    def flash_recv_debug(self):
        op, reason, text = FlashModeResponse.parse(self.recv_one_return())
        #print('[Flash-RECV] op:', FlashModeResponse.Operation(op).name, 'reason:',
        #      FlashModeResponse.ErrorCode(reason).name)
        if text:
            print('-' * 30)
            print(text)
            print('-' * 30)

        if FlashModeResponse.ErrorCode(reason) not in (FlashModeResponse.ErrorCode.ISP_RET_OK, FlashModeResponse.ErrorCode.ISP_RET_OK):
            print('Failed, retry')
            return False
        return True

    def init_flash(self, chip_type):
        chip_type = int(chip_type)
        print(INFO_MSG,"Selected Flash: ",("In-Chip", "On-Board")[chip_type],BASH_TIPS['DEFAULT'])
        out = struct.pack('II', chip_type, 0)
        crc32_checksum = struct.pack('I', binascii.crc32(out) & 0xFFFFFFFF)
        out = struct.pack('HH', 0xd7, 0x00) + crc32_checksum + out
        '''Retry when it have error'''
        retry_count = 0
        while 1:
            sent = self.write(out)
            retry_count = retry_count + 1
            try:
                op, reason, text = FlashModeResponse.parse(self.recv_one_return())
            except IndexError:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to initialize flash",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                print(WARN_MSG,"Index Error, retrying...",BASH_TIPS['DEFAULT'])
                time.sleep(0.1)
                continue
            except TimeoutError:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to initialize flash",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                print(WARN_MSG,"Timeout Error, retrying...",BASH_TIPS['DEFAULT'])
                time.sleep(0.1)
                continue
            except:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to initialize flash",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                print(WARN_MSG,"Unexcepted Error, retrying...",BASH_TIPS['DEFAULT'])
                time.sleep(0.1)
                continue
            # print('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:',
            #      FlashModeResponse.ErrorCode(reason).name)
            if FlashModeResponse.Operation(op) == FlashModeResponse.Operation.FLASHMODE_FLASH_INIT and FlashModeResponse.ErrorCode(reason) == FlashModeResponse.ErrorCode.ISP_RET_OK:
                print(INFO_MSG,"Initialization flash Successfully",BASH_TIPS['DEFAULT'])
                break
            else:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to initialize flash",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                print(WARN_MSG,"Unexcepted Return recevied, retrying...",BASH_TIPS['DEFAULT'])
                time.sleep(0.1)
                continue

    def flash_dataframe(self, data, address=0x80000000):
        DATAFRAME_SIZE = 1024
        data_chunks = chunks(data, DATAFRAME_SIZE)
        #print('[DEBUG] flash dataframe | data length:', len(data))
        total_chunk = math.ceil(len(data)/DATAFRAME_SIZE)

        time_start = time.time()
        for n, chunk in enumerate(data_chunks):
            while 1:
                #print('[INFO] sending chunk', i, '@address', hex(address), 'chunklen', len(chunk))
                out = struct.pack('II', address, len(chunk))

                crc32_checksum = struct.pack('I', binascii.crc32(out + chunk) & 0xFFFFFFFF)

                out = struct.pack('HH', 0xc3, 0x00) + crc32_checksum + out + chunk  # op: ISP_MEMORY_WRITE: 0xc3
                sent = self.write(out)
                #print('[INFO]', 'sent', sent, 'bytes', 'checksum', binascii.hexlify(crc32_checksum).decode())

                address += len(chunk)

                if self.recv_debug():
                    break

            columns, lines = get_terminal_size()
            time_delta = time.time() - time_start
            speed = ''
            if (time_delta > 1):
                speed = str(int((n + 1) * DATAFRAME_SIZE / 1024.0 / time_delta)) + 'kiB/s'
            printProgressBar(n+1, total_chunk, prefix = 'Downloading ISP:', suffix = speed, length = columns - 35)

    def dump_to_flash(self, data, address=0):
        '''
        typedef struct __attribute__((packed)) {
            uint8_t op;
            int32_t checksum; // checksum
            uint32_t address;
            uint32_t data_len;
            uint8_t data_buf[1024];
        } isp_request_t;
        '''

        DATAFRAME_SIZE = 4096
        data_chunks = chunks(data, DATAFRAME_SIZE)
        #print('[DEBUG] flash dataframe | data length:', len(data))



        for n, chunk in enumerate(data_chunks):
            #print('[INFO] sending chunk', i, '@address', hex(address))
            out = struct.pack('II', address, len(chunk))

            crc32_checksum = struct.pack('I', binascii.crc32(out + chunk) & 0xFFFFFFFF)

            out = struct.pack('HH', 0xd4, 0x00) + crc32_checksum + out + chunk
            #print("[$$$$]", binascii.hexlify(out[:32]).decode())
            retry_count = 0
            while True:
                try:
                    sent = self.write(out)
                    #print('[INFO]', 'sent', sent, 'bytes', 'checksum', crc32_checksum)
                    self.flash_recv_debug()
                except:
                    retry_count = retry_count + 1
                    if retry_count > MAX_RETRY_TIMES:
                        print(ERROR_MSG,"Error Count Exceeded, Stop Trying",BASH_TIPS['DEFAULT'])
                        sys.exit(1)
                    continue
                break
            address += len(chunk)



    def flash_erase(self):
        #print('[DEBUG] erasing spi flash.')
        self._port.write(b'\xc0\xd3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0')
        op, reason, text = FlashModeResponse.parse(self.recv_one_return())
        #print('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:',
        #      FlashModeResponse.ErrorCode(reason).name)

    def install_flash_bootloader(self, data):
        # 1.  flash bootloader
        self.flash_dataframe(data, address=0x80000000)

    def load_elf_to_sram(self, f):
        from elftools.elf.elffile import ELFFile
        from elftools.elf.descriptions import describe_p_type

        elffile = ELFFile(f)
        if elffile['e_entry'] != 0x80000000:
            print(WARN_MSG,"ELF entry is 0x%x instead of 0x80000000" % (elffile['e_entry']), BASH_TIPS['DEFAULT'])

        for segment in elffile.iter_segments():
            t = describe_p_type(segment['p_type'])
            print(INFO_MSG, ("Program Header: Size: %d, Virtual Address: 0x%x, Type: %s" % (segment['p_filesz'], segment['p_vaddr'], t)), BASH_TIPS['DEFAULT'])
            if not (segment['p_vaddr'] & 0x80000000):
                continue
            if segment['p_filesz']==0 or segment['p_vaddr']==0:
                print("Skipped")
                continue
            self.flash_dataframe(segment.data(), segment['p_vaddr'])

    def flash_firmware(self, firmware_bin: bytes, aes_key: bytes = None, address_offset = 0, sha256Prefix = True):
        #print('[DEBUG] flash_firmware DEBUG: aeskey=', aes_key)

        if sha256Prefix == True:
            # 
            # : SHA256(after)(32bytes) + AES_CIPHER_FLAG (1byte) + firmware_size(4bytes) + firmware_data
            aes_cipher_flag = b'\x01' if aes_key else b'\x00'

            # 
            if aes_key:
                enc = AES_128_CBC(aes_key, iv=b'\x00'*16).encrypt
                padded = firmware_bin + b'\x00'*15 # zero pad
                firmware_bin = b''.join([enc(padded[i*16:i*16+16]) for i in range(len(padded)//16)])

            firmware_len = len(firmware_bin)

            data = aes_cipher_flag + struct.pack('I', firmware_len) + firmware_bin

            sha256_hash = hashlib.sha256(data).digest()

            firmware_with_header = data + sha256_hash

            total_chunk = math.ceil(len(firmware_with_header)/4096)
            # 3. 
            data_chunks = chunks(firmware_with_header, 4096)  # 4kb for a sector
        else:
            total_chunk = math.ceil(len(firmware_bin)/4096)
            data_chunks = chunks(firmware_bin, 4096)

        time_start = time.time()
        for n, chunk in enumerate(data_chunks):
            chunk = chunk.ljust(4096, b'\x00')  # align by 4kb

            # 3.1 dataframe
            #print('[INFO]', 'Write firmware data piece')
            self.dump_to_flash(chunk, address= n * 4096 + address_offset)
            columns, lines = get_terminal_size()
            time_delta = time.time() - time_start
            speed = ''
            if (time_delta > 1):
                speed = str(int((n + 1) * 4096 / 1024.0 / time_delta)) + 'kiB/s'
            printProgressBar(n+1, total_chunk, prefix = 'Programming BIN:', suffix = speed, length = columns - 35)

def open_terminal(reset):
    control_signal = '0' if reset else '1'
    control_signal_b = not reset
    import serial.tools.miniterm
    # For using the terminal with MaixPy the 'filter' option must be set to 'direct'
    # because some control characters are emited
    sys.argv = ['kflash.py', _port, '115200', '--dtr='+control_signal, '--rts='+control_signal,  '--filter=direct']
    serial.tools.miniterm.main(default_port=_port, default_baudrate=115200, default_dtr=control_signal_b, default_rts=control_signal_b)
    sys.exit(0)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--port", help="COM Port", default="DEFAULT")
    parser.add_argument("-c", "--chip", help="SPI Flash type, 0 for in-chip, 1 for on-board", default=1)
    parser.add_argument("-b", "--baudrate", type=int, help="UART baudrate for uploading firmware", default=115200)
    parser.add_argument("-l", "--bootloader", help="bootloader bin path", required=False, default=None)
    parser.add_argument("-k", "--key", help="AES key in hex, if you need encrypt your firmware.", required=False, default=None)
    parser.add_argument("-v", "--verbose", help="increase output verbosity", default=False, action="store_true")
    parser.add_argument("-t", "--terminal", help="Start a terminal after finish (Python miniterm)", default=False, action="store_true")
    parser.add_argument("-n", "--noansi", help="Do not use ANSI colors, recommended in Windows CMD", default=False, action="store_true")
    parser.add_argument("-s", "--sram", help="Download firmware to SRAM and boot", default=False, action="store_true")

    parser.add_argument("-B", "--Board",required=False, type=str, help="Select dev board, e.g. kd233, dan, bit, goD, goE or trainer")
    parser.add_argument("firmware", help="firmware bin path")

    args = parser.parse_args()

    if (args.noansi == True):
        BASH_TIPS = dict(NORMAL='',BOLD='',DIM='',UNDERLINE='',
                            DEFAULT='', RED='', YELLOW='', GREEN='',
                            BG_DEFAULT='', BG_WHITE='')
        ERROR_MSG   = BASH_TIPS['RED']+BASH_TIPS['BOLD']+'[ERROR]'+BASH_TIPS['NORMAL']
        WARN_MSG    = BASH_TIPS['YELLOW']+BASH_TIPS['BOLD']+'[WARN]'+BASH_TIPS['NORMAL']
        INFO_MSG    = BASH_TIPS['GREEN']+BASH_TIPS['BOLD']+'[INFO]'+BASH_TIPS['NORMAL']
        print(INFO_MSG,'ANSI colors not used',BASH_TIPS['DEFAULT'])

    if args.port == "DEFAULT":
        if args.Board == "goE":
            list_port_info = list(serial.tools.list_ports.grep("0403")) #Take the second one
            if(len(list_port_info)==0):
                print(ERROR_MSG,"No vaild COM Port found in Auto Detect, Check Your Connection or Specify One by"+BASH_TIPS['GREEN']+'`--port/-p`',BASH_TIPS['DEFAULT'])
                sys.exit(1)
            list_port_info.sort()
            _port = list_port_info[1].device
            print(INFO_MSG,"COM Port Auto Detected, Selected ", _port, BASH_TIPS['DEFAULT'])
        elif args.Board == "trainer":
            list_port_info = list(serial.tools.list_ports.grep("0403")) #Take the first one
            if(len(list_port_info)==0):
                print(ERROR_MSG,"No vaild COM Port found in Auto Detect, Check Your Connection or Specify One by"+BASH_TIPS['GREEN']+'`--port/-p`',BASH_TIPS['DEFAULT'])
                sys.exit(1)
            list_port_info.sort()
            _port = list_port_info[0].device
            print(INFO_MSG,"COM Port Auto Detected, Selected ", _port, BASH_TIPS['DEFAULT'])
        else:
            try:
                list_port_info = next(serial.tools.list_ports.grep(VID_LIST_FOR_AUTO_LOOKUP)) #Take the first one within the list
                _port = list_port_info.device
                print(INFO_MSG,"COM Port Auto Detected, Selected ", _port, BASH_TIPS['DEFAULT'])
            except StopIteration:
                print(ERROR_MSG,"No vaild COM Port found in Auto Detect, Check Your Connection or Specify One by"+BASH_TIPS['GREEN']+'`--port/-p`',BASH_TIPS['DEFAULT'])
                sys.exit(1)
    else:
        _port = args.port
        print(INFO_MSG,"COM Port Selected Manually: ", _port, BASH_TIPS['DEFAULT'])

    loader = MAIXLoader(port=_port, baudrate=115200)
    file_format = ProgramFileFormat.FMT_BINARY

    # 0. Check firmware
    try:
        firmware_bin = open(args.firmware, 'rb')
    except FileNotFoundError:
        print(ERROR_MSG,'Unable to find the firmware at ', args.firmware, BASH_TIPS['DEFAULT'])
        sys.exit(1)

    with open(args.firmware, 'rb') as f:
        file_header = f.read(4)
        if file_header.startswith(bytes([0x50, 0x4B])):
            if ".kfpkg" != os.path.splitext(args.firmware)[1]:
                print(INFO_MSG, 'Find a zip file, but not with ext .kfpkg:', args.firmware, BASH_TIPS['DEFAULT'])
            else:
                file_format = ProgramFileFormat.FMT_KFPKG

        if file_header.startswith(bytes([0x7F, 0x45, 0x4C, 0x46])):
            file_format = ProgramFileFormat.FMT_ELF
            if args.sram:
                print(INFO_MSG, 'Find an ELF file:', args.firmware, BASH_TIPS['DEFAULT'])
            else:
                print(ERROR_MSG, 'This is an ELF file and cannot be programmed directly:', args.firmware, BASH_TIPS['DEFAULT'])
                print(ERROR_MSG, 'Please retry:', args.firmware + '.bin', BASH_TIPS['DEFAULT'])
                sys.exit(1)

    # 1. Greeting.
    print(INFO_MSG,"Trying to Enter the ISP Mode...",BASH_TIPS['DEFAULT'])

    retry_count = 0

    while 1:
        retry_count = retry_count + 1
        if retry_count > 15:
            print("\n" + ERROR_MSG,"No vaild Kendryte K210 found in Auto Detect, Check Your Connection or Specify One by"+BASH_TIPS['GREEN']+'`-p '+('/dev/ttyUSB0', 'COM3')[sys.platform == 'win32']+'`',BASH_TIPS['DEFAULT'])
            sys.exit(1)
        if args.Board == "dan" or args.Board == "bit" or args.Board == "trainer":
            try:
                print('.', end='')
                loader.reset_to_isp_dan()
                loader.greeting()
                break
            except TimeoutError:
                pass
        elif args.Board == "kd233":
            try:
                print('_', end='')
                loader.reset_to_isp_kd233()
                loader.greeting()
                break
            except TimeoutError:
                pass
        elif args.Board == "goE":
            try:
                print('*', end='')
                loader.reset_to_isp_kd233()
                loader.greeting()
                break
            except TimeoutError:
                pass
        elif args.Board == "goD":
            try:
                print('#', end='')
                loader.reset_to_isp_goD()
                loader.greeting()
                break
            except TimeoutError:
                pass
        else:
            try:
...

This file has been truncated, please download it to see its full contents.

Board Manager Definition GIST

Credits

Andri Yadi

Andri Yadi

11 projects • 86 followers
An entrepreneur. IoT, iOS, Node.js, Azure developer and influencer. Hackster Live Ambassador for Indonesia. Microsoft MVP of Azure.

Comments