Renpy Save Editor Page

def extract_variables(self): """Extract game variables from save data""" self.all_variables = {} if isinstance(self.save_data, dict): # Common Ren'Py save structure if 'variables' in self.save_data: variables_dict = self.save_data['variables'] else: variables_dict = self.save_data # Filter and categorize variables for key, value in variables_dict.items(): # Skip internal Ren'Py variables if key.startswith(('_', 'renpy', 'config')): continue var_type = type(value).__name__ self.all_variables[key] = 'value': value, 'type': var_type

var_name, var_value = var_assignment.split('=', 1)

def extract_pickle_data(self, raw_data): """Extract data from Ren'Py pickle format (simplified)""" # Real implementation would need unpickling with renpy.loader # This is a simplified version variables = {} # Look for common variable patterns in binary data # Convert to string and search for variable names text_data = raw_data.decode('latin-1', errors='ignore') # Find variable patterns like "money": 100, "name": "Player" import re patterns = [ (r'"([a-zA-Z_][a-zA-Z0-9_]*)"\s*:\s*(\d+)', 'int'), (r'"([a-zA-Z_][a-zA-Z0-9_]*)"\s*:\s*"([^"]*)"', 'str'), (r'"([a-zA-Z_][a-zA-Z0-9_]*)"\s*:\s*(true|false)', 'bool'), ] for pattern, typ in patterns: for match in re.finditer(pattern, text_data): name = match.group(1) value = match.group(2) if typ == 'bool': value = value.lower() == 'true' elif typ == 'int': value = int(value) variables[name] = value return variables renpy save editor

# Load and edit save editor = RenPySaveEditor(None) editor.current_save = save_file editor.load_save_data()

save_file = sys.argv[1] var_assignment = sys.argv[2] 'type': var_type var_name

def load_save_data(self): """Parse Ren'Py save file format""" with open(self.current_save, 'rb') as f: # Read header (Ren'Py version and metadata) header = f.read(8) # Check if it's compressed (usually zlib compressed JSON) try: # Attempt to decompress data = zlib.decompress(f.read()) self.save_data = json.loads(data) except: # Might be uncompressed or different format f.seek(8) data = f.read() try: self.save_data = json.loads(data) except: # Try to extract from pickle (advanced) self.save_data = self.extract_pickle_data(data) # Extract variables from the save structure self.extract_variables()

self.current_save = None self.save_data = None self.setup_ui() def setup_ui(self): # 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 Changes", command=self.save_changes) 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)) # Left panel - variable list left_frame = ttk.LabelFrame(main_frame, text="Variables", width=300) left_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5) self.search_var = tk.StringVar() self.search_var.trace('w', self.filter_variables) search_entry = ttk.Entry(left_frame, textvariable=self.search_var) search_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=5, padx=5) search_entry.insert(0, "Search...") self.variable_listbox = tk.Listbox(left_frame, height=25) self.variable_listbox.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5, padx=5) self.variable_listbox.bind('<<ListboxSelect>>', self.on_variable_select) scrollbar = ttk.Scrollbar(left_frame, orient="vertical", command=self.variable_listbox.yview) scrollbar.grid(row=1, column=1, sticky=(tk.N, tk.S)) self.variable_listbox.config(yscrollcommand=scrollbar.set) # Right panel - variable editor right_frame = ttk.LabelFrame(main_frame, text="Edit Variable", width=400) right_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5) ttk.Label(right_frame, text="Variable Name:").grid(row=0, column=0, sticky=tk.W, pady=5) self.var_name_label = ttk.Label(right_frame, text="") self.var_name_label.grid(row=0, column=1, sticky=tk.W, pady=5) ttk.Label(right_frame, text="Type:").grid(row=1, column=0, sticky=tk.W, pady=5) self.var_type_label = ttk.Label(right_frame, text="") self.var_type_label.grid(row=1, column=1, sticky=tk.W, pady=5) ttk.Label(right_frame, text="Value:").grid(row=2, column=0, sticky=tk.W, pady=5) self.value_entry = tk.Text(right_frame, height=10, width=40) self.value_entry.grid(row=2, column=1, pady=5, padx=5) ttk.Button(right_frame, text="Update Value", command=self.update_variable).grid(row=3, column=1, pady=10) # Status bar self.status_var = tk.StringVar() self.status_var.set("Ready") status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.grid(row=1, column=0, sticky=(tk.W, tk.E)) # Configure grid weights self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) main_frame.columnconfigure(0, weight=1) main_frame.columnconfigure(1, weight=2) main_frame.rowconfigure(0, weight=1) left_frame.columnconfigure(0, weight=1) left_frame.rowconfigure(1, weight=1) self.all_variables = {} def open_save(self): filepath = filedialog.askopenfilename( title="Select Ren'Py Save File", filetypes=[("Ren'Py Saves", "*.save"), ("All Files", "*.*")] ) if not filepath: return try: self.current_save = filepath self.load_save_data() self.display_variables() self.status_var.set(f"Loaded: os.path.basename(filepath)") except Exception as e: messagebox.showerror("Error", f"Failed to load save: str(e)") self.status_var.set("Error loading save") var_value = var_assignment.split('='

def display_variables(self): self.variable_listbox.delete(0, tk.END) for var_name in sorted(self.all_variables.keys()): self.variable_listbox.insert(tk.END, var_name)

def update_variable(self): selection = self.variable_listbox.curselection() if not selection: messagebox.showwarning("Warning", "No variable selected") return var_name = self.variable_listbox.get(selection[0]) new_value_str = self.value_entry.get(1.0, tk.END).strip() current_type = self.all_variables[var_name]['type'] try: # Convert value based on type if current_type == 'int': new_value = int(new_value_str) elif current_type == 'float': new_value = float(new_value_str) elif current_type == 'bool': new_value = new_value_str.lower() in ('true', '1', 'yes', 'on') elif current_type == 'list': new_value = json.loads(new_value_str) elif current_type == 'dict': new_value = json.loads(new_value_str) else: new_value = new_value_str # Update variable self.all_variables[var_name]['value'] = new_value self.status_var.set(f"Updated var_name = new_value") except Exception as e: messagebox.showerror("Error", f"Failed to convert value: str(e)")

def filter_variables(self, *args): search_term = self.search_var.get().lower() self.variable_listbox.delete(0, tk.END) for var_name in sorted(self.all_variables.keys()): if search_term in var_name.lower() or not search_term: self.variable_listbox.insert(tk.END, var_name)

Skip to content