Initial Commit

This commit is contained in:
2018-10-18 23:43:46 -04:00
commit 78fabd9441
3 changed files with 170 additions and 0 deletions

41
README.md Normal file
View File

@@ -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 <patch file> <base file> <output file>`
## 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.

82
gui.py Normal file
View File

@@ -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_())

47
ips.py Executable file
View File

@@ -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 <patch file> <base file> <output file> [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)