Small additions/changes I forgot to commit
This commit is contained in:
parent
486880262b
commit
bbcd64b0f2
|
@ -273,6 +273,8 @@ class FF5Reader(QMainWindow):
|
||||||
#zone_pxs += make_zone_pxs2(blocks, miniblocks, blockmaps, z, zone_px_cache)
|
#zone_pxs += make_zone_pxs2(blocks, miniblocks, blockmaps, z, zone_px_cache)
|
||||||
print('Zone pixmap cache results: {misses} misses, {hits} hits'.format(**zone_px_cache))
|
print('Zone pixmap cache results: {misses} misses, {hits} hits'.format(**zone_px_cache))
|
||||||
perfcount()
|
perfcount()
|
||||||
|
del block_cache
|
||||||
|
del zone_px_cache
|
||||||
|
|
||||||
print('Generating Battle backgrounds')
|
print('Generating Battle backgrounds')
|
||||||
battle_bgs = make_battle_backgrounds(ROM_jp)
|
battle_bgs = make_battle_backgrounds(ROM_jp)
|
||||||
|
|
|
@ -17,18 +17,21 @@
|
||||||
|
|
||||||
HEX_PREFIX = '#' # '#' '$' or '0x' are also nice
|
HEX_PREFIX = '#' # '#' '$' or '0x' are also nice
|
||||||
|
|
||||||
|
|
||||||
def divceil(numerator, denominator):
|
def divceil(numerator, denominator):
|
||||||
'''
|
'''
|
||||||
Reverse floor division for fast ceil
|
Reverse floor division for fast ceil
|
||||||
'''
|
'''
|
||||||
return -(-numerator // denominator)
|
return -(-numerator // denominator)
|
||||||
|
|
||||||
|
|
||||||
def hex_length(i):
|
def hex_length(i):
|
||||||
'''
|
'''
|
||||||
String length of hexadecimal representation of integer
|
String length of hexadecimal representation of integer
|
||||||
'''
|
'''
|
||||||
return divceil(i.bit_length(), 4)
|
return divceil(i.bit_length(), 4)
|
||||||
|
|
||||||
|
|
||||||
def hex(num, digits=2):
|
def hex(num, digits=2):
|
||||||
'''
|
'''
|
||||||
Consolidate hex formatting for consistency
|
Consolidate hex formatting for consistency
|
||||||
|
@ -36,11 +39,13 @@ def hex(num, digits=2):
|
||||||
#return '{:0{}X}₁₆'.format(num, digits)
|
#return '{:0{}X}₁₆'.format(num, digits)
|
||||||
return HEX_PREFIX + '{:0{}X}'.format(num, digits)
|
return HEX_PREFIX + '{:0{}X}'.format(num, digits)
|
||||||
|
|
||||||
def indirect(rom, start, length=2):
|
|
||||||
|
def indirect(rom, start, length=2, endian='little'):
|
||||||
'''
|
'''
|
||||||
Read little-endian value at start address in rom
|
Read little-endian value at start address in rom
|
||||||
'''
|
'''
|
||||||
return int.from_bytes(rom[start:start+length], 'little')
|
return int.from_bytes(rom[start:start+length], endian)
|
||||||
|
|
||||||
|
|
||||||
def parse_struct(rom, offset, structure):
|
def parse_struct(rom, offset, structure):
|
||||||
'''
|
'''
|
||||||
|
@ -97,3 +102,81 @@ def decompress_lzss(rom, start, header=False, length=None):
|
||||||
buffer_p = (buffer_p+1) % 0x800
|
buffer_p = (buffer_p+1) % 0x800
|
||||||
offset = (offset+1) % 0x800
|
offset = (offset+1) % 0x800
|
||||||
return bytes(output[:uncompressed_length])
|
return bytes(output[:uncompressed_length])
|
||||||
|
|
||||||
|
|
||||||
|
def decompress_lzss_FFVa(rom, start, header=False, length=None):
|
||||||
|
'''
|
||||||
|
Oops, it's just GBA BIOS decompression functions
|
||||||
|
see https://web.archive.org/web/20130323133944/http://nocash.emubase.de/gbatek.htm#biosdecompressionfunctions
|
||||||
|
'''
|
||||||
|
ptr = start
|
||||||
|
if length:
|
||||||
|
uncompressed_length = length
|
||||||
|
else:
|
||||||
|
uncompressed_length = indirect(rom, start, endian='big')
|
||||||
|
ptr += 2
|
||||||
|
output = []
|
||||||
|
while len(output) < uncompressed_length:
|
||||||
|
bitmap_byte = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
for i in reversed(range(8)):
|
||||||
|
bit = (bitmap_byte >> i) & 1
|
||||||
|
if not bit:
|
||||||
|
b = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
output.append(b)
|
||||||
|
else:
|
||||||
|
b1 = rom[ptr]
|
||||||
|
b2 = rom[ptr+1]
|
||||||
|
ptr += 2
|
||||||
|
length = ((b1 & 0xF0) >> 4) + 3
|
||||||
|
trackback = -1 - (b2 + ((b1 & 0x0F) << 8))
|
||||||
|
try:
|
||||||
|
for j in range(length):
|
||||||
|
output.append(output[trackback])
|
||||||
|
except:
|
||||||
|
print(len(output), f'0x{ptr:X}', f'0x{b1:02X}{b2:02X}', trackback)
|
||||||
|
raise
|
||||||
|
print(f'0x{ptr:X}')
|
||||||
|
return bytes(output[:uncompressed_length])
|
||||||
|
|
||||||
|
|
||||||
|
def findall(rom, string):
|
||||||
|
results = []
|
||||||
|
start = 0
|
||||||
|
while True:
|
||||||
|
val = rom.find(string, start)
|
||||||
|
if val < 0:
|
||||||
|
return results
|
||||||
|
results.append(val)
|
||||||
|
start = val + 1
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ips(data):
|
||||||
|
assert data[:5] == b'PATCH' and data[-3:] == b'EOF', 'File header and footer missing!'
|
||||||
|
patches = {}
|
||||||
|
ptr = 5
|
||||||
|
while ptr < len(data)-6:
|
||||||
|
address = int.from_bytes(data[ptr:ptr+3], 'big')
|
||||||
|
length = int.from_bytes(data[ptr+3:ptr+5], 'big')
|
||||||
|
if length > 0:
|
||||||
|
payload = data[ptr+5:ptr+5+length]
|
||||||
|
ptr += 5 + length
|
||||||
|
else:
|
||||||
|
repeats = data[ptr+5:ptr+7]
|
||||||
|
payload = data[ptr+7] * repeats
|
||||||
|
ptr += 8
|
||||||
|
patches[address] = payload
|
||||||
|
return patches
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
with open('2564 - Final Fantasy V Advance (U)(Independent).gba', 'rb') as file:
|
||||||
|
ROM = file.read()
|
||||||
|
landmark = ROM.find(b'FINAL FANTASY V ADVANCE SYGMAB')
|
||||||
|
try:
|
||||||
|
with open('Final Fantasy V Advance (Europe) (En,Fr,De,Es,It)-spritehack.ips', 'rb') as file:
|
||||||
|
spritehack_ips = file.read()
|
||||||
|
print('spritehack_ips loaded')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
|
@ -26,6 +26,8 @@ import sys
|
||||||
from midiutil import MIDIFile
|
from midiutil import MIDIFile
|
||||||
from includes.helpers import indirect, hex
|
from includes.helpers import indirect, hex
|
||||||
from includes.const import BGM_Tracks_Safe
|
from includes.const import BGM_Tracks_Safe
|
||||||
|
import struct
|
||||||
|
import wave
|
||||||
|
|
||||||
def generate_pointer_set(data):
|
def generate_pointer_set(data):
|
||||||
'''
|
'''
|
||||||
|
@ -51,14 +53,27 @@ def analyse_sample(data, pointer):
|
||||||
|
|
||||||
|
|
||||||
def decode_brr(data):
|
def decode_brr(data):
|
||||||
range = data[0] >> 4
|
'''
|
||||||
|
Decodes a single 9byte BRR packet
|
||||||
|
'''
|
||||||
|
_range = data[0] >> 4
|
||||||
filter_designation = (data[0] & 0x0C) >> 2
|
filter_designation = (data[0] & 0x0C) >> 2
|
||||||
loop = bool(data[0] & 0x02)
|
loop = bool(data[0] & 0x02)
|
||||||
end = bool(data[0] & 0x01)
|
end = bool(data[0] & 0x01)
|
||||||
samples = []
|
samples = []
|
||||||
for i in range(8):
|
for i in data[1:]:
|
||||||
samples.append((data[1+range] >> 4) << range)
|
b1 = (i >> 4)
|
||||||
samples.append((data[1+range] & 0x0F) << range)
|
b2 = (i & 0x0F)
|
||||||
|
# Sign-extend
|
||||||
|
if b1 >= 8:
|
||||||
|
b1 |= 0xFFF0
|
||||||
|
if b2 >= 8:
|
||||||
|
b2 |= 0xFFF0
|
||||||
|
samples.append((b1 << _range) & 0xFFFF)
|
||||||
|
samples.append((b2 << _range) & 0xFFFF)
|
||||||
|
# For filter arithmetic the samples need to be in signed form.
|
||||||
|
sample_bytes = struct.pack('<'+'H'*16, *samples)
|
||||||
|
samples = struct.unpack('<'+'h'*16, sample_bytes)
|
||||||
return (samples, loop, end, filter_designation)
|
return (samples, loop, end, filter_designation)
|
||||||
|
|
||||||
|
|
||||||
|
@ -275,6 +290,46 @@ def get_song_data(rom, id):
|
||||||
#data = rom[offset+2:offset+2+size]
|
#data = rom[offset+2:offset+2+size]
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
|
def get_sample_data(rom, id):
|
||||||
|
lookup_offset = 0x043C6F + (id*3)
|
||||||
|
offset = indirect(rom, lookup_offset, 3)-0xC00000
|
||||||
|
size = indirect(rom, offset)
|
||||||
|
data = rom[offset+2:offset+2+size]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def clamp_short(num):
|
||||||
|
return min(max(num, -0x7FFF), 0x7FFF)
|
||||||
|
|
||||||
|
def make_sample(rom, id):
|
||||||
|
data = get_sample_data(rom, id)
|
||||||
|
packets = [data[i:i+9] for i in range(0, len(data), 9)]
|
||||||
|
samples = [0, 0] # Two zero samples for filter purposes, strip them from the actual output
|
||||||
|
for p in packets:
|
||||||
|
c_samples, loop, end, filter = decode_brr(p)
|
||||||
|
samples += c_samples
|
||||||
|
if filter == 1:
|
||||||
|
for i in range(-8, 0, 1):
|
||||||
|
samples[i] = clamp_short(samples[i] + (samples[i-1]*15)//16)
|
||||||
|
elif filter == 2:
|
||||||
|
for i in range(-8, 0, 1):
|
||||||
|
samples[i] = clamp_short(samples[i] + (samples[i-1]*61)//32 - (samples[i-2]*15)//16 )
|
||||||
|
elif filter == 3:
|
||||||
|
for i in range(-8, 0, 1):
|
||||||
|
samples[i] = clamp_short(samples[i] + (samples[i-1]*115)//64 - (samples[i-2]*13)//16 )
|
||||||
|
if end:
|
||||||
|
break
|
||||||
|
return samples[2:]
|
||||||
|
|
||||||
|
def make_sample_wav(rom, id):
|
||||||
|
samples = make_sample(rom, id)
|
||||||
|
filename = 'Sample{}.wav'.format(id)
|
||||||
|
with wave.open(filename, 'wb') as file:
|
||||||
|
file.setnchannels(1)
|
||||||
|
file.setframerate(8000)
|
||||||
|
file.setsampwidth(2)
|
||||||
|
sample_bytes = struct.pack('<'+'h'*len(samples), *samples)
|
||||||
|
file.writeframes(sample_bytes)
|
||||||
|
|
||||||
def make_midi_file(tracks, filename='test.mid'):
|
def make_midi_file(tracks, filename='test.mid'):
|
||||||
m = SPCParser().parse(tracks)
|
m = SPCParser().parse(tracks)
|
||||||
with open(filename, 'wb') as file:
|
with open(filename, 'wb') as file:
|
||||||
|
|
Loading…
Reference in New Issue