import sys from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QWidget, QLabel, QPushButton, QFileDialog, QInputDialog, QMessageBox, QCheckBox from PyQt6.QtGui import QFont from Crypto.Cipher import AES import base64 import gzip import zlib import pyperclip import hashlib from winmica import EnableMica, BackdropType, is_mica_supported MAC_SAVE_KEY = ( b"\x69\x70\x75\x39\x54\x55\x76\x35\x34\x79\x76\x5d\x69\x73\x46\x4d" b"\x68\x35\x40\x3b\x74\x2e\x35\x77\x33\x34\x45\x32\x52\x79\x40\x7b" ) def xor(string: str, key: int) -> str: return ("").join(chr(ord(char) ^ key) for char in string) def remove_pad(data: bytes) -> bytes: last = data[-1] if last < 16: data = data[:-last] return data def add_pad(data: bytes) -> bytes: len_r = len(data) % 16 if len_r: to_add = 16 - len_r data += to_add.to_bytes(1, "little") * to_add return data def decrypt_mac_save_data(data: bytes) -> str: cipher = AES.new(MAC_SAVE_KEY, AES.MODE_ECB) return remove_pad(cipher.decrypt(data)).decode() def encrypt_mac_save_data(data: str) -> bytes: cipher = AES.new(MAC_SAVE_KEY, AES.MODE_ECB) return cipher.encrypt(add_pad(data.encode())) def decrypt_save_data(data: str) -> str: base64_decoded = base64.urlsafe_b64decode(xor(data, key=11).encode()) decompressed = gzip.decompress(base64_decoded) return decompressed.decode() def encrypt_save_data(data: str) -> str: gzipped = gzip.compress(data.encode()) base64_encoded = base64.urlsafe_b64encode(gzipped) return xor(base64_encoded.decode(), key=11) def encode_level(level_string: str, is_official_level: bool) -> str: gzipped = gzip.compress(level_string.encode()) base64_encoded = base64.urlsafe_b64encode(gzipped) if is_official_level: base64_encoded = base64_encoded[13:] return base64_encoded.decode() def decode_level(level_data: str, is_official_level: bool) -> str: if is_official_level: level_data = 'H4sIAAAAAAAAA' + level_data base64_decoded = base64.urlsafe_b64decode(level_data.encode()) decompressed = zlib.decompress(base64_decoded, 15 | 32) return decompressed.decode() def xor_cipher(input_str: str, key: str) -> str: result = "" for i in range(len(input_str)): byte = ord(input_str[i]) x_key = ord(key[i % len(key)]) result += chr(byte ^ x_key) return result def encode_gjp(password: str) -> str: encoded = xor_cipher(password, "37526") encoded_base64 = base64.b64encode(encoded.encode()).decode() encoded_base64 = encoded_base64.replace("+", "-") encoded_base64 = encoded_base64.replace("/", "_") return encoded_base64 def decode_gjp(gjp: str) -> str: decoded_base64 = base64.b64decode(gjp.encode()).decode() decoded = xor_cipher(decoded_base64, "37526") return decoded def encode_gjp2(password: str = "", salt: str = "mI29fmAnxgTs") -> str: password += salt hash = hashlib.sha1(password.encode()).hexdigest() return hash class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("GDUtils") self.setGeometry(100, 100, 400, 300) self.apply_mica(self) titleLabel = QLabel("GDUtils") font = QFont() font.setPointSize(18) font.setBold(True) titleLabel.setFont(font) saveFileLabel = QLabel("Save file") self.saveFileDecode = QPushButton("Decode") self.saveFileDecode.clicked.connect(self.on_save_decode_click) self.saveFileEncode = QPushButton("Encode") self.saveFileEncode.clicked.connect(self.on_save_encode_click) self.saveFileIsMacos = QCheckBox("macOS save?") saveFileButtons = QHBoxLayout() saveFileButtons.addStretch() saveFileButtons.addWidget(self.saveFileDecode, alignment=Qt.AlignmentFlag.AlignHCenter) saveFileButtons.addWidget(self.saveFileEncode, alignment=Qt.AlignmentFlag.AlignHCenter) saveFileButtons.addWidget(self.saveFileIsMacos, alignment=Qt.AlignmentFlag.AlignHCenter) saveFileButtons.addStretch() levelDataLabel = QLabel("Level data") self.levelDataDecode = QPushButton("Decode") self.levelDataDecode.clicked.connect(self.on_level_decode_click) self.levelDataEncode = QPushButton("Encode") self.levelDataEncode.clicked.connect(self.on_level_encode_click) self.levelDataIsOfficial = QCheckBox("Official level?") levelDataButtons = QHBoxLayout() levelDataButtons.addStretch() levelDataButtons.addWidget(self.levelDataDecode, alignment=Qt.AlignmentFlag.AlignHCenter) levelDataButtons.addWidget(self.levelDataEncode, alignment=Qt.AlignmentFlag.AlignHCenter) levelDataButtons.addWidget(self.levelDataIsOfficial, alignment=Qt.AlignmentFlag.AlignHCenter) levelDataButtons.addStretch() gjpLabel = QLabel("GJP") self.gjpDecode = QPushButton("Decode") self.gjpDecode.clicked.connect(self.on_gjp_decode_click) self.gjpEncode = QPushButton("Encode") self.gjpEncode.clicked.connect(self.on_gjp_encode_click) gjpButtons = QHBoxLayout() gjpButtons.addStretch() gjpButtons.addWidget(self.gjpDecode, alignment=Qt.AlignmentFlag.AlignHCenter) gjpButtons.addWidget(self.gjpEncode, alignment=Qt.AlignmentFlag.AlignHCenter) gjpButtons.addStretch() gjp2Label = QLabel("GJP2") self.gjp2Encode = QPushButton("Encode") self.gjp2Encode.clicked.connect(self.on_gjp2_encode_click) layout = QVBoxLayout() layout.addWidget(titleLabel, alignment=Qt.AlignmentFlag.AlignHCenter) layout.addWidget(saveFileLabel, alignment=Qt.AlignmentFlag.AlignHCenter) layout.addLayout(saveFileButtons) layout.addWidget(levelDataLabel, alignment=Qt.AlignmentFlag.AlignHCenter) layout.addLayout(levelDataButtons) layout.addWidget(gjpLabel, alignment=Qt.AlignmentFlag.AlignHCenter) layout.addLayout(gjpButtons) layout.addWidget(gjp2Label, alignment=Qt.AlignmentFlag.AlignHCenter) layout.addWidget(self.gjp2Encode, alignment=Qt.AlignmentFlag.AlignHCenter) layout.addStretch() container = QWidget() container.setLayout(layout) self.setCentralWidget(container) def apply_mica(self, widget): if is_mica_supported(): widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True) hwnd = int(widget.winId()) EnableMica(hwnd, BackdropType.MICA) def on_save_decode_click(self): file_path, _ = QFileDialog.getOpenFileName( self, "Select a GD save file", "", "DAT Files (*.dat)" ) if not file_path: return output_file_path, _ = QFileDialog.getSaveFileName( self, "Select a output location for the decoded save file", file_path.removesuffix(".dat") + ".xml", "XML Files (*.xml)" ) if not output_file_path: return with open(file_path, "r") as f: data = f.read() with open(output_file_path, "w") as f: if self.saveFileIsMacos.isChecked(): f.write(decrypt_mac_save_data(data)) else: f.write(decrypt_save_data(data)) def on_save_encode_click(self): file_path, _ = QFileDialog.getOpenFileName( self, "Select a decoded GD save file", "", "XML Files (*.xml)" ) if not file_path: return output_file_path, _ = QFileDialog.getSaveFileName( self, "Select a output location for the encoded save file", file_path.removesuffix(".xml") + ".dat", "DAT Files (*.dat)" ) if not output_file_path: return with open(file_path, "r") as f: data = f.read() if self.saveFileIsMacos.isChecked(): with open(output_file_path, "wb") as f: f.write(encrypt_mac_save_data(data)) else: with open(output_file_path, "w") as f: f.write(encrypt_save_data(data)) def on_level_decode_click(self): dialog = QInputDialog(self) dialog.setWindowTitle("Level decoder") dialog.setLabelText("Enter the level data to decode:") self.apply_mica(dialog) ok = dialog.exec() data = dialog.textValue() if ok and data: pyperclip.copy(decode_level(data, self.levelDataIsOfficial.isChecked())) msg = QMessageBox() msg.setWindowTitle("Level decoder") msg.setText("The decoded level data has been copied to your clipboard") self.apply_mica(msg) msg.exec() def on_level_encode_click(self): dialog = QInputDialog(self) dialog.setWindowTitle("Level encoder") dialog.setLabelText("Enter the level data to encode:") self.apply_mica(dialog) ok = dialog.exec() data = dialog.textValue() if ok and data: pyperclip.copy(encode_level(data, self.levelDataIsOfficial.isChecked())) msg = QMessageBox() msg.setWindowTitle("Level encoder") msg.setText("The encoded level data has been copied to your clipboard") self.apply_mica(msg) msg.exec() def on_gjp_decode_click(self): dialog = QInputDialog(self) dialog.setWindowTitle("GJP decoder") dialog.setLabelText("Enter the GJP to decode:") self.apply_mica(dialog) ok = dialog.exec() data = dialog.textValue() if ok and data: pyperclip.copy(decode_gjp(data)) msg = QMessageBox() msg.setWindowTitle("GJP decoder") msg.setText("The decoded GJP has been copied to your clipboard") self.apply_mica(msg) msg.exec() def on_gjp_encode_click(self): dialog = QInputDialog(self) dialog.setWindowTitle("GJP encoder") dialog.setLabelText("Enter the password to encode:") self.apply_mica(dialog) ok = dialog.exec() data = dialog.textValue() if ok and data: pyperclip.copy(encode_gjp(data)) msg = QMessageBox() msg.setWindowTitle("GJP encoder") msg.setText("The encoded GJP has been copied to your clipboard") self.apply_mica(msg) msg.exec() def on_gjp2_encode_click(self): dialog = QInputDialog(self) dialog.setWindowTitle("GJP2 encoder") dialog.setLabelText("Enter the password to encode:") self.apply_mica(dialog) ok = dialog.exec() data = dialog.textValue() if ok and data: pyperclip.copy(encode_gjp2(data)) msg = QMessageBox() msg.setWindowTitle("GJP2 encoder") msg.setText("The encoded GJP2 has been copied to your clipboard") self.apply_mica(msg) msg.exec() app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())