Upload files to "/"
This commit is contained in:
605
fallout_shelter_editor.py
Normal file
605
fallout_shelter_editor.py
Normal file
@@ -0,0 +1,605 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fallout Shelter Save Editor
|
||||
A comprehensive tool for editing Fallout Shelter save files.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import zlib
|
||||
import gzip
|
||||
import struct
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional, List
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
|
||||
|
||||
class FalloutShelterSave:
|
||||
"""Handles Fallout Shelter save file operations."""
|
||||
|
||||
# Fallout Shelter encryption constants (from JavaScript code)
|
||||
AES_KEY = bytes([
|
||||
(2815074099 >> 24) & 0xFF, (2815074099 >> 16) & 0xFF, (2815074099 >> 8) & 0xFF, 2815074099 & 0xFF,
|
||||
(1725469378 >> 24) & 0xFF, (1725469378 >> 16) & 0xFF, (1725469378 >> 8) & 0xFF, 1725469378 & 0xFF,
|
||||
(4039046167 >> 24) & 0xFF, (4039046167 >> 16) & 0xFF, (4039046167 >> 8) & 0xFF, 4039046167 & 0xFF,
|
||||
(874293617 >> 24) & 0xFF, (874293617 >> 16) & 0xFF, (874293617 >> 8) & 0xFF, 874293617 & 0xFF,
|
||||
(3063605751 >> 24) & 0xFF, (3063605751 >> 16) & 0xFF, (3063605751 >> 8) & 0xFF, 3063605751 & 0xFF,
|
||||
(3133984764 >> 24) & 0xFF, (3133984764 >> 16) & 0xFF, (3133984764 >> 8) & 0xFF, 3133984764 & 0xFF,
|
||||
(4097598161 >> 24) & 0xFF, (4097598161 >> 16) & 0xFF, (4097598161 >> 8) & 0xFF, 4097598161 & 0xFF,
|
||||
(3620741625 >> 24) & 0xFF, (3620741625 >> 16) & 0xFF, (3620741625 >> 8) & 0xFF, 3620741625 & 0xFF
|
||||
])
|
||||
AES_IV = bytes.fromhex("7475383967656A693334307438397532")
|
||||
|
||||
def __init__(self, filepath: str = None):
|
||||
self.filepath = filepath
|
||||
self.raw_data = None
|
||||
self.decoded_data = None
|
||||
self.save_data = None
|
||||
self.is_loaded = False
|
||||
|
||||
def load_save(self, filepath: str = None) -> bool:
|
||||
"""Load and decode a Fallout Shelter save file."""
|
||||
if filepath:
|
||||
self.filepath = filepath
|
||||
|
||||
if not self.filepath or not os.path.exists(self.filepath):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(self.filepath, 'rb') as f:
|
||||
self.raw_data = f.read()
|
||||
|
||||
# Decode base64
|
||||
self.decoded_data = base64.b64decode(self.raw_data)
|
||||
|
||||
# Try to decompress and parse
|
||||
self.save_data = self._parse_save_data()
|
||||
self.is_loaded = True
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error loading save: {e}")
|
||||
return False
|
||||
|
||||
def _parse_save_data(self) -> Dict[str, Any]:
|
||||
"""Parse the decoded save data using AES-CBC decryption."""
|
||||
try:
|
||||
# Decrypt using AES-CBC
|
||||
cipher = AES.new(self.AES_KEY, AES.MODE_CBC, self.AES_IV)
|
||||
decrypted_data = cipher.decrypt(self.decoded_data)
|
||||
|
||||
# Remove PKCS7 padding
|
||||
try:
|
||||
unpadded_data = unpad(decrypted_data, AES.block_size)
|
||||
except ValueError:
|
||||
# If unpadding fails, try without unpadding (some implementations don't pad)
|
||||
unpadded_data = decrypted_data.rstrip(b'\x00')
|
||||
|
||||
# Convert to string and parse JSON
|
||||
json_str = unpadded_data.decode('utf-8')
|
||||
return json.loads(json_str)
|
||||
|
||||
except Exception as e:
|
||||
print(f"AES decryption failed: {e}")
|
||||
# Fallback to old methods if AES fails
|
||||
return self._try_fallback_methods()
|
||||
|
||||
def _try_fallback_methods(self) -> Dict[str, Any]:
|
||||
"""Fallback methods if AES decryption fails."""
|
||||
decompression_methods = [
|
||||
self._try_raw_json,
|
||||
self._try_zlib_decompress,
|
||||
self._try_gzip_decompress,
|
||||
self._try_custom_decompress,
|
||||
]
|
||||
|
||||
for method in decompression_methods:
|
||||
try:
|
||||
result = method(self.decoded_data)
|
||||
if result:
|
||||
return result
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
# If all methods fail, return raw data info
|
||||
return {
|
||||
"error": "Could not parse save data",
|
||||
"raw_size": len(self.decoded_data),
|
||||
"raw_preview": self.decoded_data[:100].hex()
|
||||
}
|
||||
|
||||
def _try_raw_json(self, data: bytes) -> Optional[Dict]:
|
||||
"""Try to parse as raw JSON."""
|
||||
try:
|
||||
text = data.decode('utf-8')
|
||||
return json.loads(text)
|
||||
except:
|
||||
return None
|
||||
|
||||
def _try_zlib_decompress(self, data: bytes) -> Optional[Dict]:
|
||||
"""Try zlib decompression then JSON parse."""
|
||||
try:
|
||||
decompressed = zlib.decompress(data)
|
||||
text = decompressed.decode('utf-8')
|
||||
return json.loads(text)
|
||||
except:
|
||||
return None
|
||||
|
||||
def _try_gzip_decompress(self, data: bytes) -> Optional[Dict]:
|
||||
"""Try gzip decompression then JSON parse."""
|
||||
try:
|
||||
decompressed = gzip.decompress(data)
|
||||
text = decompressed.decode('utf-8')
|
||||
return json.loads(text)
|
||||
except:
|
||||
return None
|
||||
|
||||
def _try_custom_decompress(self, data: bytes) -> Optional[Dict]:
|
||||
"""Try custom Fallout Shelter decompression."""
|
||||
# Some Fallout Shelter saves use a custom format
|
||||
# This is a placeholder for reverse-engineered decompression
|
||||
try:
|
||||
# Check for common patterns in Fallout Shelter saves
|
||||
if len(data) > 4:
|
||||
# Try reading as little-endian uint32 header
|
||||
header = struct.unpack('<I', data[:4])[0]
|
||||
if header < len(data): # Reasonable size check
|
||||
# Try decompressing the rest
|
||||
compressed_data = data[4:]
|
||||
decompressed = zlib.decompress(compressed_data)
|
||||
text = decompressed.decode('utf-8')
|
||||
return json.loads(text)
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
def backup_save(self) -> str:
|
||||
"""Create a backup of the current save file."""
|
||||
if not self.filepath:
|
||||
return ""
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = f"{self.filepath}.backup_{timestamp}"
|
||||
shutil.copy2(self.filepath, backup_path)
|
||||
return backup_path
|
||||
|
||||
def save_file(self, filepath: str = None) -> bool:
|
||||
"""Save the modified data back to file."""
|
||||
if not self.save_data or not self.is_loaded:
|
||||
return False
|
||||
|
||||
save_path = filepath or self.filepath
|
||||
if not save_path:
|
||||
return False
|
||||
|
||||
try:
|
||||
# Convert back to JSON (compact format)
|
||||
json_data = json.dumps(self.save_data, separators=(',', ':'))
|
||||
json_bytes = json_data.encode('utf-8')
|
||||
|
||||
# Pad data for AES encryption
|
||||
padded_data = pad(json_bytes, AES.block_size)
|
||||
|
||||
# Encrypt using AES-CBC
|
||||
cipher = AES.new(self.AES_KEY, AES.MODE_CBC, self.AES_IV)
|
||||
encrypted_data = cipher.encrypt(padded_data)
|
||||
|
||||
# Encode to base64
|
||||
encoded = base64.b64encode(encrypted_data)
|
||||
|
||||
# Write to file
|
||||
with open(save_path, 'wb') as f:
|
||||
f.write(encoded)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error saving file: {e}")
|
||||
return False
|
||||
|
||||
|
||||
class SaveEditorGUI:
|
||||
"""GUI for the Fallout Shelter Save Editor."""
|
||||
|
||||
def __init__(self):
|
||||
self.root = tk.Tk()
|
||||
self.root.title("Fallout Shelter Save Editor")
|
||||
self.root.geometry("800x600")
|
||||
|
||||
self.save_handler = FalloutShelterSave()
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
"""Set up the user interface."""
|
||||
# Menu bar
|
||||
menubar = tk.Menu(self.root)
|
||||
self.root.config(menu=menubar)
|
||||
|
||||
file_menu = tk.Menu(menubar, tearoff=0)
|
||||
menubar.add_cascade(label="File", menu=file_menu)
|
||||
file_menu.add_command(label="Open Save", command=self.open_save)
|
||||
file_menu.add_command(label="Save", command=self.save_file)
|
||||
file_menu.add_command(label="Save As", command=self.save_as)
|
||||
file_menu.add_separator()
|
||||
file_menu.add_command(label="Create Backup", command=self.create_backup)
|
||||
file_menu.add_separator()
|
||||
file_menu.add_command(label="Exit", command=self.root.quit)
|
||||
|
||||
# Main frame
|
||||
main_frame = ttk.Frame(self.root, padding="10")
|
||||
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# File info frame
|
||||
info_frame = ttk.LabelFrame(main_frame, text="Save File Info", padding="5")
|
||||
info_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
|
||||
|
||||
self.file_label = ttk.Label(info_frame, text="No file loaded")
|
||||
self.file_label.grid(row=0, column=0, sticky=tk.W)
|
||||
|
||||
# Notebook for different edit categories
|
||||
self.notebook = ttk.Notebook(main_frame)
|
||||
self.notebook.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# Raw data tab
|
||||
self.setup_raw_tab()
|
||||
|
||||
# Vault stats tab (placeholder for when we can parse the data)
|
||||
self.setup_vault_tab()
|
||||
|
||||
# Resources tab
|
||||
self.setup_resources_tab()
|
||||
|
||||
# Dwellers tab
|
||||
self.setup_dwellers_tab()
|
||||
|
||||
# Configure grid weights
|
||||
self.root.columnconfigure(0, weight=1)
|
||||
self.root.rowconfigure(0, weight=1)
|
||||
main_frame.columnconfigure(0, weight=1)
|
||||
main_frame.rowconfigure(1, weight=1)
|
||||
|
||||
def setup_raw_tab(self):
|
||||
"""Set up the raw data viewing tab."""
|
||||
raw_frame = ttk.Frame(self.notebook)
|
||||
self.notebook.add(raw_frame, text="Raw Data")
|
||||
|
||||
# Text area for raw data
|
||||
self.raw_text = scrolledtext.ScrolledText(raw_frame, wrap=tk.WORD, height=20)
|
||||
self.raw_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
def setup_vault_tab(self):
|
||||
"""Set up the vault statistics tab."""
|
||||
vault_frame = ttk.Frame(self.notebook)
|
||||
self.notebook.add(vault_frame, text="Vault Stats")
|
||||
|
||||
# Vault info
|
||||
ttk.Label(vault_frame, text="Vault Name:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)
|
||||
self.vault_name = ttk.Entry(vault_frame, width=30)
|
||||
self.vault_name.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
|
||||
|
||||
ttk.Label(vault_frame, text="Vault Mode:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
|
||||
self.vault_number = ttk.Entry(vault_frame, width=30)
|
||||
self.vault_number.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)
|
||||
|
||||
def setup_resources_tab(self):
|
||||
"""Set up the resources editing tab."""
|
||||
resources_frame = ttk.Frame(self.notebook)
|
||||
self.notebook.add(resources_frame, text="Resources")
|
||||
|
||||
# Common resources
|
||||
resources = ["Caps", "Food", "Water", "Power", "Stimpaks", "RadAway", "Nuka Cola Quantum"]
|
||||
self.resource_entries = {}
|
||||
|
||||
for i, resource in enumerate(resources):
|
||||
ttk.Label(resources_frame, text=f"{resource}:").grid(row=i, column=0, sticky=tk.W, padx=5, pady=2)
|
||||
entry = ttk.Entry(resources_frame, width=20)
|
||||
entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=2)
|
||||
self.resource_entries[resource.lower().replace(" ", "_")] = entry
|
||||
|
||||
# Add buttons for resource modification
|
||||
button_frame = ttk.Frame(resources_frame)
|
||||
button_frame.grid(row=len(resources), column=0, columnspan=2, pady=10)
|
||||
|
||||
ttk.Button(button_frame, text="Apply Changes", command=self.apply_resource_changes).pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(button_frame, text="Max All Resources", command=self.max_all_resources).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
def setup_dwellers_tab(self):
|
||||
"""Set up the dwellers editing tab."""
|
||||
dwellers_frame = ttk.Frame(self.notebook)
|
||||
self.notebook.add(dwellers_frame, text="Dwellers")
|
||||
|
||||
# Dwellers list (placeholder)
|
||||
ttk.Label(dwellers_frame, text="Dwellers management will be available when save format is decoded").pack(pady=20)
|
||||
|
||||
def open_save(self):
|
||||
"""Open a save file."""
|
||||
filepath = filedialog.askopenfilename(
|
||||
title="Select Fallout Shelter Save File",
|
||||
filetypes=[("Save files", "*.sav"), ("All files", "*.*")]
|
||||
)
|
||||
|
||||
if filepath:
|
||||
if self.save_handler.load_save(filepath):
|
||||
self.file_label.config(text=f"Loaded: {os.path.basename(filepath)}")
|
||||
self.update_display()
|
||||
messagebox.showinfo("Success", "Save file loaded successfully!")
|
||||
else:
|
||||
messagebox.showerror("Error", "Failed to load save file")
|
||||
|
||||
def update_display(self):
|
||||
"""Update the display with loaded save data."""
|
||||
if not self.save_handler.is_loaded:
|
||||
return
|
||||
|
||||
# Update raw data tab
|
||||
if self.save_handler.save_data:
|
||||
raw_text = json.dumps(self.save_handler.save_data, indent=2)
|
||||
self.raw_text.delete(1.0, tk.END)
|
||||
self.raw_text.insert(1.0, raw_text)
|
||||
|
||||
# Try to populate other tabs if data structure is known
|
||||
self.populate_vault_info()
|
||||
self.populate_resources()
|
||||
|
||||
def populate_vault_info(self):
|
||||
"""Populate vault information if available."""
|
||||
if not self.save_handler.save_data:
|
||||
return
|
||||
|
||||
data = self.save_handler.save_data
|
||||
|
||||
# Get vault information from the correct location
|
||||
if "vault" in data and isinstance(data["vault"], dict):
|
||||
vault = data["vault"]
|
||||
|
||||
# Vault name
|
||||
if "VaultName" in vault:
|
||||
self.vault_name.delete(0, tk.END)
|
||||
self.vault_name.insert(0, str(vault["VaultName"]))
|
||||
|
||||
# Vault mode (instead of number, Fallout Shelter has mode)
|
||||
if "VaultMode" in vault:
|
||||
self.vault_number.delete(0, tk.END)
|
||||
self.vault_number.insert(0, str(vault["VaultMode"]))
|
||||
|
||||
def populate_resources(self):
|
||||
"""Populate resource information if available."""
|
||||
if not self.save_handler.save_data:
|
||||
return
|
||||
|
||||
data = self.save_handler.save_data
|
||||
|
||||
# Fallout Shelter resource field mappings (based on actual save structure)
|
||||
resource_mappings = {
|
||||
"caps": ["Nuka"], # Caps are stored as "Nuka" in the save
|
||||
"food": ["Food"],
|
||||
"water": ["Water"],
|
||||
"power": ["Energy"], # Power is stored as "Energy"
|
||||
"stimpaks": ["StimPack"],
|
||||
"radaway": ["RadAway"],
|
||||
"nuka_cola_quantum": ["NukaColaQuantum"]
|
||||
}
|
||||
|
||||
# Try to find and populate resource values
|
||||
# Look specifically in vault.storage.resources
|
||||
vault_storage = self._get_vault_storage(data)
|
||||
if vault_storage and "resources" in vault_storage:
|
||||
resources = vault_storage["resources"]
|
||||
|
||||
for resource_key, entry in self.resource_entries.items():
|
||||
possible_fields = resource_mappings.get(resource_key, [resource_key])
|
||||
|
||||
for field in possible_fields:
|
||||
if field in resources:
|
||||
entry.delete(0, tk.END)
|
||||
entry.insert(0, str(int(resources[field])))
|
||||
break
|
||||
|
||||
def _find_nested_value(self, data, key):
|
||||
"""Recursively search for a key in nested dictionaries."""
|
||||
if isinstance(data, dict):
|
||||
if key in data:
|
||||
return data[key]
|
||||
for value in data.values():
|
||||
result = self._find_nested_value(value, key)
|
||||
if result is not None:
|
||||
return result
|
||||
elif isinstance(data, list):
|
||||
for item in data:
|
||||
result = self._find_nested_value(item, key)
|
||||
if result is not None:
|
||||
return result
|
||||
return None
|
||||
|
||||
def save_file(self):
|
||||
"""Save the current file."""
|
||||
if self.save_handler.save_file():
|
||||
messagebox.showinfo("Success", "Save file updated successfully!")
|
||||
else:
|
||||
messagebox.showerror("Error", "Failed to save file")
|
||||
|
||||
def save_as(self):
|
||||
"""Save as a new file."""
|
||||
filepath = filedialog.asksaveasfilename(
|
||||
title="Save As",
|
||||
defaultextension=".sav",
|
||||
filetypes=[("Save files", "*.sav"), ("All files", "*.*")]
|
||||
)
|
||||
|
||||
if filepath:
|
||||
if self.save_handler.save_file(filepath):
|
||||
messagebox.showinfo("Success", f"Save file created: {os.path.basename(filepath)}")
|
||||
else:
|
||||
messagebox.showerror("Error", "Failed to create save file")
|
||||
|
||||
def create_backup(self):
|
||||
"""Create a backup of the current save."""
|
||||
if not self.save_handler.filepath:
|
||||
messagebox.showwarning("Warning", "No save file loaded")
|
||||
return
|
||||
|
||||
backup_path = self.save_handler.backup_save()
|
||||
if backup_path:
|
||||
messagebox.showinfo("Success", f"Backup created: {os.path.basename(backup_path)}")
|
||||
else:
|
||||
messagebox.showerror("Error", "Failed to create backup")
|
||||
|
||||
def apply_resource_changes(self):
|
||||
"""Apply resource changes to the save data."""
|
||||
if not self.save_handler.save_data:
|
||||
messagebox.showwarning("Warning", "No save data loaded")
|
||||
return
|
||||
|
||||
try:
|
||||
# Fallout Shelter resource field mappings
|
||||
resource_mappings = {
|
||||
"caps": ["Nuka"],
|
||||
"food": ["Food"],
|
||||
"water": ["Water"],
|
||||
"power": ["Energy"],
|
||||
"stimpaks": ["StimPack"],
|
||||
"radaway": ["RadAway"],
|
||||
"nuka_cola_quantum": ["NukaColaQuantum"]
|
||||
}
|
||||
|
||||
# Get vault storage
|
||||
vault_storage = self._get_vault_storage(self.save_handler.save_data)
|
||||
if not vault_storage or "resources" not in vault_storage:
|
||||
messagebox.showerror("Error", "Could not find vault storage in save data")
|
||||
return
|
||||
|
||||
resources = vault_storage["resources"]
|
||||
changes_made = 0
|
||||
|
||||
for resource_key, entry in self.resource_entries.items():
|
||||
value_str = entry.get().strip()
|
||||
if value_str:
|
||||
try:
|
||||
value = float(value_str) # Fallout Shelter uses floats for resources
|
||||
possible_fields = resource_mappings.get(resource_key, [resource_key])
|
||||
|
||||
for field in possible_fields:
|
||||
if field in resources:
|
||||
resources[field] = value
|
||||
changes_made += 1
|
||||
break
|
||||
except ValueError:
|
||||
messagebox.showerror("Error", f"Invalid value for {resource_key}: {value_str}")
|
||||
return
|
||||
|
||||
if changes_made > 0:
|
||||
messagebox.showinfo("Success", f"Applied {changes_made} resource changes")
|
||||
else:
|
||||
messagebox.showinfo("Info", "No changes were applied")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to apply changes: {e}")
|
||||
|
||||
def max_all_resources(self):
|
||||
"""Set all resources to maximum values."""
|
||||
max_values = {
|
||||
"caps": 999999,
|
||||
"food": 999999,
|
||||
"water": 999999,
|
||||
"power": 999999,
|
||||
"stimpaks": 999,
|
||||
"radaway": 999,
|
||||
"nuka_cola_quantum": 999
|
||||
}
|
||||
|
||||
for resource_key, entry in self.resource_entries.items():
|
||||
if resource_key in max_values:
|
||||
entry.delete(0, tk.END)
|
||||
entry.insert(0, str(max_values[resource_key]))
|
||||
|
||||
self.apply_resource_changes()
|
||||
|
||||
def _get_vault_storage(self, data):
|
||||
"""Get the vault storage section from save data."""
|
||||
if isinstance(data, dict) and "vault" in data:
|
||||
vault = data["vault"]
|
||||
if isinstance(vault, dict) and "storage" in vault:
|
||||
return vault["storage"]
|
||||
return None
|
||||
|
||||
def _set_nested_value(self, data, key, value):
|
||||
"""Recursively search for a key and set its value."""
|
||||
if isinstance(data, dict):
|
||||
if key in data:
|
||||
data[key] = value
|
||||
return True
|
||||
for nested_data in data.values():
|
||||
if self._set_nested_value(nested_data, key, value):
|
||||
return True
|
||||
elif isinstance(data, list):
|
||||
for item in data:
|
||||
if self._set_nested_value(item, key, value):
|
||||
return True
|
||||
return False
|
||||
|
||||
def run(self):
|
||||
"""Start the GUI."""
|
||||
self.root.mainloop()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
print("Fallout Shelter Save Editor")
|
||||
print("=" * 30)
|
||||
|
||||
# Check if GUI is available
|
||||
try:
|
||||
app = SaveEditorGUI()
|
||||
app.run()
|
||||
except ImportError:
|
||||
print("GUI not available, running in console mode")
|
||||
console_mode()
|
||||
|
||||
|
||||
def console_mode():
|
||||
"""Console-based interface."""
|
||||
print("\nConsole Mode")
|
||||
print("1. Analyze save file")
|
||||
print("2. Create backup")
|
||||
print("3. Exit")
|
||||
|
||||
while True:
|
||||
choice = input("\nEnter choice (1-3): ").strip()
|
||||
|
||||
if choice == "1":
|
||||
filepath = input("Enter save file path: ").strip()
|
||||
if os.path.exists(filepath):
|
||||
save = FalloutShelterSave(filepath)
|
||||
if save.load_save():
|
||||
print(f"Save loaded successfully!")
|
||||
print(f"Data preview: {str(save.save_data)[:200]}...")
|
||||
else:
|
||||
print("Failed to load save file")
|
||||
else:
|
||||
print("File not found")
|
||||
|
||||
elif choice == "2":
|
||||
filepath = input("Enter save file path: ").strip()
|
||||
if os.path.exists(filepath):
|
||||
save = FalloutShelterSave(filepath)
|
||||
backup_path = save.backup_save()
|
||||
if backup_path:
|
||||
print(f"Backup created: {backup_path}")
|
||||
else:
|
||||
print("Failed to create backup")
|
||||
else:
|
||||
print("File not found")
|
||||
|
||||
elif choice == "3":
|
||||
break
|
||||
else:
|
||||
print("Invalid choice")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Reference in New Issue
Block a user