Hacking Metroid Prime 4 Beyond save editing

  • Thread starter Thread starter Pj1980
  • Start date Start date
  • Views Views 8,105
  • Replies Replies 65
  • Likes Likes 8

Attachments

  • 17657424779575802650851456215083.jpg
    17657424779575802650851456215083.jpg
    1.3 MB · Views: 20
Last edited by Jordo2424,
i added bat files to first post that edit green shards in the first 2 instances it finds id. you can just drop save on to them and it will edit them, if it not enough you can edit bat files in text editor
 
  • Like
Reactions: Jordo2424
i added bat files to first post that edit green shards in the first 2 instances it finds id. you can just drop save on to them and it will edit them, if it not enough you can edit bat files in text editor
Do you think there's a save game edit you can do to make it that save stations refil ammo on hard mode?
Post automatically merged:

Do you think there's a save game edit you can do to make it that save stations refil ammo on hard mode?
Or is that more of a mod?
 
Its rough but if you want i made a small editor using what you have? modify it if you will to your findings. hope it helps. it will correct your checksum too.

Python:
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import struct
import zlib
import binascii
import copy

ITEM_DATABASE = {
    # Psychic Items
    b'\x10\xC0\xF5\x03': "Psychic Glove (1)",
    b'\x07\x66\xA5\x04': "Psychic Glove (2)",
    b'\x6D\x69\x68\x18': "Psychic Shot",
    b'\x05\x7C\xB5\x29': "Psychic Boots (1)",
    b'\xA8\x2E\xFE\x2A': "Psychic Boots (2)",
    b'\xDF\xD2\x69\x2C': "Psychic Crystal/Visor (1)",
    b'\xA0\x3C\x6C\x2D': "Psychic Crystal/Visor (2)",
    b'\x52\x21\x65\x73': "Psychic Charge Shot",
    b'\xF4\xB9\xC7\x84': "Psychic Power Bomb",
    b'\x84\x7F\x51\x8C': "Psychic Grapple (1)",
    b'\xDA\x68\xCA\x8E': "Psychic Grapple (2)",
    
    # Teleporter
    b'\x1C\x2B\x9F\x08': "Teleporter Patch (1)",
    b'\x79\x6A\xEA\x0C': "Teleporter Patch (2)",
    b'\x35\xAA\x9D\x0E': "Teleporter Patch (3)",
    b'\x22\x44\x44\x10': "Teleporter Patch (4)",
    b'\xF8\x1B\xC6\x7D': "Teleporter Chip",

    # Weapons & Upgrades
    b'\xBD\xA6\xE8\x13': "Super Thunder Shot",
    b'\x04\x97\x8C\x1E': "Thunder Chip",
    b'\x4F\x53\x59\x8A': "Thunder Shot",
    b'\x71\x94\xD8\xB0': "Charged Thunder Shot (1)",
    b'\x5B\x01\x94\xB5': "Charged Thunder Shot (2)",
    
    b'\x97\x42\xB4\x27': "Charged Ice Shot",
    b'\x71\x64\x03\x2B': "Ice Shot",
    b'\xD3\x2E\xC3\x5F': "Vi-O-La IC Suit (1)",
    b'\xFE\x04\xE3\x6A': "Vi-O-La IC Suit (2)",
    b'\x99\x52\x4F\x74': "Vi-O-La Suit",
    
    b'\x82\x96\xDD\x6C': "Fire Chip",
    b'\x85\x40\x45\x6F': "Fire Chip (2)",
    b'\x0A\x97\x20\x71': "Fire Chip (3)",
    b'\xF7\xC2\xD8\x85': "Fire Shot",

    # Movement
    b'\x77\x1D\x12\x2E': "Spring Ball",
    b'\xE6\xBB\x27\x35': "Space Jump Boots (1)",
    b'\xCB\x91\x7F\x35': "Space Jump Boots (2)",
    b'\x5A\xD7\x7F\x3D': "Space Jump Boots (3)",
    b'\xBA\x15\xD4\x44': "Space Jump Boots (4)",
    b'\xC7\x7C\xDB\x48': "Space Jump Boots (5)",
    
    b'\xE5\xDC\x3B\xD9': "Boost Ball (1)",
    b'\xF2\x6C\x79\xDC': "Boost Ball (2)",
    b'\x06\x06\xCD\xDE': "Boost Ball (3)",
    b'\xDD\x01\xB1\xE8': "Boost Ball (4)",
    b'\xD6\x0A\x10\xED': "Boost Ball (5)",
    b'\x1B\x4E\x40\xF0': "Boost Ball (6)",
    b'\xB9\xA4\x4D\xF3': "Boost Ball (7)",
    b'\xCB\x9C\x03\xF9': "Boost Ball (8)",
    b'\xF5\xB8\x46\xFB': "Boost Ball (9)",

    # Morph Ball
    b'\x76\x0C\xC9\x76': "Morph Bomb (1)",
    b'\x0B\x58\x59\x7A': "Morph Bomb (2)",

    # Resources
    b'\x66\x51\x03\x1A': "Green Shards",
    b'\x06\x9C\x51\x49': "Missiles (1)",
    b'\xCE\x41\x51\x4D': "Missiles (2)",
    b'\x1C\x5D\x23\x53': "Missiles (3)",
    b'\xDD\x54\x99\x5E': "Missiles (4)",
    
    b'\xA0\x0B\x34\xBC': "Super Missile (1)",
    b'\x2D\x56\x4E\xBC': "Super Missile (2)",
    b'\x61\xC3\xD8\xBC': "Super Missile (3)",
    b'\x5F\xEE\xC5\xBD': "Super Missile (4)",
    b'\x76\x41\xDC\xBF': "Super Missile (5)",
    b'\xFA\x8D\xEF\xC1': "Super Missile (6)",
    b'\x35\x05\x1F\xC2': "Super Missile (7)",
    b'\x16\xA7\x38\xC5': "Super Missile (8)",
    b'\x74\xE4\xB3\xC7': "Super Missile (9)",
    b'\x34\xAC\x2C\xCF': "Super Missile (10)",
    b'\x57\x48\x75\xD0': "Super Missile (11)",
    b'\xFC\xC4\x40\xD5': "Super Missile (12)",

    b'\xA6\x12\xFB\x97': "Energy Tank (1)",
    b'\xB1\x13\x03\x9D': "Energy Tank (2)",
    b'\x1F\x6C\xD8\x9D': "Energy Tank (3)",
    b'\x31\x21\xC0\xA0': "Energy Tank (4)",
    b'\xE3\x6B\x49\xA4': "Energy Tank (5)",

    b'\xA3\x29\xD5\xA6': "Shot Ammo (1)",
    b'\x6A\x7C\xA4\xAB': "Shot Ammo (2)",

    # Misc
    b'\x64\xA9\x21\x23': "Gal Fed Mech Parts (1)",
    b'\xBD\x82\xFE\x23': "Gal Fed Mech Parts (2)",
    b'\xE5\x77\x80\x25': "Gal Fed Mech Parts (3)",
    b'\xFD\x04\x8F\x25': "Gal Fed Mech Parts (4)",
    b'\x07\xBC\x73\x26': "Gal Fed Mech Parts (5)",
}

