游戏研究室

文档-资料-开源

英雄联盟SKL文件结构

文件采用小端(little-endian)字节序

char[8]

文件类型标识,没有扩展名时可以判断文件类型 

int32

Obj数量(不知道什么作用)

int32

意义不详(不知道什么作用)

int32

骨骼数量

char[88]*骨骼数量

骨骼数据

char[32]

骨骼名称

int32

父级骨骼的索引

float

缩放

float[3][4]

4×4变换矩阵的前3行数据,最后一行为0,0,0,1

其中骨骼的索引由其排列顺序决定,最先读取的骨骼索引为0,以此类推。


下面给出一段命令行C++工具代码

/*
League of Legends Custom Skin Tool-Kit
by Jahrain
This is a simple tool that can be used to compile and decompile .skn and .skl files
in order to create custom skin models for League of Legends
*/
#include<iostream>
#include<fstream>
#include<vector>
#include<string>
using namespace std;


struct Header {
	//Structure for header on skl files
	char fileType[8];
	int numObjects;
	int skeletonHash;
	int numElements;
};

struct Bone {
	//Structure for a bone in skl files
	char name[32];
	int parent;
	float scale;
	float matrix[3][4];
};

struct SkinModelHeader {
	//Structure for the Header in skn files
	int magic;
	short numMaterials;
	short numObjects;
};
struct SkinModelMaterial {
	//Structure for a material block in skn files
	int matIndex;
	char name[64];
	int startVertex;
	int numVertices;
	int startIndex;
	int numIndices;
};
struct SkinModelVertex {
	//Vertex block in skn files
	float position[3];
	char  boneIndex[4];
	float weights[4];
	float normal[3];
	float texcoords[2];
};
struct SkinModelData {
	//data block in skn files
	int numIndices;
	int numVertices;
	vector<short> indices;
	vector<SkinModelVertex> verteces;
};

struct SkinModel {
	//Skin model data structure
	SkinModelHeader header;
	vector<SkinModelMaterial> materials;
	SkinModelData modelData;


	void exportObj(string fileName) {
		//exports this skin model as a .obj file
		ofstream fout(fileName.c_str());
		for (int i = 0; i < modelData.verteces.size(); i++) {
			fout << "v " << modelData.verteces[i].position[0] << " " << modelData.verteces[i].position[1] << " " << modelData.verteces[i].position[2] << endl;
			fout << "vn " << modelData.verteces[i].normal[0] << " " << modelData.verteces[i].normal[1] << " " << modelData.verteces[i].normal[2] << endl;
			fout << "vt " << modelData.verteces[i].texcoords[0] << " " << 1-modelData.verteces[i].texcoords[1] << endl;
		}
		if (materials.size()) {
			fout << "g mat_" << materials[0].name << endl;
		}
		for (int i = 0;  i < modelData.numIndices/3; i++) {
			int a = modelData.indices[i*3] + 1;
			int b = modelData.indices[i*3 + 1] + 1;
			int c = modelData.indices[i*3 + 2] + 1; 
			fout << "f " << a << '/' << a << '/' << a << " " << b << '/' << b << '/' << b << " " << c << '/' << c << '/' << c << endl;
		}
	}


	void exportSkn(string fileName) {
		//export this SkinModel as .skn which can be used by league of legends
		ofstream fout;
		fout.open(fileName.c_str(), ios::binary);
		fout.write((char*)(void*)&header,sizeof(SkinModelHeader));
		for (int i = 0; i < header.numMaterials; i++) {
			fout.write((char*)(void*)&materials[i], sizeof(SkinModelMaterial));
		}
		fout.write((char*)(void*)&modelData.numIndices,4);
		fout.write((char*)(void*)&modelData.numVertices,4);
		for (int i = 0; i < modelData.numIndices; i++) {
			fout.write((char*)(void*)&modelData.indices[i], 2);
		}
		for (int i = 0; i < modelData.numVertices; i++) {
			fout.write((char*)(void*)&modelData.verteces[i],sizeof(SkinModelVertex));
		}
	}

