1
0
mirror of synced 2024-11-27 22:40:49 +01:00

tja2fumen.py: Rewrite song information to fumen files

On at least one song I've tested, this creates a perfect match,
meaning that we now have the ability to create a fumen file
from a "song" object.

The next challenge is reading TJA files into "song" objects.
This commit is contained in:
Viv 2023-06-02 16:33:42 -04:00
parent adbc81a9c2
commit 9879cd9b7c

View File

@ -36,6 +36,7 @@ noteTypes = {
0x22: "Unknown13", # ? (Present in some Wii1 songs)
0x62: "Drumroll2" # ?
}
typeNotes = {v: k for k, v in noteTypes.items()}
# Fumen headers are made up of smaller substrings of bytes
b_x00 = b'\x00\x00\x00\x00\x00\x00'
@ -296,6 +297,73 @@ def readStruct(file, order, format_string, seek=None):
return interpreted_string
def writeFumen(file, song):
# Fetch the byte order (little/big endian)
order = song['order']
# Write the header
file.write(simpleHeaders[0]) # Write known, valid header
file.write(song['headerUnknown']) # Write unknown header
# Preallocate space in the file
len_metadata = 8
len_measures = 0
for measureNumber in range(song['length']):
len_measures += 40
measure = song[measureNumber]
for branchNumber in range(len(branchNames)):
len_measures += 8
branch = measure[branchNames[branchNumber]]
for noteNumber in range(branch['length']):
len_measures += 24
note = branch[noteNumber]
if note['type'].lower() == "drumroll":
len_measures += 8
file.write(b'\x00' * (len_metadata + len_measures))
# Write metadata
writeStruct(file, order, format_string="B", value_list=[putBool(song['branches'])], seek=0x1b0)
writeStruct(file, order, format_string="I", value_list=[song['length']], seek=0x200)
# Write measure data
file.seek(0x208)
for measureNumber in range(song['length']):
measure = song[measureNumber]
measureStruct = [measure['bpm'], measure['fumenOffset'], int(measure['gogo']), int(measure['hidden'])]
measureStruct.extend([measure['padding1']] + measure['branchInfo'] + [measure['padding2']])
writeStruct(file, order, format_string="ffBBHiiiiiii", value_list=measureStruct)
for branchNumber in range(len(branchNames)):
branch = measure[branchNames[branchNumber]]
branchStruct = [branch['length'], branch['padding'], branch['speed']]
writeStruct(file, order, format_string="HHf", value_list=branchStruct)
for noteNumber in range(branch['length']):
note = branch[noteNumber]
noteStruct = [typeNotes[note['type']], note['pos'], note['item'], note['padding']]
# Balloon hits
if 'hits' in note.keys():
noteStruct.extend([note["hits"], note['hitsPadding']])
else:
noteStruct.extend([note['scoreInit'], int(note['scoreDiff'] * 4)])
# Drumroll or balloon duration
if 'duration' in note.keys():
noteStruct.append(note['duration'])
else:
noteStruct.append(note['durationPadding'])
writeStruct(file, order, format_string="ififHHf", value_list=noteStruct)
if note['type'].lower() == "drumroll":
file.seek(0x8, os.SEEK_CUR)
file.close()
def writeStruct(file, order, format_string, value_list, seek=None):
if seek:
file.seek(seek)
packed_bytes = struct.pack(order + format_string, *value_list)
file.write(packed_bytes)
def shortHex(number):
return hex(number)[2:]
@ -304,6 +372,10 @@ def getBool(number):
return True if number == 0x1 else False if number == 0x0 else number
def putBool(boolean):
return 0x1 if boolean is True else 0x0 if boolean is False else boolean
def nameValue(*lists):
string = []
for lst in lists:
@ -392,4 +464,6 @@ if __name__ == "__main__":
arguments = parser.parse_args()
inputFile = getattr(arguments, "file_m.bin")
parsedSong = readFumen(inputFile, arguments.order, arguments.debug)
breakpoint()
outputName = inputFile.name.split('.')[0] + "_rebuilt.bin"
outputFile = open(outputName, "wb")
writeFumen(outputFile, parsedSong)