We've just updated MediaWiki and its underlying software. If anything doesn't look or work quite right, please mention it to us. --RanAS

IPS patching code

From SnesLab
Jump to: navigation, search

IPS patching

C#

// Noobish Noobsicle wrote this IPS patching code
// romname is the original ROM, patchname is the patch to apply
FileStream romstream = new FileStream (romname, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
FileStream ipsstream = new FileStream (patchname, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
int lint = (int)ipsstream.Length;
byte[] ipsbyte = new byte[ipsstream.Length];
byte[] rombyte = new byte[romstream.Length];
IAsyncResult romresult;
IAsyncResult ipsresult = ipsstream.BeginRead (ipsbyte, 0, lint, null, null);
ipsstream.EndRead (ipsresult);
int ipson = 5;
int totalrepeats = 0;
int offset = 0;
bool keepgoing = true;
while (keepgoing == true) {
	offset = ipsbyte[ipson] * 0x10000 + ipsbyte[ipson + 1] * 0x100 + ipsbyte[ipson + 2];
	ipson++;
	ipson++;
	ipson++;
	if (ipsbyte[ipson] * 256 + ipsbyte[ipson + 1] == 0) {
		ipson++;
		ipson++;
		totalrepeats = ipsbyte[ipson] * 256 + ipsbyte[ipson + 1];
		ipson++;
		ipson++;
		byte[] repeatbyte = new byte[totalrepeats];
		for (int ontime = 0; ontime < totalrepeats; ontime++)
			repeatbyte[ontime] = ipsbyte[ipson];
		romstream.Seek (offset, SeekOrigin.Begin);
		romresult = romstream.BeginWrite (repeatbyte, 0, totalrepeats, null, null);
		romstream.EndWrite (romresult);
		ipson++;
		} else {
			totalrepeats = ipsbyte[ipson] * 256 + ipsbyte[ipson + 1];
			ipson++;
			ipson++;
			romstream.Seek (offset, SeekOrigin.Begin);
			romresult = romstream.BeginWrite (ipsbyte, ipson, totalrepeats, null, null);
			romstream.EndWrite (romresult);
			ipson = ipson + totalrepeats;
		}
		if (ipsbyte[ipson] == 69 && ipsbyte[ipson + 1] == 79 && ipsbyte[ipson + 2] == 70)
		keepgoing = false;
}
romstream.Close ();
ipsstream.Close ();

VB.NET

Dim romstream As New FileStream(romname, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)
Dim ipsstream As New FileStream(patchname, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)
Dim lint As Integer = CInt(ipsstream.Length)
Dim ipsbyte As Byte() = New Byte(ipsstream.Length - 1) {}
Dim rombyte As Byte() = New Byte(romstream.Length - 1) {}
Dim romresult As IAsyncResult
Dim ipsresult As IAsyncResult = ipsstream.BeginRead(ipsbyte, 0, lint, Nothing, Nothing)
ipsstream.EndRead(ipsresult)
Dim ipson As Integer = 5
Dim totalrepeats As Integer = 0
Dim offset As Integer = 0
Dim keepgoing As Boolean = True
'''///////////////End Init code
'''///////////////Start main code
While keepgoing = True
	offset = ipsbyte(ipson) * &H10000 + ipsbyte(ipson + 1) * &H100 + ipsbyte(ipson + 2)
	ipson += 1
	ipson += 1
	ipson += 1
	'''//////////split between repeating byte mode and standard mode
	If ipsbyte(ipson) * 256 + ipsbyte(ipson + 1) = 0 Then
		'''/////////repeating byte mode
		ipson += 1
		ipson += 1
		totalrepeats = ipsbyte(ipson) * 256 + ipsbyte(ipson + 1)
		ipson += 1
		ipson += 1
		Dim repeatbyte As Byte() = New Byte(totalrepeats - 1) {}
		For ontime As Integer = 0 To totalrepeats - 1
			repeatbyte(ontime) = ipsbyte(ipson)
		Next
		romstream.Seek(offset, SeekOrigin.Begin)
		romresult = romstream.BeginWrite(repeatbyte, 0, totalrepeats, Nothing, Nothing)
		romstream.EndWrite(romresult)
		ipson += 1
	Else
		'''/////////standard mode
		totalrepeats = ipsbyte(ipson) * 256 + ipsbyte(ipson + 1)
		ipson += 1
		ipson += 1
		romstream.Seek(offset, SeekOrigin.Begin)
		romresult = romstream.BeginWrite(ipsbyte, ipson, totalrepeats, Nothing, Nothing)
		romstream.EndWrite(romresult)
		ipson = ipson + totalrepeats
	End If
	'''//////////Test For "EOF"
	If ipsbyte(ipson) = 69 AndAlso ipsbyte(ipson + 1) = 79 AndAlso ipsbyte(ipson + 2) = 70 Then
		keepgoing = False
	End If
End While
romstream.Close()
ipsstream.Close()

Ruby

#!/usr/bin/env ruby
# -*- encoding: US-ASCII -*-
 
# ruby-ips.rb --Kernigh, March 2009
# This program is in the public domain and has no copyright.
 
require 'optparse'
 
filename = nil
sflag = false
 
opts = OptionParser.new do |opts|
	opts.banner = "usage: #{$0} [-s] [-f file] patch..."
 
	opts.on("-f FILE", "Apply patches to FILE") do |f|
		filename = f
	end
 
	opts.on("-s", "Work silently") do |l|
		sflag = true
	end
end
 
opts.parse!
 
# usage message if no arguments
if ARGV.size == 0
	puts opts
	exit 0
end
 
if sflag && (not filename)
	$stderr.puts "#{$0}: nothing to do"
	exit 1
end
 
# compatibility with Ruby before 1.8.7
unless 3.respond_to? :ord
	# with Ruby 1.8, "x"[0] is Fixnum
	# with Ruby 1.9, "x"[0] is String, "x"[0].ord is Fixnum
	class Integer
		def ord
			self
		end
	end
end
 
# subroutines
def unpack2(bytes)
	(bytes[0].ord << 8) + bytes[1].ord
end
def unpack3(bytes)
	(bytes[0].ord << 16) + (bytes[1].ord << 8) + bytes[2].ord
end
def hex(num)
	"0x#{num.to_s(16)} (#{num})"
end
 
# start work
file = open(filename, File::RDWR) if filename
 
ARGV.each do |patchname|
 
	# provide pinfo, pwarn for this patch
	pinfo = if sflag
		lambda { |m| }
	else
		$stdout.puts "#{patchname}:"
		lambda { |m| $stdout.puts "  #{m}" }
	end
	pwarn = lambda { |m| $stderr.puts "#{patchname}: #{m}" }
 
	# open patch
	patch = open(patchname, File::RDONLY)
 
	# check magic bytes
	unless patch.read(5) == "PATCH"
		pwarn.call "bad magic bytes, not an IPS patch"
		exit 1
	end
 
	# lambda to read patch and check number of bytes
	pread = lambda do |count|
		bytes = patch.read(count)
		if bytes.length < count
			pwarn.call "read #{bytes.length} bytes from patch, " +
			    "expected #{count} bytes"
			pwarn.call "unexpected end of patch file"
			exit 1
		end
		bytes
	end
 
	# process each record
	while true
		offset = pread.call(3)
 
		# "EOF" marks the end of patch
		if offset == "EOF"
			# count the remaining bytes in the file
			rem = patch.stat.size - patch.pos
 
			case rem
			when 0
				pinfo.call "end of patch"
			when 3
				# truncate support, as in Lunar IPS
				tru = unpack3(pread.call(3))
				pinfo.call "truncate to #{hex(tru)} bytes"
				file.truncate(tru) if filename
			else
				pwarn.call "unexpected data after end of " +
				    "patch, #{hex(rem)} bytes ..."
				pwarn.call "... problem with offset " +
				    "\"EOF\" #{hex("EOF")}?"
				exit 1
			end
 
			break
		end
 
		offset = unpack3(offset)
		length = unpack2(pread.call(2))
		case length
		when 0
			# RLE support
			length = unpack2(pread.call(2))
			byte = pread.call(1)
 
			pinfo.call "patch to file offset #{hex(offset)}, " +
			    "length #{hex(length)}, RLE"
			if filename
				file.pos = offset
				file.write(byte * length)
			end
		else
			pinfo.call "patch to file offset #{hex(offset)}, " +
			    "length #{hex(length)}"
			if filename
				file.pos = offset
				file.write(pread.call(length))
			else
				pread.call(length)
			end
		end
	end
 
	patch.close
end
 
file.close if filename