	void importSkn(string infile) {
		//import from .skn file
		ifstream fin;
		fin.open(infile.c_str(), ios::binary);
		if (fin.fail()) {
			cout << "ERROR: could not open " << infile << endl;
			exit(1);
		}
		fin.read((char*)(void*)&header,sizeof(SkinModelHeader));
		
		for (int i = 0; i < header.numMaterials; i++) {
			SkinModelMaterial mat;
			fin.read((char*)(void*)&mat, sizeof(SkinModelMaterial));
			materials.push_back(mat);
		}
		
		fin.read((char*)(void*)&modelData.numIndices,4);
		fin.read((char*)(void*)&modelData.numVertices,4);
		
		for (int i = 0; i < modelData.numIndices; i++) {
			short idx;
			fin.read((char*)(void*)&idx, 2);
			modelData.indices.push_back(idx);
		}
		for (int i = 0; i < modelData.numVertices; i++) {
			SkinModelVertex vtx;
			fin.read((char*)(void*)&vtx,sizeof(SkinModelVertex));
			modelData.verteces.push_back(vtx);
		}
	}

	void importMesh(string infile) {
		//import from ASCII mesh output from Maya Script
		//cout << "Compiling skin file: " << infile << " to binary file: " << outfile << endl;
		ifstream fin(infile.c_str());
		if (fin.fail()) {
			cout << "ERROR: could not open " << infile << endl;
			exit(1);
		}
		int numVerts;
		fin >> numVerts;
		modelData.numVertices = numVerts;
		for (int i = 0; i < numVerts; i++) {
			SkinModelVertex vtx;
			fin >> vtx.position[0] >> vtx.position[1] >> vtx.position[2];
			
			for (int j = 0;  j < 3; j++) {
				int boneIdx = 0;
				string boneIdxStr;
				fin >> boneIdxStr;
				if (boneIdxStr != "#NULL") {
					string tmp;
					for (int k = boneIdxStr.length() - 1; k >= 0; k--) {
						if (boneIdxStr[k] == '_') {
							boneIdxStr[k] = ' ';
							break;
						}
					}
				}
				char name[32];
				sscanf(boneIdxStr.c_str(), "%s %d",name,&boneIdx);
				vtx.boneIndex[j] = boneIdx;
				
			}
			vtx.boneIndex[3] = 0;
			fin >> vtx.weights[0] >> vtx.weights[1] >> vtx.weights[2];
			vtx.weights[3] = 0;
			//normalize weights
			float weightSum = vtx.weights[0] + vtx.weights[1] + vtx.weights[2];
			vtx.weights[0] *= 1.0f/weightSum;
			vtx.weights[1] *= 1.0f/weightSum;
			vtx.weights[2] *= 1.0f/weightSum;
			fin >> vtx.normal[0] >> vtx.normal[1] >> vtx.normal[2];
			fin >> vtx.texcoords[0] >> vtx.texcoords[1];
			vtx.texcoords[1] = 1 - vtx.texcoords[1];		//flip the UVs
			modelData.verteces.push_back(vtx);
		}
		SkinModelMaterial mat;
		memset((void*)&mat,0,sizeof(SkinModelMaterial));
		fin >> mat.name;
		int numFaces;
		fin >> numFaces;
		string nameStr = string(mat.name);
		if (nameStr != "lambert1") {
			cout << "NOTE: The material name is " << nameStr << ". Make sure this matches the same material as the original skin file!!!" << endl;
			header.numMaterials = 1;
			mat.matIndex = 1;
			mat.numIndices = numFaces*3;
			mat.numVertices = numVerts;
			mat.startIndex = 0;
			mat.startVertex = 0;
			materials.push_back(mat);
		} else {
			cout << "WARNING: The default material was used because this model used the default material lambert1. Only do this if the original .skn file has no materials." << endl;
			header.numMaterials = 0;
		}
		header.magic = 1122867;
		header.numObjects = 1;
		modelData.numIndices = numFaces*3;
		for (int i = 0; i < numFaces; i++) {
			for (int j = 0; j < 3; j++) {
				short face;
				fin >> face;
				modelData.indices.push_back(face);
			}
		}
	}

	
};


