def encodeFloat(f): # For CFF only, used in cffLib if f == 0.0: # 0.0 == +0.0 == -0.0 return realZeroBytes # Note: 14 decimal digits seems to be the limitation for CFF real numbers # in macOS. However, we use 8 here to match the implementation of AFDKO. s = "%.8G" % f if s[:2] == "0.": s = s[1:] elif s[:3] == "-0.": s = "-" + s[2:] nibbles = [] while s: c = s[0] s = s[1:] if c == "E": c2 = s[:1] if c2 == "-": s = s[1:] c = "E-" elif c2 == "+": s = s[1:] nibbles.append(realNibblesDict[c]) nibbles.append(0xf) if len(nibbles) % 2: nibbles.append(0xf) d = bytechr(30) for i in range(0, len(nibbles), 2): d = d + bytechr(nibbles[i] << 4 | nibbles[i + 1]) return d
def writePFB(path, data): chunks = findEncryptedChunks(data) with open(path, "wb") as f: for isEncrypted, chunk in chunks: if isEncrypted: code = 2 else: code = 1 f.write(bytechr(128) + bytechr(code)) f.write(longToString(len(chunk))) f.write(chunk) f.write(bytechr(128) + bytechr(3))
def compile(self, ttFont): d = self.__dict__.copy() d["nameLength"] = bytechr(len(self.baseGlyphName)) d["uniqueName"] = self.compilecompileUniqueName(self.uniqueName, 28) METAMD5List = eval(self.METAMD5) d["METAMD5"] = b"" for val in METAMD5List: d["METAMD5"] += bytechr(val) assert (len(d["METAMD5"]) == 16 ), "Failed to pack 16 byte MD5 hash in SING table" data = sstruct.pack(SINGFormat, d) data = data + tobytes(self.baseGlyphName) return data
def maskByte(self, hHints, vHints): # return hintmask bytes for known hints. numHHints = len(hHints) numVHints = len(vHints) maskVal = 0 byteIndex = 0 self.byteLength = byteLength = int((7 + numHHints + numVHints) / 8) mask = b"" self.hList.sort() for hint in self.hList: try: i = hHints.index(hint) except ValueError: # we get here if some hints have been dropped # because of the stack limit continue newbyteIndex = int(i / 8) if newbyteIndex != byteIndex: mask += bytechr(maskVal) byteIndex += 1 while byteIndex < newbyteIndex: mask += b"\0" byteIndex += 1 maskVal = 0 maskVal += 2**(7 - (i % 8)) self.vList.sort() for hint in self.vList: try: i = numHHints + vHints.index(hint) except ValueError: # we get here if some hints have been dropped # because of the stack limit continue newbyteIndex = int(i / 8) if newbyteIndex != byteIndex: mask += bytechr(maskVal) byteIndex += 1 while byteIndex < newbyteIndex: mask += b"\0" byteIndex += 1 maskVal = 0 maskVal += 2**(7 - (i % 8)) if maskVal: mask += bytechr(maskVal) if len(mask) < byteLength: mask += b"\0" * (byteLength - len(mask)) self.mask = mask return mask
def compile(self, ttFont): self.version = 0 numGlyphs = ttFont['maxp'].numGlyphs glyphOrder = ttFont.getGlyphOrder() self.recordSize = 4 * ((2 + numGlyphs + 3) // 4) pad = (self.recordSize - 2 - numGlyphs) * b"\0" self.numRecords = len(self.hdmx) data = sstruct.pack(hdmxHeaderFormat, self) items = sorted(self.hdmx.items()) for ppem, widths in items: data = data + bytechr(ppem) + bytechr(max(widths.values())) for glyphID in range(len(glyphOrder)): width = widths[glyphOrder[glyphID]] data = data + bytechr(width) data = data + pad return data
def fromXML(self, name, attrs, content): from fontTools.misc.textTools import binary2num, readHex if attrs.get("raw"): self.setBytecode(readHex(content)) return content = strjoin(content) content = content.split() program = [] end = len(content) i = 0 while i < end: token = content[i] i = i + 1 try: token = int(token) except ValueError: try: token = strToFixedToFloat(token, precisionBits=16) except ValueError: program.append(token) if token in ('hintmask', 'cntrmask'): mask = content[i] maskBytes = b"" for j in range(0, len(mask), 8): maskBytes = maskBytes + bytechr( binary2num(mask[j:j + 8])) program.append(maskBytes) i = i + 1 else: program.append(token) else: program.append(token) self.setProgram(program)
def encodeDeltaRunAsBytes_(deltas, offset, stream): runLength = 0 pos = offset numDeltas = len(deltas) while pos < numDeltas and runLength < 64: value = deltas[pos] if value < -128 or value > 127: break # Within a byte-encoded run of deltas, a single zero # is best stored literally as 0x00 value. However, # if are two or more zeroes in a sequence, it is # better to start a new run. For example, the sequence # of deltas [15, 15, 0, 15, 15] becomes 6 bytes # (04 0F 0F 00 0F 0F) when storing the zero value # literally, but 7 bytes (01 0F 0F 80 01 0F 0F) # when starting a new run. if value == 0 and pos+1 < numDeltas and deltas[pos+1] == 0: break pos += 1 runLength += 1 assert runLength >= 1 and runLength <= 64 stream.write(bytechr(runLength - 1)) for i in range(offset, pos): stream.write(struct.pack('b', otRound(deltas[i]))) return pos
def encodeDeltaRunAsWords_(deltas, offset, stream): runLength = 0 pos = offset numDeltas = len(deltas) while pos < numDeltas and runLength < 64: value = deltas[pos] # Within a word-encoded run of deltas, it is easiest # to start a new run (with a different encoding) # whenever we encounter a zero value. For example, # the sequence [0x6666, 0, 0x7777] needs 7 bytes when # storing the zero literally (42 66 66 00 00 77 77), # and equally 7 bytes when starting a new run # (40 66 66 80 40 77 77). if value == 0: break # Within a word-encoded run of deltas, a single value # in the range (-128..127) should be encoded literally # because it is more compact. For example, the sequence # [0x6666, 2, 0x7777] becomes 7 bytes when storing # the value literally (42 66 66 00 02 77 77), but 8 bytes # when starting a new run (40 66 66 00 02 40 77 77). isByteEncodable = lambda value: value >= -128 and value <= 127 if isByteEncodable(value) and pos+1 < numDeltas and isByteEncodable(deltas[pos+1]): break pos += 1 runLength += 1 assert runLength >= 1 and runLength <= 64 stream.write(bytechr(DELTAS_ARE_WORDS | (runLength - 1))) for i in range(offset, pos): stream.write(struct.pack('>h', otRound(deltas[i]))) return pos
def maskByte(self, hHints, vHints): # return hintmask bytes for known hints. numHHints = len(hHints) numVHints = len(vHints) maskVal = 0 byteIndex = 0 self.byteLength = byteLength = int((7 + numHHints + numVHints)/8) mask = b"" self.hList.sort() for hint in self.hList: try: i = hHints.index(hint) except ValueError: continue # we get here if some hints have been dropped because of the stack limit newbyteIndex = (i//8) if newbyteIndex != byteIndex: mask += bytechr(maskVal) byteIndex +=1 while byteIndex < newbyteIndex: mask += b"\0" byteIndex +=1 maskVal = 0 maskVal += 2**(7 - (i %8)) self.vList.sort() for hint in self.vList: try: i = numHHints + vHints.index(hint) except ValueError: continue # we get here if some hints have been dropped because of the stack limit newbyteIndex = (i//8) if newbyteIndex != byteIndex: mask += bytechr(maskVal) byteIndex +=1 while byteIndex < newbyteIndex: mask += b"\0" byteIndex +=1 maskVal = 0 maskVal += 2**(7 - (i %8)) if maskVal: mask += bytechr(maskVal) if len(mask) < byteLength: mask += b"\0"*(byteLength - len(mask)) self.mask = mask return mask
def toUnicode(self, errors='strict'): """ If self.string is a Unicode string, return it; otherwise try decoding the bytes in self.string to a Unicode string using the encoding of this entry as returned by self.getEncoding(); Note that self.getEncoding() returns 'ascii' if the encoding is unknown to the library. Certain heuristics are performed to recover data from bytes that are ill-formed in the chosen encoding, or that otherwise look misencoded (mostly around bad UTF-16BE encoded bytes, or bytes that look like UTF-16BE but marked otherwise). If the bytes are ill-formed and the heuristics fail, the error is handled according to the errors parameter to this function, which is passed to the underlying decode() function; by default it throws a UnicodeDecodeError exception. Note: The mentioned heuristics mean that roundtripping a font to XML and back to binary might recover some misencoded data whereas just loading the font and saving it back will not change them. """ def isascii(b): return (b >= 0x20 and b <= 0x7E) or b in [0x09, 0x0A, 0x0D] encoding = self.getEncoding() string = self.string if isinstance( string, bytes) and encoding == 'utf_16_be' and len(string) % 2 == 1: # Recover badly encoded UTF-16 strings that have an odd number of bytes: # - If the last byte is zero, drop it. Otherwise, # - If all the odd bytes are zero and all the even bytes are ASCII, # prepend one zero byte. Otherwise, # - If first byte is zero and all other bytes are ASCII, insert zero # bytes between consecutive ASCII bytes. # # (Yes, I've seen all of these in the wild... sigh) if byteord(string[-1]) == 0: string = string[:-1] elif all( byteord(b) == 0 if i % 2 else isascii(byteord(b)) for i, b in enumerate(string)): string = b'\0' + string elif byteord(string[0]) == 0 and all( isascii(byteord(b)) for b in string[1:]): string = bytesjoin(b'\0' + bytechr(byteord(b)) for b in string[1:]) string = tostr(string, encoding=encoding, errors=errors) # If decoded strings still looks like UTF-16BE, it suggests a double-encoding. # Fix it up. if all( ord(c) == 0 if i % 2 == 0 else isascii(ord(c)) for i, c in enumerate(string)): # If string claims to be Mac encoding, but looks like UTF-16BE with ASCII text, # narrow it down. string = ''.join(c for c in string[1::2]) return string
def compilePoints(points, numPointsInGlyph): # If the set consists of all points in the glyph, it gets encoded with # a special encoding: a single zero byte. if len(points) == numPointsInGlyph: return b"\0" # In the 'gvar' table, the packing of point numbers is a little surprising. # It consists of multiple runs, each being a delta-encoded list of integers. # For example, the point set {17, 18, 19, 20, 21, 22, 23} gets encoded as # [6, 17, 1, 1, 1, 1, 1, 1]. The first value (6) is the run length minus 1. # There are two types of runs, with values being either 8 or 16 bit unsigned # integers. points = list(points) points.sort() numPoints = len(points) # The binary representation starts with the total number of points in the set, # encoded into one or two bytes depending on the value. if numPoints < 0x80: result = [bytechr(numPoints)] else: result = [bytechr((numPoints >> 8) | 0x80) + bytechr(numPoints & 0xff)] MAX_RUN_LENGTH = 127 pos = 0 lastValue = 0 while pos < numPoints: run = io.BytesIO() runLength = 0 useByteEncoding = None while pos < numPoints and runLength <= MAX_RUN_LENGTH: curValue = points[pos] delta = curValue - lastValue if useByteEncoding is None: useByteEncoding = 0 <= delta <= 0xff if useByteEncoding and (delta > 0xff or delta < 0): # we need to start a new run (which will not use byte encoding) break # TODO This never switches back to a byte-encoding from a short-encoding. # That's suboptimal. if useByteEncoding: run.write(bytechr(delta)) else: run.write(bytechr(delta >> 8)) run.write(bytechr(delta & 0xff)) lastValue = curValue pos += 1 runLength += 1 if useByteEncoding: runHeader = bytechr(runLength - 1) else: runHeader = bytechr((runLength - 1) | POINTS_ARE_WORDS) result.append(runHeader) result.append(run.getvalue()) return bytesjoin(result)
def toString(self): data = bytechr(self.flags) if (self.flags & 0x3F) == 0x3F: data += struct.pack('>4s', self.tag.tobytes()) data += packBase128(self.origLength) if self.transformed: data += packBase128(self.length) return data
def encodeInt(value, fourByteOp=fourByteOp, bytechr=bytechr, pack=struct.pack, unpack=struct.unpack): if -107 <= value <= 107: code = bytechr(value + 139) elif 108 <= value <= 1131: value = value - 108 code = bytechr((value >> 8) + 247) + bytechr(value & 0xFF) elif -1131 <= value <= -108: value = -value - 108 code = bytechr((value >> 8) + 251) + bytechr(value & 0xFF) elif fourByteOp is None: # T2 only supports 2 byte ints if -32768 <= value <= 32767: code = bytechr(28) + pack(">h", value) else: # Backwards compatible hack: due to a previous bug in FontTools, # 16.16 fixed numbers were written out as 4-byte ints. When # these numbers were small, they were wrongly written back as # small ints instead of 4-byte ints, breaking round-tripping. # This here workaround doesn't do it any better, since we can't # distinguish anymore between small ints that were supposed to # be small fixed numbers and small ints that were just small # ints. Hence the warning. log.warning( "4-byte T2 number got passed to the " "IntType handler. This should happen only when reading in " "old XML files.\n") code = bytechr(255) + pack(">l", value) else: code = fourByteOp + pack(">l", value) return code
def deHexStr(hexdata): """Convert a hex string to binary data.""" hexdata = strjoin(hexdata.split()) if len(hexdata) % 2: hexdata = hexdata + "0" data = [] for i in range(0, len(hexdata), 2): data.append(bytechr(int(hexdata[i:i+2], 16))) return bytesjoin(data)
def test_unsupportedLookupType(self): data = bytesjoin([ MORX_NONCONTEXTUAL_DATA[:67], bytechr(66), MORX_NONCONTEXTUAL_DATA[69:] ]) with self.assertRaisesRegex(AssertionError, r"unsupported 'morx' lookup type 66"): morx = newTable('morx') morx.decompile(data, FakeFont(['.notdef']))
def _reverseBytes(data): if len(data) != 1: return bytesjoin(map(_reverseBytes, data)) byte = byteord(data) result = 0 for i in range(8): result = result << 1 result |= byte & 1 byte = byte >> 1 return bytechr(result)
def encodeDeltaRunAsZeroes_(deltas, offset, stream): runLength = 0 pos = offset numDeltas = len(deltas) while pos < numDeltas and runLength < 64 and deltas[pos] == 0: pos += 1 runLength += 1 assert runLength >= 1 and runLength <= 64 stream.write(bytechr(DELTAS_ARE_ZERO | (runLength - 1))) return pos
def _binary2data(binary): byteList = [] for bitLoc in range(0, len(binary), 8): byteString = binary[bitLoc:bitLoc + 8] curByte = 0 for curBit in reversed(byteString): curByte = curByte << 1 if curBit == '1': curByte |= 1 byteList.append(bytechr(curByte)) return bytesjoin(byteList)
def getnexttoken( self, # localize some stuff, for performance len=len, ps_special=ps_special, stringmatch=stringRE.match, hexstringmatch=hexstringRE.match, commentmatch=commentRE.match, endmatch=endofthingRE.match): self.skipwhite() if self.pos >= self.len: return None, None pos = self.pos buf = self.buf char = bytechr(byteord(buf[pos])) if char in ps_special: if char in b'{}[]': tokentype = 'do_special' token = char elif char == b'%': tokentype = 'do_comment' _, nextpos = commentmatch(buf, pos).span() token = buf[pos:nextpos] elif char == b'(': tokentype = 'do_string' m = stringmatch(buf, pos) if m is None: raise PSTokenError('bad string at character %d' % pos) _, nextpos = m.span() token = buf[pos:nextpos] elif char == b'<': tokentype = 'do_hexstring' m = hexstringmatch(buf, pos) if m is None: raise PSTokenError('bad hexstring at character %d' % pos) _, nextpos = m.span() token = buf[pos:nextpos] else: raise PSTokenError('bad token at character %d' % pos) else: if char == b'/': tokentype = 'do_literal' m = endmatch(buf, pos + 1) else: tokentype = '' m = endmatch(buf, pos) if m is None: raise PSTokenError('bad token at character %d' % pos) _, nextpos = m.span() token = buf[pos:nextpos] self.pos = pos + len(token) token = tostr(token, encoding=self.encoding) return tokentype, token
def test_ReservedCoverageFlags(self): # 8A BC DE = TextDirection=Vertical, Reserved=0xABCDE # Note that the lower 4 bits of the first byte are already # part of the Reserved value. We test the full round-trip # to encoding and decoding is quite hairy. data = bytesjoin([ MORX_REARRANGEMENT_DATA[:28], bytechr(0x8A), bytechr(0xBC), bytechr(0xDE), MORX_REARRANGEMENT_DATA[31:] ]) table = newTable('morx') table.decompile(data, self.font) subtable = table.table.MorphChain[0].MorphSubtable[0] self.assertEqual(subtable.Reserved, 0xABCDE) xml = getXML(table.toXML) self.assertIn(' <Reserved value="0xabcde"/>', xml) table2 = newTable('morx') for name, attrs, content in parseXML(xml): table2.fromXML(name, attrs, content, font=self.font) self.assertEqual(hexStr(table2.compile(self.font)[28:31]), "8abcde")
def writeLWFN(path, data): # Res.FSpCreateResFile was deprecated in OS X 10.5 Res.FSpCreateResFile(path, "just", "LWFN", 0) resRef = Res.FSOpenResFile(path, 2) # write-only try: Res.UseResFile(resRef) resID = 501 chunks = findEncryptedChunks(data) for isEncrypted, chunk in chunks: if isEncrypted: code = 2 else: code = 1 while chunk: res = Res.Resource( bytechr(code) + '\0' + chunk[:LWFNCHUNKSIZE - 2]) res.AddResource('POST', resID, '') chunk = chunk[LWFNCHUNKSIZE - 2:] resID = resID + 1 res = Res.Resource(bytechr(5) + '\0') res.AddResource('POST', resID, '') finally: Res.CloseResFile(resRef)
def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False): if metrics is None: metrics = self.metrics assert 0 <= row and row < metrics.height, "Illegal row access in bitmap" # Loop through each byte. This can cover two bytes in the original data or # a single byte if things happen to be aligned. The very last entry might # not be aligned so take care to trim the binary data to size and pad with # zeros in the row data. Bit aligned data is somewhat tricky. # # Example of data cut. Data cut represented in x's. # '|' represents byte boundary. # data = ...0XX|XXXXXX00|000... => XXXXXXXX # or # data = ...0XX|XXXX0000|000... => XXXXXX00 # or # data = ...000|XXXXXXXX|000... => XXXXXXXX # or # data = ...000|00XXXX00|000... => XXXX0000 # dataList = [] bitRange = self._getBitRange(row, bitDepth, metrics) stepRange = bitRange + (8, ) for curBit in range(*stepRange): endBit = min(curBit + 8, bitRange[1]) numBits = endBit - curBit cutPoint = curBit % 8 firstByteLoc = curBit // 8 secondByteLoc = endBit // 8 if firstByteLoc < secondByteLoc: numBitsCut = 8 - cutPoint else: numBitsCut = endBit - curBit curByte = _reverseBytes(self.imageData[firstByteLoc]) firstHalf = byteord(curByte) >> cutPoint firstHalf = ((1 << numBitsCut) - 1) & firstHalf newByte = firstHalf if firstByteLoc < secondByteLoc and secondByteLoc < len( self.imageData): curByte = _reverseBytes(self.imageData[secondByteLoc]) secondHalf = byteord(curByte) << numBitsCut newByte = (firstHalf | secondHalf) & ((1 << numBits) - 1) dataList.append(bytechr(newByte)) # The way the data is kept is opposite the algorithm used. data = bytesjoin(dataList) if not reverseBytes: data = _reverseBytes(data) return data
def compile(self, isCFF2=False): if self.bytecode is not None: return opcodes = self.opcodes program = self.program if isCFF2: # If present, remove return and endchar operators. if program and program[-1] in ("return", "endchar"): program = program[:-1] elif program and not isinstance(program[-1], str): raise CharStringCompileError( "T2CharString or Subr has items on the stack after last operator." ) bytecode = [] encodeInt = self.getIntEncoder() encodeFixed = self.getFixedEncoder() i = 0 end = len(program) while i < end: token = program[i] i = i + 1 if isinstance(token, str): try: bytecode.extend(bytechr(b) for b in opcodes[token]) except KeyError: raise CharStringCompileError("illegal operator: %s" % token) if token in ('hintmask', 'cntrmask'): bytecode.append(program[i]) # hint mask i = i + 1 elif isinstance(token, int): bytecode.append(encodeInt(token)) elif isinstance(token, float): bytecode.append(encodeFixed(token)) else: assert 0, "unsupported type: %s" % type(token) try: bytecode = bytesjoin(bytecode) except TypeError: log.error(bytecode) raise self.setBytecode(bytecode)
def bezDecrypt(bezDataBuffer): r = 11586 i = 0 # input buffer byte position index lenBuffer = len(bezDataBuffer) byteCnt = 0 # output buffer byte count. newBuffer = "" while 1: cipher = 0 # restricted to int plain = 0 # restricted to int j = 2 # used to combine two successive bytes # process next two bytes, skipping whitespace. while j > 0: j -=1 try: while bezDataBuffer[i].isspace(): i +=1 ch = bezDataBuffer[i] except IndexError: return newBuffer if not ch.islower(): ch = ch.lower() if ch.isdigit(): ch = byteord(ch) - byteord('0') else: ch = byteord(ch) - byteord('a') + 10 cipher = (cipher << 4) & 0xFFFF cipher = cipher | ch i += 1 plain = cipher ^ (r >> 8) r = (cipher + r) * 902381661 + 341529579 if r > 0xFFFF: r = r & 0xFFFF byteCnt +=1 if (byteCnt > LEN_IV): newBuffer += bytechr(plain) if i >= lenBuffer: break return newBuffer
def maskByte(self, hHints, vHints): # return hintmask bytes for known hints. num_hhints = len(hHints) num_vhints = len(vHints) self.byteLength = byteLength = int((7 + num_hhints + num_vhints) / 8) maskVal = 0 byteIndex = 0 mask = b"" if self.h_list: mask, maskVal, byteIndex = self.addMaskBits( hHints, self.h_list, 0, mask, maskVal, byteIndex) if self.v_list: mask, maskVal, byteIndex = self.addMaskBits( vHints, self.v_list, num_hhints, mask, maskVal, byteIndex) if maskVal: mask += bytechr(maskVal) if len(mask) < byteLength: mask += b"\0" * (byteLength - len(mask)) self.mask = mask return mask
def readPFB(path, onlyHeader=False): """reads a PFB font file, returns raw data""" data = [] with open(path, "rb") as f: while True: if f.read(1) != bytechr(128): raise T1Error('corrupt PFB file') code = byteord(f.read(1)) if code in [1, 2]: chunklen = stringToLong(f.read(4)) chunk = f.read(chunklen) assert len(chunk) == chunklen data.append(chunk) elif code == 3: break else: raise T1Error('bad chunk code: ' + repr(code)) if onlyHeader: break data = bytesjoin(data) assertType1(data) return data
def addMaskBits(allHints, maskHints, numPriorHints, mask, maskVal, byteIndex): # sort in allhints order. sort_list = [[allHints.index(hint) + numPriorHints, hint] for hint in maskHints if hint in allHints] if not sort_list: # we get here if some hints have been dropped # because of # the stack limit, so that none of the items in maskHints are # not in allHints return mask, maskVal, byteIndex sort_list.sort() (idx_list, maskHints) = zip(*sort_list) for i in idx_list: newbyteIndex = int(i / 8) if newbyteIndex != byteIndex: mask += bytechr(maskVal) byteIndex += 1 while byteIndex < newbyteIndex: mask += b"\0" byteIndex += 1 maskVal = 0 maskVal += 2**(7 - (i % 8)) return mask, maskVal, byteIndex
def checkFlags(self, flags, textDirection, processingOrder, checkCompile=True): data = bytesjoin([ MORX_REARRANGEMENT_DATA[:28], bytechr(flags << 4), MORX_REARRANGEMENT_DATA[29:] ]) xml = [] for line in MORX_REARRANGEMENT_XML: if line.startswith(' <TextDirection '): line = ' <TextDirection value="%s"/>' % textDirection elif line.startswith(' <ProcessingOrder '): line = ' <ProcessingOrder value="%s"/>' % processingOrder xml.append(line) table1 = newTable('morx') table1.decompile(data, self.font) self.assertEqual(getXML(table1.toXML), xml) if checkCompile: table2 = newTable('morx') for name, attrs, content in parseXML(xml): table2.fromXML(name, attrs, content, font=self.font) self.assertEqual(hexStr(table2.compile(self.font)), hexStr(data))
def _encryptChar(plain, R): plain = byteord(plain) cipher = ((plain ^ (R >> 8))) & 0xFF R = ((cipher + R) * 52845 + 22719) & 0xFFFF return bytechr(cipher), R
def _decryptChar(cipher, R): cipher = byteord(cipher) plain = ((cipher ^ (R >> 8))) & 0xFF R = ((cipher + R) * 52845 + 22719) & 0xFFFF return bytechr(plain), R
def packPStrings(strings): data = b"" for s in strings: data = data + bytechr(len(s)) + tobytes(s, encoding="latin1") return data
def longToString(long): s = b"" for i in range(4): s += bytechr((long & (0xff << (i * 8))) >> i * 8) return s