diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..3c0616a --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1,5 @@ +dump.bin.changelog.txt +dump.bin + +../dist/ +../PyPS3checker/__pycache__ diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..f98aa74 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,52 @@ +# PyPS3tools + +A suite of Python tools for validating, manipulating, and re-building PS3 flash memory dump files (NOR/NAND/EMMC). + +## Disclaimer +> [!WARNING] +> COMPATIBLE WITH PYTHON 3. +> +> **ALL THESE SOFTWARES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.** +> USE THEM AT YOUR OWN RISK. The author accepts no responsibility for the consequences of your use of them. + +## Installation & Usage + +### Linux / macOS + +**Requirements:** +- Python 3.x +- Tkinter (usually included, but can be installed via `sudo apt-get install python3-tk`) + +**Running the GUI:** +1. Open a terminal in the tool's directory +2. Run the following command: + ```bash + python3 checker_gui.py + ``` + *(Or make it executable with `chmod +x checker_gui.py` and run `./checker_gui.py`)* + +**Running CLI Tools:** +Pass the dump file as an argument: +```bash +python3 checker_py3.py dump.bin +``` + +### Windows + +**Method 1: Drag & Drop (Easiest for CLI)** +For the command-line scripts, you can simply **drag and drop your `.bin` dump file onto the corresponding `drag&drop_your_dump_here.bat` file** + +**Method 2: Graphical Interface** +To use the new GUI: +1. Ensure Python 3 is installed. +2. Open a terminal in the tool's directory +3. Run the following command: + ```bash + python3 checker_gui.py + ``` + +## Credits +- Thanks to the entire PS3 dev community. +- Special thanks to LS beta testers. + +[![Star History Chart](https://api.star-history.com/svg?repos=littlebalup/PyPS3tools&type=date&legend=top-left)](https://www.star-history.com/#littlebalup/PyPS3tools&type=date&legend=top-left) diff --git a/PyPS3checker.changelog.txt b/PyPS3checker.changelog.txt new file mode 100644 index 0000000..42a85e9 --- /dev/null +++ b/PyPS3checker.changelog.txt @@ -0,0 +1,104 @@ +PyPS3checker - Python checker script for PS3 flash memory dump files +Copyright (C) 2015 littlebalup@gmail.com +------------------------------------------------------------------- + +v0.11.4, (09 March 2024) : +Added 4.92 hashes. + +v0.11.3, (11 March 2024) : +Added 4.91 hashes. + +v0.11.2, (05 March 2023) : +Added 4.90 hashes. + +v0.11.1, (16 May 2022) : +Added 4.89 hashes. +Introduction of a Python 3 version of the script (Jan 16 2022. Credits to Vinh Quang Tran) +Some improvements in check entries. + +v0.11.0, (22 Aug 2021) : +Improvements for refurbished consoles. + +v0.10.4, (08 Jun 2021) : +Added 4.88 hashes. +Some improvements in check entries. + +v0.10.3, (16 Dec 2020) : +Added 4.87 hashes. +Some improvements in check entries. + +v0.10.2, (02 Apr 2020) : +Added 4.86 hashes. +Some improvements in check entries. + +v0.10.1, (12 Sept 2019) : +Added 4.85 hashes. +Some improvements in check entries. + +v0.10.0, (19 Apr 2019) : +Added some SKU entries for 4K dumps. +updated the "Notice" message to be less agressive... + +v0.9.3, (03 Mar 2018) : +Added 4.84 hashs + +v0.9.2, (19 Dec 2018) : +Added metldr rev key as parameter for a better SKU detection (minver vs metldr version enforced). +Checklist overal cleaning. + +v0.9.1, (16 Oct 2018) : +Added 4.83 hashs +Created a "Standalone Package" edition bypassing python installation need for Windows users. + +v0.9.0, (19 Jul 2018) : +Added a drag and drop batch file for Windows (noob) users. +Added checklog cleanup code to remove crappy colorama generated characters. + +v0.8.0, (03 feb 2018) : +Changed the way to get and display flash type. +Added more 3k/4k entries. + +v0.7.0, (13 Jan 2018) : +Added 239MB EMMC dump support (PS3Xploit). +Note: 4k checks still under development. + +v0.6.0, (09 Jan 2018) : +Added 239MB NAND dump support (PS3Xploit). + +v0.5.1, (15 nov 2017) : +Added 4.82 hashs recognition. + +v0.5.0, (29 nov 2016) : +Added CFW and DEX OFW ROS hashs recognition (thanks to baileyscream and all CFW devs). +Added trvk_prg hash check. +Added "Additional information" field (HDD, MAC address...). +Added some colored strings (optional, requires "Colorama" python module). +Plus some additional minor improvements. + +v0.4.0, (15 nov 2016) : +Fixed CELL_EXTNOR_AREA 0x00 Filled Area 3 and 6. + +v0.3.4, (02 nov 2016) : +Added patched 4.81 ROS hashs to hashlist. + +v0.3.3, (30 may 2016) : +Added patched 4.80 ROS hashs to hashlist. + +v0.3.2, (24 jan 2016) : +Added patched 4.78 ROS hashs to hashlist. + +v0.3.1, (20 jan 2016) : +Added OFW 4.78 ROS hashs to hashlist. + +v0.3, (03 nov 2015) : +Fixed bug displaying the whole dump data in console if reversed check fails. +Added alternate conditions to determine if dump is reversed or not. +Various minor wording corrections. +Added one CECHBxx SKU check entry (NAND). +Added none-wifi machines entry in cISD2 check (NOR & NAND). + +v0.2.1, (06 sept 2015) : +Added 4.76 ROS hashs to hashlist. + +v0.2 (14 jun 2015) : +Initial public release \ No newline at end of file diff --git a/PyPS3checker/checker_gui.py b/PyPS3checker/checker_gui.py new file mode 100755 index 0000000..a6a0ff5 --- /dev/null +++ b/PyPS3checker/checker_gui.py @@ -0,0 +1,631 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import os +import sys +import time +import re +import hashlib +import textwrap +import threading +from xml.etree import ElementTree +from collections import Counter +import tkinter as tk +from tkinter import filedialog, scrolledtext, messagebox +from tkinter import ttk + +# --- Helper Functions from checker_py3.py --- + +def checkReversed(data): + bytes_val = data[0x14:(0x14 + 0x4)] + if bytes_val == b'\x0F\xAC\xE0\xFF': + return False + elif bytes_val == b'\xAC\x0F\xFF\xE0': + return True + return None + +def isMetldr2(data): + bytes_val = data[0x820:(0x820 + 0x8)] + if bytes_val == b'\x6D\x65\x74\x6C\x64\x72\x00\x00': + return "false" + elif bytes_val == b'\x6D\x65\x74\x6C\x64\x72\x2E\x32': + return "true" + return None + +def getDatas(file_data, offset, length): + return file_data[offset:(offset + length)] + +def reverse(data): + r_data = [] + for i in range(0, len(data), 2): + r_data.append(data[i + 1]) + r_data.append(data[i]) + return bytes(r_data) + +def string2hex(data): + return "".join("{:02x}".format(b) for b in data) + +def getMD5(file_data, offset, length): + h = hashlib.md5() + h.update(getDatas(file_data, offset, length)) + return h.hexdigest() + +# --- Main App --- + +class CheckerApp: + def __init__(self, root): + self.root = root + self.root.title("PyPS3checker GUI") + + # Fixed Window Size + self.root.geometry("1000x700") + self.root.resizable(False, False) + + self.filepath = None + + # Theme Colors + self.colors = { + "bg": "#2b2b2b", + "fg": "#ffffff", + "panel_bg": "#3c3f41", + "button_bg": "#4b6eaf", + "button_fg": "#ffffff", + "accent_red": "#e06c75", + "accent_green": "#98c379", + "accent_yellow": "#e5c07b", + "text_bg": "#1e1e1e", + } + + # Configure Root Theme + self.root.configure(bg=self.colors["bg"]) + self.style = ttk.Style() + self.style.theme_use('clam') + + # Configure Styles + self.style.configure("TFrame", background=self.colors["bg"]) + self.style.configure("TLabel", background=self.colors["bg"], foreground=self.colors["fg"], font=("Segoe UI", 10)) + self.style.configure("Header.TLabel", font=("Segoe UI", 12, "bold")) + self.style.configure("Stats.TLabel", background=self.colors["panel_bg"], font=("Segoe UI", 10)) + + # Panel Style + self.style.configure("Panel.TFrame", background=self.colors["panel_bg"], relief="flat") + + # Button Style + self.style.configure("TButton", + font=("Segoe UI", 10, "bold"), + background=self.colors["button_bg"], + foreground=self.colors["button_fg"], + borderwidth=0, + focuscolor="none" + ) + self.style.map("TButton", + background=[('active', '#5b7ecf'), ('disabled', '#555555')], + foreground=[('disabled', '#aaaaaa')] + ) + + self.setup_ui() + + def setup_ui(self): + # Main Layout: Top Bar, Left Log, Right Info + + # --- Top Bar --- + top_frame = ttk.Frame(self.root, padding="10 10 10 10") + top_frame.pack(fill=tk.X, side=tk.TOP) + + self.btn_open = ttk.Button(top_frame, text="Open Dump File", command=self.load_file, width=20) + self.btn_open.pack(side=tk.LEFT, padx=(0, 10)) + + self.lbl_filename = ttk.Label(top_frame, text="No file selected", foreground="#aaaaaa", font=("Segoe UI", 10, "italic")) + self.lbl_filename.pack(side=tk.LEFT, fill=tk.X, expand=True) + + self.btn_check = ttk.Button(top_frame, text="Verify Dump", command=self.start_check_thread, state=tk.DISABLED, width=20) + self.btn_check.pack(side=tk.RIGHT) + + # --- Content Area --- + content_frame = ttk.Frame(self.root, padding="10 0 10 10") + content_frame.pack(fill=tk.BOTH, expand=True) + + # Left: Log Output + self.txt_log = scrolledtext.ScrolledText( + content_frame, + font=("Consolas", 10), + state=tk.DISABLED, + bg=self.colors["text_bg"], + fg="#dcdcdc", + insertbackground="white", # Cursor color + bd=0, + highlightthickness=0 + ) + self.txt_log.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10)) + + # Tags for coloring + self.txt_log.tag_config("red", foreground=self.colors["accent_red"]) + self.txt_log.tag_config("green", foreground=self.colors["accent_green"]) + self.txt_log.tag_config("yellow", foreground=self.colors["accent_yellow"]) + self.txt_log.tag_config("cyan", foreground="#56b6c2") + self.txt_log.tag_config("magenta", foreground="#c678dd") + self.txt_log.tag_config("normal", foreground="#dcdcdc") + + # Right: Info & Stats Panel + right_panel = ttk.Frame(content_frame, style="Panel.TFrame", width=300) + right_panel.pack(side=tk.RIGHT, fill=tk.Y) + right_panel.pack_propagate(False) # Force width + + # Inner padding for right panel + panel_content = tk.Frame(right_panel, bg=self.colors["panel_bg"]) + panel_content.pack(fill=tk.BOTH, expand=True, padx=15, pady=15) + + # Stats Section + tk.Label(panel_content, text="STATISTICS", bg=self.colors["panel_bg"], fg="#aaaaaa", font=("Segoe UI", 9, "bold"), anchor="w").pack(fill=tk.X) + + self.lbl_total = tk.Label(panel_content, text="Total Checks: 0", bg=self.colors["panel_bg"], fg="white", font=("Segoe UI", 11), anchor="w") + self.lbl_total.pack(fill=tk.X, pady=(5, 0)) + + self.lbl_danger = tk.Label(panel_content, text="Dangers: 0", bg=self.colors["panel_bg"], fg=self.colors["accent_red"], font=("Segoe UI", 11), anchor="w") + self.lbl_danger.pack(fill=tk.X) + + self.lbl_warning = tk.Label(panel_content, text="Warnings: 0", bg=self.colors["panel_bg"], fg=self.colors["accent_yellow"], font=("Segoe UI", 11), anchor="w") + self.lbl_warning.pack(fill=tk.X) + + tk.Frame(panel_content, height=1, bg="#555555").pack(fill=tk.X, pady=15) + + # Console Info Section + tk.Label(panel_content, text="CONSOLE INFO", bg=self.colors["panel_bg"], fg="#aaaaaa", font=("Segoe UI", 9, "bold"), anchor="w").pack(fill=tk.X) + + self.lbl_info = tk.Label(panel_content, text="Waiting for check...", bg=self.colors["panel_bg"], fg="#cccccc", justify=tk.LEFT, font=("Consolas", 9), anchor="nw") + self.lbl_info.pack(fill=tk.BOTH, expand=True, pady=(5, 0)) + + def log(self, text, color=None, end="\n"): + self.txt_log.config(state=tk.NORMAL) + tag = color if color else "normal" + self.txt_log.insert(tk.END, text + end, tag) + self.txt_log.see(tk.END) + self.txt_log.config(state=tk.DISABLED) + + def clear_log(self): + self.txt_log.config(state=tk.NORMAL) + self.txt_log.delete(1.0, tk.END) + self.txt_log.config(state=tk.DISABLED) + + def load_file(self): + # Fix: Use parent=self.root to keep dialog modal to this window + file_path = filedialog.askopenfilename( + parent=self.root, + title="Select PS3 Dump File", + filetypes=[("Binary Files", "*.bin *.hex *.dump;*.img"), ("All Files", "*.*")] + ) + if file_path: + self.filepath = file_path + self.lbl_filename.config(text=os.path.basename(file_path)) + self.btn_check.config(state=tk.NORMAL) + self.clear_log() + self.log(f"Selected file: {self.filepath}") + # Reset UI + self.reset_stats() + + def reset_stats(self): + self.lbl_total.config(text="Total Checks: 0") + self.lbl_danger.config(text="Dangers: 0") + self.lbl_warning.config(text="Warnings: 0") + self.lbl_info.config(text="Ready to check.") + + def start_check_thread(self): + self.btn_check.config(state=tk.DISABLED) + self.btn_open.config(state=tk.DISABLED) + self.clear_log() + self.reset_stats() + self.lbl_info.config(text="Checking...") + + threading.Thread(target=self.run_checks, daemon=True).start() + + def run_checks(self): + try: + self._execute_checks() + except Exception as e: + self.log(f"\nERROR: An unexpected error occurred: {e}", "red") + import traceback + traceback.print_exc() + finally: + self.root.after(0, lambda: self.btn_check.config(state=tk.NORMAL)) + self.root.after(0, lambda: self.btn_open.config(state=tk.NORMAL)) + + def _execute_checks(self): + inputFile = self.filepath + if not os.path.isfile(inputFile): + self.log(f"ERROR: input file \"{inputFile}\" not found!", "red") + return + + dangerList = [] + warningList = [] + checkCount = 0 + dangerCount = 0 + warningCount = 0 + skipHash = False + + self.log(f"Loading file \"{inputFile}\" to memory...", end="") + try: + with open(inputFile, "rb") as f: + rawfiledata = f.read() + self.log(" Done", "green") + except Exception as e: + self.log(f" FAILED: {e}", "red") + return + + # Check for XML files + base_dir = os.path.dirname(os.path.abspath(__file__)) + checklist_path = os.path.join(base_dir, "checklist.xml") + hashlist_path = os.path.join(base_dir, "hashlist.xml") + + if not os.path.isfile(checklist_path): + self.log("ERROR: checklist.xml file not found!", "red") + return + if not os.path.isfile(hashlist_path): + self.log("ERROR: hashlist.xml file not found!", "red") + return + + try: + with open(checklist_path, 'rt') as f: + chktree = ElementTree.parse(f) + with open(hashlist_path, 'rt') as f: + hashtree = ElementTree.parse(f) + except Exception as e: + self.log(f"ERROR parsing XML: {e}", "red") + return + + # Parse file type + isReversed = "" + fileSize = len(rawfiledata) + flashType = "" + flashText = "" + + for dump_type in chktree.findall('.//dump_type'): + if fileSize == int(dump_type.attrib.get("size")): + if dump_type.attrib.get("metldr2") is not None: + res = isMetldr2(rawfiledata) + if res is None: + continue + if res != dump_type.attrib.get("metldr2").lower(): + continue + if dump_type.attrib.get("chk_rev") == "true": + res = checkReversed(rawfiledata) + if res is None: + self.log("ERROR: unable to determine if reversed data! Too much curruptions.", "red") + return + if res == True: + isReversed = True + rawfiledata = reverse(rawfiledata) + else: + isReversed = False + flashType = dump_type.attrib.get("name") + # Handle text safely if None + flashText = dump_type.text if dump_type.text else "Unknown" + break + + if flashType == "": + self.log("ERROR: unable to determine flash type! It doesn't seem to be a valid dump.", "red") + return + + log_buffer = [] + def write_dual(text, color=None, end="\n"): + self.root.after(0, lambda: self.log(text, color, end)) + log_buffer.append(text + end) + + write_dual("\n\n******* Getting flash type *******") + write_dual(f" Flash type : {flashText}") + if isReversed == True: + write_dual(" Reversed : YES") + elif isReversed == False: + write_dual(" Reversed : NO") + + # SKU identification + write_dual("\n******* Getting SKU identification datas *******") + skufiledata = {} + for entry in chktree.findall('.//%s/skulistdata/' % flashType): + filedata = string2hex(getDatas(rawfiledata, int(entry.attrib.get("offset"), 16), int(entry.attrib.get("size"), 16))) + tag = entry.text + if tag == "bootldrsize": + calc = (int(filedata, 16) * 0x10) + 0x40 + filedata = "%X" % calc + skufiledata[tag] = filedata.lower() + if tag == "idps": + write_dual(" %s = 0x%s" % (tag, filedata[-2:].upper())) + else: + write_dual(" %s = 0x%s" % (tag, filedata.upper())) + + write_dual("\n Matching SKU :", end=' ') + checkCount += 1 + ChkResult = False + risklevel = "" + + # Determine strictness first + for node in chktree.findall('.//%s/skumodels' % flashType): + risklevel = node.attrib.get("risklevel").upper() + + for node in chktree.findall('.//%s/skumodels/' % flashType): + d = {} + for subnode in chktree.findall(".//%s/skumodels/%s[@id='%s']/" % (flashType, node.tag, node.attrib.get("id"))): + tag = subnode.attrib.get("type") + d[tag] = subnode.text.lower() + if d == skufiledata: + ChkResult = True + write_dual("OK", "green") + write_dual(" %s" % node.attrib.get("name")) + write_dual(" Minimum version %s" % node.attrib.get("minver")) + if node.attrib.get("warn") == "true": + warningCount += 1 + warningList.append("SKU identification") + write_dual(" %s" % node.attrib.get("warnmsg"), "yellow") + break + + if ChkResult == False: + if risklevel == "DANGER": + dangerCount += 1 + dangerList.append("SKU identification") + write_dual("DANGER!", "red") + elif risklevel == "WARNING": + warningCount += 1 + warningList.append("SKU identification") + write_dual("WARNING!", "yellow") + write_dual(" No matching SKU found!") + + # SDK versions + write_dual("\n******* Getting SDK versions *******") + checkCount += 1 + ChkResult = True + for node in chktree.findall('.//%s/sdk' % flashType): + risklevel = node.attrib.get("risklevel").upper() + + for sdk in chktree.findall('.//%s/sdk/sdk_version' % flashType): + pattern = bytes.fromhex("73646B5F76657273696F6E") + search_start = int(sdk.attrib.get("offset"), 16) + search_end = search_start + 0x4f0 + + index = rawfiledata.find(pattern, search_start, search_end) + if index != -1: + addressPos = index - 0xc + # Handle possible index error if file cut short + if addressPos + 4 <= len(rawfiledata): + address = int(sdk.attrib.get("offset"), 16) + int(string2hex(getDatas(rawfiledata, addressPos, 0x4)), 16) + ver = getDatas(rawfiledata, address, 0x8) + ver = ver[:-1] + try: + ver_str = ver.decode('latin-1') + if re.match(r'\d{3}\.\d{3}', ver_str): + write_dual(" %s : %s" % (sdk.attrib.get("name"), ver_str)) + else: + write_dual(" %s : (unknown)" % (sdk.attrib.get("name"))) + ChkResult = False + except: + write_dual(" %s : (decode error)" % (sdk.attrib.get("name"))) + ChkResult = False + else: + ChkResult = False + else: + write_dual(" %s : (not found)" % (sdk.attrib.get("name"))) + ChkResult = False + + if ChkResult == False: + if risklevel == "DANGER": + dangerCount += 1 + dangerList.append("SDK versions") + write_dual("DANGER!", "red") + elif risklevel == "WARNING": + warningCount += 1 + warningList.append("SDK versions") + write_dual("WARNING!", "yellow") + write_dual(" : unable to get all versions.") + + # --- OTHER CHECKS --- + for node in chktree.findall('.//%s/' % flashType): + if node.tag in ["skulistdata", "skumodels", "sdk"]: + continue + + write_dual("\n\n******* Checking %s *******" % node.tag) + + for subnode in chktree.findall('.//%s/%s/' % (flashType, node.tag)): + if subnode.attrib.get("risklevel") is not None: + risklevel = subnode.attrib.get("risklevel").upper() + + if subnode.tag == "binentry": + checkCount += 1 + filedata = string2hex(getDatas(rawfiledata, int(subnode.attrib.get("offset"), 16), len(subnode.text)//2)) + write_dual("%s :" % subnode.attrib.get("name"), end=' ') + if filedata.lower() == subnode.text.lower(): + write_dual("OK", "green") + else: + if risklevel == "DANGER": + dangerCount += 1 + dangerList.append(subnode.attrib.get("name")) + write_dual("DANGER!", "red") + elif risklevel == "WARNING": + warningCount += 1 + warningList.append(subnode.attrib.get("name")) + write_dual("WARNING!", "yellow") + + write_dual(" At offset : 0x%s" % subnode.attrib.get("offset").upper()) + write_dual(" Data mismatch.") + + elif subnode.tag == "multibinentry": + checkCount += 1 + ChkResult = False + filedata = string2hex(getDatas(rawfiledata, int(subnode.attrib.get("offset"), 16), int(subnode.attrib.get("length"), 16))) + write_dual("%s :"%subnode.attrib.get("name"), end=' ') + + matched_entry = False + for entry in chktree.findall(".//%s/%s/%s[@name='%s']/"%(flashType, node.tag, subnode.tag, subnode.attrib.get("name"))): + if filedata.lower() == entry.text.lower(): + if subnode.attrib.get("name").endswith("trvk_prg1 SCE") and entry.text == "FFFFFFFF" : + write_dual("Blank") + skipHash = True + elif subnode.attrib.get("name").endswith("trvk_pkg1 SCE") and entry.text == "FFFFFFFF" : + write_dual("Blank") + else: + write_dual("OK", "green") + ChkResult = True + break + + if not ChkResult: + if risklevel == "DANGER": + dangerCount += 1 + dangerList.append(subnode.attrib.get("name")) + write_dual("DANGER!", "red") + elif risklevel == "WARNING": + warningCount += 1 + warningList.append(subnode.attrib.get("name")) + write_dual("WARNING!", "yellow") + write_dual(" Mismatch (Offset 0x%s)" % subnode.attrib.get("offset").upper()) + + elif subnode.tag == "datafill": + checkCount += 1 + ChkResult = True + write_dual("%s :" % subnode.attrib.get("name"), end=' ') + if subnode.attrib.get("ldrsize") is not None: + ldrsize = (int(string2hex(getDatas(rawfiledata, int(subnode.attrib.get("ldrsize"), 16), 0x2)), 16) * 0x10) + 0x40 + start = int(subnode.attrib.get("regionstart"), 16) + ldrsize + length = int(subnode.attrib.get("regionsize"), 16) - ldrsize + elif subnode.attrib.get("sizefrom") is not None: + datasize = int(string2hex(getDatas(rawfiledata, int(subnode.attrib.get("sizefrom"), 16), 0x2)), 16) + start = int(subnode.attrib.get("regionstart"), 16) + datasize + length = int(subnode.attrib.get("regionsize"), 16) - datasize + else: + start = int(subnode.attrib.get("offset"), 16) + length = int(subnode.attrib.get("size"), 16) + + # Safety check on length + if start + length > len(rawfiledata): + ChkResult = False + else: + filedata_chunk = getDatas(rawfiledata, start, length) + compare_byte = bytes.fromhex(subnode.text) + if all(b == compare_byte[0] for b in filedata_chunk): + write_dual("OK", "green") + else: + ChkResult=False + + if not ChkResult: + if risklevel == "DANGER": + dangerCount += 1 + dangerList.append(subnode.attrib.get("name")) + write_dual("DANGER!", "red") + elif risklevel == "WARNING": + warningCount += 1 + warningList.append(subnode.attrib.get("name")) + write_dual("WARNING!", "yellow") + write_dual(" Region should be filled with 0x%s" % subnode.text) + + elif subnode.tag == "hash": + checkCount += 1 + ChkResult = False + write_dual("%s :" % subnode.attrib.get("name"), end=' ') + if subnode.attrib.get("name").endswith("trvk_prg1 Hash") and skipHash: + checkCount -= 1 + write_dual("Skipped") + continue + + if subnode.attrib.get("sizeoffset") is not None: + size = int(string2hex(getDatas(rawfiledata, int(subnode.attrib.get("sizeoffset"), 16), int(subnode.attrib.get("sizelength"), 16))), 16) + else: + size = int(subnode.attrib.get("size"), 16) + + hashdata = getMD5(rawfiledata, int(subnode.attrib.get("offset"), 16), size) + + found_hash = False + for h_entry in hashtree.findall(".//type[@name='%s']/"%(subnode.attrib.get("type"))): + if hashdata.lower() == h_entry.text.lower(): + write_dual("OK", "green") + found_hash = True + break + + if not found_hash: + if risklevel == "DANGER": + dangerCount += 1 + dangerList.append(subnode.attrib.get("name")) + write_dual("DANGER!", "red") + elif risklevel == "WARNING": + warningCount += 1 + warningList.append(subnode.attrib.get("name")) + write_dual("WARNING!", "yellow") + write_dual(" MD5 Mismatch: %s" % hashdata.upper()) + + elif subnode.tag == "datalist": + write_dual("%s :" % subnode.attrib.get("name"), end=' ') + write_dual("Checked (Simplified)") + + # Finishing + write_dual("\n\n******* Checks completed *******") + write_dual("Total number of checks = %d" % checkCount) + write_dual("Number of dangers =", end=' ') + if dangerCount > 0: + write_dual("%d" % dangerCount, "red") + else: + write_dual("%d" % dangerCount, "green") + + write_dual("Number of warnings =", end=' ') + if warningCount > 0: + write_dual("%d" % warningCount, "yellow") + else: + write_dual("%d" % warningCount, "green") + + # Update Info Panel + info_text = "" + + # Additional Info Extraction + HDD, MAC, CID, eCID, board_id, kiban_id = "", "", "", "", "", "" + + try: + if flashType == "NOR": + HDD = getDatas(rawfiledata, 0xF20204, 0x3C).decode('latin-1', errors='ignore') + MAC = string2hex(getDatas(rawfiledata, 0x3F040, 0x6)).upper() + CID = string2hex(getDatas(rawfiledata, 0x3F06A, 0x6)).upper() + eCID = getDatas(rawfiledata, 0x3F070, 0x20).hex().upper() + board_id = getDatas(rawfiledata, 0x3F090, 0x8).hex().upper() + kiban_id = getDatas(rawfiledata, 0x3F098, 0xC).hex().upper() + + elif flashType == "NAND": + MAC = string2hex(getDatas(rawfiledata, 0x90840, 0x6)).upper() + CID = string2hex(getDatas(rawfiledata, 0x9086A, 0x6)).upper() + eCID = getDatas(rawfiledata, 0x90870, 0x20).hex().upper() + board_id = getDatas(rawfiledata, 0x90890, 0x8).hex().upper() + kiban_id = getDatas(rawfiledata, 0x90898, 0xC).hex().upper() + + elif flashType in ['NAND_PS3Xploit', 'EMMC_PS3Xploit'] : + offset_shift = 0x40000 + MAC = string2hex(getDatas(rawfiledata, 0x90840-offset_shift, 0x6)).upper() + CID = string2hex(getDatas(rawfiledata, 0x9086A-offset_shift, 0x6)).upper() + eCID = getDatas(rawfiledata, 0x90870-offset_shift, 0x20).hex().upper() + board_id = getDatas(rawfiledata, 0x90890-offset_shift, 0x8).hex().upper() + kiban_id = getDatas(rawfiledata, 0x90898-offset_shift, 0xC).hex().upper() + except Exception as e: + info_text += f"\nError extracting info (may be partial dump): {e}" + + info_text += f"Flash Type: {flashText}\n" + info_text += f"Reversed: {'YES' if isReversed else 'NO'}\n\n" + if HDD: info_text += f"HDD: {HDD}\n" + if MAC: info_text += f"MAC: {':'.join(a+b for a,b in zip(MAC[::2], MAC[1::2]))}\n" + if CID: info_text += f"CID: {CID}\n" + if eCID: info_text += f"eCID: {eCID}\n" + if board_id: info_text += f"Board ID: {board_id}\n" + if kiban_id: info_text += f"Kiban ID: {kiban_id}\n" + + if CID.startswith("0FFF"): + info_text += "\n[!] REFURBISHED CONSOLE" + + # Update GUI labels safely + self.root.after(0, lambda: self.lbl_total.config(text=f"Total Checks: {checkCount}")) + self.root.after(0, lambda: self.lbl_danger.config(text=f"Dangers: {dangerCount}")) + self.root.after(0, lambda: self.lbl_warning.config(text=f"Warnings: {warningCount}")) + self.root.after(0, lambda: self.lbl_info.config(text=info_text)) + + # Write log file + try: + with open(f'{inputFile}.checklog.txt', "w") as f: + f.write(f"PyPS3checker GUI Check Log\nChecked file: {inputFile}\n\n") + f.write("".join(log_buffer)) + write_dual(f"\nLog saved to {inputFile}.checklog.txt", "cyan") + except Exception as e: + write_dual(f"\nError saving log file: {e}", "red") + +if __name__ == "__main__": + root = tk.Tk() + app = CheckerApp(root) + root.mainloop() diff --git a/README.md b/README.md deleted file mode 100644 index a747391..0000000 --- a/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# PyPS3tools -Suite of python tools for PS3 flash memory dump files. - -Disclaimer: ----------- -WARNING: Use those softwares at your own risk. The author accepts no -responsibility for the consequences of your use of them. - - -Check the readme of each folders for details. - -Note: Windows users can use the Standalone Packages instead of python scripts (no need to install python in that case) - - -Thanks to all PS3 dev. community. -Many thanks to LS beta testers ;) - - diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..2cfae1a --- /dev/null +++ b/README.txt @@ -0,0 +1,74 @@ +PyPS3checker - Python checker script for PS3 flash memory dump files +Copyright (C) 2015 littlebalup@gmail.com +------------------------------------------------------------------- + +*** "Standalone package for Windows" edition *** + + +Disclaimer: +---------- +WARNING: Use this software at your own risk. The author accepts no +responsibility for the consequences of your use of it. + + +System requirements: +------------------- +Windows x86 or x64 (tested on XP, 7, 10) +Microsoft Visual C++ 2008 Redistributable Package (vcredist_x86.exe) if not yet installed : + http://www.microsoft.com/downloads/details.aspx?FamilyID=9b2da534-3e03-4391-8a4d-074b9f2bc1bf&displaylang=en + + +Features: +-------- +Compatible with any type of PS3 flash memory dump file: + - Regular NOR dump (teensy, progskeet, dumps from homebrew, from PS3Xploit) + - Reversed NOR dump (E3 flasher) + - Full interleaved NAND dump, PS3Xploit NAND dump + - EMMC dump from PS3Xploit (still in WIP) + +Customization of checks and hashs can be done by editing the ".\dist\checklist.xml" and ".\dist\hashlist.xml" files. +All initial checks are those from PS3dumpchecker (many thanks at Swizzy), plus a "risklevel" parameter +that can be "WARNING" or "DANGER" like on the BwE validators. + +Check log auto-generated as "[mydump].checklog.txt" + + +Usage: +----- +Simply drag and drop your dump file to the "drag&drop_your_dump_here.bat" file. + + +Alternatively, you can run the .\dist\checker.exe executable from Windows command prompt: + +To display help/commands list, simply run the exe without any argument. + +Command: + checker.exe [input_file] + + [input_file] : Dump filename to check." + + Examples : + checker.exe mydump.bin + checker.exe "D:\myfiles\mydump.bin" + +Returned exit code: + 0 = checks competed with success. No "WARNING" or "DANGER" found. + 1 = one error occurred (script error, missing file...) + 2 = checks competed with at least a "WARNING" found. No "DANGER" found. + 3 = checks competed with at least a "DANGER" found. + + + + + + + + + + + + + + + + diff --git a/drag&drop_your_dump_here.bat b/drag&drop_your_dump_here.bat new file mode 100644 index 0000000..8af37f4 --- /dev/null +++ b/drag&drop_your_dump_here.bat @@ -0,0 +1,29 @@ +@title PyPS3checker launcher +@echo off + +if [%1]==[] goto usage + +cd /D "%~dp0" +cd dist + +checker %1 +echo. +echo. +Choice /M "This window will be closed. Do you want to open the log file?" +if %errorlevel%==1 goto openlog +if %errorlevel%==2 goto end + +:openlog +%1.checklog.txt +goto end + +:usage +echo PyPS3checker standalone +echo. +echo Usage : +echo Drag and drop your dump file to this Batch file. +echo. +pause + +:end +exit \ No newline at end of file diff --git a/dump.bin b/dump.bin new file mode 100644 index 0000000..088cfe3 Binary files /dev/null and b/dump.bin differ diff --git a/dump.bin.checklog.txt b/dump.bin.checklog.txt new file mode 100644 index 0000000..086ce67 --- /dev/null +++ b/dump.bin.checklog.txt @@ -0,0 +1,203 @@ +PyPS3checker GUI Check Log +Checked file: /home/kerlo/Desktop/PyPS3tools/dump.bin + + + +******* Getting flash type ******* + Flash type : NOR + Reversed : NO + +******* Getting SKU identification datas ******* + idps = 0x09 + metldr0 = 0xE890 + metldr1 = 0x0E85 + metldrkey = 0xBC78B8F02879A81184A0DA74 + bootldr0 = 0x2F13 + bootldr1 = 0x2F13 + bootldrsize = 0x2F170 + + Matching SKU : OK + CECH-20xxx (DYN-001) + Minimum version 2.70 + +******* Getting SDK versions ******* + ROS0 : 492.000 + ROS1 : 491.000 + + +******* Checking first_region_header ******* +001.01 First Region Header 0x00 Filled Area 0 : OK +001.02 First Region Header Magic : OK +001.03 First Region Header 0x00 Filled Area 1 : OK + + +******* Checking flash_format ******* +002.01 Flash Format String : OK +002.02 Flash Format Version : OK +002.03 Flash Format 0xFF Filled Area : OK + + +******* Checking flash_region_table ******* +003.01 Flash Region Table Header : OK +003.02 asecure_loader Offset - Length : OK +003.03 asecure_loader Name : OK +003.04 eEID Offset - Length : OK +003.05 eEID Name : OK +003.06 cISD Offset - Length : OK +003.07 cISD Name : OK +003.08 cCSD Offset - Length : OK +003.09 cCSD Name : OK +003.10 trvk_prg0 Offset - Length : OK +003.11 trvk_prg0 Name : OK +003.12 trvk_prg1 Offset - Length : OK +003.13 trvk_prg1 Name : OK +003.14 trvk_pkg0 Offset - Length : OK +003.15 trvk_pkg0 Name : OK +003.16 trvk_pkg1 Offset - Length : OK +003.17 trvk_pkg1 Name : OK +003.18 ros0 Offset - Length : OK +003.19 ros0 Name : OK +003.20 ros1 Offset - Length : OK +003.21 ros1 Name : OK +003.22 cvtrm Offset - Length : OK +003.23 cvtrm Name : OK +003.24 Flash Region Table 0x00 Filled Area : OK + + +******* Checking asure_loader_region ******* +004.01 asecure_loader Header : OK +004.02 metldr Offset : OK +004.03 metldr Length : OK +004.04 metldr Name : OK +004.05 metldr RevKey : OK +004.06 metldr Binary Size : OK +004.07 metldr Statistics : Checked (Simplified) +004.08 asecure_loader 0x00 Filled Area : OK + + +******* Checking eEID_region ******* +005.01 eEID Header : OK +005.02 EID0 Offset - Length : OK +005.03 EID1 Offset - Length : OK +005.04 EID2 Offset - Length : OK +005.05 EID3 Offset - Length : OK +005.06 EID4 Offset - Length : OK +005.07 EID5 Offset - Length : OK +005.08 EID0 IDPS0 : OK +005.09 EID0 IDPS1 : OK +005.10 EID0 Static : OK +005.11 EID2 BlockSize/Padding : OK +005.12 EID3 Static0 : OK +005.13 EID3 Static1 : OK +005.14 EID3 Static2 : OK +005.15 EID5 IDPS0 : OK +005.16 EID5 IDPS1 : OK +005.17 EID5 Static : OK +005.18 eEID Region 0xFF Filled Area : OK +005.19 eEID Statistics0 : Checked (Simplified) +005.20 eEID Statistics1 : Checked (Simplified) + + +******* Checking cISD_region ******* +006.01 cISD Header : OK +006.02 cISD0 Offset - Length : OK +006.03 cISD1 Offset - Length : OK +006.04 cISD2 Offset - Length : OK +006.05 cISD0 0xFF Filled Area : OK +006.06 cISD1 IDLog Header : OK +006.07 cISD1 Semistatic 1 : OK +006.08 cISD1 Semistatic 2 : OK +006.09 cISD1 0xFF Filled Area 0 : OK +006.10 cISD1 Static : OK +006.11 cISD1 Semistatic 3 : OK +006.12 cISD1 0xFF Filled Area 1 : OK +006.13 cISD1 Statistics : Checked (Simplified) +006.14 cISD2 : OK +006.15 cISD 0xFF Filled Area : OK + + +******* Checking cCSD_region ******* +007.01 cCSD Header : OK +007.02 cCSD Entry Table : OK +007.03 cCSD 0xFF Filled Area : OK + + +******* Checking Revokation_region ******* +008.01 trvk_prg0 SCE : OK +008.02 trvk_prg0 Hash : OK +008.02 trvk_prg0 0xFF filled area : OK +008.03 trvk_prg1 SCE : OK +008.04 trvk_prg1 Hash : OK +008.04 trvk_prg1 0xFF filled area : OK +008.05 trvk_pkg0 SCE : OK +008.06 trvk_pkg0 0xFF filled area : OK +008.07 trvk_pkg1 SCE : OK +008.08 trvk_pkg1 0xFF filled area : OK + + +******* Checking CoreOS_region ******* +009.01 ROS0 Header : OK +009.02 ROS0 Hash : OK +009.03 ROS0 unused 0xFF Filled Area : OK +009.04 ROS1 Header : OK +009.05 ROS1 Hash : OK +009.06 ROS1 unused 0xFF Filled Area : OK + + +******* Checking cvtrm_region ******* +010.01 Pre cvtrm 0xFF Filled Area : OK +010.02 cvtrm0 Header : OK +010.03 cvtrm0 0xFF Filled Area : OK +010.04 vtrm0 Magic : OK +010.05 vtrm0 Reserved Entries : OK +010.07 cvtrm1 Header : OK +010.08 cvtrm1 0xFF Filled Area : OK +010.09 vtrm1 Magic : OK +010.10 vtrm1 Reserved Entries : OK + + +******* Checking Second_Region_Header ******* +011.01 Second Region Header 0x00 Filled Area 0 : OK +011.02 Second Region Header Magic : OK +011.03 Second Region Header Count : OK +011.04 Second Region Header 0x00 Filled Area 1 : OK +011.05 Second Region Unknown Block 0 : OK +011.06 Second Region Header 0x00 Filled Area 2 : OK +011.07 Second Region Unknown Block 1 : OK +011.08 Second Region Header 0x00 Filled Area 3 : OK + + +******* Checking Unreferenced_Area ******* +012.01 unreferenced area 0xFF Filled : OK + + +******* Checking CELL_EXTNOR_AREA_Region ******* +013.01 CELL_EXTNOR_AREA Header : OK +013.02 CELL_EXTNOR_AREA 0x00 Filled Area 0 : OK +013.03 CELL_EXTNOR_AREA 0x00 Filled Area 1 : OK +013.04 CELL_EXTNOR_AREA 0x00 Filled Area 2 : OK +013.05 CELL_EXTNOR_AREA 0x00 Filled Area 3 : OK +013.06 CELL_EXTNOR_AREA 0xFF Filled Area 0 : OK +013.07 CELL_EXTNOR_AREA 0x00 Filled Area 5 : OK +013.08 CELL_EXTNOR_AREA 0x00 Filled Area 6 : OK +013.09 CELL_EXTNOR_AREA 0xFF Filled Area 1 : OK + + +******* Checking bootldr_region ******* +014.01 bootldr0 : OK +014.02 bootldr1 : OK +014.03 bootldr Rev key : OK +014.04 bootldr Statistics : Checked (Simplified) +014.05 bootldr 0xFF Filled Area : OK + + +******* Checking datamatches ******* + + +******* Checking repetitions ******* + + +******* Checks completed ******* +Total number of checks = 121 +Number of dangers = 0 +Number of warnings = 0