void dumpSkeleton(string infile, string outfile) {
	//dump out the contents of a .skl file as ASCII for importing into Maya or some other 3d Application.
	ifstream fin;
	fin.open(infile.c_str(), ios::binary);
	if (fin.fail()) {
		cout << "ERROR: could not open " << infile << endl;
		exit(1);
	}
	ofstream fout(outfile.c_str());
	Header header;
	fin.read((char*)(void*)&header, sizeof(header));
	cout << "Found " << header.numElements << " bones" << endl;
	fout << header.fileType << endl;
	fout << header.numObjects << endl;
	fout << header.skeletonHash << endl;
	fout << header.numElements << endl;
	for (int i = 0; i < header.numElements; i++) {
		Bone bone;
		fin.read((char*)(void*)&bone, sizeof(Bone));
		fout << bone.name << endl;
		fout << bone.parent << endl;
		fout << bone.scale << endl;
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 4; j++) {
				fout << bone.matrix[i][j] << " ";
			}
			fout << endl;
		}
	}
}


void compileSkeleton(string infile, string outfile) {
	//Compile the ASCII skeleton file back into a .skl file
	ifstream fin;
	fin.open(infile.c_str());
	if (fin.fail()) {
		cout << "ERROR: could not open " << infile << endl;
		exit(1);
	}
	ofstream fout;
	fout.open(outfile.c_str(), ios::binary);
	Header header;
	
	fin >> header.fileType;
	fin >> header.numObjects;
	fin >> header.skeletonHash;
	fin >> header.numElements;

	fout.write((const char*)&header,sizeof(Header));
	for (int i = 0; i < header.numElements; i++) {
		Bone bone;
		memset(bone.name,0,32);
		fin >> bone.name;
		fin >> bone.parent;
		fin >> bone.scale;
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 4; j++) {
				fin >> bone.matrix[i][j];
			}
		}
		fout.write((const char*)&bone, sizeof(Bone));
	}
}


int main(int argc, char *argv[]) {
	
	if (argc != 5) {
		cout << "Usage: lolskintool <command> <type> <inputFile> <outputFile>" << endl;
		cout << "Valid commands are: c (compile), d (decompile)" << endl;
		cout << "Supported types are: skl (skeleton), skn (skin)" << endl;
		return 2;
	}
	string cmd = string(argv[1]);
	string type = string(argv[2]);
	string inputFile = string(argv[3]);
	string outputFile = string(argv[4]);

	if (cmd == "compile" || cmd == "c") {
		if (type == "skl" || type == "skeleton") {
			cout << "Compiling ASCII skeleton file: " << inputFile << " to binary .skl file: " << outputFile << endl;
			compileSkeleton(inputFile, outputFile);
			cout << "Done!" << endl;
		} else if (type == "skn" || type == "skin") {
			SkinModel model;
			cout << "Compiling ASCII skin mesh file: " << inputFile << " to binary .skn file: " << outputFile << endl;
			model.importMesh(inputFile);
			model.exportSkn(outputFile);
			cout << "Done!" << endl;
		} else {
			cout << "Unsupported file type" << endl;
			return 2;
		}
	} else if (cmd == "decompile" || cmd == "d") {
		if (type == "skl" || type == "skeleton") {
			cout << "Decompiling binary .skl file: " << inputFile << " to ASCII file: " << outputFile << endl;
			dumpSkeleton(inputFile, outputFile);
			cout << "Done!" << endl;
		} else if (type == "skn" || type == "skin") {
			cout << "Decompiling binary .skn file: " << inputFile << " to .obj file: " << outputFile << endl;
			SkinModel model;
			model.importSkn(inputFile);
			model.exportObj(outputFile);
			cout << "Done!" << endl;
		} else {
			cout << "Unsupported file type" << endl;
			return 2;
		}
	} else {
		cout << "Unsupported command" << endl;
		return 2;
	}
	return 0;
}

, , ,

发表评论