commit 78fabd94413b5acd5dbc3e06a3f1bc9406472c80 Author: Alex Date: Thu Oct 18 23:43:46 2018 -0400 Initial Commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0a8431 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# OpenIPS + +An IPS patcher designed to be fully FOSS and cross-platform. + +## Requirements + +Python3, PyQT5 for the GUI version + +## Usage + +Either run the GUI version, or: + +`ips.py ` + + +## TODO + +1. Implement support for IPS32 +2. Improve the CLI +3. Add support for creating IPS patches (both IPS and IPS32) +4. Maybe more? + +## Credits + +[File Formats Wiki](http://fileformats.archiveteam.org/wiki/IPS_\(binary_patch_format\)) + +## License + +Copyright [2018] [Alex Taber (astronautlevel2)] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this project except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..8966d51 --- /dev/null +++ b/gui.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +from ips import perform_ips_patch +from PyQt5 import QtCore, QtGui, QtWidgets + +import sys + +class ApplicationWindow(QtWidgets.QWidget): + def __init__(self): + super().__init__() + self.initUI() + + def initUI(self): + self.setWindowTitle("OpenIPS Patcher") + + self.base_rom_label = QtWidgets.QLineEdit(self) + self.base_rom_label.setReadOnly(True) + + self.patch_file_label = QtWidgets.QLineEdit(self) + self.patch_file_label.setReadOnly(True) + + self.output_file_label = QtWidgets.QLineEdit(self) + self.output_file_label.setReadOnly(True) + + self.base_rom_select = QtWidgets.QPushButton(self) + self.base_rom_select.setText("Select base rom") + self.base_rom_select.clicked.connect(self.selRomFile) + + self.patch_file_select = QtWidgets.QPushButton(self) + self.patch_file_select.setText("Select IPS Patch File") + self.patch_file_select.clicked.connect(self.selIPSFile) + + self.output_file_select = QtWidgets.QPushButton(self) + self.output_file_select.setText("Select output location") + self.output_file_select.clicked.connect(self.selSaveFile) + + self.ips32_mode = QtWidgets.QCheckBox(self) + self.ips32_mode.setText("IPS32") + self.run_patch = QtWidgets.QPushButton(self) + self.run_patch.setText("Apply patch") + self.run_patch.clicked.connect(self.runPatch) + + grid = QtWidgets.QGridLayout() + grid.addWidget(self.base_rom_label, 0, 0) + grid.addWidget(self.base_rom_select, 0, 1) + grid.addWidget(self.patch_file_label, 1, 0) + grid.addWidget(self.patch_file_select, 1, 1) + grid.addWidget(self.output_file_label, 2, 0) + grid.addWidget(self.output_file_select, 2, 1) + grid.addWidget(self.ips32_mode, 3, 0) + grid.addWidget(self.run_patch, 3, 1) + + self.setLayout(grid) + self.show() + + def runPatch(self): + patch_file = open(self.patch_file_label.text(), "rb") + error = perform_ips_patch(patch_file, self.base_rom_label.text(), self.output_file_label.text(), False) + if error == 0: + QtWidgets.QErrorMessage.qtHandler().showMessage("File patched successfully!") + elif error == -1: + QtWidgets.QErrorMessage.qtHandler().showMessage("Error: Invalid IPS") + + def selRomFile(self): + fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self,"Select base ROM", "","All Files (*)") + if fileName: + self.base_rom_label.setText(fileName) + + def selIPSFile(self): + fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self,"Select IPS patch", "","IPS Files (*.ips);;All Files (*)") + if fileName: + self.patch_file_label.setText(fileName) + + def selSaveFile(self): + fileName, _ = QtWidgets.QFileDialog.getSaveFileName(self,"Select Output File Location","","All Files (*)") + if fileName: + self.output_file_label.setText(fileName) + +if __name__ == "__main__": + app = QtWidgets.QApplication(sys.argv) + mw = ApplicationWindow() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/ips.py b/ips.py new file mode 100755 index 0000000..d1f85e4 --- /dev/null +++ b/ips.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +import sys, os +import shutil + +def perform_ips_patch(patch_file, base_file, output_file, ips32): + # IPS32 features not yet implemented + magic = patch_file.read(5) + if magic != b"PATCH": + print("Invalid ips patch!") + return -1 + + shutil.copyfile(base_file, output_file) + output_file = open(output_file, "rb+") + + offset = patch_file.read(3) + while offset != b"EOF": + offset = (int(offset[0]) << 16) + (int(offset[1]) << 8) + int(offset[2]) + length = patch_file.read(2) + length = (int(length[0]) << 8) + int(length[1]) + output_file.seek(offset) + if length != 0: # Normal mode + data = patch_file.read(length) + output_file.write(data) + else: # RLE Mode + count = patch_file.read(2) + count = (int(count[0]) << 8) + int(count[1]) + byte = patch_file.read(1) + output_file.write(byte * count) + offset = patch_file.read(3) + return 0 + + +if __name__ == '__main__': + + if len(sys.argv) < 4: + print("Usage: ips.py [ips32] \ + \nSpecifying anything for ips32 will result in it patching as if the patch format is ips32 instead of ips") + exit() + + if not (os.path.exists(sys.argv[1]) and os.path.exists(sys.argv[2])): + print("Warning: one of the input files does not exist") + exit() + + patch_file = open(sys.argv[1], "rb") + + perform_ips_patch(patch_file, sys.argv[2], sys.argv[3], len(sys.argv) > 4)