Last active
September 12, 2025 02:24
-
-
Save shuantsu/6083e7a652e3bc994bd5999f1848b64e to your computer and use it in GitHub Desktop.
SHAPEZ 2 BLUEPRINTS DECODER
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import tkinter as tk | |
from tkinter import ttk | |
from tkinter import messagebox | |
import base64 | |
import gzip | |
import json | |
import re | |
from typing import Any, Dict, Optional | |
FONTSIZE = 14 | |
class BlueprintConverter: | |
"""A class to handle the decoding, modification, and encoding of Shapez.io blueprints.""" | |
_PRINTABLE_BYTES_RE: re.Pattern = re.compile(rb'[\x20-\x7E]+') | |
_SHAPE_PREFIX = b'\x06\x01\x01\x08\x00' | |
def __init__(self, game_string: str = ""): | |
self.game_string = game_string | |
self.decoded_bp: Optional[dict[str, Any]] = None | |
def decode_blueprint(self) -> bool: | |
"""Decodes the Shapez save string.""" | |
prefix = "SHAPEZ2-3-" | |
suffix = "$" | |
if not (self.game_string.startswith(prefix) and self.game_string.endswith(suffix)): | |
return False | |
payload = self.game_string[len(prefix):-len(suffix)] | |
payload = payload.strip() | |
if (pad := (-len(payload) % 4)) != 0: | |
payload += "=" * pad | |
try: | |
decoded_bytes = base64.b64decode(payload, validate=False) | |
decompressed = gzip.decompress(decoded_bytes) | |
self.decoded_bp = json.loads(decompressed.decode("utf-8")) | |
return True | |
except Exception: | |
return False | |
def decode_c_fields(self) -> None: | |
"""Recursively decodes 'C' fields into 'C-decoded'.""" | |
if not self.decoded_bp: | |
return | |
def process_node(node: Any): | |
if isinstance(node, dict): | |
if "C" in node and isinstance(node["C"], str): | |
b64 = node["C"].strip() | |
if (pad := (-len(b64) % 4)) != 0: | |
b64 += "=" * pad | |
try: | |
raw = base64.b64decode(b64, validate=False) | |
# Special case: null value | |
if raw == b'\x01': | |
node["C-decoded"] = "null" | |
# Special case: conflict value | |
elif raw == b'\x02': | |
node["C-decoded"] = "conflict" | |
# Number decoding (type 0x03, 4 data bytes) | |
elif len(raw) == 5 and raw[0] == 0x03: | |
num_value = int.from_bytes(raw[1:], 'little', signed=True) | |
node["C-decoded"] = str(num_value) | |
# Color decoding (type 0x07, length 0x01, 1 data byte) | |
elif len(raw) == 3 and raw[0] == 0x07 and raw[1] == 0x01: | |
node["C-decoded"] = chr(raw[2]) | |
# Shape string decoding | |
elif raw.startswith(self._SHAPE_PREFIX): | |
string_part = raw[len(self._SHAPE_PREFIX):].decode("utf-8") | |
node["C-decoded"] = string_part | |
else: | |
string_part = raw.decode("utf-8", errors="ignore") | |
node["C-decoded"] = string_part | |
except Exception as exc: | |
node["C-decoded"] = f"[base64 error: {exc}]" | |
for k, v in node.items(): | |
process_node(v) | |
elif isinstance(node, list): | |
for item in node: | |
process_node(item) | |
process_node(self.decoded_bp) | |
def encode_c_fields(self) -> None: | |
"""Recursively re-encodes 'C-decoded' keys back to 'C'.""" | |
if not self.decoded_bp: | |
return | |
def process_node(node: Any): | |
if isinstance(node, dict): | |
if "C-decoded" in node: | |
decoded_value = node["C-decoded"] | |
new_c_value = None | |
if decoded_value == "null": | |
new_c_value = base64.b64encode(b'\x01').decode("utf-8") | |
elif decoded_value == "conflict": | |
new_c_value = base64.b64encode(b'\x02').decode("utf-8") | |
else: | |
try: | |
num_value = int(decoded_value) | |
raw_bytes = bytearray([0x03]) + num_value.to_bytes(4, 'little', signed=True) | |
new_c_value = base64.b64encode(raw_bytes).decode("utf-8") | |
except (ValueError, TypeError): | |
if isinstance(decoded_value, str) and len(decoded_value) == 1 and decoded_value in "rgbwycmu": | |
raw_bytes = bytearray([0x07, 0x01, ord(decoded_value)]) | |
new_c_value = base64.b64encode(raw_bytes).decode("utf-8") | |
else: | |
raw_bytes = self._SHAPE_PREFIX + decoded_value.encode("utf-8") | |
new_c_value = base64.b64encode(raw_bytes).decode("utf-8") | |
if new_c_value: | |
node["C"] = new_c_value | |
del node["C-decoded"] | |
for k, v in list(node.items()): | |
process_node(v) | |
elif isinstance(node, list): | |
for item in node: | |
process_node(item) | |
process_node(self.decoded_bp) | |
def encode_blueprint(self) -> Optional[str]: | |
"""Encodes the modified blueprint back into a Shapez save string.""" | |
if not self.decoded_bp: | |
return None | |
try: | |
json_bytes = json.dumps(self.decoded_bp, separators=(',', ':'), sort_keys=True).encode("utf-8") | |
compressed_bytes = gzip.compress(json_bytes) | |
base64_payload = base64.b64encode(compressed_bytes).decode("utf-8") | |
encoded_string = f"SHAPEZ2-3-{base64_payload}$" | |
return encoded_string | |
except Exception: | |
return None | |
class App: | |
def __init__(self, root): | |
self.root = root | |
self.root.title("Shapez Blueprint Converter") | |
self.converter = BlueprintConverter() | |
self.setup_ui() | |
def setup_ui(self): | |
global FONTSIZE | |
# Set a universal font size for all ttk widgets | |
style = ttk.Style() | |
style.configure('.', font=('TkDefaultFont', FONTSIZE)) | |
main_frame = ttk.Frame(self.root, padding="10") | |
main_frame.pack(fill=tk.BOTH, expand=True) | |
# Input Frame | |
input_frame = ttk.LabelFrame(main_frame, text="Game String Input", padding="5") | |
input_frame.pack(fill=tk.BOTH, expand=True, pady=5) | |
self.input_text = tk.Text(input_frame, height=5, wrap=tk.WORD, font=("Consolas", FONTSIZE)) | |
self.input_text.pack(fill=tk.BOTH, expand=True) | |
# Control Buttons | |
button_frame = ttk.Frame(main_frame) | |
button_frame.pack(fill=tk.X, pady=5) | |
decode_btn = ttk.Button(button_frame, text="Decode to JSON ⬇", command=self.decode_blueprint) | |
decode_btn.pack(side=tk.LEFT, expand=True, padx=5, pady=2) | |
encode_btn = ttk.Button(button_frame, text="Encode to Game String ⬆", command=self.encode_blueprint) | |
encode_btn.pack(side=tk.LEFT, expand=True, padx=5, pady=2) | |
# Output Frame | |
output_frame = ttk.LabelFrame(main_frame, text="JSON Output (Editable)", padding="5") | |
output_frame.pack(fill=tk.BOTH, expand=True, pady=5) | |
self.output_text = tk.Text(output_frame, height=20, wrap=tk.WORD, font=("Consolas", 12)) | |
self.output_text.pack(fill=tk.BOTH, expand=True) | |
self.status_label = ttk.Label(main_frame, text="Ready.") | |
self.status_label.pack(fill=tk.X, pady=5) | |
def decode_blueprint(self): | |
game_string = self.input_text.get("1.0", tk.END).strip() | |
self.converter.game_string = game_string | |
self.status_label.config(text="Decoding...") | |
self.root.update() | |
if not self.converter.decode_blueprint(): | |
messagebox.showerror("Decoding Error", "Invalid blueprint string. Please check the format.") | |
self.status_label.config(text="Decoding failed.") | |
return | |
self.converter.decode_c_fields() | |
try: | |
# Pretty-print the JSON with a 2-space indent | |
json_output = json.dumps(self.converter.decoded_bp, indent=2) | |
self.output_text.delete("1.0", tk.END) | |
self.output_text.insert(tk.END, json_output) | |
self.status_label.config(text="Decoding successful!") | |
except Exception as e: | |
messagebox.showerror("Formatting Error", f"Could not format JSON: {e}") | |
self.status_label.config(text="Decoding failed.") | |
def encode_blueprint(self): | |
json_input = self.output_text.get("1.0", tk.END).strip() | |
self.status_label.config(text="Encoding...") | |
self.root.update() | |
try: | |
self.converter.decoded_bp = json.loads(json_input) | |
except json.JSONDecodeError as e: | |
messagebox.showerror("Encoding Error", f"Invalid JSON format: {e}") | |
self.status_label.config(text="Encoding failed.") | |
return | |
self.converter.encode_c_fields() | |
new_game_string = self.converter.encode_blueprint() | |
if new_game_string: | |
self.input_text.delete("1.0", tk.END) | |
self.input_text.insert(tk.END, new_game_string) | |
self.status_label.config(text="Encoding successful!") | |
else: | |
messagebox.showerror("Encoding Error", "Failed to encode blueprint. Check the JSON structure.") | |
self.status_label.config(text="Encoding failed.") | |
if __name__ == "__main__": | |
root = tk.Tk() | |
app = App(root) | |
root.mainloop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment