EmbroidePy / pyembroidery

pyembroidery library for reading and writing a variety of embroidery formats.
MIT License
195 stars 35 forks source link

DMCA and the CSD format, omission. #59

Closed tatarize closed 5 years ago

tatarize commented 5 years ago

The CSD reader exists in libembroidery but was not ported over or utilized in this product because it contained an encryption scheme. By definition bypassing encryption violates DMCA.

In PyEmbroidery add the supported format:

    yield ({
        "description": "Singer / Poem",
        "extension": "csd",
        "mimetype": "application/x-csd",
        "category": "embroidery",
        "reader": CsdReader,
    })

And the reader:

from EmbThread import EmbThread
from .ReadHelper import read_int_8

CsdSubMaskSize = 479
CsdXorMaskSize = 501

_subMask = [0] * CsdSubMaskSize
_xorMask = [0] * CsdXorMaskSize

_decryptArray = [
    0x43, 0x6E, 0x72, 0x7A, 0x76, 0x6C, 0x61, 0x6F, 0x7C, 0x29, 0x5D, 0x62, 0x60, 0x6E, 0x61, 0x62, 0x20,
    0x41, 0x66, 0x6A, 0x3A, 0x35, 0x5A, 0x63, 0x7C, 0x37, 0x3A, 0x2A, 0x25, 0x24, 0x2A, 0x33, 0x00, 0x10,
    0x14, 0x03, 0x72, 0x4C, 0x48, 0x42, 0x08, 0x7A, 0x5E, 0x0B, 0x6F, 0x45, 0x47, 0x5F, 0x40, 0x54, 0x5C,
    0x57, 0x55, 0x59, 0x53, 0x3A, 0x32, 0x6F, 0x53, 0x54, 0x50, 0x5C, 0x4A, 0x56, 0x2F, 0x2F, 0x62, 0x2C,
    0x22, 0x65, 0x25, 0x28, 0x38, 0x30, 0x38, 0x22, 0x2B, 0x25, 0x3A, 0x6F, 0x27, 0x38, 0x3E, 0x3F, 0x74,
    0x37, 0x33, 0x77, 0x2E, 0x30, 0x3D, 0x34, 0x2E, 0x32, 0x2B, 0x2C, 0x0C, 0x18, 0x42, 0x13, 0x16, 0x0A,
    0x15, 0x02, 0x0B, 0x1C, 0x1E, 0x0E, 0x08, 0x60, 0x64, 0x0D, 0x09, 0x51, 0x25, 0x1A, 0x18, 0x16, 0x19,
    0x1A, 0x58, 0x10, 0x14, 0x5B, 0x08, 0x15, 0x1B, 0x5F, 0xD5, 0xD2, 0xAE, 0xA3, 0xC1, 0xF0, 0xF4, 0xE8,
    0xF8, 0xEC, 0xA6, 0xAB, 0xCD, 0xF8, 0xFD, 0xFB, 0xE2, 0xF0, 0xFE, 0xFA, 0xF5, 0xB5, 0xF7, 0xF9, 0xFC,
    0xB9, 0xF5, 0xEF, 0xF4, 0xF8, 0xEC, 0xBF, 0xC3, 0xCE, 0xD7, 0xCD, 0xD0, 0xD7, 0xCF, 0xC2, 0xDB, 0xA4,
    0xA0, 0xB0, 0xAF, 0xBE, 0x98, 0xE2, 0xC2, 0x91, 0xE5, 0xDC, 0xDA, 0xD2, 0x96, 0xC4, 0x98, 0xF8, 0xC9,
    0xD2, 0xDD, 0xD3, 0x9E, 0xDE, 0xAE, 0xA5, 0xE2, 0x8C, 0xB6, 0xAC, 0xA3, 0xA9, 0xBC, 0xA8, 0xA6, 0xEB,
    0x8B, 0xBF, 0xA1, 0xAC, 0xB5, 0xA3, 0xBB, 0xB6, 0xA7, 0xD8, 0xDC, 0x9A, 0xAA, 0xF9, 0x82, 0xFB, 0x9D,
    0xB9, 0xAB, 0xB3, 0x94, 0xC1, 0xA0, 0x8C, 0x8B, 0x8E, 0x95, 0x8F, 0x87, 0x99, 0xE7, 0xE1, 0xA3, 0x83,
    0x8B, 0xCF, 0xA3, 0x85, 0x9D, 0x83, 0xD4, 0xB7, 0x83, 0x84, 0x91, 0x97, 0x9F, 0x88, 0x8F, 0xDD, 0xAD,
    0x90]

