Yeah, daemon1 and Frankelstner did a really really great job.
Thanks again guys for your work and patience. Useless post for most of readers, maybe, but kind of important for those who did help us (I guess).
FatalBulletHit: are you talking about extracting BF1 sounds? Because the oldest tools didn't work.
Important information: this site is currently scheduled to go offline indefinitely by end of the year.
Frostbite 2 sound extraction research
- FatalBulletHit
- beginner
- Posts: 32
- Joined: Sun Nov 06, 2016 7:29 pm
- Has thanked: 12 times
- Been thanked: 6 times
- Contact:
Re: Frostbite 2 sound extraction research
Nope, sry:Vosvoy wrote:FatalBulletHit: are you talking about extracting BF1 sounds? Because the oldest tools didn't work.
FatalBulletHit wrote:However, if sb after me wants to have all the bf3 sounds and doesn't know how to...
Re: Frostbite 2 sound extraction research
Hi,
I am interested in grabbing handheld weapons sounds from bf3. I extracted everything and I noticed that gunshot ebx files are significantly bigger and different than reloads ebx files. They also doesn't convert to wave using fb2audio.py. The script reads them, but doesnt convert to wave just as it does with reloads ebx. Does anyone know how to convert gunshots ebxes ?
I am interested in grabbing handheld weapons sounds from bf3. I extracted everything and I noticed that gunshot ebx files are significantly bigger and different than reloads ebx files. They also doesn't convert to wave using fb2audio.py. The script reads them, but doesnt convert to wave just as it does with reloads ebx. Does anyone know how to convert gunshots ebxes ?
- FatalBulletHit
- beginner
- Posts: 32
- Joined: Sun Nov 06, 2016 7:29 pm
- Has thanked: 12 times
- Been thanked: 6 times
- Contact:
Re: Frostbite 2 sound extraction research
I don't, but as far as I know, every single gun shot is made with the sounds in the "shared_content" folder and not with the files you are talking about. No guarantee, tho, and I don't have a clue why there are additional bigger files in each of these sub folders.rpopulik wrote:Does anyone know how to convert gunshots ebxes ?
However, if you are trying to get a clean gun shot you will get the job done quite well with the sounds in "shared_content". It also gives you all the sounds you need for an immersive gun shot, e.g. a M82 shot in a forest with deploying the bipod, handling the safety, a case dropping on dirt, reflection of the shot, bolt action, etc.
Hope I was able to help, feel free to report back if you have more questions!
- Vosvoy
- veteran
- Posts: 127
- Joined: Fri Feb 18, 2011 4:58 pm
- Has thanked: 15 times
- Been thanked: 15 times
Re: Frostbite 2 sound extraction research
You can convert those .ebx files to .txt files via a python script made by Frankelstner. Those files doesn't contain any sounds but informations about them.FatalBulletHit wrote:I don't, but as far as I know, every single gun shot is made with the sounds in the "shared_content" folder and not with the files you are talking about. No guarantee, tho, and I don't have a clue why there are additional bigger files in each of these sub folders.rpopulik wrote:Does anyone know how to convert gunshots ebxes ?
However, if you are trying to get a clean gun shot you will get the job done quite well with the sounds in "shared_content". It also gives you all the sounds you need for an immersive gun shot, e.g. a M82 shot in a forest with deploying the bipod, handling the safety, a case dropping on dirt, reflection of the shot, bolt action, etc.
Hope I was able to help, feel free to report back if you have more questions!
Imagine that the wave samples are culinary ingredients, the ebx files are the recipes and the engine is the cook.
For example, I'm gonna make a REX MP412 gunfire sound. Thanks to the ebx file, I know what I need:
- - " CoreBassClose_OneShot_DoublePunch_Wave " for the punch/power (Shared_Content\CoreBassClose_OneShot\...).
- " HiFi_Revolver_Wave " for the mechanical effect (Shared_Content\HiFi_OneShot\...) and put the speed of this sample at -60 in Audacity.
- " Noise_Close_Rifle_Wave " to make the main stereo layer (Shared_Content\Noise_Layers\...).
It worked for BF3 and BF4 but I don't know if it works for BF1.
If you don't have it already, try this script:
Code: Select all
#Requires Python 2.7
import string
import sys
from binascii import hexlify
from struct import unpack
import os
from cStringIO import StringIO
import cProfile
import cPickle
#adjust input and output folders here
inputFolder=r"H:\BF3SNDS\DUMP\bundles\ebx\sound"
outputFolder=r"H:\BF3SNDS\EBX"
guidTableName="guidTable bf" #name of the guid table file
EXTENSION=".txt"
SEP=" "
#the script can use the actual filenames in the explorer for the guid table (fast)
#or it can parse almost the entire file to retrieve the filename (slow, but necessary when the explorer names are just hashes)
#in case #2, create a separate guidTable file, in case#1 do not create that file.
#True/False
useExplorerNames=True
#ignore all instances and fields with these names when converting to text:
IGNOREINSTANCES=[]
IGNOREFIELDS=[]
##IGNOREINSTANCES=["ShaderAdjustmentData","SocketData","WeaponSkinnedSocketObjectData","WeaponRegularSocketObjectData"]
##IGNOREFIELDS=["Mesh3pTransforms","Mesh3pRigidMeshSocketObjectTransforms"]
#run createGuidTable or dumpText, or both (preferably in the right order)
#When using explorer names, do not change anything below.
#When not using explorer names you might want to make the guid table first, then restart the script to dump text only,
#though it should work fine without change too.
def main():
createGuidTable()
dumpText()
##############################################################
##############################################################
if useExplorerNames:
def createGuidTable(): #guid vs filename
for dir0, dirs, ff in os.walk(inputFolder):
for fname in ff:
path=os.path.join(dir0,fname)
f=open(path,"rb")
if f.read(4)!="\xCE\xD1\xB2\x0F":
f.close()
continue
#grab the file guid directly, absolute offset 48 bytes
f.seek(48)
fileguid=f.read(16)
f.close()
filename=path[len(inputFolder):-4].replace("\\","/")
guidTable[fileguid]=filename
else:
def createGuidTable():
for dir0, dirs, ff in os.walk(inputFolder):
for fname in ff:
f=open(dir0+"\\"+fname,"rb")
if f.read(4)!="\xCE\xD1\xB2\x0F":
f.close()
continue
dbx=Dbx(f)
guidTable[dbx.fileGUID]=dbx.trueFilename
f5=open(guidTableName,"wb") #write the table
cPickle.dump(guidTable,f5)
f5.close()
def dumpText():
for dir0, dirs, ff in os.walk(inputFolder):
for fname in ff:
f=open(dir0+"\\"+fname,"rb")
if f.read(4)!="\xCE\xD1\xB2\x0F":
f.close()
continue
dbx=Dbx(f)
dbx.dump(outputFolder)
try:
from ctypes import *
floatlib = cdll.LoadLibrary("floattostring")
def formatfloat(num):
bufType = c_char * 100
buf = bufType()
bufpointer = pointer(buf)
floatlib.convertNum(c_double(num), bufpointer, 100)
rawstring=(buf.raw)[:buf.raw.find("\x00")]
if rawstring[:2]=="-.": return "-0."+rawstring[2:]
elif rawstring[0]==".": return "0."+rawstring[1:]
elif "e" not in rawstring and "." not in rawstring: return rawstring+".0"
return rawstring
except:
def formatfloat(num):
return str(num)
def hasher(keyword): #32bit FNV-1 hash with FNV_offset_basis = 5381 and FNV_prime = 33
hash = 5381
for byte in keyword:
hash = (hash*33) ^ ord(byte)
return hash & 0xffffffff # use & because Python promotes the num instead of intended overflow
class Header:
def __init__(self,varList): ##all 4byte unsigned integers
self.absStringOffset = varList[0] ## absolute offset for string section start
self.lenStringToEOF = varList[1] ## length from string section start to EOF
self.numGUID = varList[2] ## number of external GUIDs
self.null = varList[3] ## 00000000
self.numInstanceRepeater = varList[4]
self.numComplex = varList[5] ## number of complex entries
self.numField = varList[6] ## number of field entries
self.lenName = varList[7] ## length of name section including padding
self.lenString = varList[8] ## length of string section including padding
self.numArrayRepeater = varList[9]
self.lenPayload = varList[10] ## length of normal payload section; the start of the array payload section is absStringOffset+lenString+lenPayload
class FieldDescriptor:
def __init__(self,varList,keywordDict):
self.name = keywordDict[varList[0]]
self.type = varList[1]
self.ref = varList[2] #the field may contain another complex
self.offset = varList[3] #offset in payload section; relative to the complex containing it
self.secondaryOffset = varList[4]
class ComplexDescriptor:
def __init__(self,varList,keywordDict):
self.name = keywordDict[varList[0]]
self.fieldStartIndex = varList[1] #the index of the first field belonging to the complex
self.numField = varList[2] #the total number of fields belonging to the complex
self.alignment = varList[3]
self.type = varList[4]
self.size = varList[5] #total length of the complex in the payload section
self.secondarySize = varList[6] #seems deprecated
class InstanceRepeater:
def __init__(self,varList):
self.null = varList[0] #called "internalCount", seems to be always null
self.repetitions = varList[1] #number of instance repetitions
self.complexIndex = varList[2] #index of complex used as the instance
class arrayRepeater:
def __init__(self,varList):
self.offset = varList[0] #offset in array payload section
self.repetitions = varList[1] #number of array repetitions
self.complexIndex = varList[2] #not necessary for extraction
class Complex:
def __init__(self,desc):
self.desc=desc
class Field:
def __init__(self,desc):
self.desc=desc
numDict={0x0035:("I",4),0xc10d:("I",4),0xc14d:("d",8),0xc0ad:("?",1),0xc0fd:("i",4),0xc0bd:("B",1),0xc0ed:("h",2), 0xc0dd:("H",2), 0xc13d:("f",4)}
class Dbx:
def __init__(self, f):
#metadata
self.trueFilename=""
self.header=Header(unpack("11I",f.read(44)))
self.arraySectionstart=self.header.absStringOffset+self.header.lenString+self.header.lenPayload
self.fileGUID, self.primaryInstanceGUID = f.read(16), f.read(16)
self.externalGUIDs=[(f.read(16),f.read(16)) for i in xrange(self.header.numGUID)]
self.keywords=str.split(f.read(self.header.lenName),"\x00")
self.keywordDict=dict((hasher(keyword),keyword) for keyword in self.keywords)
self.fieldDescriptors=[FieldDescriptor(unpack("IHHII",f.read(16)), self.keywordDict) for i in xrange(self.header.numField)]
self.complexDescriptors=[ComplexDescriptor(unpack("IIBBHHH",f.read(16)), self.keywordDict) for i in xrange(self.header.numComplex)]
self.instanceRepeaters=[InstanceRepeater(unpack("3I",f.read(12))) for i in xrange(self.header.numInstanceRepeater)]
while f.tell()%16!=0: f.seek(1,1) #padding
self.arrayRepeaters=[arrayRepeater(unpack("3I",f.read(12))) for i in xrange(self.header.numArrayRepeater)]
#payload
f.seek(self.header.absStringOffset+self.header.lenString)
self.internalGUIDs=[]
self.instances=[] # (guid, complex)
for instanceRepeater in self.instanceRepeaters:
for repetition in xrange(instanceRepeater.repetitions):
instanceGUID=f.read(16)
self.internalGUIDs.append(instanceGUID)
if instanceGUID==self.primaryInstanceGUID: self.isPrimaryInstance=True
else: self.isPrimaryInstance=False
self.instances.append( (instanceGUID,self.readComplex(instanceRepeater.complexIndex,f)) )
f.close()
def readComplex(self, complexIndex,f):
complexDesc=self.complexDescriptors[complexIndex]
cmplx=Complex(complexDesc)
startPos=f.tell()
cmplx.fields=[]
for fieldIndex in xrange(complexDesc.fieldStartIndex,complexDesc.fieldStartIndex+complexDesc.numField):
f.seek(startPos+self.fieldDescriptors[fieldIndex].offset)
cmplx.fields.append(self.readField(fieldIndex,f))
f.seek(startPos+complexDesc.size)
return cmplx
def readField(self,fieldIndex,f):
fieldDesc = self.fieldDescriptors[fieldIndex]
field=Field(fieldDesc)
if fieldDesc.type in (0x0029, 0xd029,0x0000):
field.value=self.readComplex(fieldDesc.ref,f)
elif fieldDesc.type==0x0041:
arrayRepeater=self.arrayRepeaters[unpack("I",f.read(4))[0]]
arrayComplexDesc=self.complexDescriptors[fieldDesc.ref]
## if arrayRepeater.repetitions==0: field.value = "*nullArray*"
f.seek(self.arraySectionstart+arrayRepeater.offset)
arrayComplex=Complex(arrayComplexDesc)
arrayComplex.fields=[self.readField(arrayComplexDesc.fieldStartIndex,f) for repetition in xrange(arrayRepeater.repetitions)]
field.value=arrayComplex
elif fieldDesc.type in (0x407d, 0x409d):
startPos=f.tell()
f.seek(self.header.absStringOffset+unpack("I",f.read(4))[0])
string=""
while 1:
a=f.read(1)
if a=="\x00": break
else: string+=a
f.seek(startPos+4)
if len(string)==0: field.value="*nullString*" #actually the string is ""
else: field.value=string
if self.isPrimaryInstance and self.trueFilename=="" and fieldDesc.name=="Name": self.trueFilename=string
elif fieldDesc.type==0x0089: #incomplete implementation, only gives back the selected string
compareValue=unpack("I",f.read(4))[0]
enumComplex=self.complexDescriptors[fieldDesc.ref]
if enumComplex.numField==0:
field.value="*nullEnum*"
for fieldIndex in xrange(enumComplex.fieldStartIndex,enumComplex.fieldStartIndex+enumComplex.numField):
if self.fieldDescriptors[fieldIndex].offset==compareValue:
field.value=self.fieldDescriptors[fieldIndex].name
break
elif fieldDesc.type==0xc15d:
field.value=f.read(16)
else:
(typ,length)=numDict[fieldDesc.type]
num=unpack(typ,f.read(length))[0]
field.value=num
return field
def dump(self,outputFolder):
dirName=os.path.dirname(outputFolder+self.trueFilename)
if not os.path.isdir(dirName): os.makedirs(dirName)
if not self.trueFilename: self.trueFilename=hexlify(self.fileGUID)
f2=open(outputFolder+self.trueFilename+EXTENSION,"wb")
print self.trueFilename
for (guid,instance) in self.instances:
if instance.desc.name not in IGNOREINSTANCES: #############
if guid==self.primaryInstanceGUID: f2.write(instance.desc.name+" "+hexlify(guid)+ " #primary instance\r\n")
else: f2.write(instance.desc.name+" "+hexlify(guid)+ "\r\n")
self.recurse(instance.fields,f2,0)
f2.close()
def recurse(self, fields, f2, lvl): #over fields
lvl+=1
for field in fields:
if field.desc.type in (0xc14d, 0xc0fd, 0xc10d, 0xc0ed, 0xc0dd, 0xc0bd, 0xc0ad, 0x407d, 0x409d, 0x0089):
f2.write(lvl*SEP+field.desc.name+" "+str(field.value)+"\r\n")
elif field.desc.type == 0xc13d:
f2.write(lvl*SEP+field.desc.name+" "+formatfloat(field.value)+"\r\n")
elif field.desc.type == 0xc15d:
f2.write(lvl*SEP+field.desc.name+" "+hexlify(field.value).upper()+"\r\n") #upper case=> chunk guid
elif field.desc.type == 0x0035:
towrite=""
if field.value>>31:
extguid=self.externalGUIDs[field.value&0x7fffffff]
try: towrite=guidTable[extguid[0]]+"/"+hexlify(extguid[1])
except: towrite=hexlify(extguid[0])+"/"+hexlify(extguid[1])
elif field.value==0: towrite="*nullGuid*"
else: towrite=hexlify(self.internalGUIDs[field.value-1])
f2.write(lvl*SEP+field.desc.name+" "+towrite+"\r\n")
elif field.desc.type==0x0041 and len(field.value.fields)==0:
f2.write(lvl*SEP+field.desc.name+" "+"*nullArray*"+"\r\n")
else:
if field.desc.name not in IGNOREFIELDS: #############
f2.write(lvl*SEP+field.desc.name+"::"+field.value.desc.name+"\r\n")
self.recurse(field.value.fields,f2,lvl)
if outputFolder[-1] not in ("/","\\"): outputFolder+="/"
if inputFolder[-1] not in ("/","\\"): inputFolder+="/"
#if there's a guid table already, use it
try:
f5=open(guidTableName,"rb")
guidTable=cPickle.load(f5)
f5.close()
except:
guidTable=dict()
main()
Cordialy.
Vosvoy
- FatalBulletHit
- beginner
- Posts: 32
- Joined: Sun Nov 06, 2016 7:29 pm
- Has thanked: 12 times
- Been thanked: 6 times
- Contact:
Re: Frostbite 2 sound extraction research
Works totally fine, had the exact same script already on my HDD but apparently never used it.Vosvoy wrote:(I'm not at home now so I just copy/pasted this script from an ancient post on this thread so tell me if this script doesn't work)
Just one question:
"Weapon_SMG_UMP45_Silenced_02.txt" has 5498 lines of code... I understand the 56 paths, but what are the other 5442 lines about?
I mean, what is important and how do you know stuff like this:
?Vosvoy wrote: [...] put the speed of this sample at -60 in Audacity.
-
- mega-veteran
- Posts: 164
- Joined: Sun Aug 22, 2010 10:14 pm
- Has thanked: 40 times
- Been thanked: 11 times
Re: Frostbite 2 sound extraction research
Any change someone could look at the audio stuff from battlefront 2 not sure if it's the sounds that's changed or the ebx format has changed but if anyone needs anything else like a chunk file to go along with that ebx file tell me the names...ect and i'll send it
You do not have the required permissions to view the files attached to this post.
- Vosvoy
- veteran
- Posts: 127
- Joined: Fri Feb 18, 2011 4:58 pm
- Has thanked: 15 times
- Been thanked: 15 times
Re: Frostbite 2 sound extraction research
Here:FatalBulletHit wrote:Vosvoy wrote: Just one question:
"Weapon_SMG_UMP45_Silenced_02.txt" has 5498 lines of code... I understand the 56 paths, but what are the other 5442 lines about?
I mean, what is important and how do you know stuff like this:Vosvoy wrote: [...] put the speed of this sample at -60 in Audacity.
Wave ebx/sound/weapons/shared_content/hifi_oneshot/hifi_revolver_wave/8cac79083715480ad77264b7c25229c8
BasePitch 0.600000023842
Loop LtNone
ShuffleSegments False
Plugins::array*
I decided a long time ago to mess around with BF3 sounds and I found this conclusion:
+/- 0.10 Ebx BasePitch points = +/- 10 speed points in Audacity
E.g:
> BasePitch: 1.00 = default.
> BasePitch: 0.60 = -40 speed points in Audacity [0.60 (ebx base pitch) - 1.00 (default base pitch) = -0.40].
> BasePitch: 2.00 = +200 speed points in Audacity.
> BasePitch: 1.10 = +10 speed points in Audacity.
> BasePitch: 0.80 = -20 speed points in Audacity.
Etc...
I don't speak english so I don't even know how to explain all this thing, but I have one last advice for you.
Don't even try to understand the whole ebx mumbo jumbo. The most important things are "BasePitch" points and "Wave ebx/sound/..." directories. That's all.
EDIT: I corrected a few things.
Vosvoy
-
- ultra-n00b
- Posts: 5
- Joined: Mon Nov 25, 2019 11:36 pm
- Been thanked: 1 time
Re: Frostbite 2 sound extraction research
I've tweaked the python script a little. It will ask for game and extraction paths, if those have not been set, and check if the directories are valid.
https://1drv.ms/u/s!Ahb8fw3iR21phWlB2iI ... n?e=Y9qbT6
https://1drv.ms/u/s!Ahb8fw3iR21phWlB2iI ... n?e=Y9qbT6
-
- double-veteran
- Posts: 840
- Joined: Sat Nov 06, 2010 12:27 am
- Has thanked: 435 times
- Been thanked: 235 times
Re: Frostbite 2 sound extraction research
Just out of curiosity, was modified to do what exactly? Anything more specific?
Also, what is wrong with the following, because it is more complete than anything else there is out there, above included, which have become fully obsolete.
https://github.com/NicknineTheEagle/Frostbite-Scripts
Will save you lots of headache.