class AnagramFile(): def __init__(self, filename = None): self.filename = filename self.type = None self.solution_index = -1 self.solution = None self.extra_index = -1 self.extra = None self.__unknown = None self.easy = None self.normal = None self.hard = None self.easy_orig = None self.normal_orig = None self.hard_orig = None if not filename == None: self.load(filename) def load(self, filename): if not os.path.isfile(filename): self.filename = None return dat_file = ConstBitStream(filename = self.filename) type = dat_file.read(16) if not type == DEMO_FLAG and not type == FULL_FLAG: raise Exception("Invalid Anagram", "Invalid anagram type.") if type == DEMO_FLAG: type = ANAGRAM_TYPE.Demo else: type = ANAGRAM_TYPE.Full num_letters = dat_file.read('uintle:16') magic = dat_file.read(16) if not magic == ANAGRAM_MAGIC: raise Exception("Invalid Anagram", "Invalid anagram magic.") solution_index = dat_file.read('uintle:16') extra_index = dat_file.read('uintle:16') unknown = dat_file.read(UNKNOWN_LENGTH[type] * 8) easy = dat_file.read(num_letters * 2 * 8) normal = dat_file.read(num_letters * 2 * 8) hard = None if type == ANAGRAM_TYPE.Full: hard = dat_file.read(num_letters * 2 * 8) easy_orig = None normal_orig = None hard_orig = None if dat_file.pos >= dat_file.len: # If we don't have more data, then consider this untranslated. # Therefore, the data we collected becomes the data for the original anagram. easy_orig = easy normal_orig = normal hard_orig = hard num_letters = 0 easy = None normal = None hard = None else: # If we DO have more data, consider this translated and grab the # info about the untranslated anagram. This data is not used by the game, # only the translators. letters_orig = dat_file.read('uintle:16') easy_orig = dat_file.read(letters_orig * 2 * 8) normal_orig = dat_file.read(letters_orig * 2 * 8) if type == ANAGRAM_TYPE.Full: hard_orig = dat_file.read(letters_orig * 2 * 8) ########################################################## ### Now convert all this into a useful format. ########################################################## base_dir = os.path.dirname(self.filename) self.type = type self.solution_index = solution_index self.solution = ScriptFile(os.path.join(base_dir, ANAGRAM_DIR, "%04d.txt" % self.solution_index)) self.extra_index = extra_index self.extra = ScriptFile(os.path.join(base_dir, ANAGRAM_DIR, "%04d.txt" % self.extra_index)) self.__unknown = unknown self.easy = self.parse_shown(easy) self.normal = self.parse_shown(normal) self.hard = self.parse_shown(hard) self.easy_orig = self.parse_shown(easy_orig) self.normal_orig = self.parse_shown(normal_orig) self.hard_orig = self.parse_shown(hard_orig) ############################################################################## ### @fn pack() ### @desc Converts all the data into the anagram file format. ### @param for_game -- Whether to include the original, untranslated data. ### True = exclude untranslated, since we don't need it. ############################################################################## def pack(self, for_game = False): is_translated = False if not self.solution[common.editor_config.lang_trans] == "": is_translated = True # SAVE! output = BitStream() # Type flag if self.type == ANAGRAM_TYPE.Demo: output += DEMO_FLAG else: output += FULL_FLAG # Number of letters if is_translated: output += ConstBitStream(uintle = len(self.solution[common.editor_config.lang_trans]), length = 16) else: output += ConstBitStream(uintle = len(self.solution[common.editor_config.lang_orig]), length = 16) # Magic output += ANAGRAM_MAGIC # File indexes output += ConstBitStream(uintle = self.solution_index, length = 16) output += ConstBitStream(uintle = self.extra_index, length = 16) # Unknown output += self.__unknown # Shown/unshown if is_translated: num_letters = len(self.solution[common.editor_config.lang_trans]) output += self.pack_shown(self.easy, num_letters) output += self.pack_shown(self.normal, num_letters) if self.type == ANAGRAM_TYPE.Full: output += self.pack_shown(self.hard, num_letters) if not for_game: num_letters = len(self.solution[common.editor_config.lang_orig]) output += ConstBitStream(uintle = num_letters, length = 16) if not is_translated or not for_game: # This shows up either way. num_letters = len(self.solution[common.editor_config.lang_orig]) output += self.pack_shown(self.easy_orig, num_letters) output += self.pack_shown(self.normal_orig, num_letters) if self.type == ANAGRAM_TYPE.Full: output += self.pack_shown(self.hard_orig, num_letters) return output def save(self, filename = None): if filename == None: if self.filename == None: return else: filename = self.filename output = self.pack(for_game = False) f = open(filename, "wb") output.tofile(f) f.close() dirname = os.path.dirname(filename) dirname = os.path.join(dirname, ANAGRAM_DIR) if not os.path.isdir(dirname): os.makedirs(dirname) self.solution.save(os.path.join(dirname, "%04d.txt" % self.solution_index)) self.extra.save (os.path.join(dirname, "%04d.txt" % self.extra_index)) def backup(self): backup_loc = time.strftime("%Y.%m.%d_%H.%M.%S_ANAGRAM") dirname = common.editor_config.backup_dir dirname = os.path.join(dirname, backup_loc) # Make sure we have a place to put it. if not os.path.isdir(dirname): os.makedirs(dirname) basename = os.path.basename(self.filename) target = os.path.join(dirname, basename) shutil.copy(self.filename, target) # Now for the text files themselves. source_dir = os.path.dirname(self.filename) source_dir = os.path.join(source_dir, ANAGRAM_DIR) solution_source = os.path.join(source_dir, "%04d.txt" % self.solution_index) extra_source = os.path.join(source_dir, "%04d.txt" % self.extra_index) target_dir = os.path.join(dirname, ANAGRAM_DIR) # Make sure we have a place to put it. if not os.path.isdir(target_dir): os.makedirs(target_dir) solution_target = os.path.join(target_dir, "%04d.txt" % self.solution_index) extra_target = os.path.join(target_dir, "%04d.txt" % self.extra_index) shutil.copy(solution_source, solution_target) shutil.copy(extra_source, extra_target) def parse_shown(self, data): if data == None: return None # Two bytes per letter. num_letters = (data.len / 16) shown = [] for i in range(1, num_letters + 1): letter = data.read(16) if letter == LETTER_VISIBLE: shown.append(i) return shown def pack_shown(self, shown, num_letters): data = BitStream() if shown == None: shown = [] for i in range(1, num_letters + 1): if i in shown: data += LETTER_VISIBLE else: data += LETTER_HIDDEN return data
def load(self, filename): if not os.path.isfile(filename): self.filename = None return dat_file = ConstBitStream(filename = self.filename) type = dat_file.read(16) if not type == DEMO_FLAG and not type == FULL_FLAG: raise Exception("Invalid Anagram", "Invalid anagram type.") if type == DEMO_FLAG: type = ANAGRAM_TYPE.Demo else: type = ANAGRAM_TYPE.Full num_letters = dat_file.read('uintle:16') magic = dat_file.read(16) if not magic == ANAGRAM_MAGIC: raise Exception("Invalid Anagram", "Invalid anagram magic.") solution_index = dat_file.read('uintle:16') extra_index = dat_file.read('uintle:16') unknown = dat_file.read(UNKNOWN_LENGTH[type] * 8) easy = dat_file.read(num_letters * 2 * 8) normal = dat_file.read(num_letters * 2 * 8) hard = None if type == ANAGRAM_TYPE.Full: hard = dat_file.read(num_letters * 2 * 8) easy_orig = None normal_orig = None hard_orig = None if dat_file.pos >= dat_file.len: # If we don't have more data, then consider this untranslated. # Therefore, the data we collected becomes the data for the original anagram. easy_orig = easy normal_orig = normal hard_orig = hard num_letters = 0 easy = None normal = None hard = None else: # If we DO have more data, consider this translated and grab the # info about the untranslated anagram. This data is not used by the game, # only the translators. letters_orig = dat_file.read('uintle:16') easy_orig = dat_file.read(letters_orig * 2 * 8) normal_orig = dat_file.read(letters_orig * 2 * 8) if type == ANAGRAM_TYPE.Full: hard_orig = dat_file.read(letters_orig * 2 * 8) ########################################################## ### Now convert all this into a useful format. ########################################################## base_dir = os.path.dirname(self.filename) self.type = type self.solution_index = solution_index self.solution = ScriptFile(os.path.join(base_dir, ANAGRAM_DIR, "%04d.txt" % self.solution_index)) self.extra_index = extra_index self.extra = ScriptFile(os.path.join(base_dir, ANAGRAM_DIR, "%04d.txt" % self.extra_index)) self.__unknown = unknown self.easy = self.parse_shown(easy) self.normal = self.parse_shown(normal) self.hard = self.parse_shown(hard) self.easy_orig = self.parse_shown(easy_orig) self.normal_orig = self.parse_shown(normal_orig) self.hard_orig = self.parse_shown(hard_orig)
def changedSelection(self, current, prev): if current == None or current.childCount() != 0: return file = common.qt_to_unicode(current.text(0)) path = tree.tree_item_to_path(current.parent()) self.setWindowTitle("%s - %s" % (self.menu_name, os.path.join(path, file))) path = dir_tools.expand_dir(path) file = os.path.join(path, file) file1 = os.path.join(self.folder1, file) file2 = os.path.join(self.folder2, file) if not os.path.isfile(file1): script1 = ScriptFile() else: script1 = ScriptFile(file1) if not os.path.isfile(file2): script2 = ScriptFile() else: script2 = ScriptFile(file2) # So we can loop this shit. to_diff = [ # Text 1 Text 2 Text Box 1 Text Box 2 (script1[common.editor_config.lang_trans], script2[common.editor_config.lang_trans], self.ui.txtTranslated1, self.ui.txtTranslated2), (script1[common.editor_config.lang_orig], script2[common.editor_config.lang_orig], self.ui.txtOriginal1, self.ui.txtOriginal2), (script1.comments, script2.comments, self.ui.txtComments1, self.ui.txtComments2), ] # Save us a little bit of time recalculating. if file in self.saved_diffs: diffs = self.saved_diffs[file] else: diffs = [None] * len(to_diff) for i, (text1, text2, box1, box2) in enumerate(to_diff): if diffs[i] == None: diffs[i] = DIFFER.diff_main(text1, text2) DIFFER.diff_cleanupSemantic(diffs[i]) box1.setPlainText(text1) box2.setPlainText(text2) highlight1, highlight2 = parse_diffs(diffs[i]) cursor1 = box1.textCursor() cursor2 = box2.textCursor() cursor1.select(QTextCursor.Document) cursor2.select(QTextCursor.Document) cursor1.setCharFormat(self.format_plain) cursor2.setCharFormat(self.format_plain) cursor1.movePosition(QTextCursor.Start) cursor2.movePosition(QTextCursor.Start) for pos, length in highlight1: cursor1.setPosition(pos, QTextCursor.MoveAnchor) cursor1.setPosition(pos + length, QTextCursor.KeepAnchor) cursor1.setCharFormat(self.format1) cursor1.movePosition(QTextCursor.Start) for pos, length in highlight2: cursor2.setPosition(pos, QTextCursor.MoveAnchor) cursor2.setPosition(pos + length, QTextCursor.KeepAnchor) cursor2.setCharFormat(self.format2) cursor2.movePosition(QTextCursor.Start) box1.setTextCursor(cursor1) box2.setTextCursor(cursor2) # for i, (text1, text2, box1, box2) in enumerate(to_diff): self.saved_diffs[file] = diffs
def load_dir(self, directory, umdimage="umdimage"): self.script_files = [] self.directory = directory self.wrd = None self.wrd_file = None self.py_file = None base_name = directory directory, wrd_file = dir_tools.parse_dir(directory, umdimage) if directory == None: self.directory = "" raise Exception("Directory \"" + base_name + "\" not found.") full_dir = os.path.join(umdimage, directory) scene_info = [] if not wrd_file == None and os.path.isfile(wrd_file): # Even of the first directory existed and we have a .wrd file, # it's possible there aren't actually any text files here. if not os.path.isdir(full_dir): raise Exception("There are no text files in \"" + directory + "\".") self.wrd = WrdFile() py_file = os.path.splitext(wrd_file)[0] + ".py" if os.path.isfile(py_file): try: self.wrd.load_python(py_file) except: _LOGGER.warning( "%s failed to load. Parsing wrd file instead. Exception info:\n%s" % (py_file, traceback.format_exc())) self.wrd.load_bin(wrd_file) else: # If we succeeded in loading the python file, compile it to binary. # _LOGGER.info("%s loaded successfully. Compiling to binary." % py_file) # self.wrd.save_bin(wrd_file) _LOGGER.info("%s loaded successfully." % py_file) else: _LOGGER.info("Decompiled wrd file not found. Generating %s" % py_file) self.wrd.load_bin(wrd_file) self.wrd.save_python(py_file) scene_info = self.wrd.to_scene_info() self.wrd_file = wrd_file self.py_file = py_file else: scene_info = None self.wrd = None self.wrd_file = None self.script_files = [] if scene_info == None: text_files = list_all_files(full_dir) for file in text_files: self.script_files.append(ScriptFile(file)) else: # Get our files in the order listed by the wrd. for info in scene_info: filename = os.path.join(full_dir, "%04d.txt" % info.file_id) script_file = ScriptFile(filename, info) if script_file.filename == None: _LOGGER.warning( "File %s referenced by %s does not exist." % (filename, wrd_file)) continue self.script_files.append(script_file) chapter, scene, room, mode = common.get_dir_info(base_name) for file in self.script_files: if file.scene_info.chapter == -1: file.scene_info.chapter = chapter if file.scene_info.scene == -1: file.scene_info.scene = scene if file.scene_info.room == -1: file.scene_info.room = room if file.scene_info.mode == None: file.scene_info.mode = mode
def load_dir(self, directory, base_dir="data01"): if not directory: return # directory, wrd_file = dir_tools.parse_dir(directory, base_dir) # Only expands if necessary. full_dir = dir_tools.expand_script_pak(directory) directory1 = normalize(directory) match = RE_FONT_PAK.match(directory1) if match: full_dir = os.path.join(base_dir, SCRIPT_BIN_DIR, full_dir) # if not os.path.isdir(full_dir): # full_dir = os.path.join(base_dir, SCRIPT_BIN_DIR, directory) else: full_dir = os.path.join(base_dir, SCRIPT_DIR, full_dir) if not os.path.isdir(full_dir): raise Exception("Directory \"" + full_dir + "\" not found.") self.script_files = [] self.directory = directory self.wrd = None self.wrd_file = None self.py_file = None scene_info = [] wrd_file = os.path.join(full_dir, os.path.splitext(directory)[0] + ".scp.wrd") if os.path.isfile(wrd_file): self.wrd = WrdFile() py_file = os.path.splitext(wrd_file)[0] + ".py" if os.path.isfile(py_file): try: self.wrd.load_python(py_file) except: _LOGGER.warning( "%s failed to load. Parsing wrd file instead. Exception info:\n%s" % (py_file, traceback.format_exc())) self.wrd.load_bin(wrd_file) else: # If we succeeded in loading the python file, compile it to binary. # _LOGGER.info("%s loaded successfully. Compiling to binary." % py_file) # self.wrd.save_bin(wrd_file) _LOGGER.info("%s loaded successfully." % py_file) else: _LOGGER.info("Decompiled wrd file not found. Generating %s" % py_file) self.wrd.load_bin(wrd_file) self.wrd.save_python(py_file) scene_info = self.wrd.to_scene_info() self.wrd_file = wrd_file self.py_file = py_file else: scene_info = None self.wrd = None self.wrd_file = None self.py_file = None self.script_files = [] if scene_info == None: text_files = [ filename for filename in os.listdir(full_dir) if os.path.splitext(filename)[1].lower() == ".txt" ] for filename in text_files: self.script_files.append( ScriptFile(os.path.join(full_dir, filename))) else: # Get our files in the order listed by the wrd. for info in scene_info: if info.file_id == None: script_file = ScriptJump(info) else: filename = os.path.join(full_dir, "%04d.txt" % info.file_id) script_file = ScriptFile(filename, info) if script_file.filename == None: _LOGGER.warning( "File %s referenced by %s does not exist." % (filename, wrd_file)) continue self.script_files.append(script_file) chapter, scene, room, mode = common.get_dir_info(directory) for file in self.script_files: if file.scene_info.chapter == -1: file.scene_info.chapter = chapter if file.scene_info.scene == -1: file.scene_info.scene = scene if file.scene_info.room == -1: file.scene_info.room = room if file.scene_info.mode == None: file.scene_info.mode = mode
def load(self, filename): if not os.path.isfile(filename): self.filename = None return dat_file = ConstBitStream(filename=self.filename) type = dat_file.read(16) if not type == DEMO_FLAG and not type == FULL_FLAG: raise Exception("Invalid Anagram", "Invalid anagram type.") if type == DEMO_FLAG: type = ANAGRAM_TYPE.Demo else: type = ANAGRAM_TYPE.Full num_letters = dat_file.read('uintle:16') magic = dat_file.read(16) if not magic == ANAGRAM_MAGIC: raise Exception("Invalid Anagram", "Invalid anagram magic.") solution_index = dat_file.read('uintle:16') extra_index = dat_file.read('uintle:16') unknown = dat_file.read(UNKNOWN_LENGTH[type] * 8) easy = dat_file.read(num_letters * 2 * 8) normal = dat_file.read(num_letters * 2 * 8) hard = None if type == ANAGRAM_TYPE.Full: hard = dat_file.read(num_letters * 2 * 8) easy_orig = None normal_orig = None hard_orig = None if dat_file.pos >= dat_file.len: # If we don't have more data, then consider this untranslated. # Therefore, the data we collected becomes the data for the original anagram. easy_orig = easy normal_orig = normal hard_orig = hard num_letters = 0 easy = None normal = None hard = None else: # If we DO have more data, consider this translated and grab the # info about the untranslated anagram. This data is not used by the game, # only the translators. letters_orig = dat_file.read('uintle:16') easy_orig = dat_file.read(letters_orig * 2 * 8) normal_orig = dat_file.read(letters_orig * 2 * 8) if type == ANAGRAM_TYPE.Full: hard_orig = dat_file.read(letters_orig * 2 * 8) ########################################################## ### Now convert all this into a useful format. ########################################################## base_dir = os.path.dirname(self.filename) self.type = type self.solution_index = solution_index self.solution = ScriptFile( os.path.join(base_dir, ANAGRAM_DIR, "%04d.txt" % self.solution_index)) self.extra_index = extra_index self.extra = ScriptFile( os.path.join(base_dir, ANAGRAM_DIR, "%04d.txt" % self.extra_index)) self.__unknown = unknown self.easy = self.parse_shown(easy) self.normal = self.parse_shown(normal) self.hard = self.parse_shown(hard) self.easy_orig = self.parse_shown(easy_orig) self.normal_orig = self.parse_shown(normal_orig) self.hard_orig = self.parse_shown(hard_orig)
class AnagramFile(): def __init__(self, filename=None): self.filename = filename self.type = None self.solution_index = -1 self.solution = None self.extra_index = -1 self.extra = None self.__unknown = None self.easy = None self.normal = None self.hard = None self.easy_orig = None self.normal_orig = None self.hard_orig = None if not filename == None: self.load(filename) def load(self, filename): if not os.path.isfile(filename): self.filename = None return dat_file = ConstBitStream(filename=self.filename) type = dat_file.read(16) if not type == DEMO_FLAG and not type == FULL_FLAG: raise Exception("Invalid Anagram", "Invalid anagram type.") if type == DEMO_FLAG: type = ANAGRAM_TYPE.Demo else: type = ANAGRAM_TYPE.Full num_letters = dat_file.read('uintle:16') magic = dat_file.read(16) if not magic == ANAGRAM_MAGIC: raise Exception("Invalid Anagram", "Invalid anagram magic.") solution_index = dat_file.read('uintle:16') extra_index = dat_file.read('uintle:16') unknown = dat_file.read(UNKNOWN_LENGTH[type] * 8) easy = dat_file.read(num_letters * 2 * 8) normal = dat_file.read(num_letters * 2 * 8) hard = None if type == ANAGRAM_TYPE.Full: hard = dat_file.read(num_letters * 2 * 8) easy_orig = None normal_orig = None hard_orig = None if dat_file.pos >= dat_file.len: # If we don't have more data, then consider this untranslated. # Therefore, the data we collected becomes the data for the original anagram. easy_orig = easy normal_orig = normal hard_orig = hard num_letters = 0 easy = None normal = None hard = None else: # If we DO have more data, consider this translated and grab the # info about the untranslated anagram. This data is not used by the game, # only the translators. letters_orig = dat_file.read('uintle:16') easy_orig = dat_file.read(letters_orig * 2 * 8) normal_orig = dat_file.read(letters_orig * 2 * 8) if type == ANAGRAM_TYPE.Full: hard_orig = dat_file.read(letters_orig * 2 * 8) ########################################################## ### Now convert all this into a useful format. ########################################################## base_dir = os.path.dirname(self.filename) self.type = type self.solution_index = solution_index self.solution = ScriptFile( os.path.join(base_dir, ANAGRAM_DIR, "%04d.txt" % self.solution_index)) self.extra_index = extra_index self.extra = ScriptFile( os.path.join(base_dir, ANAGRAM_DIR, "%04d.txt" % self.extra_index)) self.__unknown = unknown self.easy = self.parse_shown(easy) self.normal = self.parse_shown(normal) self.hard = self.parse_shown(hard) self.easy_orig = self.parse_shown(easy_orig) self.normal_orig = self.parse_shown(normal_orig) self.hard_orig = self.parse_shown(hard_orig) ############################################################################## ### @fn pack() ### @desc Converts all the data into the anagram file format. ### @param for_game -- Whether to include the original, untranslated data. ### True = exclude untranslated, since we don't need it. ############################################################################## def pack(self, for_game=False): is_translated = False if not self.solution[common.editor_config.lang_trans] == "": is_translated = True # SAVE! output = BitStream() # Type flag if self.type == ANAGRAM_TYPE.Demo: output += DEMO_FLAG else: output += FULL_FLAG # Number of letters if is_translated: output += ConstBitStream(uintle=len( self.solution[common.editor_config.lang_trans]), length=16) else: output += ConstBitStream(uintle=len( self.solution[common.editor_config.lang_orig]), length=16) # Magic output += ANAGRAM_MAGIC # File indexes output += ConstBitStream(uintle=self.solution_index, length=16) output += ConstBitStream(uintle=self.extra_index, length=16) # Unknown output += self.__unknown # Shown/unshown if is_translated: num_letters = len(self.solution[common.editor_config.lang_trans]) output += self.pack_shown(self.easy, num_letters) output += self.pack_shown(self.normal, num_letters) if self.type == ANAGRAM_TYPE.Full: output += self.pack_shown(self.hard, num_letters) if not for_game: num_letters = len( self.solution[common.editor_config.lang_orig]) output += ConstBitStream(uintle=num_letters, length=16) if not is_translated or not for_game: # This shows up either way. num_letters = len(self.solution[common.editor_config.lang_orig]) output += self.pack_shown(self.easy_orig, num_letters) output += self.pack_shown(self.normal_orig, num_letters) if self.type == ANAGRAM_TYPE.Full: output += self.pack_shown(self.hard_orig, num_letters) return output def save(self, filename=None): if filename == None: if self.filename == None: return else: filename = self.filename output = self.pack(for_game=False) f = open(filename, "wb") output.tofile(f) f.close() dirname = os.path.dirname(filename) dirname = os.path.join(dirname, ANAGRAM_DIR) if not os.path.isdir(dirname): os.makedirs(dirname) self.solution.save( os.path.join(dirname, "%04d.txt" % self.solution_index)) self.extra.save(os.path.join(dirname, "%04d.txt" % self.extra_index)) def backup(self): backup_loc = time.strftime("%Y.%m.%d_%H.%M.%S_ANAGRAM") dirname = common.editor_config.backup_dir dirname = os.path.join(dirname, backup_loc) # Make sure we have a place to put it. if not os.path.isdir(dirname): os.makedirs(dirname) basename = os.path.basename(self.filename) target = os.path.join(dirname, basename) shutil.copy(self.filename, target) # Now for the text files themselves. source_dir = os.path.dirname(self.filename) source_dir = os.path.join(source_dir, ANAGRAM_DIR) solution_source = os.path.join(source_dir, "%04d.txt" % self.solution_index) extra_source = os.path.join(source_dir, "%04d.txt" % self.extra_index) target_dir = os.path.join(dirname, ANAGRAM_DIR) # Make sure we have a place to put it. if not os.path.isdir(target_dir): os.makedirs(target_dir) solution_target = os.path.join(target_dir, "%04d.txt" % self.solution_index) extra_target = os.path.join(target_dir, "%04d.txt" % self.extra_index) shutil.copy(solution_source, solution_target) shutil.copy(extra_source, extra_target) def parse_shown(self, data): if data == None: return None # Two bytes per letter. num_letters = (data.len / 16) shown = [] for i in range(1, num_letters + 1): letter = data.read(16) if letter == LETTER_VISIBLE: shown.append(i) return shown def pack_shown(self, shown, num_letters): data = BitStream() if shown == None: shown = [] for i in range(1, num_letters + 1): if i in shown: data += LETTER_VISIBLE else: data += LETTER_HIDDEN return data