def BuildDecryptionTable(seed):
    mul1 = 0x41C64E6D
    add1 = 0x3039
    for i in range(0, CsdSubMaskSize):
        seed *= mul1
        seed += add1
        seed &= 0xFFFFFFFF
        _subMask[i] = (seed >> 16) & 0xFF
    for i in range(0, CsdXorMaskSize):
        seed *= mul1
        seed += add1
        seed &= 0xFFFFFFFF
        _xorMask[i] = (seed >> 16) & 0xFF

def DecodeCsdByte(fileOffset, val, type):
    if type != 0:
        fileOffsetHigh = fileOffset & 0xFFFFFF00
        fileOffsetLow = fileOffset & 0xFF
        newOffset = fileOffsetLow
        fileOffsetLow = fileOffsetHigh
        final = fileOffsetLow % 0x300
        if final != 0x100 and final != 0x200:
            newOffset = _decryptArray[newOffset] | fileOffsetHigh
        elif final != 0x100 and final == 0x200:
            if newOffset == 0:
                fileOffsetHigh = fileOffsetHigh - 0x100
            newOffset = _decryptArray[newOffset] | fileOffsetHigh
        elif newOffset != 1 and newOffset != 0:
            newOffset = _decryptArray[newOffset] | fileOffsetHigh
        else:
            fileOffsetHigh = fileOffsetHigh - 0x100
            newOffset = _decryptArray[newOffset] | fileOffsetHigh
    else:
        newOffset = fileOffset
    return ((val ^ _xorMask[newOffset % CsdXorMaskSize]) - _subMask[newOffset % CsdSubMaskSize]) & 0xFF

# /*! Reads a file with the given \a fileName and loads the data into \a pattern.
#  *  Returns \c true if successful, otherwise returns \c false. */

def read_csd_stitches(f, out):
    type = 0
    identifier = [0] * 8
    colorChange = -1
    colorOrder = [0] * 14
    for i in range(0, 8):
        identifier[i] = read_int_8(f)

    if identifier[0] != 0x7C and identifier[2] != 0xC3:
        type = 1
    if type == 0:
        BuildDecryptionTable(0xC)
    else:
        BuildDecryptionTable(identifier[0])
    f.seek(8, 0)  # Seek Set
    for i in range(0, 16):
        thread = EmbThread()
        r = DecodeCsdByte(f.tell(), read_int_8(f), type)
        g = DecodeCsdByte(f.tell(), read_int_8(f), type)
        b = DecodeCsdByte(f.tell(), read_int_8(f), type)
        thread.set_color(r, g, b)
        thread.catalogNumber = ""
        thread.description = ""
        out.add_thread(thread)
    unknown1 = DecodeCsdByte(f.tell(), read_int_8(f), type)
    unknown2 = DecodeCsdByte(f.tell(), read_int_8(f), type)

    for i in range(0, 14):
        colorOrder[i] = DecodeCsdByte(f.tell(), read_int_8(f), type)

    while True:
        b0 = DecodeCsdByte(f.tell(), read_int_8(f), type)
        b1 = DecodeCsdByte(f.tell(), read_int_8(f), type)
        b2 = DecodeCsdByte(f.tell(), read_int_8(f), type)

        dx = int(b2)
        dy = int(b1)

        if b0 == 0xF8 or b0 == 0x87 or b0 == 0x91:
            break

        negativeX = ((b0 & 0x20) > 0)
        negativeY = ((b0 & 0x40) > 0)

        if negativeX:
            dx = -dx
        if not negativeY:
            dy = -dy

        b0 &= 0xFF ^ 0xE0

        if (b0 & 0x1F) == 0:
            out.stitch(dx, dy)
        elif (b0 & 0x0C) > 0:
            out.color_change()
            if colorChange >= 14:
                break  # Invalid color change.
            colorChange += 1
        elif (b0 & 0x1F) > 0:
            out.trim()
            out.move(dx, dy)
        else:
            out.stitch(dx, dy)

def read(f, out, settings=None):
    read_csd_stitches(f, out)