-
Notifications
You must be signed in to change notification settings - Fork 58
Build 506: 4.93 firmware support #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
6bd88ad
434efdc
52f7683
c8105c2
09ec2ed
55c3114
11b386b
ad2b122
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,120 @@ | ||||||
| name: Build and Release | ||||||
|
|
||||||
| # Builds PS3DumpChecker in Release_Embedded and publishes the compiled exe | ||||||
| # plus refreshed default.cfg / default.hashlist (and their MD5s) as a | ||||||
| # GitHub Release. Runs on every version tag push (v*), so the release | ||||||
| # workflow is: | ||||||
| # 1. commit source changes + AssemblyInfo bump + changelog entry | ||||||
| # 2. git tag v1.0.XXX && git push origin v1.0.XXX | ||||||
| # 3. Actions builds in a clean windows-latest runner and publishes the | ||||||
| # artefacts. No compiled binaries ship inside the repo. | ||||||
|
|
||||||
| on: | ||||||
| push: | ||||||
| tags: | ||||||
| - 'v*' | ||||||
| workflow_dispatch: | ||||||
|
|
||||||
| permissions: | ||||||
| contents: write | ||||||
|
|
||||||
| jobs: | ||||||
| build: | ||||||
| runs-on: windows-latest | ||||||
|
|
||||||
| steps: | ||||||
| - name: Checkout | ||||||
| uses: actions/checkout@v4 | ||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We really should be using the latest one, not old ones. |
||||||
|
|
||||||
| - name: Setup MSBuild | ||||||
| uses: microsoft/setup-msbuild@v2 | ||||||
|
|
||||||
| - name: Setup Python | ||||||
| uses: actions/setup-python@v5 | ||||||
| with: | ||||||
| python-version: '3.11' | ||||||
|
|
||||||
| - name: Validate data files before build | ||||||
| run: python tools/validate_data.py | ||||||
|
|
||||||
| # The csproj targets .NET Framework 4.5 which is EOL and no longer | ||||||
| # ships targeting packs on GitHub-hosted runners. 4.8 is | ||||||
| # backward-compatible with 4.5 source, is preinstalled on | ||||||
| # windows-latest, and does not require changing the csproj. This | ||||||
| # override affects only the CI build; the checked-in csproj is | ||||||
| # untouched. | ||||||
| - name: Build (Release_Embedded) | ||||||
| run: msbuild src\PS3DumpChecker\PS3DumpChecker.csproj /p:Configuration=Release_Embedded /p:Platform=AnyCPU /p:TargetFrameworkVersion=v4.8 /nologo /v:minimal /t:Rebuild | ||||||
|
|
||||||
| # The post-build step (UpdateLatestCompiledVersion.bat) invokes the | ||||||
| # bundled MD5Gen.exe and DotNETAssembly2Version.exe. On a hosted | ||||||
| # runner those may or may not run cleanly, so we regenerate the | ||||||
| # metadata explicitly here in Python (matches the .bat's output | ||||||
| # format: 32 uppercase hex, no newline). | ||||||
| - name: Refresh Latest Compiled Version artefacts | ||||||
| shell: pwsh | ||||||
| run: | | ||||||
| $ver = (Get-Item "Latest Compiled Version\PS3DumpChecker.exe").VersionInfo.FileVersion | ||||||
| Set-Content -Path "Latest Compiled Version\PS3DumpChecker.exe.version" -Value "$ver " -NoNewline | ||||||
| Copy-Item "src\PS3DumpChecker\hashlist.xml" "Latest Compiled Version\default.hashlist" -Force | ||||||
| Copy-Item "src\PS3DumpChecker\config.xml" "Latest Compiled Version\default.cfg" -Force | ||||||
| python -c @" | ||||||
| import hashlib, pathlib | ||||||
| d = pathlib.Path('Latest Compiled Version') | ||||||
| for f in ['default.hashlist', 'default.cfg', 'PS3DumpChecker.exe']: | ||||||
| p = d / f | ||||||
| (d / (f + '.md5')).write_text( | ||||||
| hashlib.md5(p.read_bytes()).hexdigest().upper() | ||||||
| ) | ||||||
| print(f, (d / (f + '.md5')).read_text()) | ||||||
| "@ | ||||||
| # Clean the GAC fallback junk MSBuild sometimes drops into the | ||||||
| # output dir when reference assemblies are missing. | ||||||
| Remove-Item "Latest Compiled Version\mscorlib.dll", | ||||||
| "Latest Compiled Version\norm*.nlp", | ||||||
| "Latest Compiled Version\es" ` | ||||||
| -Recurse -Force -ErrorAction SilentlyContinue | ||||||
|
|
||||||
| - name: Sanity check embedded patch.bin MD5 | ||||||
| shell: pwsh | ||||||
| run: | | ||||||
| python -c @" | ||||||
| import hashlib, pathlib | ||||||
| exe = pathlib.Path('Latest Compiled Version/PS3DumpChecker.exe').read_bytes() | ||||||
| needle = bytes.fromhex('000000010000001900000000006fffe0') | ||||||
| i = exe.find(needle) | ||||||
| if i < 0: | ||||||
| raise SystemExit('embedded patch.bin signature not found in exe') | ||||||
| md5 = hashlib.md5(exe[i:i + 0x6FFFE0]).hexdigest().upper() | ||||||
|
Swizzy marked this conversation as resolved.
|
||||||
| print('embedded patch.bin MD5:', md5) | ||||||
| "@ | ||||||
|
|
||||||
| - name: Upload build artefacts | ||||||
| uses: actions/upload-artifact@v4 | ||||||
| with: | ||||||
| name: PS3DumpChecker-${{ github.ref_name }} | ||||||
| path: | | ||||||
| Latest Compiled Version/PS3DumpChecker.exe | ||||||
| Latest Compiled Version/PS3DumpChecker.exe.md5 | ||||||
| Latest Compiled Version/PS3DumpChecker.exe.version | ||||||
| Latest Compiled Version/default.cfg | ||||||
| Latest Compiled Version/default.cfg.md5 | ||||||
| Latest Compiled Version/default.hashlist | ||||||
| Latest Compiled Version/default.hashlist.md5 | ||||||
| Latest Compiled Version/changelog | ||||||
| if-no-files-found: error | ||||||
|
|
||||||
| - name: Create GitHub Release | ||||||
| if: startsWith(github.ref, 'refs/tags/v') | ||||||
| uses: softprops/action-gh-release@v2 | ||||||
| with: | ||||||
| generate_release_notes: true | ||||||
| files: | | ||||||
| Latest Compiled Version/PS3DumpChecker.exe | ||||||
| Latest Compiled Version/PS3DumpChecker.exe.md5 | ||||||
| Latest Compiled Version/PS3DumpChecker.exe.version | ||||||
| Latest Compiled Version/default.cfg | ||||||
| Latest Compiled Version/default.cfg.md5 | ||||||
| Latest Compiled Version/default.hashlist | ||||||
| Latest Compiled Version/default.hashlist.md5 | ||||||
| Latest Compiled Version/changelog | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| noFSM 4.92 (Evilnat based) | ||
| noFSM 4.93 (Evilnat based) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| # tools | ||
|
|
||
| Helpers for adding support for a new PS3 firmware release. | ||
|
|
||
| Adding a firmware has always been a manual process: unpack two PUPs, decrypt | ||
| the CoreOS out of each, MD5 the ROS region, hand-edit `hashlist.xml`, and drop | ||
| the new payload in as `patch.bin`. These scripts cover the parts that can be | ||
| checked by a machine, so a typo in a hex digit fails here instead of on a | ||
| user's dump. | ||
|
|
||
| Python 3. No third-party packages. | ||
|
|
||
| ## ros_hash.py | ||
|
|
||
| Computes the ROS MD5 that `hashlist.xml` stores, and prints the XML entries | ||
| ready to paste. | ||
|
|
||
| ``` | ||
| python tools/ros_hash.py ofw_content cfw_content --version 4.93 | ||
| python tools/ros_hash.py content --name "4.93 CEX" | ||
| python tools/ros_hash.py content --name "4.93 CEX Patched (Evilnat based)" --patched | ||
| ``` | ||
|
|
||
| Input is a *decrypted* CoreOS payload -- the `content` file from an unpacked | ||
| `CORE_OS_PACKAGE.pkg`, not the `.pkg` itself and not the PUP. It is the same | ||
| 7,340,000-byte blob that ships as `Patches/patch.bin`. | ||
|
|
||
| The hash is the MD5 of the first `0x6FFFE0` bytes, matching | ||
| `HashCheck.CheckHash`. The byte-swapped MD5 is printed alongside it: NOR dumps | ||
| store the region swapped and NAND dumps do not, so seeing both makes a | ||
| mismatch obvious rather than silent. | ||
|
|
||
| Sanity check -- this reproduces the 4.92 entry already in `hashlist.xml`: | ||
|
|
||
| ``` | ||
| $ python tools/ros_hash.py src/PS3DumpChecker/Patches/patch.bin \ | ||
| --name "4.92 CEX Patched (Evilnat based)" --patched | ||
| MD5 : 36BD44795F06B59EECBDAAD6982BE426 | ||
| ``` | ||
|
|
||
| ## validate_data.py | ||
|
|
||
| Checks `hashlist.xml` and `config.xml` before they ship. Run it after editing | ||
| either file. | ||
|
|
||
| ``` | ||
| python tools/validate_data.py | ||
| ``` | ||
|
|
||
| Catches duplicate MD5s, duplicate entry names, malformed hashes, non-hex size | ||
| and offset attributes, bad `patched` values, offsets missing attributes, and | ||
| hashes referencing a type that was never declared. Exits non-zero on error. | ||
|
|
||
| ## build_nofsm_patch.py | ||
|
|
||
| Rebuilds the exact `patch.bin` that PS3Xploit flash-writer 4.93 writes to | ||
| flash. Uses only public inputs (OFW 4.93 CoreOS + `flash493.P3T` from | ||
| `aldostools/flashwriter`). Result matches | ||
| MD5 `AFE831050C31EFB381F9BE4098F1834C` byte-for-byte, i.e. the same ROS | ||
| `pyPS3patcher` and the PS3 Toolset will ship for 4.93 -- so the three tools | ||
| stay in sync and the patched ROS is the safe non-Cobra variant, not the | ||
| Cobra CFW CoreOS. | ||
|
Comment on lines
+56
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Use the noFSM builder in the end-to-end workflow. This section correctly requires the OFW plus Proposed documentation fix-5. Copy the CFW `content` to `src/PS3DumpChecker/Patches/patch.bin` and update
- `Patches/patch_info.txt` to `noFSM X.XX (Evilnat based)`.
+5. Run `python tools/build_nofsm_patch.py ofw_out/content flash493.P3T
+ src/PS3DumpChecker/Patches/patch.bin`, verify the expected MD5, and update
+ `Patches/patch_info.txt` to the noFSM patch description.🤖 Prompt for AI Agents |
||
|
|
||
| ``` | ||
| python tools/build_nofsm_patch.py ofw_content flash493.P3T patch.bin | ||
| ``` | ||
|
|
||
| Get the inputs with: | ||
|
|
||
| - `python tools/coreos_decrypt.py PS3UPDAT_493_OFW.PUP ofw_out` | ||
| (yields `ofw_out/content`) | ||
| - `curl -LO https://raw.githubusercontent.com/aldostools/flashwriter/main/493/flash493.P3T` | ||
|
|
||
| The transformation is a byte splice: | ||
|
|
||
| patched_ros = OFW[0 : 0x1D0] + P3T[:] + OFW[0x1D0 + len(P3T) :] | ||
|
|
||
| The P3T is a partial-ROS overlay whose body mirrors the OFW ROS from | ||
| offset 0x1D0 with only a few SELFs actually modified | ||
| (`sdk_version`, `spu_pkg_rvk_verifier.self`, `default.spp`, `lv1.self`). | ||
|
|
||
| ## coreos_decrypt.py | ||
|
|
||
| Decrypts `CORE_OS_PACKAGE.pkg` out of a PUP and writes the `content` blob -- | ||
| the 7,340,000-byte ROS payload `ros_hash.py` needs. | ||
|
|
||
| ``` | ||
| python tools/coreos_decrypt.py PS3UPDAT.PUP out_dir | ||
| python tools/coreos_decrypt.py --extract-selfs PS3UPDAT.PUP out_dir | ||
| ``` | ||
|
|
||
| Requires `pycryptodome` (`python -m pip install --user pycryptodome`). No other | ||
| external tools -- reimplements `pupunpack`, `unpkg`, and `cosunpkg` from | ||
| fail0verflow/ps3tools in Python, using the public retail PKG keys embedded in | ||
| the script. | ||
|
|
||
| `--extract-selfs` writes each SELF from the decrypted content next to | ||
| `content`. Useful for sanity-checking the extraction (each file should start | ||
| with the `SCE\0` magic). | ||
|
|
||
| ## Getting the decrypted CoreOS | ||
|
|
||
| 1. Get both PUPs and verify them: | ||
| - official `PS3UPDAT.PUP` for the firmware, from Sony's update CDN | ||
| - the matching **CEX** Evilnat CFW -- not `noBD`, `noBT` or `noBD+noBT`, | ||
| which drop modules and therefore hash differently | ||
| 2. `python tools/coreos_decrypt.py PS3UPDAT.PUP out_dir` on each. The | ||
| `out_dir/content` file is what `ros_hash.py` consumes. | ||
| 3. `python tools/ros_hash.py ofw_out/content cfw_out/content --version X.XX`. | ||
|
|
||
| `pup_info.py` prints a PUP's entry table if you want to confirm the layout | ||
| before decrypting. | ||
|
|
||
| ## Adding a firmware, end to end | ||
|
|
||
| 1. Verify both PUP downloads against their published checksums. The Evilnat | ||
| `.rar` ships an `md5.txt`; the official PUP has a SHA-256 published | ||
| alongside it. | ||
| 2. `python tools/coreos_decrypt.py OFW.PUP ofw_out` and again for the CFW. | ||
| 3. `python tools/ros_hash.py ofw_out/content cfw_out/content --version X.XX` | ||
| 4. Paste both entries at the top of `<type name="ROS">` in | ||
| `src/PS3DumpChecker/hashlist.xml`, newest first. | ||
| 5. Copy the CFW `content` to `src/PS3DumpChecker/Patches/patch.bin` and update | ||
| `Patches/patch_info.txt` to `noFSM X.XX (Evilnat based)`. | ||
| 6. `python tools/validate_data.py` | ||
| 7. Bump `AssemblyVersion` and `AssemblyFileVersion` in | ||
| `Properties/AssemblyInfo.cs`, and add a changelog entry. | ||
| 8. Build Release_Embedded. The post-build step refreshes | ||
| `Latest Compiled Version/`, which is what the in-app updater serves -- | ||
| `default.cfg`, `default.hashlist` and their `.md5` files must be regenerated | ||
| or existing installs will keep fetching the old data. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| build_nofsm_patch.py - Reproduce the PS3Xploit flash-writer 4.93 patched | ||
| CoreOS ROS from public inputs. | ||
|
|
||
| Given an OFW 4.93 CoreOS payload (decrypted with `coreos_decrypt.py`) and | ||
| the `flash493.P3T` shipped by `aldostools/flashwriter`, this reconstructs | ||
| byte-for-byte the same 4.93 CEX Patched (Evilnat-based) ROS that | ||
| PS3Xploit's noFSM flow writes to flash. It is the exact blob PS3DumpChecker | ||
| recognises as `4.93 CEX Patched (Evilnat based)`, | ||
| MD5 = `AFE831050C31EFB381F9BE4098F1834C`. | ||
|
|
||
| Transformation (reverse-engineered from the flash-writer ROP flow): | ||
|
|
||
| patched_ros = OFW[0 : 0x1D0] + P3T[:] + OFW[0x1D0 + len(P3T) :] | ||
|
|
||
| The P3T file is a partial ROS overlay whose body is mostly identical to | ||
| the OFW ROS beginning at offset 0x1D0. Only four SELFs are actually | ||
| modified in it (sdk_version, spu_pkg_rvk_verifier.self, default.spp, | ||
| lv1.self). The remaining bytes match the OFW verbatim, so splicing the | ||
| whole P3T over the OFW at 0x1D0 yields the fully-patched CoreOS. | ||
|
|
||
| Usage: | ||
| python build_nofsm_patch.py OFW_CONTENT flash493.P3T OUT_PATH | ||
|
|
||
| Where: | ||
| OFW_CONTENT the decrypted 4.93 OFW CoreOS `content` file | ||
| (7,340,000 bytes = 0x6FFFE0) | ||
| flash493.P3T the PS3Xploit flash-writer 4.93 overlay | ||
| (from aldostools/flashwriter/493/flash493.P3T) | ||
| OUT_PATH where to write the reconstructed patch.bin | ||
|
|
||
| Requires no third-party packages. Exit codes: 0 ok, 1 error. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import hashlib | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| OVERLAY_OFFSET = 0x1D0 | ||
| EXPECTED_ROS_SIZE = 0x6FFFE0 | ||
| TARGET_MD5 = "AFE831050C31EFB381F9BE4098F1834C" | ||
|
|
||
|
|
||
| def build_patch(ofw: bytes, p3t: bytes) -> bytes: | ||
| if len(ofw) != EXPECTED_ROS_SIZE: | ||
| raise ValueError( | ||
| "OFW ROS size 0x%X != expected 0x%X" % (len(ofw), EXPECTED_ROS_SIZE) | ||
| ) | ||
| end = OVERLAY_OFFSET + len(p3t) | ||
| if end > len(ofw): | ||
| raise ValueError( | ||
| "P3T overlay end 0x%X exceeds ROS size 0x%X" % (end, len(ofw)) | ||
| ) | ||
| out = bytearray(ofw) | ||
| out[OVERLAY_OFFSET:end] = p3t | ||
| return bytes(out) | ||
|
|
||
|
|
||
| def main(argv: list[str] | None = None) -> int: | ||
| ap = argparse.ArgumentParser( | ||
| description="Rebuild the 4.93 CEX Patched (Evilnat) ROS from OFW + P3T." | ||
| ) | ||
| ap.add_argument("ofw_content", type=Path, | ||
| help="decrypted OFW 4.93 CoreOS content (0x6FFFE0 bytes)") | ||
| ap.add_argument("p3t", type=Path, | ||
| help="flash493.P3T from aldostools/flashwriter") | ||
| ap.add_argument("out", type=Path, help="output patch.bin path") | ||
| args = ap.parse_args(argv) | ||
|
|
||
| try: | ||
| ofw = args.ofw_content.read_bytes() | ||
| p3t = args.p3t.read_bytes() | ||
| patched = build_patch(ofw, p3t) | ||
| except (OSError, ValueError) as exc: | ||
| print("error: %s" % exc, file=sys.stderr) | ||
| return 1 | ||
|
|
||
| args.out.write_bytes(patched) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Write the patch only after the MD5 check succeeds.
Proposed fix- args.out.write_bytes(patched)
md5 = hashlib.md5(patched).hexdigest().upper()
print("OFW : %s (0x%X bytes)" % (args.ofw_content, len(ofw)))
print("P3T : %s (0x%X bytes)" % (args.p3t, len(p3t)))
- print("OUT : %s (0x%X bytes)" % (args.out, len(patched)))
print("MD5 : %s" % md5)
print("WANT : %s" % TARGET_MD5)
if md5 != TARGET_MD5:
print("MISMATCH", file=sys.stderr)
return 1
+ try:
+ args.out.write_bytes(patched)
+ except OSError as exc:
+ print("error: %s" % exc, file=sys.stderr)
+ return 1
+ print("OUT : %s (0x%X bytes)" % (args.out, len(patched)))
print("MATCH")🧰 Tools🪛 ast-grep (0.45.2)[warning] 82-82: Do not use insecure functions (insecure-hash-functions) 🤖 Prompt for AI Agents |
||
| md5 = hashlib.md5(patched).hexdigest().upper() | ||
|
|
||
| print("OFW : %s (0x%X bytes)" % (args.ofw_content, len(ofw))) | ||
| print("P3T : %s (0x%X bytes)" % (args.p3t, len(p3t))) | ||
| print("OUT : %s (0x%X bytes)" % (args.out, len(patched))) | ||
| print("MD5 : %s" % md5) | ||
| print("WANT : %s" % TARGET_MD5) | ||
|
|
||
| if md5 != TARGET_MD5: | ||
| print("MISMATCH", file=sys.stderr) | ||
| return 1 | ||
| print("MATCH") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
Uh oh!
There was an error while loading. Please reload this page.