# --- CORE LOGIC ---

class SaveEditor:
    def __init__(self, master):
        self.master = master
        self.master.title("Metroid Prime 4 Beyond - Save Editor")
        self.master.geometry("750x600")
        
        self.file_path = None
        self.data = bytearray()
        self.items_found = [] # List of dicts: {'name', 'offset', 'id', 'val1', 'val2', 'val3'}

        # UI Setup
        self._setup_ui()

    def _setup_ui(self):
        # Top Frame: File Operations
        top_frame = tk.Frame(self.master, padx=10, pady=10)
        top_frame.pack(fill=tk.X)
        
        self.btn_load = tk.Button(top_frame, text="Load Save File", command=self.load_file, width=20)
        self.btn_load.pack(side=tk.LEFT, padx=5)
        
        self.btn_save = tk.Button(top_frame, text="Save Changes", command=self.save_file, width=20, state=tk.DISABLED)
        self.btn_save.pack(side=tk.LEFT, padx=5)
        
        self.lbl_status = tk.Label(top_frame, text="No file loaded", fg="gray")
        self.lbl_status.pack(side=tk.LEFT, padx=20)

        # Main Frame: Item List
        main_frame = tk.Frame(self.master, padx=10, pady=5)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # Header for columns
        cols_frame = tk.Frame(main_frame)
        cols_frame.pack(fill=tk.X)
        tk.Label(cols_frame, text="Item Name", width=25, anchor="w", font=('bold')).pack(side=tk.LEFT)
        tk.Label(cols_frame, text="Value 1 (Amt)", width=12, font=('bold')).pack(side=tk.LEFT)
        tk.Label(cols_frame, text="Value 2", width=12, font=('bold')).pack(side=tk.LEFT)
        tk.Label(cols_frame, text="Value 3 (Cap)", width=12, font=('bold')).pack(side=tk.LEFT)

        # Canvas for scrolling
        self.canvas = tk.Canvas(main_frame)
        self.scrollbar = ttk.Scrollbar(main_frame, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = tk.Frame(self.canvas)

        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        )

        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)

        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")
        
        # Bottom Frame: Bulk Actions
        btm_frame = tk.Frame(self.master, padx=10, pady=10)
        btm_frame.pack(fill=tk.X)
        
        tk.Label(btm_frame, text="Note: Backup your save before editing!", fg="red").pack(side=tk.LEFT)

    def load_file(self):
        path = filedialog.askopenfilename(filetypes=[("All Files", "*.*")])
        if not path:
            return

        try:
            with open(path, "rb") as f:
                self.data = bytearray(f.read())
            
            self.file_path = path
            
            # 1. Header Parsing
            if len(self.data) < 8:
                raise ValueError("File too small to be a valid save.")

            stored_crc = struct.unpack('<I', self.data[0:4])[0]
            stored_size = struct.unpack('<I', self.data[4:8])[0]
            
            # 2. Check Size
            actual_size = len(self.data) - 8
            
            checksum_data = self.data[8:]
            calculated_crc = zlib.crc32(checksum_data) ^ 0xFFFFFFFF
            
            status_msg = f"Loaded. Size: {actual_size}"
            if calculated_crc == stored_crc:
                status_msg += " | CRC: Valid"
                self.lbl_status.config(text=status_msg, fg="green")
            else:
                status_msg += f" | CRC: INVALID (Exp {hex(calculated_crc)}, Got {hex(stored_crc)})"
                self.lbl_status.config(text=status_msg, fg="orange")

            self.parse_items()
            self.btn_save.config(state=tk.NORMAL)

        except Exception as e:
            messagebox.showerror("Error", f"Failed to load file: {str(e)}")

    def parse_items(self):
        # Clear existing GUI items
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()
        
        self.items_found = []
        
        # Search for IDs
        # Structure: [ID 4bytes] [Val1 Float] [Val2 Float] [Val3 Float] = 16 bytes total
        
        # We search the data from offset 8 onwards
        
        row_idx = 0
        for id_bytes, name in ITEM_DATABASE.items():
            start = 0
            while True:
                offset = self.data.find(id_bytes, start)
                if offset == -1:
                    break
                
                # Check if we have room for the floats
                if offset + 16 > len(self.data):
                    break
                    
                # Read floats
                try:
                    v1 = struct.unpack('<f', self.data[offset+4:offset+8])[0]
                    v2 = struct.unpack('<f', self.data[offset+8:offset+12])[0]
                    v3 = struct.unpack('<f', self.data[offset+12:offset+16])[0]
                    
                    item_entry = {
                        'name': name,
                        'offset': offset,
                        'id_bytes': id_bytes,
                        'v1_var': tk.StringVar(value=str(v1)),
                        'v2_var': tk.StringVar(value=str(v2)),
                        'v3_var': tk.StringVar(value=str(v3))
                    }
                    
                    self.items_found.append(item_entry)
                    self._create_item_row(item_entry, row_idx)
                    row_idx += 1
                except:
                    pass # Parse error, skip instance
                
                start = offset + 1

    def _create_item_row(self, item, row):
        # Create widgets for this item
        f = tk.Frame(self.scrollable_frame)
        f.pack(fill=tk.X, pady=2)
        
        # Name
        tk.Label(f, text=item['name'], width=25, anchor="w").pack(side=tk.LEFT)
        
        # Value 1
        tk.Entry(f, textvariable=item['v1_var'], width=12).pack(side=tk.LEFT, padx=2)
        
        # Value 2
        tk.Entry(f, textvariable=item['v2_var'], width=12).pack(side=tk.LEFT, padx=2)
        
        # Value 3
        tk.Entry(f, textvariable=item['v3_var'], width=12).pack(side=tk.LEFT, padx=2)

    def save_file(self):
        if not self.file_path:
            return
            
        try:
            # 1. Update Data from GUI
            for item in self.items_found:
                off = item['offset']
                try:
                    v1 = float(item['v1_var'].get())
                    v2 = float(item['v2_var'].get())
                    v3 = float(item['v3_var'].get())
                    
                    # Pack floats back to bytes (Little Endian)
                    b_v1 = struct.pack('<f', v1)
                    b_v2 = struct.pack('<f', v2)
                    b_v3 = struct.pack('<f', v3)
                    
                    # Write to bytearray
                    self.data[off+4:off+8] = b_v1
                    self.data[off+8:off+12] = b_v2
                    self.data[off+12:off+16] = b_v3
                    
                except ValueError:
                    messagebox.showwarning("Value Error", f"Invalid number for {item['name']}. Skipping.")
            
            # The size header is len(data) - 8
            new_size = len(self.data) - 8
            self.data[4:8] = struct.pack('<I', new_size)
            
            # 3. Recalculate CRC
            # Checksum covers from offset 8 to end
            checksum_data = self.data[8:]
            new_crc = zlib.crc32(checksum_data) ^ 0xFFFFFFFF
            self.data[0:4] = struct.pack('<I', new_crc)
            
            # 4. Write to disk
            new_path = filedialog.asksaveasfilename(defaultextension=".sav", initialfile=self.file_path)
            if new_path:
                with open(new_path, "wb") as f:
                    f.write(self.data)
                messagebox.showinfo("Success", "File saved successfully!")
                
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save: {str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = SaveEditor(root)
    root.mainloop()
1765747606062.png


Your description of data was a lil weird so i didnt use any specific patterns atm aside from the exact AOB you gave for each in your list lol
 
  • Like
Reactions: Pj1980
Its rough but if you want i made a small editor using what you have? modify it if you will to your findings. hope it helps. it will correct your checksum too.

Python:
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import struct
import zlib
import binascii
import copy

ITEM_DATABASE = {
    # Psychic Items
    b'\x10\xC0\xF5\x03': "Psychic Glove (1)",
    b'\x07\x66\xA5\x04': "Psychic Glove (2)",
    b'\x6D\x69\x68\x18': "Psychic Shot",
    b'\x05\x7C\xB5\x29': "Psychic Boots (1)",
    b'\xA8\x2E\xFE\x2A': "Psychic Boots (2)",
    b'\xDF\xD2\x69\x2C': "Psychic Crystal/Visor (1)",
    b'\xA0\x3C\x6C\x2D': "Psychic Crystal/Visor (2)",
    b'\x52\x21\x65\x73': "Psychic Charge Shot",
    b'\xF4\xB9\xC7\x84': "Psychic Power Bomb",
    b'\x84\x7F\x51\x8C': "Psychic Grapple (1)",
    b'\xDA\x68\xCA\x8E': "Psychic Grapple (2)",
   
    # Teleporter
    b'\x1C\x2B\x9F\x08': "Teleporter Patch (1)",
    b'\x79\x6A\xEA\x0C': "Teleporter Patch (2)",
    b'\x35\xAA\x9D\x0E': "Teleporter Patch (3)",
    b'\x22\x44\x44\x10': "Teleporter Patch (4)",
    b'\xF8\x1B\xC6\x7D': "Teleporter Chip",

    # Weapons & Upgrades
    b'\xBD\xA6\xE8\x13': "Super Thunder Shot",
    b'\x04\x97\x8C\x1E': "Thunder Chip",
    b'\x4F\x53\x59\x8A': "Thunder Shot",
    b'\x71\x94\xD8\xB0': "Charged Thunder Shot (1)",
    b'\x5B\x01\x94\xB5': "Charged Thunder Shot (2)",
   
    b'\x97\x42\xB4\x27': "Charged Ice Shot",
    b'\x71\x64\x03\x2B': "Ice Shot",
    b'\xD3\x2E\xC3\x5F': "Vi-O-La IC Suit (1)",
    b'\xFE\x04\xE3\x6A': "Vi-O-La IC Suit (2)",
    b'\x99\x52\x4F\x74': "Vi-O-La Suit",
   
    b'\x82\x96\xDD\x6C': "Fire Chip",
    b'\x85\x40\x45\x6F': "Fire Chip (2)",
    b'\x0A\x97\x20\x71': "Fire Chip (3)",
    b'\xF7\xC2\xD8\x85': "Fire Shot",

    # Movement
    b'\x77\x1D\x12\x2E': "Spring Ball",
    b'\xE6\xBB\x27\x35': "Space Jump Boots (1)",
    b'\xCB\x91\x7F\x35': "Space Jump Boots (2)",
    b'\x5A\xD7\x7F\x3D': "Space Jump Boots (3)",
    b'\xBA\x15\xD4\x44': "Space Jump Boots (4)",
    b'\xC7\x7C\xDB\x48': "Space Jump Boots (5)",
   
    b'\xE5\xDC\x3B\xD9': "Boost Ball (1)",
    b'\xF2\x6C\x79\xDC': "Boost Ball (2)",
    b'\x06\x06\xCD\xDE': "Boost Ball (3)",
    b'\xDD\x01\xB1\xE8': "Boost Ball (4)",
    b'\xD6\x0A\x10\xED': "Boost Ball (5)",
    b'\x1B\x4E\x40\xF0': "Boost Ball (6)",
    b'\xB9\xA4\x4D\xF3': "Boost Ball (7)",
    b'\xCB\x9C\x03\xF9': "Boost Ball (8)",
    b'\xF5\xB8\x46\xFB': "Boost Ball (9)",

    # Morph Ball
    b'\x76\x0C\xC9\x76': "Morph Bomb (1)",
    b'\x0B\x58\x59\x7A': "Morph Bomb (2)",

    # Resources
    b'\x66\x51\x03\x1A': "Green Shards",
    b'\x06\x9C\x51\x49': "Missiles (1)",
    b'\xCE\x41\x51\x4D': "Missiles (2)",
    b'\x1C\x5D\x23\x53': "Missiles (3)",
    b'\xDD\x54\x99\x5E': "Missiles (4)",
   
    b'\xA0\x0B\x34\xBC': "Super Missile (1)",
    b'\x2D\x56\x4E\xBC': "Super Missile (2)",
    b'\x61\xC3\xD8\xBC': "Super Missile (3)",
    b'\x5F\xEE\xC5\xBD': "Super Missile (4)",
    b'\x76\x41\xDC\xBF': "Super Missile (5)",
    b'\xFA\x8D\xEF\xC1': "Super Missile (6)",
    b'\x35\x05\x1F\xC2': "Super Missile (7)",
    b'\x16\xA7\x38\xC5': "Super Missile (8)",
    b'\x74\xE4\xB3\xC7': "Super Missile (9)",
    b'\x34\xAC\x2C\xCF': "Super Missile (10)",
    b'\x57\x48\x75\xD0': "Super Missile (11)",
    b'\xFC\xC4\x40\xD5': "Super Missile (12)",

    b'\xA6\x12\xFB\x97': "Energy Tank (1)",
    b'\xB1\x13\x03\x9D': "Energy Tank (2)",
    b'\x1F\x6C\xD8\x9D': "Energy Tank (3)",
    b'\x31\x21\xC0\xA0': "Energy Tank (4)",
    b'\xE3\x6B\x49\xA4': "Energy Tank (5)",

    b'\xA3\x29\xD5\xA6': "Shot Ammo (1)",
    b'\x6A\x7C\xA4\xAB': "Shot Ammo (2)",

    # Misc
    b'\x64\xA9\x21\x23': "Gal Fed Mech Parts (1)",
    b'\xBD\x82\xFE\x23': "Gal Fed Mech Parts (2)",
    b'\xE5\x77\x80\x25': "Gal Fed Mech Parts (3)",
    b'\xFD\x04\x8F\x25': "Gal Fed Mech Parts (4)",
    b'\x07\xBC\x73\x26': "Gal Fed Mech Parts (5)",
}

# --- CORE LOGIC ---

class SaveEditor:
    def __init__(self, master):
        self.master = master
        self.master.title("Metroid Prime 4 Beyond - Save Editor")
        self.master.geometry("750x600")
       
        self.file_path = None
        self.data = bytearray()
        self.items_found = [] # List of dicts: {'name', 'offset', 'id', 'val1', 'val2', 'val3'}

        # UI Setup
        self._setup_ui()

    def _setup_ui(self):
        # Top Frame: File Operations
        top_frame = tk.Frame(self.master, padx=10, pady=10)
        top_frame.pack(fill=tk.X)
       
        self.btn_load = tk.Button(top_frame, text="Load Save File", command=self.load_file, width=20)
        self.btn_load.pack(side=tk.LEFT, padx=5)
       
        self.btn_save = tk.Button(top_frame, text="Save Changes", command=self.save_file, width=20, state=tk.DISABLED)
        self.btn_save.pack(side=tk.LEFT, padx=5)
       
        self.lbl_status = tk.Label(top_frame, text="No file loaded", fg="gray")
        self.lbl_status.pack(side=tk.LEFT, padx=20)

        # Main Frame: Item List
        main_frame = tk.Frame(self.master, padx=10, pady=5)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # Header for columns
        cols_frame = tk.Frame(main_frame)
        cols_frame.pack(fill=tk.X)
        tk.Label(cols_frame, text="Item Name", width=25, anchor="w", font=('bold')).pack(side=tk.LEFT)
        tk.Label(cols_frame, text="Value 1 (Amt)", width=12, font=('bold')).pack(side=tk.LEFT)
        tk.Label(cols_frame, text="Value 2", width=12, font=('bold')).pack(side=tk.LEFT)
        tk.Label(cols_frame, text="Value 3 (Cap)", width=12, font=('bold')).pack(side=tk.LEFT)

        # Canvas for scrolling
        self.canvas = tk.Canvas(main_frame)
        self.scrollbar = ttk.Scrollbar(main_frame, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = tk.Frame(self.canvas)

        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        )

        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)

        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")
       
        # Bottom Frame: Bulk Actions
        btm_frame = tk.Frame(self.master, padx=10, pady=10)
        btm_frame.pack(fill=tk.X)
       
        tk.Label(btm_frame, text="Note: Backup your save before editing!", fg="red").pack(side=tk.LEFT)

    def load_file(self):
        path = filedialog.askopenfilename(filetypes=[("All Files", "*.*")])
        if not path:
            return

        try:
            with open(path, "rb") as f:
                self.data = bytearray(f.read())
           
            self.file_path = path
           
            # 1. Header Parsing
            if len(self.data) < 8:
                raise ValueError("File too small to be a valid save.")

            stored_crc = struct.unpack('<I', self.data[0:4])[0]
            stored_size = struct.unpack('<I', self.data[4:8])[0]
           
            # 2. Check Size
            actual_size = len(self.data) - 8
           
            checksum_data = self.data[8:]
            calculated_crc = zlib.crc32(checksum_data) ^ 0xFFFFFFFF
           
            status_msg = f"Loaded. Size: {actual_size}"
            if calculated_crc == stored_crc:
                status_msg += " | CRC: Valid"
                self.lbl_status.config(text=status_msg, fg="green")
            else:
                status_msg += f" | CRC: INVALID (Exp {hex(calculated_crc)}, Got {hex(stored_crc)})"
                self.lbl_status.config(text=status_msg, fg="orange")

            self.parse_items()
            self.btn_save.config(state=tk.NORMAL)

        except Exception as e:
            messagebox.showerror("Error", f"Failed to load file: {str(e)}")

    def parse_items(self):
        # Clear existing GUI items
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()
       
        self.items_found = []
       
        # Search for IDs
        # Structure: [ID 4bytes] [Val1 Float] [Val2 Float] [Val3 Float] = 16 bytes total
       
        # We search the data from offset 8 onwards
       
        row_idx = 0
        for id_bytes, name in ITEM_DATABASE.items():
            start = 0
            while True:
                offset = self.data.find(id_bytes, start)
                if offset == -1:
                    break
               
                # Check if we have room for the floats
                if offset + 16 > len(self.data):
                    break
                   
                # Read floats
                try:
                    v1 = struct.unpack('<f', self.data[offset+4:offset+8])[0]
                    v2 = struct.unpack('<f', self.data[offset+8:offset+12])[0]
                    v3 = struct.unpack('<f', self.data[offset+12:offset+16])[0]
                   
                    item_entry = {
                        'name': name,
                        'offset': offset,
                        'id_bytes': id_bytes,
                        'v1_var': tk.StringVar(value=str(v1)),
                        'v2_var': tk.StringVar(value=str(v2)),
                        'v3_var': tk.StringVar(value=str(v3))
                    }
                   
                    self.items_found.append(item_entry)
                    self._create_item_row(item_entry, row_idx)
                    row_idx += 1
                except:
                    pass # Parse error, skip instance
               
                start = offset + 1

    def _create_item_row(self, item, row):
        # Create widgets for this item
        f = tk.Frame(self.scrollable_frame)
        f.pack(fill=tk.X, pady=2)
       
        # Name
        tk.Label(f, text=item['name'], width=25, anchor="w").pack(side=tk.LEFT)
       
        # Value 1
        tk.Entry(f, textvariable=item['v1_var'], width=12).pack(side=tk.LEFT, padx=2)
       
        # Value 2
        tk.Entry(f, textvariable=item['v2_var'], width=12).pack(side=tk.LEFT, padx=2)
       
        # Value 3
        tk.Entry(f, textvariable=item['v3_var'], width=12).pack(side=tk.LEFT, padx=2)

    def save_file(self):
        if not self.file_path:
            return
           
        try:
            # 1. Update Data from GUI
            for item in self.items_found:
                off = item['offset']
                try:
                    v1 = float(item['v1_var'].get())
                    v2 = float(item['v2_var'].get())
                    v3 = float(item['v3_var'].get())
                   
                    # Pack floats back to bytes (Little Endian)
                    b_v1 = struct.pack('<f', v1)
                    b_v2 = struct.pack('<f', v2)
                    b_v3 = struct.pack('<f', v3)
                   
                    # Write to bytearray
                    self.data[off+4:off+8] = b_v1
                    self.data[off+8:off+12] = b_v2
                    self.data[off+12:off+16] = b_v3
                   
                except ValueError:
                    messagebox.showwarning("Value Error", f"Invalid number for {item['name']}. Skipping.")
           
            # The size header is len(data) - 8
            new_size = len(self.data) - 8
            self.data[4:8] = struct.pack('<I', new_size)
           
            # 3. Recalculate CRC
            # Checksum covers from offset 8 to end
            checksum_data = self.data[8:]
            new_crc = zlib.crc32(checksum_data) ^ 0xFFFFFFFF
            self.data[0:4] = struct.pack('<I', new_crc)
           
            # 4. Write to disk
            new_path = filedialog.asksaveasfilename(defaultextension=".sav", initialfile=self.file_path)
            if new_path:
                with open(new_path, "wb") as f:
                    f.write(self.data)
                messagebox.showinfo("Success", "File saved successfully!")
               
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save: {str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = SaveEditor(root)
    root.mainloop()
View attachment 545399

Your description of data was a lil weird so i didnt use any specific patterns atm aside from the exact AOB you gave for each in your list lol
Why are there multiple copies of the same item?
 
Just the way i formatted it based on what they provided in their blurb. can fix if you want
Are you still working on the save editor? Will you be adding more items to it like energy tanks and green shards?
Post automatically merged:

Are you still working on the save editor? Will you be adding more items to it like energy tanks and green shards?
Cause im on hard mode right now and I could really use the upgrades
 
Are you still working on the save editor? Will you be adding more items to it like energy tanks and green shards?
Post automatically merged:


Cause im on hard mode right now and I could really use the upgrades
If you guys find more values in the save data then i can add and correct things. i didnt bother to parse the save data myself. just quickly put this together based on what op has put up
 
10900 green shards
What items are safe to add to an early game save? For example volt forge tower 2 before turning on the second generator?
Post automatically merged:

What items are safe to add to an early game save? For example volt forge tower 2 before turning on the second generator?
Im wondering this because I need help in hard mode
Post automatically merged:

What items are safe to add to an early game save? For example volt forge tower 2 before turning on the second generator?
Post automatically merged:


Im wondering this because I need help in hard mode
Post automatically merged:

This how to edit Metroid Prime 4 saves. I am not sure save order other than it might be all the auto saves first then all the manual. if you have only used one save slot it should be easy to work out. searching for VSM in text might be the start of each save.

Saves Have Checksum
There a Checksum fixer in zip file unzip and drop save onto bat file it will fix the checksum so won't need to use Hxd to fix it. The Checksum fixer uses Bucanero's cli app and a bat file. Bat file is a patch file if you have experience with ps4 save wizard you can add quick codes to it.

Fix Checksum With Hxd

The saves have a checksum it is the first 4 bytes. the next 4 bytes is the whole size of save minus the first 8 bytes if your not changing save size you can ignore this. the checksum covers from after 8th byte or from 35 to end of save. the checksum is CRC-32/JAMCRC. i used hxd to fix checksum. i selected the whole save minus the first 8 bytes you can go into edit and select block. start is 8 and end you can just type ffffff. you then go into analysis select checksum. in generate checksums box select custom CRC (32bit) then select custom crc... in the custom CRC box you need to put 04C11DB7 in poly, FFFFFFFF in intial and in output xor 00000000. also need to tick in and out for reflection. click ok for this and previous box and you should have checksum. the bytes in checksum needs to be reversed you can see in picture below what i mean. both checksum and size are in little endian meaning bytes are reversed.
View attachment 544780

Editing
To find green shards search for 66 51 03 1A shards will be after that in reverse float. max green shards is 0000af46. also max amount you can have is also after the amount you have so if the id doesn't work you can search for max shard value and go back 4 bytes
View attachment 544774
it looks like for items its id then amount then cap total repeated twice (so thats 4 byte id and 3 reverse float values). any id with 00 00 80 3F repeated 3 times is probably something unlocked. will need a completed save to see what the right floats should be after the ids.
I don't think id change order so not a problem but if the ids are like the scan section it would be float values then Id

To find scan section search for AF 05 00 00 00 you should find a section similar to whats in picture. all you need to do is put 00 3F in front of the Ids the ids range from 00 to 0A. the 00 ones are easy workout after filling the others in.
As mentioned below changing all might cause a softlock it could be the 0A ones. in picture below in the boxes is the ids
View attachment 544970

here are some ids i got from a end game save with their float values. I added names that genesis nova found
Code:
Name                        ID                 Float 1            Float 2            Float 3        Float 1 to dec   Float 3 to dec

Psychic Glove               10 C0 F5 03        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            07 66 A5 04        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Teleporter Patches          1C 2B 9F 08        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            79 6A EA 0C        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            35 AA 9D 0E        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            22 44 44 10        00 00 00 00        00 00 00 00        00 00 80 3F                     1

Super Thunder Shot          BD A6 E8 13        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Psychic Shot                6D 69 68 18        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Green Shards                66 51 03 1A        00 00 AF 46        00 00 AF 46        F0 23 74 49        22400        999999

Thunder Chip                04 97 8C 1E        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Gal Fed Mech Parts          64 A9 21 23        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            BD 82 FE 23        00 00 C8 42        00 00 C8 42        00 00 C8 42        100          100

                            E5 77 80 25        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            FD 04 8F 25        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            07 BC 73 26        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

charged ice Shot            97 42 B4 27        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Psychic Boots               05 7C B5 29        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            A8 2E FE 2A        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Ice shot                    71 64 03 2B        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Psychic Crystal/Visor       DF D2 69 2C        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            A0 3C 6C 2D        00 00 80 40        00 00 80 40        00 00 80 40        4            4

Spring Ball                 77 1D 12 2E        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Space Jump Boots            E6 BB 27 35        00 00 40 40        00 00 40 40        00 00 40 40        3            3

                            CB 91 7F 35        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            5A D7 7F 3D        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            BA 15 D4 44        00 00 00 00        00 00 00 00        00 00 80 3F                     1

                            C7 7C DB 48        00 00 80 3F        00 00 80 3F        00 00 00 40        1            2

Missiles                    06 9C 51 49        00 00 7A 43        00 00 7A 43        00 00 7A 43        250          250

                            CE 41 51 4D        00 00 80 3F        00 00 80 3F        00 00 80 42        1            64

                            1C 5D 23 53        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            DD 54 99 5E        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Vi-O-La IC Suit/Vi-O-La IC  D3 2E C3 5F        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            FE 04 E3 6A        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Fire Chip                   82 96 DD 6C        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            85 40 45 6F        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            0A 97 20 71        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Psychic Charge Shot         52 21 65 73        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Vi-O-La Suit                99 52 4F 74        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Morph Bomb                  76 0C C9 76        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            0B 58 59 7A        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Teleporter Chip             F8 1B C6 7D        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Psychic Power Bomb          F4 B9 C7 84        00 00 00 41        00 00 00 41        00 00 00 41        8            8

Fire Shot                   F7 C2 D8 85        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Thunder Shot                4F 53 59 8A        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Psychic Grapple             84 7F 51 8C        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            DA 68 CA 8E        00 00 A0 40        00 00 A0 40        00 00 A0 40        5            5

Tanks                       A6 12 FB 97        00 60 BB 44        00 60 BB 44        00 60 BB 44        1499         1499

                            B1 13 03 9D        00 00 00 00        00 00 96 43        00 00 96 43                     300

                            1F 6C D8 9D        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            31 21 C0 A0        00 00 80 3F        00 00 80 3F        00 00 00 41        1            8

                            E3 6B 49 A4        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Shot Ammo                   A3 29 D5 A6        00 80 04 44        00 80 04 44        00 80 04 44        530          530

                            6A 7C A4 AB        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Charged Thunder Shot        71 94 D8 B0        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            5B 01 94 B5        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

Super Missile               A0 0B 34 BC        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            2D 56 4E BC        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            61 C3 D8 BC        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            5F EE C5 BD        00 00 00 00        00 00 00 00        00 00 A0 40                     5

                            76 41 DC BF        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            FA 8D EF C1        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            35 05 1F C2        00 00 00 00        00 00 C6 42        00 00 C6 42                     99

                            16 A7 38 C5        00 00 00 00        00 00 00 00        00 00 40 40                     3

                            74 E4 B3 C7        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            34 AC 2C CF        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            57 48 75 D0        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            FC C4 40 D5        00 00 C0 40        00 00 C0 40        00 00 C6 42        6            99

Boost Ball                  E5 DC 3B D9        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            F2 6C 79 DC        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            06 06 CD DE        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            DD 01 B1 E8        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            D6 0A 10 ED        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            1B 4E 40 F0        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            B9 A4 4D F3        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            CB 9C 03 F9        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1

                            F5 B8 46 FB        00 00 80 3F        00 00 80 3F        00 00 80 3F        1            1
you can use this site if you want to edit float values just enter decimal value in float value box and tick the box Swap to use big-endian https://gregstoll.com/~gregstoll/floattohex/
There a zip file with bat files that edit green shards it should edit the first auto and manual save you just drop save on to them. you can open them in text editor and change amount they write use the gregstoll site to create hex values you don't have to tick swap.
im having some trouble in hard mode can you give me some items to help me out?
 

Attachments

If you guys find more values in the save data then i can add and correct things. i didnt bother to parse the save data myself. just quickly put this together based on what op has put up
the checksum works but stuff without a name might not be connected to what comes before, i have added genesis nova info to first page. I converted py into app using pyinstaller as well as use the updated ids. https://www.mediafire.com/file/6a6anzvua3zcpmg/editor.zip/file
depending on how many slots are filled there will be 2 instances per slot so there will either be 2, 4 or 6 of each. also here is the py file. is it possible to search for a value then have multiple offsets from it.
 

Attachments

Last edited by Pj1980,
  • Like
Reactions: OblivionReign
For those looking to edit the files themselves, the first 4 byte 'value' after the ID is your current amount, second 4 byte value is your current max, and the third is the max value seemingly allowed by the game.
 
  • Like
Reactions: Pj1980
Have you tried editing all 3 values

I have edited the first two on some of the items that aren't a max of 1, such as missiles, shot ammo, and power bombs, and that does track when what I've seen. I saw no reason to look into increasing the third value.

For example here's my current save file's power bomb section.
F4 B9 C7 84 00 00 00 00 00 00 80 40 00 00 00 41
I have 0 current power bombs
Current max of 4, with 8 max allowed by the game.
 
These are the offsets from AF 05 00 00 00 00 for the different scans
Code:
0674    Galactic federation
1974    Galactic federation
1E3C    Galactic federation
2024    Galactic federation
2184    Galactic federation
2BF4    Galactic federation
0104    Larmonian Records
0334    Larmonian Records
037C    Larmonian Records
050C    Larmonian Records
0BEC    Larmonian Records
0EA4    Larmonian Records
0FEC    Larmonian Records
101C    Larmonian Records
1094    Larmonian Records
119C    Larmonian Records
217C    Larmonian Records
256C    Larmonian Records
270C    Larmonian Records
2884    Larmonian Records
28C4    Larmonian Records
29BC    Larmonian Records
2CBC    Larmonian Records
01A4    Larmonian Writings
0544    Larmonian Writings
0754    Larmonian Writings
0B54    Larmonian Writings
0E4C    Larmonian Writings
108C    Larmonian Writings
16C4    Larmonian Writings
1C3C    Larmonian Writings
2014    Larmonian Writings
2A94    Larmonian Writings
0114    Machines
04D4    Machines
0624    Machines
083C    Machines
0A04    Machines
0AE4    Machines
0B4C    Machines
0E5C    Machines
0F4C    Machines
10E4    Machines
12C4    Machines
13BC    Machines
145C    Machines
15BC    Machines
1944    Machines
200C    Machines
250C    Machines
2CFC    Machines
2D24    Machines
001C    Research
0054    Research
02DC    Research
0684    Research
0774    Research
0894    Research
0C44    Research
0CD4    Research
0D84    Research
0EFC    Research
13FC    Research
16CC    Research
179C    Research
181C    Research
1994    Research
1C04    Research
2464    Research
2824    Research
011C    Sylux Troops
07EC    Sylux Troops
09AC    Sylux Troops
0BC4    Sylux Troops
008C    Viewros
00F4    Viewros
046C    Viewros
052C    Viewros
05AC    Viewros
05DC    Viewros
084C    Viewros
08BC    Viewros
0914    Viewros
0A44    Viewros
0A6C    Viewros
0A9C    Viewros
0AF4    Viewros
0B94    Viewros
0C24    Viewros
0C9C    Viewros
0CAC    Viewros
0E1C    Viewros
10A4    Viewros
115C    Viewros
124C    Viewros
12AC    Viewros
12D4    Viewros
133C    Viewros
13F4    Viewros
154C    Viewros
155C    Viewros
1704    Viewros
1744    Viewros
195C    Viewros
19EC    Viewros
1A24    Viewros
1A84    Viewros
1B34    Viewros
1C8C    Viewros
1CFC    Viewros
1D14    Viewros
1EDC    Viewros
1F2C    Viewros
1F9C    Viewros
2074    Viewros
20C4    Viewros
2114    Viewros
2124    Viewros
2214    Viewros
226C    Viewros
235C    Viewros
261C    Viewros
267C    Viewros
27AC    Viewros
291C    Viewros
29D4    Viewros
2D14    Viewros
 
  • Like
Reactions: [Truth]
the checksum works but stuff without a name might not be connected to what comes before, i have added genesis nova info to first page. I converted py into app using pyinstaller as well as use the updated ids. https://www.mediafire.com/file/6a6anzvua3zcpmg/editor.zip/file
depending on how many slots are filled there will be 2 instances per slot so there will either be 2, 4 or 6 of each. also here is the py file. is it possible to search for a value then have multiple offsets from it.

Is this editor updated from the one you showed me yesterday? Does it include other things like log book entries?
 
Is this editor updated from the one you showed me yesterday? Does it include other things like log book entries?
yes editor is updated it now shows different saves and you can edit log scans, check the first post. i used claude.ai to add stuff to original py file like offsets and separate stuff into individual saves. it dose list saves as save 1, save 2 and so on. the odd numbers should be manual saves and even auto saves. Save editor skips completed saves.
 
Last edited by Pj1980,
great!
Post automatically merged:

yes editor is updated it now shows different saves and you can edit log scans, check the first post. i used claude.ai to add stuff to original py file like offsets and separate stuff into individual saves. it dose list saves as save 1, save 2 and so on. the odd numbers should be manual saves and even auto saves. Save editor skips completed saves.
Btw the psychic grapple i added into my inventory doesn't work apparently there's probably another code to activate it like power bombs
Post automatically merged:

yes editor is updated it now shows different saves and you can edit log scans, check the first post. i used claude.ai to add stuff to original py file like offsets and separate stuff into individual saves. it dose list saves as save 1, save 2 and so on. the odd numbers should be manual saves and even auto saves. Save editor skips completed saves.
Does the new enhanced editor contain all the logbook entries?
 
Last edited by Jordo2424,

Site & Scene News

Popular threads in this forum