0
0
镜像自地址 https://github.com/THZoria/NX_Firmware.git 已同步 2026-04-09 10:41:13 +00:00

Merge pull request #22 from JeremKOYTB/main

Massive Upgrade: Implement Strict NCA Crypto-Verification and Deterministic Archives
这个提交包含在:
Zoria
2026-04-08 21:07:07 +02:00
提交者 GitHub
当前提交 a9e20653cf
修改 2 个文件,包含 70 行新增46 行删除

查看文件

@@ -74,43 +74,24 @@ jobs:
fi fi
set -e set -e
- name: 💻 Execute download script - name: 💻 Execute download script & Extract Release Notes
id: download id: download
if: steps.version_check.outputs.new_version == 'true' if: steps.version_check.outputs.new_version == 'true'
run: | run: |
python3 firmware_downloader.py
VERSION="${{ steps.version_check.outputs.firmware_version }}" VERSION="${{ steps.version_check.outputs.firmware_version }}"
echo "firmware_version=$VERSION" >> $GITHUB_OUTPUT
- name: 🧹 Clean and Generate Changelog
id: cleanup
if: steps.version_check.outputs.new_version == 'true'
run: |
VERSION="${{ steps.download.outputs.firmware_version }}"
DIR="Firmware $VERSION"
if [ -d "$DIR" ]; then # Exécution SANS paramètre pour que le script interroge lui-même l'API Nintendo
# Suppression des fichiers .1.nca et fragments python3 firmware_downloader.py | tee script_output.log
find "$DIR" -type f -name "*.[0-9].nca" -delete echo "firmware_version=$VERSION" >> $GITHUB_OUTPUT
find "$DIR" -type f -name "*.nca.*" -delete
# Extraction stricte des dernières lignes générées par le script Python
# Extraction des SystemVersion NCA (fichiers de ~128KB) sed -n '/Archive created:/,$p' script_output.log > changelog_body.txt
# On cherche les fichiers NCA et on les trie par taille pour identifier les SystemVersion
SV_FAT=$(ls -S "$DIR"/*.nca | tail -n 2 | head -n 1 | xargs basename) # Stockage sécurisé et multi-lignes du texte pour GitHub Actions
SV_EXFAT=$(ls -S "$DIR"/*.nca | tail -n 1 | xargs basename) EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "CHANGELOG_CONTENT<<$EOF" >> $GITHUB_ENV
# Création du changelog personnalisé cat changelog_body.txt >> $GITHUB_ENV
echo "Archive created: Firmware $VERSION.zip" > changelog_body.txt echo "$EOF" >> $GITHUB_ENV
echo "SystemVersion NCA FAT: $SV_FAT" >> changelog_body.txt
echo "SystemVersion NCA exFAT: $SV_EXFAT" >> changelog_body.txt
echo "Verify hashes before installation!" >> changelog_body.txt
# Compression
zip -rj "Firmware $VERSION.zip" "$DIR/" -i "*.nca"
else
echo "Dossier non trouvé"
exit 1
fi
- name: 📦 Create Tag and Release - name: 📦 Create Tag and Release
if: steps.version_check.outputs.new_version == 'true' if: steps.version_check.outputs.new_version == 'true'
@@ -119,12 +100,10 @@ jobs:
tag_name: ${{ steps.download.outputs.firmware_version }} tag_name: ${{ steps.download.outputs.firmware_version }}
name: Firmware ${{ steps.download.outputs.firmware_version }} name: Firmware ${{ steps.download.outputs.firmware_version }}
body: | body: |
Automatic download of the official Nintendo Switch firmware version **${{ steps.download.outputs.firmware_version }}**. Automatic download of the official Nintendo Switch firmware version ${{ steps.download.outputs.firmware_version }}.
**Downloaded file details:** Downloaded file details:
```text ${{ env.CHANGELOG_CONTENT }}
$(cat changelog_body.txt)
```
files: | files: |
Firmware ${{ steps.download.outputs.firmware_version }}.zip Firmware ${{ steps.download.outputs.firmware_version }}.zip
env: env:

查看文件

@@ -13,7 +13,7 @@ from os import makedirs, remove
from os.path import basename, exists, join from os.path import basename, exists, join
from configparser import ConfigParser from configparser import ConfigParser
from sys import argv from sys import argv
from zipfile import ZipFile, ZIP_DEFLATED from zipfile import ZipFile, ZIP_STORED, ZipInfo
from requests import request from requests import request
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
@@ -121,15 +121,15 @@ def nin_request(method, url, headers=None):
def parse_cnmt(nca): def parse_cnmt(nca):
ncaf = basename(nca) ncaf = basename(nca)
# --- MODIFICATION CLÉ --- # --- KEY MODIFICATION ---
# Force l'utilisation de l'exécutable hactool dans le répertoire courant. # Force the use of the hactool executable in the current directory.
# Dans le workflow, hactool-linux a été renommé en hactool et rendu exécutable. # In the workflow, hactool-linux was renamed to hactool and made executable.
hactool_bin = "hactool.exe" if os.name == "nt" else "./hactool" hactool_bin = "hactool.exe" if os.name == "nt" else "./hactool"
# ----------------------- # -----------------------
cnmt_temp_dir = f"cnmt_tmp_{ncaf}" cnmt_temp_dir = f"cnmt_tmp_{ncaf}"
# Le script tente de lancer './hactool' # The script attempts to run './hactool'
run( run(
[hactool_bin, "-k", "prod.keys", nca, "--section0dir", cnmt_temp_dir], [hactool_bin, "-k", "prod.keys", nca, "--section0dir", cnmt_temp_dir],
stdout=PIPE, stderr=PIPE stdout=PIPE, stderr=PIPE
@@ -217,12 +217,22 @@ def dltitle(title_id, version, is_su=False):
)) ))
def zipdir(src_dir, out_zip): def zipdir(src_dir, out_zip):
with ZipFile(out_zip, "w", compression=ZIP_DEFLATED) as zf: with ZipFile(out_zip, "w", compression=ZIP_STORED) as zf:
for root, _, files in os.walk(src_dir): for root, dirs, files in os.walk(src_dir):
for name in files: dirs.sort()
for name in sorted(files):
full = os.path.join(root, name) full = os.path.join(root, name)
rel = os.path.relpath(full, start=src_dir) rel = os.path.relpath(full, start=src_dir)
zf.write(full, arcname=rel) os.utime(full, (1780315200, 1780315200))
zinfo = ZipInfo.from_file(full, arcname=rel)
zinfo.date_time = (2026, 1, 1, 0, 0, 0)
zinfo.create_system = 0
zinfo.external_attr = 0
zinfo.compress_type = ZIP_STORED
with open(full, 'rb') as f:
zf.writestr(zinfo, f.read())
if __name__ == "__main__": if __name__ == "__main__":
if not exists("certificat.pem"): if not exists("certificat.pem"):
@@ -311,13 +321,48 @@ if __name__ == "__main__":
if failed: if failed:
exit(1) exit(1)
print("\nINFO: Starting detailed verification of NCA hashes...")
hash_failed = False
for url, dirc, fname, expected_hash in update_dls:
fpath = join(dirc, fname)
if exists(fpath):
h = hashlib.sha256()
with open(fpath, "rb") as f:
for chunk in iter(lambda: f.read(1048576), b""):
h.update(chunk)
actual_hash = h.hexdigest()
if actual_hash == expected_hash:
print(f"[OK] {fname}")
print(f" -> Verified Hash: {actual_hash}")
else:
print(f"[ERROR] {fname}")
print(f" Expected : {expected_hash}")
print(f" Actual : {actual_hash}")
hash_failed = True
else:
print(f"[MISSING] {fname}")
hash_failed = True
if hash_failed:
print("\nCRITICAL: Hash verification failed for one or more files. Archive will not be created.")
exit(1)
else:
print("\nINFO: All files successfully verified against CNMT records.")
out_zip = f"{ver_dir}.zip" out_zip = f"{ver_dir}.zip"
if exists(out_zip): if exists(out_zip):
remove(out_zip) remove(out_zip)
zipdir(ver_dir, out_zip) zipdir(ver_dir, out_zip)
h = hashlib.sha256()
with open(out_zip, "rb") as f:
for chunk in iter(lambda: f.read(1048576), b""):
h.update(chunk)
zip_sha256 = h.hexdigest()
print("\nDOWNLOAD COMPLETE!") print("\nDOWNLOAD COMPLETE!")
print(f"Archive created: {out_zip}") print(f"Archive created: {out_zip}")
print(f"SystemVersion NCA FAT: {sv_nca_fat or 'Not Found'}") print(f"SystemVersion NCA FAT: {sv_nca_fat or 'Not Found'}")
print(f"SystemVersion NCA exFAT: {sv_nca_exfat or 'Not Found'}") print(f"SystemVersion NCA exFAT: {sv_nca_exfat or 'Not Found'}")
print(f"Archive SHA256: {zip_sha256}")
print("Verify hashes before installation!") print("Verify hashes before installation!")