66
77 --[ 0x00 - What This Does
88
9- Builds both release binaries for distribution:
10-
9+ Builds release binaries for distribution:
10+
1111 1. firmware_vX.X.X.bin - Flash at 0x10000 via esptool-js
1212 (preserves XP, for upgrades)
13-
13+
1414 2. porkchop_vX.X.X_m5burner.bin - Flash at 0x0 via M5 Burner
1515 (full image, nukes NVS)
1616
17+ 3. porkchop_vX.X.X_m5burner.zip - M5Burner catalog package
18+ (m5burner.json + address-named bins)
19+
1720 Output lands in m5porkchop_builds/ directory.
1821
1922
3942import subprocess
4043import shutil
4144import re
45+ import json
46+ import zipfile
4247from pathlib import Path
4348
4449# Phrack-style banner
4550BANNER = r"""
46- ██▓███ ▒█████ ██▀███ ██ ▄█▀ ▄████▄ ██░ ██ ▒█████ ██▓███
51+ ██▓███ ▒█████ ██▀███ ██ ▄█▀ ▄████▄ ██░ ██ ▒█████ ██▓███
4752▓██░ ██▒▒██▒ ██▒▓██ ▒ ██▒ ██▄█▒ ▒██▀ ▀█ ▓██░ ██▒▒██▒ ██▒▓██░ ██▒
4853▓██░ ██▓▒▒██░ ██▒▓██ ░▄█ ▒▓███▄░ ▒▓█ ▄ ▒██▀▀██░▒██░ ██▒▓██░ ██▓▒
4954▒██▄█▓▒ ▒▒██ ██░▒██▀▀█▄ ▓██ █▄ ▒▓▓▄ ▄██▒░▓█ ░██ ▒██ ██░▒██▄█▓▒ ▒
@@ -73,109 +78,169 @@ def log_info(msg):
7378def get_version ():
7479 """Extract version from platformio.ini"""
7580 ini_path = Path (__file__ ).parent .parent / "platformio.ini"
76-
81+
7782 if not ini_path .exists ():
7883 log_err ("platformio.ini not found - are you in the right directory?" )
7984 sys .exit (1 )
80-
85+
8186 with open (ini_path , "r" ) as f :
8287 content = f .read ()
83-
84- # Look for custom_version = X.X.X or X.X.X_suffix
85- match = re .search (r'custom_version\s*=\s*([\d.]+[\w_]* )' , content )
88+
89+ # Match version strings like 0.1.8b-PSTH, 0.1.8b_suffix, 0.1.8b
90+ match = re .search (r'custom_version\s*=\s*([\w.\-]+ )' , content )
8691 if match :
8792 return match .group (1 )
88-
93+
8994 log_err ("Could not find custom_version in platformio.ini" )
9095 sys .exit (1 )
9196
9297
9398def run_cmd (cmd , description ):
9499 """Run a command and handle errors"""
95100 log_info (f"{ description } ..." )
96-
101+
97102 try :
98103 result = subprocess .run (
99104 cmd ,
100105 shell = True ,
101106 capture_output = True ,
102107 text = True
103108 )
104-
109+
105110 if result .returncode != 0 :
106111 log_err (f"Command failed: { cmd } " )
107112 log_err (result .stderr )
108113 sys .exit (1 )
109-
114+
110115 return result .stdout
111116 except Exception as e :
112117 log_err (f"Exception running command: { e } " )
113118 sys .exit (1 )
114119
115120
121+ def create_m5burner_package (builds_dir , version , bootloader_src , partitions_src , firmware_src ):
122+ """Create M5Burner-compatible ZIP package with manifest and address-named bins"""
123+ log_info ("Creating M5Burner catalog package..." )
124+
125+ pkg_dir = builds_dir / f"porkchop_v{ version } _m5burner_pkg"
126+ fw_dir = pkg_dir / "firmware"
127+
128+ # Clean previous package
129+ if pkg_dir .exists ():
130+ shutil .rmtree (pkg_dir )
131+ fw_dir .mkdir (parents = True )
132+
133+ # Copy bins with M5Burner address-naming convention
134+ shutil .copy2 (bootloader_src , fw_dir / "bootloader_0x0.bin" )
135+ shutil .copy2 (partitions_src , fw_dir / "partitions_0x8000.bin" )
136+ shutil .copy2 (firmware_src , fw_dir / "porkchop_0x10000.bin" )
137+
138+ # Generate m5burner.json manifest
139+ manifest = {
140+ "name" : "PORKCHOP" ,
141+ "version" : version ,
142+ "description" : "WiFi pentesting & wardriving tool for M5Stack Cardputer" ,
143+ "keywords" : "wifi,wardriving,pentesting,handshake,spectrum,deauth" ,
144+ "author" : "0ct0" ,
145+ "repository" : "https://github.com/0ct0-porkchop/porkchop" ,
146+ "framework" : "arduino" ,
147+ "firmware_category" : {
148+ "PORKCHOP" : {
149+ "path" : "firmware" ,
150+ "device" : ["M5Cardputer" ],
151+ "default_baud" : 921600
152+ }
153+ }
154+ }
155+
156+ manifest_path = pkg_dir / "m5burner.json"
157+ with open (manifest_path , "w" ) as f :
158+ json .dump (manifest , f , indent = 2 )
159+
160+ # Create ZIP
161+ zip_path = builds_dir / f"porkchop_v{ version } _m5burner.zip"
162+ with zipfile .ZipFile (zip_path , "w" , zipfile .ZIP_DEFLATED ) as zf :
163+ for file in pkg_dir .rglob ("*" ):
164+ if file .is_file ():
165+ arcname = file .relative_to (pkg_dir )
166+ zf .write (file , arcname )
167+
168+ # Cleanup staging directory
169+ shutil .rmtree (pkg_dir )
170+
171+ size_kb = zip_path .stat ().st_size / 1024
172+ log_ok (f"Created: { zip_path .name } ({ size_kb :.1f} KB)" )
173+
174+ return zip_path
175+
176+
116177def main ():
117178 print (BANNER )
118-
179+
119180 # Get project root (parent of scripts/)
120181 project_root = Path (__file__ ).parent .parent
121182 os .chdir (project_root )
122-
183+
123184 log_info (f"Working directory: { project_root } " )
124-
185+
125186 # Get version
126187 version = get_version ()
127188 log_ok (f"Building version: { version } " )
128-
189+
129190 # Create output directory
130191 builds_dir = project_root / "m5porkchop_builds"
131192 builds_dir .mkdir (exist_ok = True )
132193 log_ok (f"Output directory: { builds_dir } " )
133-
194+
134195 # Build paths
135196 build_dir = project_root / ".pio" / "build" / "m5cardputer"
136197 firmware_src = build_dir / "firmware.bin"
137198 bootloader_src = build_dir / "bootloader.bin"
138199 partitions_src = build_dir / "partitions.bin"
139-
200+
140201 # Output filenames
141202 firmware_dst = builds_dir / f"firmware_v{ version } .bin"
142203 m5burner_dst = builds_dir / f"porkchop_v{ version } _m5burner.bin"
143-
204+
144205 # Step 1: Build with PlatformIO
145206 log ("" )
146207 log ("=" * 60 )
147208 log ("STEP 1: Building firmware with PlatformIO" )
148209 log ("=" * 60 )
149-
210+
150211 run_cmd ("pio run -t clean -e m5cardputer" , "Cleaning build artifacts" )
151212 run_cmd ("pio run -e m5cardputer" , "Compiling" )
152213 log_ok ("Firmware compiled successfully" )
153-
214+
154215 # Verify build artifacts exist
155216 if not firmware_src .exists ():
156217 log_err (f"firmware.bin not found at { firmware_src } " )
157218 sys .exit (1 )
158-
219+
159220 if not bootloader_src .exists ():
160221 log_err (f"bootloader.bin not found at { bootloader_src } " )
161222 sys .exit (1 )
162-
223+
224+ if not partitions_src .exists ():
225+ log_err (f"partitions.bin not found at { partitions_src } " )
226+ sys .exit (1 )
227+
163228 # Step 2: Copy firmware.bin for esptool-js upgrades
164229 log ("" )
165230 log ("=" * 60 )
166231 log ("STEP 2: Copying firmware for esptool-js upgrades" )
167232 log ("=" * 60 )
168-
233+
169234 shutil .copy2 (firmware_src , firmware_dst )
170235 size_kb = firmware_dst .stat ().st_size / 1024
171236 log_ok (f"Created: { firmware_dst .name } ({ size_kb :.1f} KB)" )
172-
173- # Step 3: Create merged binary for M5 Burner
237+
238+ # Step 3: Create merged binary for M5 Burner (direct flash)
174239 log ("" )
175240 log ("=" * 60 )
176241 log ("STEP 3: Creating merged binary for M5 Burner" )
177242 log ("=" * 60 )
178-
243+
179244 # Build the esptool merge command
180245 merge_cmd = (
181246 f'pio pkg exec -p tool-esptoolpy -- esptool.py '
@@ -186,12 +251,22 @@ def main():
186251 f'0x8000 "{ partitions_src } " '
187252 f'0x10000 "{ firmware_src } "'
188253 )
189-
254+
190255 run_cmd (merge_cmd , "Merging binaries" )
191-
256+
192257 size_kb = m5burner_dst .stat ().st_size / 1024
193258 log_ok (f"Created: { m5burner_dst .name } ({ size_kb :.1f} KB)" )
194-
259+
260+ # Step 4: Create M5Burner catalog package (ZIP with manifest)
261+ log ("" )
262+ log ("=" * 60 )
263+ log ("STEP 4: Creating M5Burner catalog package" )
264+ log ("=" * 60 )
265+
266+ zip_path = create_m5burner_package (
267+ builds_dir , version , bootloader_src , partitions_src , firmware_src
268+ )
269+
195270 # Summary
196271 log ("" )
197272 log ("=" * 60 )
@@ -206,9 +281,12 @@ def main():
206281 log (f" { m5burner_dst .name } " )
207282 log (f" -> Flash at 0x0 via M5 Burner (full install)" )
208283 log ("" )
284+ log (f" { zip_path .name } " )
285+ log (f" -> M5Burner catalog package (m5burner.json + bins)" )
286+ log ("" )
209287 log_info (f"Files are in: { builds_dir } " )
210288 log ("" )
211-
289+
212290 return 0
213291
214292
0 commit comments