-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
299 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,299 @@ | ||
import os, sys, re | ||
|
||
def extrCombAudio(): | ||
def find_segments(file_path): | ||
with open(file_path, 'rb') as f: | ||
data = f.read() | ||
|
||
segments = [] | ||
start_idx = 0 | ||
while True: | ||
fsb5_start = data.find(b'FSB5', start_idx) | ||
if fsb5_start == -1: | ||
break | ||
|
||
fsb5_end = data.find(b'FSB5', fsb5_start + 4) | ||
if fsb5_end == -1: | ||
fsb5_end = len(data) | ||
|
||
segments.append(data[fsb5_start:fsb5_end]) | ||
start_idx = fsb5_end | ||
return segments | ||
|
||
def save_segments(segments): | ||
os.makedirs('.\\out_path', exist_ok=True) | ||
for i, segment in enumerate(segments): | ||
with open(f'.\\out_path\\segment_{i}.fsb', 'wb') as f: | ||
f.write(segment) | ||
|
||
file_path = sys.argv[2] | ||
file_path = file_path.replace("\\",'/') | ||
segments = find_segments(file_path) | ||
save_segments(segments) | ||
|
||
def find_segment_name(): | ||
file_path = sys.argv[2] | ||
search_pattern = b'\x00\x04\x00\x00\x00' | ||
max_search_length = 0xFF | ||
|
||
try: | ||
with open(file_path, 'rb') as f: | ||
segment = f.read(max_search_length) | ||
except FileNotFoundError: | ||
print(f"File not found: {file_path}") | ||
return None | ||
except IOError as e: | ||
print(f"Error reading file: {e}") | ||
return None | ||
|
||
pattern_start = segment.find(search_pattern) | ||
if pattern_start == -1: | ||
print("Pattern not found") | ||
return None | ||
|
||
string_start = pattern_start + len(search_pattern) | ||
string_end = string_start | ||
while string_end < len(segment) and segment[string_end] != 0x00: | ||
string_end += 1 | ||
|
||
segment_name = segment[string_start:string_end].decode('utf-8') | ||
return segment_name | ||
|
||
def rename_segment(): | ||
segment_file = sys.argv[2] | ||
segment_name1 = find_segment_name() | ||
seg_dir = os.path.dirname(segment_file) | ||
os.rename(segment_file,f"{seg_dir}\\{segment_name1}.fsb") | ||
|
||
def collect_header(): | ||
audio_file = sys.argv[2] | ||
os.makedirs('.\\out_path', exist_ok=True) | ||
with open(audio_file,'rb+') as f: | ||
data = f.read(0x1A2C) | ||
with open(f".\\out_path\\header_data.bin",'wb') as of: | ||
of.write(data) | ||
|
||
def rebCombAudio(): | ||
audio_files = sys.argv[2] | ||
header = f"{audio_files}\\header_data.bin" | ||
if os.path.exists(header) != True: | ||
print(f"Please Extract the Header from your CombinedAudio.bin File.") | ||
sys.exit(1) | ||
if os.path.exists(f"{audio_files}\\segment_0.fsb") != True: | ||
print(f"This Function Requires that the Audio Files are Named into the following scheme:\n 'segment_XXX.fsb' nothing else.") | ||
sys.exit(1) | ||
|
||
with open(header,'rb+') as header_file: | ||
header_data = header_file.read() | ||
|
||
with open('.\\ModifiedCombinedAudio.bin','wb+') as f: | ||
f.write(header_data) | ||
for i in range(558): | ||
with open(f"{audio_files}\\segment_{i}.fsb",'rb+') as sf: | ||
fsb_data = sf.read() | ||
f.write(fsb_data) | ||
|
||
def extractByName(): | ||
audio_path = sys.argv[2] | ||
search_text = sys.argv[3] | ||
search_text = search_text.lower() | ||
file_names = os.path.basename(audio_path) | ||
directory_name = os.path.dirname(__file__) | ||
|
||
with open(audio_path, 'rb') as file: | ||
content = file.read() | ||
|
||
search_text_bytes = search_text.encode('utf-8') | ||
fsb_magic = b'FSB5' | ||
search_text_index = -1 | ||
|
||
while True: | ||
search_text_index = content.find(search_text_bytes, search_text_index + 1) | ||
if search_text_index == -1: | ||
print(f"\nSearch text '{search_text}' not found surrounded by null bytes in file.\nDid you mistype the Sound-ID?") | ||
sys.exit(1) | ||
|
||
if content[search_text_index - 1] == 0x00 and content[search_text_index + len(search_text_bytes)] == 0x00: | ||
break | ||
|
||
closest_fsb_index = content.rfind(fsb_magic, 0, search_text_index) | ||
if closest_fsb_index == -1: | ||
print("\nFSB5 Magic String not found before the search text.\nAre you sure this is a Soundbank FSB Archive?\n") | ||
sys.exit(1) | ||
|
||
next_fsb_index = content.find(fsb_magic, closest_fsb_index + len(fsb_magic)) | ||
if next_fsb_index == -1: | ||
next_fsb_index = len(content) | ||
|
||
extracted_data = content[closest_fsb_index:next_fsb_index] | ||
|
||
output_path = os.path.join(directory_name, f'{search_text}.fsb') | ||
with open(output_path, 'wb') as output_file: | ||
output_file.write(extracted_data) | ||
|
||
print(f"\nExtracted Soundbank FSB From '{file_names}' Archive.\nTo Output PATH: '{output_path}'.\n") | ||
|
||
def addPadding(): | ||
originalFile = sys.argv[2] | ||
modifiedFile = sys.argv[3] | ||
with open(originalFile, 'rb') as f: | ||
originalData = f.read() | ||
ogDataLen = len(originalData) | ||
|
||
with open(modifiedFile,'rb') as mf: | ||
modifiedData = mf.read() | ||
modDataLen = len(modifiedData) | ||
mf.close() | ||
|
||
difference = ogDataLen-modDataLen | ||
with open(modifiedFile,'ab') as of: | ||
for i in range(difference): | ||
of.write(b'\x00') | ||
|
||
def info_help(): | ||
print("""\n | ||
python CATool.py | ||
--eca, extract-ca [CombinedAudioPATH] > Extracts All FSB Soundbank Files from the CombinedAudio.bin Archive. | ||
--fn, find-sn [SegmentFilePATH] > Find the Segment Name from an Extracted FSB Soundbank File via --eca Flag. | ||
--rs, rename-s [SegmentFilePATH] > Rename a Segment File FSB Soundbank File back to it's Original Filename. | ||
--gh, get-header [CombinedAudioPATH] > Extracts the Header from the CombinedAudio.bin Archive. | ||
--rca, rebuild-ca [SegmentOutFolderPATH] > Rebuilds the CombinedAudio.bin Archive via Segment Files. | ||
--ap, add-padding [OriginalFilePATH] [ModifiedFilePATH] > Adds padding to set the Modified File Equal to the Original File Size. | ||
--ne, name-extract [CombinedAudioPATH] [Sound Name/ID] > Extracts a FSB Soundbank File from the CombinedAudio.bin Archive via Name/SoundID. | ||
--ra, rename-all [SegmentFolderPATH] > Renames all Segment Files back to their original FSB Soundbank Filename(s). | ||
--gsid, get-soundid [CombinedAudioPATH] > Gets all Sound Names/ID's and Dumps them to a *.txt File. | ||
--gssg, get-size-seg [SegmentOutFolderPATH] > Gets the size of all Segment Soundbank FSB Files. | ||
--gs, get-size [SegmentFilePATH] > Gets the size of a specific Segment Soundbank FSB File. | ||
--h, help > Displays this Message.\n\n\n""") | ||
os.system('pause') | ||
|
||
def getSoundId(): | ||
filename = sys.argv[2] | ||
with open(filename, "rb") as file: | ||
data = file.read() | ||
|
||
fsb5_tag = b"FSB5" | ||
byte_array = b"\x00\x04\x00\x00\x00" | ||
null_byte = b"\x00" | ||
fsb5_positions = [] | ||
start = 0 | ||
while True: | ||
pos = data.find(fsb5_tag, start) | ||
if pos == -1: | ||
break | ||
fsb5_positions.append(pos) | ||
start = pos + len(fsb5_tag) | ||
|
||
with open('.\\ExtractedSoundIDs.txt','w') as f: | ||
for fsb5_pos in fsb5_positions: | ||
search_limit = fsb5_pos + 0xFF | ||
if search_limit > len(data): | ||
search_limit = len(data) | ||
|
||
array_pos = data.find(byte_array, fsb5_pos, search_limit) | ||
if array_pos != -1: | ||
string_start = array_pos + len(byte_array) | ||
string_end = data.find(null_byte, string_start) | ||
if string_end != -1: | ||
extracted_string = data[string_start:string_end].decode('utf-8', errors='replace') | ||
print(f"Extracted string: {extracted_string}") | ||
f.write(f"{extracted_string}\n") | ||
|
||
def renameSegments(): | ||
directory = sys.argv[2] | ||
files = [f for f in os.listdir(directory) if f.startswith("segment_") and f.endswith(".fsb")] | ||
i = 0 | ||
for file_name in files: | ||
file_path = os.path.join(directory, file_name) | ||
with open(file_path, 'rb') as file: | ||
data = file.read(0xFF) | ||
|
||
target_sequence = b'\x00\x04\x00\x00\x00' | ||
index = data.find(target_sequence) | ||
if index != -1: | ||
start_index = index + 5 | ||
end_index = start_index | ||
while end_index < len(data) and data[end_index] != 0x00: | ||
end_index += 1 | ||
|
||
new_name = data[start_index:end_index].decode('utf-8') | ||
new_file_name = new_name + '.fsb' | ||
new_file_path = os.path.join(directory, new_file_name) | ||
count = 1 | ||
while os.path.exists(new_file_path): | ||
new_file_name = f"{new_name}({count}).fsb" | ||
new_file_path = os.path.join(directory, new_file_name) | ||
count += 1 | ||
|
||
try: | ||
os.rename(file_path, new_file_path) | ||
print(f'Renamed "{file_name}" to "{new_file_name}".') | ||
|
||
except FileExistsError: | ||
pass | ||
|
||
def getSize(): | ||
directory = sys.argv[2] | ||
output_file = ".\\ExtractedAudioSizes.txt" | ||
with open(output_file, 'w') as out: | ||
for filename in os.listdir(directory): | ||
if filename.startswith("segment_") and filename.endswith(".fsb"): | ||
with open(f"{directory}\\{filename}",'rb') as f: | ||
data_length = len(f.read()) | ||
out.write(f"Filename: {filename} | Bytes: {data_length}.\n") | ||
print(f"Wrote: {filename} with {data_length} Bytes, to file: '{output_file}'.") | ||
|
||
def getSizeFile(): | ||
audioFile = sys.argv[2] | ||
baseFile = os.path.basename(audioFile) | ||
if '.fsb' not in baseFile: | ||
print(f"Error: File provided '{baseFile}' isn't *.fsb File.") | ||
sys.exit(1) | ||
baseFileName = baseFile.replace('.fsb','') | ||
outFile = f".\\Extracted{baseFileName}Size.txt" | ||
with open(audioFile,'rb') as f: | ||
data_length = len(f.read()) | ||
with open(outFile,'w') as of: | ||
of.write(f"Filename: {baseFileName} | Bytes: {data_length}.\n") | ||
print(f"Wrote: {baseFileName} with {data_length} Bytes, to file: '{outFile}'.") | ||
|
||
if __name__ == '__main__': | ||
try: | ||
callable = sys.argv[1] | ||
if callable == 'extract-ca' or callable == '--eca': | ||
extrCombAudio() | ||
|
||
if callable == 'find-sn' or callable == '--fn': | ||
print(find_segment_name()) | ||
|
||
if callable == 'rename-s' or callable == '--rs': | ||
rename_segment() | ||
|
||
if callable == 'get-header' or callable == '--gh': | ||
collect_header() | ||
|
||
if callable == 'rebuild-ca' or callable == '--rca': | ||
rebCombAudio() | ||
|
||
if callable == 'add-padding' or callable == '--ap': | ||
addPadding() | ||
|
||
if callable == "name-extract" or callable == '--ne': | ||
extractByName() | ||
|
||
if callable == 'help' or callable == '--h': | ||
info_help() | ||
|
||
if callable == 'get-soundid' or callable == '--gsid': | ||
getSoundId() | ||
|
||
if callable == 'rename-all' or callable == '--ra': | ||
renameSegments() | ||
|
||
if callable == 'get-size' or callable == '--gs': # New | ||
getSize() | ||
|
||
if callable == 'get-size-seg' or callable == '--gssg': # New | ||
getSizeFile() | ||
except IndexError: | ||
info_help() |