You have literally BMS script for unpacking it above your post...Is it still not possible to extract the contents of bf3.ard?
They changed bdat storage format, cant edit anything right now
Upload it here I guess? English and Japanese ones to figure out encoding differences.Anyone smart and/or bored enough to join me trying to crack the new bdat?
I got some of the basics down - positions for file length, number of table rows, the table offset - but beyond that I'm struggling.
I tried it and I get this error, could you please help me?You have literally BMS script for unpacking it above your post...
Upload it here I guess? English and Japanese ones to figure out encoding differences.
//High level
if (table.ReadUTF8(0, 4) != "BDAT") return;
int fileLength = file.ReadInt32(12); // Assuming for now ReadUInt/Int32 reads 4 bytes (e.g. 0x12-0x15)
// Remaining bytes until a "BDAT.0" contain information for multi-tables
// Individual table \\
// Assuming we're starting on the byte right AFTER the "0" in BDAT.0
//x00-x01 - First 2 bytes after always empty?
??Type = table.ReadUInt32(2); // "03 00" in all Evt + Game bdats, "07 00" in everything else (e.g. tq bdats)
ItemSize = table.ReadUInt32(6); // Number of rows in the table aka individual lines
?? = table.ReadUInt32(10); // Consistently "01" in Evt
?? = table.ReadUInt32(14); // Empty in dialogue, has values in Game bdats
?? = table.ReadUInt32(18); // Consistently "30" in Evt
??Type = table.ReadUInt32(22); // "39" in all Evt, "45" in everything else
?? = table.ReadUInt32(26); // ??
TableOffset? = table.ReadUInt32(34); // An offset for some part of the table
// For shorter dialogues, this takes you from first ??Type (2) to the byte right before the whole "MÒ}ŒDé..."
// But not valid for longer dialogues
StartOfDialogueString = "MÒ}ŒDéNUðC¯Û";
StartOfDialogueHex = { 0x4D, 0xD2, 0x7D, 0x8C, 0x44, 0xE9, 0x4E, 0x55, 0xF0, 0x43, 0xAF, 0xDB };
You must copy both arh and ard to one folder, then open arh through quickbms.I tried it and I get this error, could you please help me? View attachment 320632
So those individual strings in BDATS are read by some offsets or have some size checks? If not, then they are pretty easy to dump and push back from what I see.Good idea. I've attached EN and JP bdats and then a pair of JP/EN for a dialogue that only has 2 lines.
As for my progress, I was planning on just forking the XBTool so forgive the formatting
C#://High level if (table.ReadUTF8(0, 4) != "BDAT") return; int fileLength = file.ReadInt32(12); // Assuming for now ReadUInt/Int32 reads 4 bytes (e.g. 0x12-0x15) // Remaining bytes until a "BDAT.0" contain information for multi-tables // Individual table \\ // Assuming we're starting on the byte right AFTER the "0" in BDAT.0 //x00-x01 - First 2 bytes after always empty? ??Type = table.ReadUInt32(2); // "03 00" in all Evt + Game bdats, "07 00" in everything else (e.g. tq bdats) ItemSize = table.ReadUInt32(6); // Number of rows in the table aka individual lines ?? = table.ReadUInt32(10); // Consistently "01" in Evt ?? = table.ReadUInt32(14); // Empty in dialogue, has values in Game bdats ?? = table.ReadUInt32(18); // Consistently "30" in Evt ??Type = table.ReadUInt32(22); // "39" in all Evt, "45" in everything else ?? = table.ReadUInt32(26); // ?? TableOffset? = table.ReadUInt32(34); // An offset for some part of the table // For shorter dialogues, this takes you from first ??Type (2) to the byte right before the whole "MÒ}ŒDé..." // But not valid for longer dialogues StartOfDialogueString = "MÒ}ŒDéNUðC¯Û"; StartOfDialogueHex = { 0x4D, 0xD2, 0x7D, 0x8C, 0x44, 0xE9, 0x4E, 0x55, 0xF0, 0x43, 0xAF, 0xDB };
Feels like a lot more 32 bit properties when compared to 2/DE which were primarily 16, so take my findings with a grain of salt.
Thank you!You must copy both arh and ard to one folder, then open arh through quickbms.
Ok, made a script for dumping texts.Good idea. I've attached EN and JP bdats and then a pair of JP/EN for a dialogue that only has 2 lines.
As for my progress, I was planning on just forking the XBTool so forgive the formatting
C#://High level if (table.ReadUTF8(0, 4) != "BDAT") return; int fileLength = file.ReadInt32(12); // Assuming for now ReadUInt/Int32 reads 4 bytes (e.g. 0x12-0x15) // Remaining bytes until a "BDAT.0" contain information for multi-tables // Individual table \\ // Assuming we're starting on the byte right AFTER the "0" in BDAT.0 //x00-x01 - First 2 bytes after always empty? ??Type = table.ReadUInt32(2); // "03 00" in all Evt + Game bdats, "07 00" in everything else (e.g. tq bdats) ItemSize = table.ReadUInt32(6); // Number of rows in the table aka individual lines ?? = table.ReadUInt32(10); // Consistently "01" in Evt ?? = table.ReadUInt32(14); // Empty in dialogue, has values in Game bdats ?? = table.ReadUInt32(18); // Consistently "30" in Evt ??Type = table.ReadUInt32(22); // "39" in all Evt, "45" in everything else ?? = table.ReadUInt32(26); // ?? TableOffset? = table.ReadUInt32(34); // An offset for some part of the table // For shorter dialogues, this takes you from first ??Type (2) to the byte right before the whole "MÒ}ŒDé..." // But not valid for longer dialogues StartOfDialogueString = "MÒ}ŒDéNUðC¯Û"; StartOfDialogueHex = { 0x4D, 0xD2, 0x7D, 0x8C, 0x44, 0xE9, 0x4E, 0x55, 0xF0, 0x43, 0xAF, 0xDB };
Feels like a lot more 32 bit properties when compared to 2/DE which were primarily 16, so take my findings with a grain of salt.
import glob
import json
import os
import sys
#import pymmh3
def read8(file, _signed = False):
return int.from_bytes(file.read(1), byteorder="little", signed=_signed)
def read16(file, _signed = False):
return int.from_bytes(file.read(2), byteorder="little", signed=_signed)
def read32(file, _signed = False):
return int.from_bytes(file.read(4), byteorder="little", signed=_signed)
def readString(myfile):
chars = []
while True:
c = myfile.read(1)
if c == b'\x00':
return str(b"".join(chars).decode("UTF-8"))
chars.append(c)
def CheckMagic(file):
if (file.read(4) != b"BDAT"):
print("WRONG MAGIC!")
print("offset: 0x%x" % (file.tell() - 4))
sys.exit()
def CheckVersion(file):
version = read8(BDAT_file)
if (version != 4):
print("Unsupported version of BDAT: %d" % version)
sys.exit()
if (os.path.isdir(os.path.normpath(os.path.abspath(sys.argv[1])))):
files = glob.glob("%s/**/*.bdat"% sys.argv[1], recursive=True)
elif (os.path.isfile(os.path.abspath(sys.argv[1]))):
files = [os.path.abspath(sys.argv[1])]
else:
print("os couldn't detect if it's a file or directory!")
sys.exit()
for i in range(0, len(files)):
print(files[i])
BDAT_file = open(files[i], "rb")
CheckMagic(BDAT_file)
CheckVersion(BDAT_file)
header_size = read16(BDAT_file)
isArchive = bool(read8(BDAT_file))
assert(isArchive == True)
subfile_count = read32(BDAT_file)
file_size = read32(BDAT_file)
assert(header_size == BDAT_file.tell())
offset_table = []
for x in range(0, subfile_count):
offset_table.append(read32(BDAT_file))
assert(offset_table[0] == BDAT_file.tell())
DUMP = []
for x in range(0, subfile_count):
BDAT_file.seek(offset_table[x])
start_subfile_offset = BDAT_file.tell()
CheckMagic(BDAT_file)
CheckVersion(BDAT_file)
header_size = read16(BDAT_file)
isArchive = bool(read8(BDAT_file))
assert(isArchive == False)
TypeInfo = read32(BDAT_file)
match(TypeInfo):
case 3 | 7:
pass
case _:
print("detected unknown infotype: %d" % TypeInfo)
sys.exit()
entry_count = read32(BDAT_file) #This doesn't match always string count
if (entry_count == 0):
continue
unk = read32(BDAT_file)
unk = read32(BDAT_file)
table1_offset = read32(BDAT_file) #unk
table2_offset = read32(BDAT_file) #Table consists of [int32 hash, int32 ID]
table3_offset = read32(BDAT_file)
sizeof_table3_entry = read32(BDAT_file)
string_block_offset = read32(BDAT_file)
string_block_size = read32(BDAT_file)
BDAT_file.seek(start_subfile_offset + table2_offset)
table2 = []
for y in range(0, entry_count):
entry = {}
entry["Hash"] = BDAT_file.read(4).hex().upper()
entry["ID"] = read32(BDAT_file)
table2.append(entry)
BDAT_file.seek(start_subfile_offset + table3_offset)
table3 = []
match(TypeInfo):
case 3:
assert (sizeof_table3_entry == 0xA)
for y in range(0, entry_count):
entry = {}
entry["Hash"] = BDAT_file.read(4).hex().upper()
entry["unk"] = read16(BDAT_file)
entry["StringOffset"] = read32(BDAT_file) #relative to string_block_offset
table3.append(entry)
case 7:
assert (sizeof_table3_entry == 0x18)
for y in range(0, entry_count):
entry = {}
entry["Hash"] = BDAT_file.read(4).hex().upper()
entry["ControlStringOffset"] = read32(BDAT_file) #relative to string_block_offset
entry["unk"] = BDAT_file.read(0xC).hex().upper()
entry["StringOffset"] = read32(BDAT_file) #relative to string_block_offset
table3.append(entry)
BDAT_file.seek(start_subfile_offset + string_block_offset)
BDAT_file.seek(1, 1)
#it's murmur3 hash related to original filename without type, we can assume all hashes are murmur3
hash = read32(BDAT_file, _signed=True)
#assert doesn't work for "game" folder, guess those archives contain files that were originally named differently.
#assert (hash == pymmh3.hash(os.path.basename(files[i])[:-5]))
BDAT_file.seek(4, 1)
STRINGS = []
for y in range(0, entry_count):
match(TypeInfo):
case 3:
BDAT_file.seek(start_subfile_offset + string_block_offset + table3[y]["StringOffset"])
STRINGS.append(readString(BDAT_file))
case 7:
entry = []
BDAT_file.seek(start_subfile_offset + string_block_offset + table3[y]["ControlStringOffset"])
entry.append(readString(BDAT_file))
BDAT_file.seek(start_subfile_offset + string_block_offset + table3[y]["StringOffset"])
entry.append(readString(BDAT_file))
STRINGS.append(entry)
DUMP.append(STRINGS)
if (len(DUMP) != 0):
os.makedirs("DUMP/%s" % os.path.relpath(os.path.dirname(files[i]), os.getcwd()), exist_ok=True)
JSON_file = open("DUMP/%s.json" % os.path.relpath(files[i], os.getcwd())[:-5], "w", encoding="UTF-8")
json.dump(DUMP, JSON_file, indent="\t", ensure_ascii=False)
JSON_file.close()
else:
print("Strings not detected in this file!")
[
[
[
"3",
"Anyone else get the feeling\nColony 9 folks might be eating\na bit too many Spongy Spuds...?"
],
[
"6",
"Seemed to me like they really\nfell in love with them, yeah.\nThey seem to be always eating them."
],
[
"5",
"Eating 'em's one thing, but\ndid you hear 'em talkin' about\n\"saving Aionios\" with 'em?"
],
[
"4",
"Isn't that what we're doing too,\ncuriously enough?"
],
[
"3",
"'Course it's not! ...Uh, or is it?"
],
[
"2",
"Well, if nothing else, Spongy Spuds are\nhelping ease the food shortages, so people\ndon't have to fight so hard over resources."
],
[
"2",
"And if they don't have to fight,\nisn't that saving the world...?\nIn some sense, at least?"
],
[
"3",
"Don't get me wrong, I'm not arguing with\nthe logic, I'm just trying to point out that\nthere's an important point you're missing."
],
[
"4",
"And that is...?"
],
[
"3",
"It's this: eating nothing but potatoes...\nis *boring* as all get-out!"
],
[
"tV",
"Manana cannot take that lying down!"
],
[
"tV",
"Potato cuisine contain hidden depth!\nAmount of possible variation so high,\nis practically endless!"
],
[
"tV",
"With wings of Manana at spatula,\ncan eat potatoes all life and never\nnot be satisfied!"
],
[
"2",
"Well, I daresay this might just be\nManana's time to shine, then."
],
[
"5",
"Seeing as how that lot always basically\njust steam their spuds, I think that might\nbe a mercy."
],
[
"tV",
"Manana will get on case immediately!\nTime to save world with potatoes is *now*!"
]
]
]
[
[
"[ML:undisp ](background audio of playing with boy B in fountain)",
"[ML:undisp ](background audio of playing with boy A in fountain)",
"C'mon, hurry up!",
"The Queen's Anniversary's gonna start without us, guys!",
"Yeah, move your feet!",
"Hup, hup. Run like you mean it!",
"[ML:undisp ](adlib breathing hard while running)",
"(pant) Slow down, guys!",
"Is it true, though? There's gonna be fireworks?",
"Yeah! Saw them setting it up yesterday.",
"There were loads of them.\nIt'll be worth it, promise!",
"[ML:undisp ](adlib astonished gasp)"
]
]
Hi. I was searching in Google for a tool to edit bdat files (the text, translation etc). So I found this thread nad registered an account to ask you about something.if you want to edit those BDATs, you must watch out for those things when making tool for applying translation:
- file_size (main BDAT)
- offset_table (main BDAT, if it contains more than 1 sub BDAT - like in quest.bdat)
- string_block_size (sub BDAT)
- table3 (sub BDAT, since it contains entries with offsets of used strings)
Because json is a custom storage format and script must be adjusted to specific way how data are stored by author in json.So far, there are no tools available for converting Json back to bdat
Then provide something that won't ruin offsets and size checks. Saying "it's very simple" without actually providing anything helps nobody.And if you use some very simple scripts, it is easy to convert excel data back to bdat
Although one more step, it doesn't consume too much time
As I said - I'm not interested.Hi. I was searching in Google for a tool to edit bdat files (the text, translation etc). So I found this thread nad registered an account to ask you about something.
You look like someone who has pretty much good understanding of this kind of things. I tried your unpacking script and it worked as it should.
Now tell me, would it be possible for you to make a script that is packing these edited json files back to bdat? That would be extremely helpful for many people (including me, and I but I know some people from Croatia that were interested in translating this game).
I don't have much money but I could donate you in crypto a little. It looks like you're the only person who actually could make it without spending days on figuring out how to make it working. Thanks in advance!
I do work on it and have just finished my edits - I just need some time ......Because json is a storage format and script must adjusted to specific way how data are stored by author in json.
Then provide something that won't ruin offsets and size checks. Saying "it's very simple" without actually providing anything helps nobody.
As I said - I'm not interested.
the textures are separate from the model files and are in the chr/tex/nx/h folderAnyone got progress on texture and bone extraction? '3'
Tried the modified XC2 dumper from BlockBuilder's github but only the meshes import. Save for the first time where I got Noah's jacket texture but that's it, idk why nothing else comes out.
Might try to contact whoever made the 3DSMax script, just wanna use these puppies already. ; ;