Permalink
Browse files

Initial import into git. Parts of the code are still a bit messy, but…

… it works very well.
  • Loading branch information...
Plombo committed Jan 22, 2011
0 parents commit 8498fd4ed90340df6fd100eaee6bf0406b64e88d
Showing with 1,868 additions and 0 deletions.
  1. +167 −0 brrencode3.py
  2. +68 −0 ccfarchive.py
  3. +74 −0 huf8.py
  4. +142 −0 lz77.py
  5. +266 −0 lzh8.py
  6. +42 −0 nes_rom_extract.py
  7. +49 −0 romc.py
  8. +432 −0 romchu.py
  9. +135 −0 snesrestore.py
  10. +165 −0 u8archive.py
  11. +328 −0 wiimetadata.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+# Author: Bryan Cain
+# Based on but heavily modified from BRRTools by Bregalad (written in Java)
+# Date: January 16, 2011
+# Description: Encodes 16-bit signed PCM data to SNES BRR format.
+
+import wave
+import struct
+
+class BRREncoder(object):
+ def __init__(self, pcm, brr):
+ self.pcm_owner = False
+ self.brr_owner = False
+
+ if type(pcm) == type(''):
+ pcm = open(pcm, 'rb')
+ self.pcm_owner = True
+ if type(brr) == type(''):
+ brr = open(brr, 'wb')
+ self.brr_owner = True
+
+ self.pcm = pcm
+ self.brr = brr
+ self.p1 = 0
+ self.p2 = 0
+
+ # clamps value to a signed short
+ def sshort(self, n):
+ if n > 0x7FFF: return (n - 0x10000)
+ elif n < -0x8000: return n & 0x7FFF
+ else: return n
+
+ # short clamp_16(int n)
+ def clamp_16(self, n):
+ if n > 0x7FFF: return (0x7FFF - (n>>24))
+ else: return n
+
+ # void ADPCMBlockMash(short[] PCMData)
+ def ADPCMBlockMash(self, PCMData):
+ smin=0
+ kmin=0
+ dmin=2**31
+
+ for s in range(13, 0, -1):
+ for k in range(4):
+ d = self.ADPCMMash(s, k, PCMData, False)
+ if d < dmin:
+ kmin = k # Memorize the filter, shift values with smaller error
+ dmin = d
+ smin = s
+ if dmin == 0.0: break
+ if dmin == 0.0: break
+
+ self.BRRBuffer[0] = (smin<<4)|(kmin<<2)
+ self.ADPCMMash(smin, kmin, PCMData, True)
+
+ # double ADPCMMash(int shiftamount, int filter, short[] PCMData, boolean write)
+ def ADPCMMash(self, shiftamount, filter, PCMData, write):
+ d2=0.0
+ vlin=0
+ l1 = self.p1
+ l2 = self.p2
+ step = 1<<shiftamount
+
+ for i in range(16):
+ # Compute linear prediction for filters
+ if filter == 0:
+ pass
+ elif filter == 1:
+ vlin = l1 >> 1
+ vlin += (-l1) >> 5
+ elif filter == 2:
+ vlin = l1
+ vlin += (-(l1 +(l1>>1)))>>5
+ vlin -= l2 >> 1
+ vlin += l2 >> 5
+ else:
+ vlin = l1
+ vlin += (-(l1+(l1<<2) + (l1<<3)))>>7
+ vlin -= l2>>1
+ vlin += (l2+(l2>>1))>>4
+
+ d = (PCMData[i]>>1) - vlin # Difference between linear prediction and current sample
+ da = abs(d)
+
+ if da > 16384 and da < 32768:
+ d = d - 32768 * ( d >> 24 ) # Take advantage of wrapping
+ dp = d + (step << 2) + (step >> 2)
+ c = 0
+ if dp > 0:
+ if step > 1:
+ c = dp /(step>>1)
+ else:
+ c = dp<<1
+ if c > 15:
+ c = 15
+ c -= 8
+ dp = (c<<(shiftamount-1)) # quantized estimate of samp - vlin
+ # edge case, if caller even wants to use it */
+ if shiftamount > 12:
+ dp = ( dp >> 14 ) & ~0x7FF
+ c &= 0x0f # mask to 4 bits
+ l2 = l1 # shift history
+ l1 = self.sshort(self.clamp_16(vlin + dp)*2)
+ d = PCMData[i]-l1
+ d2 += float(d)*d # update square-error
+
+ if write: # if we want output, put it in proper place */
+ self.BRRBuffer[(i>>1)+1] |= c<<(4-((i&0x01)<<2))
+
+ if write:
+ self.p2 = l2
+ self.p1 = l1
+
+ return d2
+
+ # encodes the entire PCM file to BRR
+ def encode(self):
+ self.BRRBuffer = [0, 0, 0, 0, 0, 0, 0, 0, 0] # byte[9]
+
+ #wav = wave.open(wav, 'rb')
+ #if wav.getsampwidth() != 2: raise ValueError('must be 16 bits per sample')
+ pcm = self.pcm
+ brr = self.brr
+ self.p1 = 0
+ self.p2 = 0
+
+ samples2 = pcm.read(32) # the PCM samples in VC PCM files are misaligned for some reason
+ while len(samples2) == 32:
+ samples2 = struct.unpack('>16h', samples2)
+ self.BRRBuffer = [0, 0, 0, 0, 0, 0, 0, 0, 0]
+ self.ADPCMBlockMash(samples2)
+ brr.write(struct.pack('9B', *self.BRRBuffer))
+ #samples2 = wav.readframes(16)
+ samples2 = pcm.read(32)
+
+ #wav.close()
+ if self.pcm_owner: pcm.close()
+ if self.brr_owner: brr.close()
+
+ # offset: PCM offset (measured in samples, NOT in bytes)
+ # returns: 9-byte BRR block
+ def encode_block(self, offset):
+ # read PCM - the PCM samples in VC PCM files are misaligned for some reason
+ self.pcm.seek(offset * 2)
+ samples2 = self.pcm.read(32)
+
+ if len(samples2) != 32:
+ raise ValueError('invalid PCM offset %d (file offset %d)' % (offset, offset*2))
+
+ samples2 = struct.unpack('>16h', samples2)
+
+ # encode to BRR
+ self.BRRBuffer = [0, 0, 0, 0, 0, 0, 0, 0, 0]
+ self.ADPCMBlockMash(samples2)
+
+ return struct.pack('9B', *self.BRRBuffer)
+
+
+if __name__ == '__main__':
+ import sys
+ if len(sys.argv) != 3:
+ print 'Usage: %s input.pcm output.brr' % sys.argv[0]
+ enc = BRREncoder(sys.argv[1], sys.argv[2])
+ enc.encode()
+ print 'Wrote file %s' % sys.argv[2]
+
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# Author: Bryan Cain (Plombo)
+# Date: December 27, 2010
+# Description: Reads Wii CCF archives, which contain Genesis and Master System ROMs.
+
+import struct
+import zlib
+from cStringIO import StringIO
+
+class CCFArchive(object):
+ # archive: a file-like object containing the CCF archive, OR the path to a CCF archive
+ def __init__(self, archive):
+ if type(archive) == type(''):
+ self.file = open(archive, 'rb')
+ else:
+ self.file = archive
+ self.files = []
+ self.readheader()
+
+ def readheader(self):
+ magic, zeroes1, rootnode_offset, numfiles, zeroes2 = struct.unpack('<4s12sII8s', self.file.read(32))
+ assert magic == 'CCF\0'
+ assert zeroes1 == 12 * '\0'
+ assert rootnode_offset == 0x20
+ assert zeroes2 == 8 * '\0'
+ for i in range(numfiles):
+ fd = FileDescriptor(self.file)
+ self.files.append(fd)
+
+ def hasfile(self, path):
+ for f in self.files:
+ if f.name == path: return True
+ return False
+
+ def getfile(self, path):
+ assert self.hasfile(path)
+ fd = None
+ for f in self.files:
+ if f.name == path: fd = f
+ return self.getfile2(fd)
+
+ def getfile2(self, fd):
+ self.file.seek(fd.data_offset * 32)
+ string = self.file.read(fd.size)
+ if fd.compressed:
+ string = zlib.decompress(string)
+ assert len(string) == fd.decompressed_size
+ return StringIO(string)
+
+ # returns the requested file, even if the name is cut off inside the archive
+ def find(self, name):
+ for fd in self.files:
+ if name.startswith(fd.name.rstrip()) or fd.name.startswith(name.rstrip()): return self.getfile2(fd)
+ return None
+
+class FileDescriptor(object):
+ # f: a file-like object of a CCF file at the position of this file descriptor
+ def __init__(self, f):
+ self.name, self.data_offset, self.size, self.decompressed_size = struct.unpack('<20sIII', f.read(32))
+ self.name = self.name[0:self.name.find('\0')]
+ self.compressed = (self.size != self.decompressed_size)
+
+if __name__ == '__main__':
+ import os
+ arc = CCFArchive(os.getenv('HOME') + '/wii/spinball/data.ccf')
+ arc.getfile('SonicSpinball_USA.S')
+
+
74 huf8.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+# Author: Bryan Cain (translated to Python from the "puff8" program by hcs, written in C)
+# Date: January 17, 2011
+# Description: Decompresses Nintendo's Huf8 compression used in Virtual Console games.
+
+import os, struct
+from array import array
+
+def decompress(infile, outfile):
+ infile.seek(0, os.SEEK_END)
+ file_length = infile.tell()
+ infile.seek(0, os.SEEK_SET)
+
+ # read header
+ magic_declength, symbol_count = struct.unpack('<IB', infile.read(5))
+ if (magic_declength & 0xFF) != 0x28:
+ raise ValueError("not 8-bit Huffman")
+ decoded_length = magic_declength >> 8
+ symbol_count += 1
+
+ # read decode table
+ decode_table_size = symbol_count * 2 - 1
+ decode_table = array('B', infile.read(decode_table_size))
+
+ '''
+ print "encoded size = %ld bytes (%d header + %ld body)" % (
+ file_length, 5 + decode_table_size,
+ file_length - (5 + decode_table_size))
+ print "decoded size = %ld bytes" % decoded_length
+ '''
+
+ # decode
+ bits = 0
+ bits_left = 0
+ table_offset = 0
+ bytes_decoded = 0
+
+ while bytes_decoded < decoded_length:
+ if bits_left == 0:
+ bits = struct.unpack("<I", infile.read(4))[0]
+ bits_left = 32
+
+ current_bit = ((bits & 0x80000000) != 0)
+ next_offset = (((table_offset + 1) / 2 * 2) + 1 +
+ (decode_table[table_offset] & 0x3f) * 2 +
+ current_bit)
+
+ if next_offset >= decode_table_size:
+ raise ValueError("reading past end of decode table")
+
+ if ((not current_bit and (decode_table[table_offset] & 0x80)) or
+ ( current_bit and (decode_table[table_offset] & 0x40))):
+ outfile.write(chr(decode_table[next_offset]))
+ bytes_decoded += 1
+ # print "%02x" % decode_table[next_offset]
+ next_offset = 0
+
+ if next_offset == table_offset:
+ raise ValueError("infinite loop in Huf8 decompression")
+ table_offset = next_offset
+ bits_left -= 1
+ bits <<= 1
+
+if __name__ == "__main__":
+ import sys
+ if len(sys.argv) != 3:
+ sys.stderr.write("Usage: %s infile outfile\n" % sys.argv[0])
+
+ infile = open(sys.argv[1], "rb")
+ outfile = open(sys.argv[2], "wb")
+
+ decompress(infile, outfile)
+ outfile.close()
+
Oops, something went wrong.

0 comments on commit 8498fd4

Please sign in to comment.