wow的shader都是放在bls文件中的,前几天对这些文件分析了一下,发现wowdev.org里对bls的解析不大对,这里对bls的文件格式做一下说明。
文件前四个字节肯定是"SVXG"或"SPXG",分别表示这个bls里保存的是vs或ps。
接下来一个uint表示版本号,没什么用。
接下来一个uint是用来表示每个profile里的shader数目,以下检测pc。
接下来是data block的偏移量,如果是vs,会有6个偏移量,如果是ps,会有12个偏移量,其中有些可能会是0。
然后就可以根据这些偏移量,移动到文件中的指定位置,开始读取shader的数据和代码了。
以下是我写的一个解析bls文件的代码,可以把指定的bls里的shader输出到一个文本文件中。
/********************************************************************
filename: Blsparser.cpp
created: 2006/07/29
author: Weiliang Xie (feiyurainy at 163.com)
purpose: To parse the wow shader file(.bls),and log the shader data
a file.
*********************************************************************/
#include <iostream>
#include <fstream>
#include <string>
#include <assert.h>
#include <d3dx9.h>
#include <dxerr9.h>
typedef unsigned int uint32;
struct BlsHeader
{
char shaderType[4]; /// shader type,must be "SPXG" or "SVXG"
uint32 version; /// shader version,useless
uint32 shaderCount; /// shader count in each shader block
};
/// global shader header for the input bls file
BlsHeader gBlsHeader;
/// input bls file stream
std::ifstream gBlsFStream;
/// output log file stream
std::ofstream gOutputFile;
/** read the data from the gBlsFStream
@param count num of byte to read
@return num of byte read
*/
size_t _readData(void* buf, size_t count)
{
gBlsFStream.read( static_cast<char*>(buf), static_cast<std::streamsize>(count) );
return gBlsFStream.gcount();
}
/** parse the bls file,and log the shader data to file
@param offsetCount the num of block offset, in vertex shader file, it must
be 6, and in pixel shader file, it must be 12
*/
void _parseShaderData(const size_t offsetCount)
{
uint32* blockOffset = new uint32[offsetCount];
// read the offsets of blocks
_readData(blockOffset, sizeof(uint32) * offsetCount);
// for each block
for (size_t offsetIndex = 0; offsetIndex < offsetCount; ++offsetIndex)
{
uint32 offset = blockOffset[offsetIndex];
// some block offset is zero, i don't know why
if (offset > 0)
{
// go to the address of the block
gBlsFStream.seekg(offset, std::ios::beg);
// for each shader in this block
for (uint32 shaderIndex = 0; shaderIndex < gBlsHeader.shaderCount; ++shaderIndex)
{
gOutputFile << "===== Shader start : " << std::endl;
/** block header is four uint
0x0 number of shader parameters
if this number is not zero, there will be some string follow it,
each string contain a parameter name and value, and the size is 0x90
0x4 number of shader texture names
if this number is not zero, there will be some string follow it,
each string contain a texture name, and the size is 0x90
0x8 always 0 or 2
useless
0xc number byte of shader code text
the size of code text
*/
uint32 blockHeader[4] = {0};
_readData(&blockHeader[0], sizeof(uint32));
if (blockHeader[0] > 0)
{
gOutputFile << "Parameter section start :" << std::endl;
// log each parameter name
for (uint32 i = 0; i < blockHeader[0]; ++i)
{
char paraName[0x90];
_readData(paraName, 0x90);
gOutputFile << paraName << std::endl;
}
gOutputFile << "Parameter section end/n" << std::endl;
}
_readData(&blockHeader[1], sizeof(uint32));
if (blockHeader[1] > 0)
{
gOutputFile << "Texture names section start :/n" << std::endl;
// log each texture name
for (uint32 i = 0; i < blockHeader[1]; ++i)
{
char texName[0x90];
_readData(texName, 0x90);
gOutputFile << texName << std::endl;
}
gOutputFile << "Texture names section end/n" << std::endl;
}
_readData(&blockHeader[2], sizeof(uint32));
// get the size of shader code text
_readData(&blockHeader[3], sizeof(uint32));
uint32 codeSize = blockHeader[3];
if (codeSize > 0)
{
gOutputFile << "Code section start :" << std::endl;
char* codeText = new char[codeSize];
// read code text
size_t read = _readData(codeText, codeSize);
assert (read == codeSize);
size_t pos = gBlsFStream.tellg();
// if the first char is '!', this shader is written by
// arbfp or arbvp, and there will be some unused datas
// between two shaders
if (codeText[0] == '!')
{
gOutputFile << codeText << std::endl;
// find out the length of the unused datas
size_t nowPos = gBlsFStream.tellg();
size_t skipCount = 0;
if (nowPos % 4 == 0)
skipCount = 0;
else
skipCount = 4 - nowPos % 4;
// and skip to the next shader
gBlsFStream.seekg(skipCount, std::ios::cur);
}
else // it is the dx shader
{
// use d3dx to get the shader code from the binary shader code
LPD3DXBUFFER codeBuffer;
HRESULT hr = D3DXDisassembleShader(
static_cast<DWORD*>( static_cast<void*>(codeText) ),
0, NULL, &codeBuffer );
if (FAILED(hr))
{
std::cout << DXGetErrorDescription9(hr) << std::endl;
}
gOutputFile << static_cast<char*>( codeBuffer->GetBufferPointer() ) << std::endl;
}
gOutputFile << "Code section end/n" << std::endl;
delete [] codeText;
}
gOutputFile << "===== Shader end/n" << std::endl;
} // end of each shader
}
} // end of each block
delete [] blockOffset;
}
int main(int argc, char **argv)
{
gBlsFStream.open(argv[1], std::ios::binary | std::ios::in);
if (!gBlsFStream)
{
std::cout << "can't find the file : " << argv[1] << std::endl;
return 1;
}
std::string outputName(argv[1]);
outputName += ".log";
gOutputFile.open(outputName.c_str(), std::ios::out | std::ios::binary);
if (!gOutputFile)
{
std::cout << "can't create the output file : " << outputName << std::endl;
return 1;
}
// read the header
_readData(&gBlsHeader, sizeof(BlsHeader));
gOutputFile << "shader type : " << gBlsHeader.shaderType << std::endl;
size_t offsetCount;
// find out this bls file is vs or ps
if (gBlsHeader.shaderType[1] == 'V')
{
offsetCount = 6;
}
else if (gBlsHeader.shaderType[1] == 'P')
{
offsetCount = 12;
}
else
{
std::cout << "the content of the file is wrong : " << argv[1] << std::endl;
return 1;
}
_parseShaderData(offsetCount);
gBlsFStream.close();
gOutputFile.close();
return 0;
}