/* * This is the original version of * http://www.smwcentral.net/?p=thread&pid=273935 */ [quote=Kaizo][code]FileStream romstream = new FileStream(romname, IAsyncResult romresult; FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); ... romstream.Seek(offset, SeekOrigin.Begin); romresult = romstream.BeginWrite(ipsbyte, ipson, totalrepeats, null, null); romstream.EndWrite(romresult);[/code][/quote] [i](going offtopic...)[/i] This code surprises me because it seems to do [i]asynchronous I/O[/i]. I am not a C# hacker and I never saw FileStream#BeginWrite and FileStream#EndWrite before now. I knew that FreeBSD gives [url=http://www.freebsd.org/cgi/man.cgi?query=aio_write&sektion=2&apropos=0&manpath=FreeBSD+7.1-RELEASE]aio_write(2)[/url] and [url=http://www.freebsd.org/cgi/man.cgi?query=aio_waitcomplete&sektion=2&apropos=0&manpath=FreeBSD+7.1-RELEASE]aio_waitcomplete(2)[/url] to C language, but most programming languages do not have an equivalent feature like you have in C#. (This is guess, because I never use C# nor FreeBSD.) I think that the benefit of async IO is to put code between BeginWrite and EndWrite, but you have no code there. Why not use the blocking IO? Internet tells me that FileStream#Write exists. [i](returning to topic...)[/i] [url=http://www.ruby-lang.org/en/]Ruby language[/url] is not popular like C#, but I use Ruby with SMW. Kaizo and Ersanio have three pieces in C#, I can redo them in Ruby 1.9. ------------Ruby 1.9--------------- [b]1. Getting ROM data in to a byte array[/b] This is the code: [code]rombuffer = open(path, "rb") { |f| f.read }[/code] This is an example to use my code at the interactive Ruby prompt: [code]irb(main):001:0> path = "smw-clean.smc" => "smw-clean.smc" irb(main):002:0> rombuffer = open(path, "rb") { |f| f.read }; 0 => 0 irb(main):003:0> rombuffer.length => 524800 irb(main):004:0> rombuffer.index "SUPER" => 33216 irb(main):005:0> rombuffer[33216, 21] => "SUPER MARIOWORLD "[/code] [b]2. Writing data to a ROM file[/b] This is the code: [code]open(path, "wb") { |f| f.write(rombuffer) }[/code] This continues my example: [code]irb(main):006:0> rombuffer[33216, 21] = "Super Ruby 1.9 World " => "Super Ruby 1.9 World " irb(main):007:0> path = "smw-ruby.smc" => "smw-ruby.smc" irb(main):008:0> open(path, "wb") { |f| f.write(rombuffer) } => 524800[/code] Now if emulator plays smw-ruby.smc, then the title of ROM is "Super Ruby 1.9 World " (and the checksum is bad). [b]3. Patching an IPS patch[/b] I already wrote a program for this: [url=http://bin.smwcentral.net/994]ruby-ips.rb[/url] This program is how I normally apply IPS patch before I play SMW hack. My program has one bug at line 115; the program will raise an exception instead of displaying my error message. Also the code style might not be good because I used "lambda" too much. I have some code to expand a ROM and compute the checksum, but I cut and derived the code from a longer Ruby program that I wrote for my SMW hack, so some variable names might be strange. [b]4. ROM expansion with clean ROM check[/b] This code makes an expanded copy of a ROM and also checks that the original ROM is clean Super Mario World. The clean ROM must be headerless or have a valid SMC header. [b]CAVEAT: the expanded ROM will be headerless.[/b] (I am not sure how to make the header, because I noticed that SMW hacks have invalid SMC headers. I need to learn more about SMC headers.) Also, the expanded ROM will have a bad checksum. This code might [i]not be ready[/i] for the code library, because I would need to give SMC header to expanded ROM, and to separate the ROM expansion and the clean ROM check into two pieces of code. Change [i]clean_rom[/i] to path to clean ROM. Change [i]expanded_rom[/i] to path to expanded ROM. Change [i]Expanded_banks[/i] to 32 for 1 MB, 64 for 2 MB, or 128 for 4 MB. Other numbers from 17 through 127 will play in snes9x, but the ROM size in the Super NES header will wrong, because it will be the next power of 2. [code]require 'digest/sha1' clean_rom = "smw-ruby.smc" expanded_rom = "expanded.sfc" # each lorom bank has 0x8000 bytes SMW_banks = 16 Expanded_banks = 128 # SHA1 of the clean SMW ROM (all 16 banks, without SMC header) SMW_clean = "6b47bb75d16514b6a476aa0c73a683a2a4c18765" smw = nil rom = nil begin # open SMW ROM image for reading; seek past any SMC header smw = open(clean_rom, "rb") size = smw.stat.size goal = SMW_banks * 0x8000 if size == goal # headerless ROM elsif size >= (goal + 512) # check SMC header size = smw.read(2).unpack("v")[0] * 0x2000 if size == goal # seek past header smw.pos = 512 else raise "#{clean_rom}: wrong size in SMC header" end else raise "#{clean_rom}: wrong size" end rom = open(expanded_rom, "wb") # copy banks check = Digest::SHA1.new SMW_banks.times do bank = smw.read(0x8000) check.update(bank) rom.write(bank) end smw.close unless check.hexdigest == SMW_clean raise "${clean_rom}: not a clean SMW ROM" end # expand ROM bank = "\xff" * 0x8000 (Expanded_banks - SMW_banks).times do rom.write(bank) end # Write ROM size to Super NES header. If the number of # banks is not a power of 2, then round upward. # byte = (Math.log2(Expanded_banks) + 5).ceil.to_i rom.pos = 0x7fd7 rom.write([byte].pack "C") rom.close rescue Exception => e smw.close if smw and not smw.closed? rom.close if rom and not rom.closed? File.delete expanded_rom # delete bad file raise e end[/code] [b]5. Recompute checksum in Super NES header[/b] This is a good programming exercise. This code recomputes the checksum, but my version only works with LoROM (like Super Mario World) and only if the ROM contains a whole number of banks. If the number of banks is not a power of 2, then my code will mirror the banks, so that the checksum equals what snes9x would compute. (Lunar Magic seems to adjust some bytes so that the ROM has the same checksum as before, instead of recomputing the checksum.) Change [i]target_rom[/i] to path to ROM. [code]target_rom = "expanded.sfc" io = nil begin io = open(target_rom, "rb+") size = io.stat.size bank_count = size / 0x8000 case size % 0x8000 when 0 base = 0 # no SMC header when 512 base = 512 # SMC header else raise "#{rom}: wrong size" end # Compute checksum in Super NES header. Use the formula # from wlalink/compute.c of WLA DX, which is the same # as that from memmap.c of Snes9x ("from NSRT"). # io.pos = base + 0x7fdc io.write([0xffff, 0x0000].pack "vv") # compute checksum of each bank banksum = [] io.pos = base bank_count.times do |bank| banksum[bank] = io.read(0x8000).sum(16) end # compute checksum of all banks checksum = 0 (2 ** Math.log2(bank_count).ceil).times do |bank| # handle the mirror banks bank >>= 1 while bank >= banksum.length checksum += banksum[bank] end checksum &= 0xffff # write checksum io.pos = base + 0x7fdc io.write([checksum ^ 0xffff, checksum].pack "vv") ensure io.close end[/code] [quote=HyperHacker]Reading/writing the entire file at once is not a good method for most programs. It's slow (lots of unnecessary disk I/O), uses more RAM than it needs to, and if you have the file also open in another program (e.g. hex editor) and change something completely unrelated to what you're working on, the change gets wiped out.[/quote] This also applies to my above example when I changed "SUPER MARIOWORLD" to "Super Ruby 1.9 World". My example is slow and wasteful because I used the code that reads and writes the entire file, but I only needed to seek and write 21 bytes.