import argparse import copy import os import datetime import csv from bs4 import BeautifulSoup import rich.console DEFAULT_SAVEGAMEPATH = "c:\\Program Files (x86)\\GOG Galaxy\\Games\\SpaceHaven\\savegames\\" # Only "normal storage" is used to insert requested items normal_storage_ids = [82, 632] weapon_ids = [726, 725, 3383, 760, 3069, 3070, 3071, 3072, 3384] storage_ids = { 82: {"name": "Small storage", "capacity": 50}, 632: {"name": "Large storage", "capacity": 250}, 3062: {"name": "Body storage", "capacity": 20}, 3068: {"name": "Robot storage", "capacity": 20}, } console = rich.console.Console(highlight=False) # A single item with its code, name, and quantity class Item: item_ids = None @classmethod def load_ids(cls, item_filename): if cls.item_ids is not None: return else: cls.item_ids = {} with open(item_filename, 'r') as item_file: item_reader = csv.reader(item_file, delimiter='\t') for row in item_reader: cls.item_ids[int(row[0])] = row[1].strip() @classmethod def get_name_from_code(cls, code): return cls.item_ids.get(code, None) if cls.item_ids is not None else None @classmethod def validate_code(cls, code): return code in cls.id_dict if cls.item_ids is not None else False def __init__(self, code, name, quantity): self.code = code self.name = name self.quantity = quantity class ResearchRequirement: """ Requires that item database be loaded first. """ requirements = None @classmethod def load_requirements(cls, requirement_filename): if cls.requirements is not None: return cls.requirements = {} with open(requirement_filename, 'r') as requirement_file: item_reader = csv.reader(requirement_file, delimiter='\t') for row in item_reader: if len(row) == 0 or row[0][0] == "#": continue id = int(row[0]) name = row[1] workbench_items = None if row[2] != '-': workbench_items = {} wb_items = row[2].split(',') for wbi in wb_items: quantity = wbi[0] code = int(wbi[2:]) workbench_items[quantity] = Item.get_name_from_code(code) first_level_research = row[3] if row[3] != '-' else None second_level_research = row[4] if row[4] != '-' else None req = ResearchRequirement(id, name, workbench_items, first_level_research, second_level_research) cls.requirements[id] = req @classmethod def get_name_from_code(cls, code): return cls.requirements[code].name @classmethod def test_listing(cls): for k, v in cls.requirements.items(): console.print("{} : {} : {} : {}".format(k, v.name, v.first_level_research, v.second_level_research)) if v.workbench_items is not None: for x, y in v.workbench_items.items(): console.print("{} : {}".format(x, y)) def __init__(self, id, name, workbench_items, first_level_research, second_level_research): self.id = id self.name = name self.workbench_items = workbench_items self.first_level_research = first_level_research self.second_level_research = second_level_research class ResearchItem: def __init__(self, id, num_stages, active_stage, paused, states, tag): self.id = id self.name = ResearchRequirement.get_name_from_code(id) # Paused is self-explanatory I assume but appears rarely used self.paused = paused self.num_stages = num_stages # activeStageIndex results (i.e. 1, 2) should correspond to the number of states for the research item (always 1 or 2) - a value of 0 means no active stage self.active_stage = active_stage # Measures of the status of each sub-state for the research item self.stage_states = states # A reference to the actual tag for convenience self.tag = tag # Do not forgot the which appears to be related to the state of research workbench pre-research requirements def __str__(self): return "{} : {} : {} : {}".format(self.id, self.name, self.num_stages, self.active_stage) class ResearchItemState: def __init__(self, done, stage, blocksdone): self.done = done self.stage = int(stage) self.blocksdone = blocksdone def __str__(self): return " {} : {} : {}".format(self.done, self.stage, self.blocksdone) # A single storage area, with a list of contents (Items) class StorageArea: def __init__(self, tag): self.tag = tag self.type_id = None self.items = [] self.is_normal_storage = False def add_item(self, item): self.items.append(item) def get_capacity(self): return storage_ids[self.type_id]['capacity'] # Returns how full the storage area is based on its contents def get_total_occupancy(self): total_quantity = 0 for item in self.items: total_quantity += item.quantity return total_quantity class Character: skill_ids = None attribute_ids = None trait_ids = None condition_ids = None @classmethod def load_ids(cls, skill_filename, attribute_filename, trait_filename, condition_filename): if cls.skill_ids is None: cls.skill_ids = {} with open(skill_filename, 'r') as skill_file: skill_reader = csv.reader(skill_file, delimiter='\t') for row in skill_reader: cls.skill_ids[int(row[0])] = row[1].strip() if cls.attribute_ids is None: cls.attribute_ids = {} with open(attribute_filename, 'r') as attribute_file: attribute_reader = csv.reader(attribute_file, delimiter='\t') for row in attribute_reader: cls.attribute_ids[int(row[0])] = row[1].strip() if cls.trait_ids is None: cls.trait_ids = {} with open(trait_filename, 'r') as trait_file: trait_reader = csv.reader(trait_file, delimiter='\t') for row in trait_reader: cls.trait_ids[int(row[0])] = row[1].strip() if cls.condition_ids is None: cls.condition_ids = {} with open(condition_filename, 'r') as condition_file: condition_reader = csv.reader(condition_file, delimiter='\t') for row in condition_reader: cls.condition_ids[int(row[0])] = row[1].strip() @classmethod def get_skill_from_code(cls, code): return cls.skill_ids.get(code, None) if cls.skill_ids is not None else None @classmethod def get_attribute_from_code(cls, code): return cls.attribute_ids.get(code, None) if cls.attribute_ids is not None else None @classmethod def get_trait_from_code(cls, code): return cls.trait_ids.get(code, None) if cls.trait_ids is not None else None @classmethod def get_condition_from_code(cls, code): return cls.condition_ids.get(code, None) if cls.condition_ids is not None else None def __init__(self, name, tag): self.name = name self.tag = tag self.is_clone = False self.skills = [] self.attributes = [] self.conditions = [] def set_skills(self, skill_list): # Expected format is a list of dictionaries # Each dictionary has keys id, mxn, exp, expd, level self.skills = skill_list def set_attributes(self, attribute_list): # Expected format is a list of dictionaries # Each dictionary has keys id, points self.attributes = attribute_list def maximize_skills(self): # Max LEVEL is 5 # Max MXN should be 8 for some reason # Exp* keys are probably for a future experience system but I don't know how they work so I won't touch them for skill in self.skills: skill['level'] = 7 skill['mxn'] = 8 def maximize_attributes(self): # Max POINTS is 6 for attribute in self.attributes: attribute['points'] = 6 def clear_conditions(self): self.conditions = [1550, 2246, 3311] def clone(self, new_name): # How to deep copy the skills/attributes appropriately? print("Cloning character {}".format(self.name)) new_char = Character(new_name, copy.copy(self.tag)) new_char.is_clone = True new_char.tag['name'] = new_name new_char.skills = [s.copy() for s in self.skills] new_char.attributes = [a.copy() for a in self.attributes] return new_char def print_summary(self): console.print("Name: [bright_cyan]{}[/]".format(self.name)) console.print() console.print("Skills:") for i, skill in enumerate(self.skills): row_string = "{:12} [bright_cyan]{:2}[/]".format(Character.get_skill_from_code(skill['id']), skill['level']) if i % 2 == 0: console.print(" [on bright_black]{}[/]".format(row_string)) else: console.print(" {}".format(row_string)) console.print() console.print("Attributes:") for i, attribute in enumerate(self.attributes): row_string = "{:12} [bright_cyan]{:2}[/]".format(Character.get_attribute_from_code(attribute['id']), attribute['points']) if i % 2 == 0: console.print(" [on bright_black]{}[/]".format(row_string)) else: console.print(" {}".format(row_string)) def __repr__(self): return "{} - Skills: {} - Attributes: {}".format(self.name, repr(self.skills), repr(self.attributes)) class Ship: def __init__(self, name, owner, state, tag): self.name = name self.owner = owner self.state = state self.tag = tag # Soup tag corresponding to this ship's node self.storage_areas = [] self.characters = [] def add_storage_area(self, storage_area): self.storage_areas.append(storage_area) def add_character(self, character): self.characters.append(character) def add_item(self, item): normal_storage_areas = [sa for sa in self.storage_areas if sa.is_normal_storage] min_storage_area = normal_storage_areas[0] min_occupancy = min_storage_area.get_total_occupancy() for storage_area in normal_storage_areas: if storage_area.get_total_occupancy() < min_occupancy: min_storage_area = storage_area min_occupancy = min_storage_area.get_total_occupancy() min_storage_area.add_item(item) def redistribute_storage(self): normal_storage_areas = [sa for sa in self.storage_areas if sa.is_normal_storage] total = 0 area_capacity = 0 for area in normal_storage_areas: total += area.get_total_occupancy() area_capacity += storage_ids[area.type_id]['capacity'] console.print("Total used: {}".format(total)) console.print("Total actual capacity: {}".format(area_capacity)) # Consolidate items console.print("Consolidating items for {}".format(self.name)) unique_codes = [] new_items = [] for area in normal_storage_areas: console.print("----- New storage area") for item in area.items: if item.code not in unique_codes: unique_codes.append(item.code) console.print("Considering item with code {}".format(item.code)) old_item_found = False for new_item in new_items: if new_item.code == item.code: new_item.quantity += item.quantity old_item_found = True # console.print("Adding new item with code {}".format(item.code)) if old_item_found is False: new_items.append(item) console.print("Total number of new items: {}".format(len(new_items))) console.print("Total number of unique codes: {}".format(len(unique_codes))) new_amount = 0 for item in new_items: new_amount += item.quantity console.print(new_amount) for area in normal_storage_areas: area.items = [] for item in new_items: console.print("{} : {} : {}".format(item.code, item.name, item.quantity)) min_occupancy = 2000 min_area = None for area in normal_storage_areas: if area.get_total_occupancy() < min_occupancy: min_area = area min_area.items.append(item) item_number = 0 for area in normal_storage_areas: for item in area.items: item_number += 1 console.print("{} : {} : {}".format(item.code, item.name, item.quantity)) console.print("Item number: {}".format(item_number)) def consolidate_weapons(self): normal_storage_areas = [sa for sa in self.storage_areas if sa.is_normal_storage] for area in normal_storage_areas: for item in area.items: if item.code in weapon_ids: console.print("{} : {}".format(item.code, item.quantity)) class Player: def __init__(self, currency, tag): self.currency = currency self.tag = tag class GameData: def __init__(self, soup, item_database): self.player = None self.ships = [] self.research = [] self.soup = soup self.item_database = item_database self.base_res_tag = None def populate(self): # Step 1 - Player data char_tag = self.soup.find('playerBank') currency = int(char_tag['ca']) self.player = Player(currency, char_tag) # Step 2 - Ship data ship_tags = self.soup.find_all('ship') for ship_tag in ship_tags: # Something strange is happening here # In some unexplored ships/derelicts (?fog="true" in the tag) the name appears in the game but NOWHERE in the file ship_name = "UNNAMED (?fogged)" if ship_tag.has_attr('sname'): ship_name = ship_tag['sname'] # console.print(ship_name) # Forgotten - why did I specify owner as required here - what ships don't have owners? owner_node = ship_tag.find('settings', owner=True) ship_owner = owner_node['owner'] ship_state = owner_node['state'] ship = Ship(ship_name, ship_owner, ship_state, ship_tag) self.ships.append(ship) # Step 3 - Storage area data for inv_tag in ship_tag.find_all('feat', eatAllowed=True): storage_area = StorageArea(inv_tag.find('inv')) # 632 is the "m" id code for LARGE storage storage_area.type_id = int(inv_tag.parent.parent['m']) storage_area.is_normal_storage = storage_area.type_id in normal_storage_ids ship.add_storage_area(storage_area) # Items within storage area for s_tag in inv_tag.find_all('s'): item_code = int(s_tag['elementaryId']) item_name = Item.get_name_from_code(item_code) item_quantity = int(s_tag['inStorage']) item = Item(item_code, item_name, item_quantity) storage_area.add_item(item) # console.print("{:4}: {} - {}".format(item_code, item_name, item_quantity)) # Step 4 - Character data for character_list in ship_tag.find_all('characters'): character_tags = character_list.find_all('c', attrs={'name': True}) for character_tag in character_tags: char_name = character_tag['name'] character = Character(char_name, character_tag) ship.add_character(character) skills = [] skill_tag = character_tag.find('skills') for sk_tag in skill_tag.find_all('s'): skill_id = sk_tag['sk'] skill_level = sk_tag['level'] skill_mxn = sk_tag['mxn'] skill_exp = sk_tag['exp'] skill_expd = sk_tag['expd'] skill_dict = { 'id': int(skill_id), 'level': int(skill_level), 'mxn': int(skill_mxn), 'exp': int(skill_exp), 'expd': int(skill_expd), } skills.append(skill_dict) character.set_skills(skills) attributes = [] attribute_tag = character_tag.find('attr') for a_tag in attribute_tag.find_all('a'): attribute_id = a_tag['id'] attribute_points = a_tag['points'] attribute_dict = { 'id': int(attribute_id), 'points': int(attribute_points), } attributes.append(attribute_dict) character.set_attributes(attributes) # Step 5 - Research Data # console.print('Finding research...') res_tag = self.soup.find("research", treeId=True) self.base_res_tag = res_tag.find('states') item_ids = [] for item in res_tag.find_all('l', techId=True): active_stage = int(item['activeStageIndex']) paused = item['paused'] techId = int(item['techId']) item_ids.append(int(item['techId'])) stages = [] for stage in item.find_all('l', done=True, stage=True): done = stage['done'] stage_no = stage['stage'] # console.print(stage) blocksdone_tag = stage.find('blocksDone') blocksdone = None if blocksdone_tag is not None: blocksdone = [int(blocksdone_tag['level1']), int(blocksdone_tag['level2']), int(blocksdone_tag['level3'])] ris = ResearchItemState(done, stage_no, blocksdone) stages.append(ris) ri = ResearchItem(techId, len(stages), active_stage, paused, stages, item) self.research.append(ri) """ # Research data console.print('Research stage counts:') res_tag = self.soup.find('research', treeId=True) for category in res_tag.find_all('l', techId=True): console.print("{} : {}".format(category['techId'], len(category.find_all('l', done=True, stage=True)))) """ def writeback(self): def replace_id(dict, old_key, new_key): dict_copy = dict.copy() dict_copy[new_key] = dict_copy[old_key] del dict_copy[old_key] return dict_copy # Purpose of this mission is to take all our data and replace the relevant parts of the soup # Suspect this may be harder than it sounds - original saved game editor more or less deleted and rewrote some sections (e.g. item lists) # Shouldn't need to update player tag as this will be done directly # Only need to update things with structure / lists """ Step 1 - Update characters Step 2 - Update storage areas Step 3 - Update research """ # Step 1 - Update characters # Names etc will be done automatically - just need to reconstruct skills and attributes self.player.tag['ca'] = self.player.currency for ship in self.ships: # console.print('Doing chars...') for character in ship.characters: # Cloned character tags have to be added to the list if character.is_clone: print("ADDING CLONED CHARACTER") charlist_tag = ship.tag.find('characters') charlist_tag.append(character.tag) skill_tag = character.tag.find('skills') skill_tag.clear() for skill in character.skills: skill_copy = replace_id(skill, 'id', 'sk') new_tag = self.soup.new_tag('s', attrs=skill_copy) skill_tag.append(new_tag) attribute_tag = character.tag.find('attr') attribute_tag.clear() for attribute in character.attributes: new_tag = self.soup.new_tag('a', attrs=attribute) attribute_tag.append(new_tag) condition_tag = character.tag.find('conditions') condition_tag.clear() for condition in character.conditions: attrs = { "id": str(condition), "level" : "1", "rs" : "1"} new_tag = self.soup.new_tag('c', attrs=attrs) # What do the tags mean with ht and wt attrs? # What does the ac attr mean on the m mood sub tag mean? new_mood_tag = self.soup.new_tag('mood') new_submood_tag = self.soup.new_tag('m', ac="5") new_tag.append(new_mood_tag) new_mood_tag.append(new_submood_tag) condition_tag.append(new_tag) # Step 2 - Update storage areas for storage_area in ship.storage_areas: area_tag = storage_area.tag area_tag.clear() for item in storage_area.items: tag_dict = {'elementaryId': item.code, 'inStorage': item.quantity, 'onTheWayIn': 0, 'onTheWayOut': 0} new_tag = self.soup.new_tag('s', attrs=tag_dict) area_tag.append(new_tag) # Step 3 - Update research for research_item in self.research: pass def add_item(self, item_code, item_quantity): item_name = self.item_database.get_name_from_code(item_code) item = Item(item_code, item_name, item_quantity) for ship in self.ships: if ship.owner == "Player": ship.add_item(item) break def buff_characters(self): for ship in self.ships: if ship.owner == "Player": for character in ship.characters: character.maximize_skills() character.maximize_attributes() character.clear_conditions() def add_currency(self, amount): self.player.currency += amount def clone_character(self, character_name, new_name): console.print("Clone char method called") console.print("Warning names are case sensitive!") for ship in self.ships: for character in ship.characters: if character.name == character_name: print("Found char to clone") new_char = character.clone(new_name) ship.characters.append(new_char) return def print_detailed_character_summary(self): for ship in self.ships: console.print("Listing characters for ship [magenta]{}[/] (owned by [bright_cyan]{}[/], state [bright_cyan]{}[/]):".format(ship.name, ship.owner, ship.state)) if len(ship.characters) == 0: console.print(" This ship has no characters, skipping...") console.print() console.print() continue for character in ship.characters: console.print() character.print_summary() console.print() console.print('-----') console.print() def print_detailed_item_summary(self): for ship in self.ships: rich.print("Inventory for ship [#aa00ff]{}[/] (owned by [bright_cyan]{}[/], state [bright_cyan]{}[/]):".format(ship.name, ship.owner, ship.state)) if len(ship.storage_areas) == 0: console.print(" This ship has no storage areas, skipping...") console.print() console.print() continue for index, storage_area in enumerate(ship.storage_areas): console.print(" Storage area [bright_cyan]{}[/] (type: [bright_cyan]{}[/], normal storage: [bright_cyan]{}[/]):".format(index, storage_ids[storage_area.type_id]["name"], storage_area.is_normal_storage)) if len(storage_area.items) == 0: console.print(" This storage area is empty.") console.print() continue console.print("[bold] {:>4} {:30} {:>3}[/]".format("ID", "Name", "#")) total_quantity = 0 for i, item in enumerate(storage_area.items): row_string = " [bright_cyan]{:4}[/] {:30} [bright_cyan]{:3}[/]".format(item.code, item.name, item.quantity) total_quantity += item.quantity if i % 2 == 0: console.print(" [on bright_black]{}[/]".format(row_string)) else: console.print(" {}".format(row_string)) storage_area_capacity = storage_ids[storage_area.type_id]["capacity"] console.print(" Total quantity: [bright_cyan]{}[/]/[bright_cyan]{}[/]".format(total_quantity, storage_area_capacity)) if total_quantity > storage_area_capacity: console.print(" [bright_red]WARNING: Storage capacity exceeded[/]") console.print() console.print() def print_summary(self): console.print("Start game summary:") console.print(" Player currency: {}".format(self.player.currency)) console.print(" Number of ships: {}".format(len(self.ships))) for ship in self.ships: console.print(" {} (owner {}, state {})".format(ship.name, ship.owner, ship.state)) console.print(" Contains {} storage area(s):".format(len(ship.storage_areas))) for index, storage_area in enumerate(ship.storage_areas): console.print(" Storage area {} - contains {} item(s) - occupancy {} unit(s)".format(index, len(storage_area.items), storage_area.get_total_occupancy())) console.print(" Has {} character(s):".format(len(ship.characters))) for char in ship.characters: console.print(" {}".format(char.name)) # char.print_summary() def redistribute(self): for ship in self.ships: if ship.owner != 'Player' or ship.state != 'Normal': continue console.print(ship.name, ship.state) ship.redistribute_storage() def list_research(self): return for research in sorted(self.research, key=lambda item: item.id): console.print(research) for ris in research.stage_states: console.print(ris) def consolidate_weapons(self): for ship in self.ships: if ship.owner == "Player": ship.consolidate_weapons() def parse_item_file(filename): results = [] for line in open(filename): line = line.strip() if line[0] == '#': continue components = line.split() item_code = int(components[0]) item_quantity = int(components[1]) results.append((item_code, item_quantity)) return results def main(): parser = argparse.ArgumentParser(prog="Space Haven Saved Game Inspector", description="As above.") parser.add_argument('filename', metavar='SAVEGAME_GAME_FILE') parser.add_argument('--add-item', required=False, metavar=('ITEM_CODE', 'ITEM_QUANTITY'), type=int, nargs=2, help="Add more of an existing item to storage by CODE - refer to accompanying data file reference for codes. First number is the code, second is the desired quantity.") parser.add_argument('--buff-chars', required=False, action='store_true', help="For all characters, increases all skills and attributes to maximum. Use wisely.") parser.add_argument('--money', required=False, type=int, nargs=1, metavar='AMOUNT', help="Give the player credits of the specified amount") parser.add_argument('--list-ships', required=False, action='store_true', help="List all ships with names and their respective owners") parser.add_argument('--test-gamedata', required=False, action='store_true', help="Test of new class-based system of storing game information") parser.add_argument('--clone-character', required=False, type=str, nargs=2, metavar=('OLD_NAME', 'NEW_NAME'), help="Clones a character of one name into another") parser.add_argument('--add-item-set', required=False, type=str, nargs=1, metavar='PACK_FILENAME', help="Takes a file containing a list of item codes and quantities (whitespace separated) and adds all of these to player storage") parser.add_argument('--detailed-items', required=False, action='store_true', help='Print a detailed item listing from player inventory') parser.add_argument('--detailed-chars', required=False, action='store_true', help='Print a comprehensive listing of player character details') parser.add_argument('--redistribute', required=False, action='store_true', help='Redistribute internal storage') parser.add_argument('--list-research', required=False, action='store_true', help='List all research/research states') parser.add_argument('--consolidate-weapons', required=False, action='store_true', help='Put all weapons in one storage area') parser.add_argument('--replace-original', required=False, action='store_true', help='Replace original file instead of creating edited alternative. Renames original for backup, but USE WITH CAUTION') args = parser.parse_args() # console.print(args) console.print("--- Space Haven Saved Game Inspector ---") console.print() filename = os.path.join(DEFAULT_SAVEGAMEPATH, args.filename, "save", "game") # console.print(filename) full_text = "" for line in open(filename): full_text += line # console.print(full_text) soup = BeautifulSoup(full_text, "xml") Item.load_ids('item_ids.tsv') console.print("Item code database successfully loaded.") console.print() Character.load_ids('char_skills.tsv', 'char_attributes.tsv', 'char_traits.tsv', 'char_conditions.tsv') console.print("Character code database successfully loaded.") console.print() ResearchRequirement.load_requirements('research2.tsv') ResearchRequirement.test_listing() console.print("Research requirements successfully loaded.") console.print() game_data = GameData(soup, Item) game_data.populate() edits_made = False if args.buff_chars: console.print("Buffing chars...") game_data.buff_characters() edits_made = True print("Buffed chars") if args.add_item: console.print("Adding item...") game_data.add_item(args.add_item[0], args.add_item[1]) edits_made = True if args.money: console.print("Adding currency...") game_data.add_currency(args.money[0]) edits_made = True if args.list_ships: console.print("Listing ships...") game_data.print_summary() if args.clone_character: console.print("Char clone requested...") game_data.clone_character(args.clone_character[0], args.clone_character[1]) edits_made = True if args.add_item_set: console.print("Adding item set...") item_list = parse_item_file(args.add_item_set[0]) # console.print(item_list) console.print("Items to be added:") for (item_code, item_quantity) in item_list: console.print("{} : {}".format(Item.get_name_from_code(item_code), item_quantity)) game_data.add_item(item_code, item_quantity) edits_made = True if args.detailed_items: console.print("Printing detailed item list...") game_data.print_detailed_item_summary() if args.detailed_chars: console.print("Printing detailed character list...") game_data.print_detailed_character_summary() if args.redistribute: console.print("Item redistribution requested...") game_data.redistribute() if args.list_research: console.print("Listing research status...") # game_data.list_research() console.print(len(ResearchRequirement.requirements)) if args.consolidate_weapons: console.print("Consolidating weapons...") game_data.consolidate_weapons() game_data.writeback() if args.test_gamedata: game_data = GameData(soup) game_data.populate() # game_data.add_item(1759, 100) # game_data.buff_characters() # game_data.add_currency(10000) game_data.print_summary() game_data.print_detailed_item_summary() # game_data.clone_character("Byron", "Anthony") # game_data.print_detailed_character_summary() game_data.writeback() if edits_made: if args.replace_original: console.print('Renaming original file') filename = os.path.join(DEFAULT_SAVEGAMEPATH, args.filename, "save", "game") datetime_suffix = datetime.datetime.now().strftime('%Y-%m-%d-%H%M_%S_%f') new_filename = os.path.join(DEFAULT_SAVEGAMEPATH, args.filename, "save", "game" + '-' + datetime_suffix) # console.print(datetime_suffix) os.rename(filename, new_filename) console.print('Now rewriting game file with new information') text = soup.prettify() # Delete XML header - game doesn't have it sansfirstline = '\n'.join(text.split('\n')[1:]) f = open(filename, 'w') f.write(sansfirstline) f.close() console.print('Complete.') else: text = soup.prettify() # Delete XML header - game doesn't have it sansfirstline = '\n'.join(text.split('\n')[1:]) f = open('game.xml', 'w') f.write(sansfirstline) f.close() if __name__ == "__main__": main()