package tk.captainsplexx.Resource.EBX;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import tk.captainsplexx.Resource.FileHandler;
import tk.captainsplexx.Resource.FileSeeker;
import tk.captainsplexx.Resource.EBX.EBXHandler.FieldValueType;
public class EBXLoader {
public byte[] ebxFileBytes;
public String guidTablePath;
//public HashMap<Integer, Integer> numDict = new HashMap<Integer, Integer>();
public HashMap<Integer, String> keywordDict;
public static byte[] littleHeader = new byte[] { (byte) 0xCE, (byte) 0xD1,
(byte) 0xB2, (byte) 0x0F };
public byte[] bigHeader = new byte[] { (byte) 0x0F, (byte) 0xB2,
(byte) 0xD1, (byte) 0xCE };
private ByteOrder order;
public EBXHeader header;
private int arraySectionstart;
private String trueFilename;
private String fileGUID;
private EBXExternalGUID[] externalGUIDs;
private ArrayList<String> internalGUIDs;
private EBXFieldDescriptor[] fieldDescriptors;
private EBXComplexDescriptor[] complexDescriptors;
private EBXInstanceRepeater[] instanceRepeaters;
private EBXArrayRepeater[] arrayRepeaters;
private ArrayList<EBXInstance> instances;
private ArrayList<EBXField> fields;
private ArrayList<Integer> internalGUIDFieldIndexs;
private FileSeeker seeker;
private boolean isPrimaryInstance;
private String filePath;
public ArrayList<EBXInstance> getInstances(){
return instances;
}
public String getTrueFilename(){
return trueFilename;
}
public static String getGUID(byte[] ebxFile){
if (ebxFile==null){return null;}
//4Bytes Magic + 36Bytes Header == FileGUID Offset at 40. Byte (0x28)
return FileHandler.bytesToHex(FileHandler.readByte(ebxFile, 0x28, 16)).toUpperCase();
}
public boolean loadEBX(byte[] ebxFileBytes){
if (ebxFileBytes==null){
return false;
}
this.seeker = new FileSeeker();
this.ebxFileBytes = ebxFileBytes;
this.trueFilename = "";
if (Arrays.equals(readMagic(ebxFileBytes), littleHeader)){
order = ByteOrder.LITTLE_ENDIAN;
}else if (Arrays.equals(readMagic(ebxFileBytes), bigHeader)){
order = ByteOrder.BIG_ENDIAN;
}else{
System.err.println("EBX File does not match Header's: "+filePath);
}
//Decrypt Header 36 Bytes long 3I6H3I
seeker.setOffset(0x04);
header = new EBXHeader(
FileHandler.readInt(ebxFileBytes, seeker, order), //absolute offset for string section start 0x04 4bytes
FileHandler.readInt(ebxFileBytes, seeker, order), //length from string section start to EOF 0x08 4bytes
FileHandler.readInt(ebxFileBytes, seeker, order), //number of external GUIDs 0x0C 4bytes
FileHandler.readShort(ebxFileBytes, seeker, order), //total number of instance repeaters 0x10 2byte
FileHandler.readShort(ebxFileBytes, seeker, order), //instance repeaters with GUID 0x12 2bytes
FileHandler.readShort(ebxFileBytes, seeker, order), //0x14 2bytes ??!
FileHandler.readShort(ebxFileBytes, seeker, order), //number of complex entries 0x16 2bytes
FileHandler.readShort(ebxFileBytes, seeker, order), //number of field entries 0x18 2bytes
FileHandler.readShort(ebxFileBytes, seeker, order), //length of name section including padding 0x1A 2bytes
FileHandler.readInt(ebxFileBytes, seeker, order), //length of string section including padding 0x1C 4bytes
FileHandler.readInt(ebxFileBytes, seeker, order), //num array repeater 0x20 4bytes
FileHandler.readInt(ebxFileBytes, seeker, order)); //length of normal payload section; the start of the array payload section is absStringOffset+lenString+lenPayload 0x24 4bytes
if (seeker.hasError()){
return false;
}
//Calculate ArraySection Start Offset
arraySectionstart=header.getAbsStringOffset()+header.getLenString()+header.getLenPayload();
if (seeker.hasError()){
return false;
}
//Read out file GUID as HEXSTRING!
fileGUID=FileHandler.bytesToHex(FileHandler.readByte(ebxFileBytes, seeker, 16)).toUpperCase();
if (seeker.hasError()){
return false;
}
//Calc of externalGUIDs
while (seeker.getOffset()%16!=0){ seeker.seek(1); } //#padding
externalGUIDs = new EBXExternalGUID[header.getNumGUID()];
for (int i=0;i<externalGUIDs.length;i++){
externalGUIDs[i] = new EBXExternalGUID(
(FileHandler.bytesToHex(FileHandler.readByte(ebxFileBytes, seeker, 16)).toUpperCase()),
(FileHandler.bytesToHex(FileHandler.readByte(ebxFileBytes, seeker, 16)).toUpperCase())
);
if (seeker.hasError()){
return false;
}
}
//Get Keywords and Calculate Hashes
this.keywordDict = new HashMap<Integer, String>();
int startKeyOffset = 0x40+((header.getNumGUID())*0x20);
int keyOffset = 0;
while (startKeyOffset+keyOffset < startKeyOffset+header.getLenName()){
String s = readString(ebxFileBytes, startKeyOffset+keyOffset);
keywordDict.put(EBXHandler.hasher(s.getBytes()), s);
keyOffset += s.length()+1;
if (seeker.hasError()){
return false;
}
}
//fieldDescriptors
seeker.setOffset(startKeyOffset+header.getLenName());
this.fieldDescriptors = new EBXFieldDescriptor[header.getNumField()];
for (int i=0;i<fieldDescriptors.length;i++){
fieldDescriptors[i] = new EBXFieldDescriptor(keywordDict.get(FileHandler.readInt(ebxFileBytes, seeker)),
FileHandler.readShort(ebxFileBytes, seeker, order),
FileHandler.readShort(ebxFileBytes, seeker, order),
FileHandler.readInt(ebxFileBytes, seeker, order),
FileHandler.readInt(ebxFileBytes, seeker, order));
if (seeker.hasError()){
return false;
}
}
//ComplexDescriptor
complexDescriptors = new EBXComplexDescriptor[header.getNumComplex()];
for (int i=0;i<complexDescriptors.length;i++){
complexDescriptors[i] = new EBXComplexDescriptor(keywordDict.get(FileHandler.readInt(ebxFileBytes, seeker, order)),
FileHandler.readInt(ebxFileBytes, seeker, order),
(char) FileHandler.readByte(ebxFileBytes, seeker),
(char) FileHandler.readByte(ebxFileBytes, seeker),
FileHandler.readShort(ebxFileBytes, seeker, order),
FileHandler.readShort(ebxFileBytes, seeker, order),
FileHandler.readShort(ebxFileBytes, seeker, order));
if (seeker.hasError()){
return false;
}
}
//instanceRepeaters
instanceRepeaters = new EBXInstanceRepeater[header.getNumInstanceRepeater()];
for (int i=0;i<instanceRepeaters.length;i++){
instanceRepeaters[i] = new EBXInstanceRepeater(
FileHandler.readShort(ebxFileBytes, seeker, order),
FileHandler.readShort(ebxFileBytes, seeker, order));
if (seeker.hasError()){
return false;
}
}
//arrayRepeaters
while (seeker.getOffset()%16!=0){ seeker.seek(1);} // #padding
arrayRepeaters = new EBXArrayRepeater[header.getNumArrayRepeater()];
for (int i=0; i<arrayRepeaters.length;i++){
arrayRepeaters[i] = new EBXArrayRepeater(
FileHandler.readInt(ebxFileBytes, seeker, order),
FileHandler.readInt(ebxFileBytes, seeker, order),
FileHandler.readInt(ebxFileBytes, seeker, order));
if (seeker.hasError()){
return false;
}
}
//PayLoad
internalGUIDs = new ArrayList<String>();
fields = new ArrayList<EBXField>();
instances = new ArrayList<EBXInstance>();
internalGUIDFieldIndexs = new ArrayList<Integer>();
seeker.setOffset(header.absStringOffset+header.lenString);
//int nonGUIDindex = 0;
String tempGUID = "";
isPrimaryInstance = true;
for (int repeater=0; repeater<instanceRepeaters.length;repeater++){
EBXInstanceRepeater ir = instanceRepeaters[repeater];
for (int repetition=0; repetition<ir.getRepetitions();repetition++){
while (seeker.getOffset()%complexDescriptors[ir.complexIndex].alignment!=0){ seeker.seek(1); } //#obey alignment of the instance; peek into the complex for that
if (repeater<header.getNumGUIDRepeater()){
tempGUID = FileHandler.bytesToHex(FileHandler.readByte(ebxFileBytes, seeker, 16));
}else{
tempGUID = FileHandler.bytesToHex(ByteBuffer.allocate(4).putInt(internalGUIDs.size()).array());//lets use the index as guid
//tempGUID = FileHandler.bytesToHex(ByteBuffer.allocate(4).putInt(nonGUIDindex).array());
//nonGUIDindex++;
}
internalGUIDs.add(tempGUID);
instances.add(new EBXInstance(tempGUID, readComplex(ir.getComplexIndex(), true, false)));
isPrimaryInstance = false;
if (seeker.hasError()){
return false;
}
}
if (seeker.hasError()){
return false;
}
}
//Internal GUIDs are defined by their index.
//So we have to proc. the instancec first and set
//the real value after...
for (Integer i : internalGUIDFieldIndexs){
EBXField field = fields.get(i);
Integer tempValue = (Integer) field.getValue();
String intGuid = internalGUIDs.get(tempValue-1);
field.setValue(intGuid, FieldValueType.Guid); //set guid from temp value!
}
ebxFileBytes = null;
return true;
}
private EBXComplex readComplex(int complexIndex, boolean isInstance, boolean hasEmtyPayload) {
EBXComplexDescriptor complexDesc = complexDescriptors[complexIndex];
EBXComplex cmplx = new EBXComplex(complexDesc);
cmplx.setOffset(seeker.getOffset());
EBXField[] fields = new EBXField[complexDesc.getNumField()];
//#alignment 4 instances require subtracting 8 for all field offsets and the complex size
int obfuscationShift=0;
if (isInstance && cmplx.getComplexDescriptor().getAlignment()==4){
obfuscationShift = 8;
}
for (int fieldIndex=0; fieldIndex<complexDesc.getNumField(); fieldIndex++){
seeker.setOffset(cmplx.getOffset()+fieldDescriptors[complexDesc.getFieldStartIndex()+fieldIndex].getOffset()-obfuscationShift);
fields[fieldIndex] = readField(complexDesc.getFieldStartIndex()+fieldIndex, hasEmtyPayload);
if (seeker.hasError()){
return null;
}
}
cmplx.fields = fields;
seeker.setOffset(cmplx.getOffset()+complexDesc.getSize()-obfuscationShift);
return cmplx;
}
public EBXField readField(int fieldIndex, boolean hasEmtyPayload){
EBXFieldDescriptor fieldDesc = fieldDescriptors[fieldIndex];
EBXField field = new EBXField(fieldDesc, seeker.getOffset());
fields.add(field);
/*<DECODE>*/
if (fieldDesc.getType() == (short) 0x0029|| fieldDesc.getType() == (short) 0xd029 || fieldDesc.getType() == (short) 0x0000 || fieldDesc.getType() == (short) 0x8029){ //COMPLEX
field.setValue(readComplex(fieldDesc.getRef(), false, hasEmtyPayload), EBXHandler.FieldValueType.Complex);
}else if (fieldDesc.getType() == 0x0041){ //ARRAYCOMPLEX
int repeaterIndex = FileHandler.readInt(ebxFileBytes, seeker);
if (repeaterIndex>arrayRepeaters.length){
System.err.println("Out of bounds!");
}
EBXArrayRepeater arrayRepeater = arrayRepeaters[repeaterIndex];
EBXComplexDescriptor arrayComplexDesc = complexDescriptors[fieldDesc.getRef()];
seeker.setOffset(arraySectionstart+arrayRepeater.offset);
EBXComplex arrayComplex = new EBXComplex(arrayComplexDesc);
EBXField[] fields = null;
if (arrayRepeater.getRepetitions()>0){//Guids,Strings,Integer... =) ?
fields = new EBXField[arrayRepeater.getRepetitions()];
for (int i=0; i<fields.length;i++){
fields[i] = readField(arrayComplexDesc.getFieldStartIndex(), false);
}
}else if (arrayComplexDesc.getNumField()>0){//Field is prob, Complex||| ------------>The complexDescr does exist but without any payload ??
int index = FileHandler.readInt(ebxFileBytes, new FileSeeker(seeker.getOffset()));//array section contains a integer for it ?
//System.out.println("ArrayComplex-Integer-Debug: "+index);
fields = new EBXField[arrayComplexDesc.getNumField()];
for (int i=0; i<fields.length;i++){
fields[i] = readField(arrayComplexDesc.getFieldStartIndex()+i, true);//has no payload data!
}
}
if (fields!=null){
arrayComplex.setFields(fields);
field.setValue(arrayComplex, FieldValueType.ArrayComplex);
}else{
field.setValue("*nullArray*", FieldValueType.ArrayComplex);
}
}else if (fieldDesc.getType() == (short) 0x407D || fieldDesc.getType() == (short) 0x409D){//STRING
if (hasEmtyPayload){
field.setValue(null, FieldValueType.String);
}else{
int stringOffset = FileHandler.readInt(ebxFileBytes, seeker);
if (stringOffset<=-1){
field.setValue("*nullString*", FieldValueType.String);
}else{
field.setValue(readString(ebxFileBytes, header.getAbsStringOffset()+stringOffset),FieldValueType.String);
}
//TRUEFILENAME
if (isPrimaryInstance && fieldDesc.getName().equals("Name") && trueFilename.equals("")){
trueFilename = (String) field.getValue();
}
}
}else if (fieldDesc.getType() == (short) 0x0089 || fieldDesc.getType() == (short) 0xC089){
int compareValue = -1;
if (!hasEmtyPayload){
compareValue = FileHandler.readInt(ebxFileBytes, seeker);
}
EBXComplexDescriptor enumComplex = complexDescriptors[fieldDesc.getRef()];
if (enumComplex.getNumField()==0){
field.setValue("*nullEnum*", FieldValueType.Enum);
}else{
HashMap<EBXFieldDescriptor, Boolean> enums = new HashMap<>();
for (int index=0; index<enumComplex.getNumField(); index++){
if (fieldDescriptors[enumComplex.getFieldStartIndex()+index].getOffset()==compareValue){
enums.put(fieldDescriptors[enumComplex.getFieldStartIndex()+index], true);//SELECTED
}else{
enums.put(fieldDescriptors[enumComplex.getFieldStartIndex()+index], false);//NOT SELECTED
}
}
field.setValue(enums, FieldValueType.Enum);
}
}else if (fieldDesc.getType()== (short) 0xC15D){ //GUID CHUNK
if (hasEmtyPayload){
field.setValue(null, FieldValueType.ChunkGuid);
}else{
field.setValue(FileHandler.bytesToHex(FileHandler.readByte(ebxFileBytes, seeker,16)), FieldValueType.ChunkGuid);
}
}else if (fieldDesc.getType()== (short) 0x417D){ //8HEX
if (hasEmtyPayload){
field.setValue(null, FieldValueType.Hex8);
}else{
field.setValue(FileHandler.bytesToHex(FileHandler.readByte(ebxFileBytes,seeker, 8)), FieldValueType.Hex8);
}
}else if (fieldDesc.getType()==(short) 0xC13D){//FLOAT
if (hasEmtyPayload){
field.setValue(null, FieldValueType.Float);
}else{
field.setValue(FileHandler.readFloat(ebxFileBytes, seeker), FieldValueType.Float);
}
}else if (fieldDesc.getType()==(short) 0xc10d){//uint
if (hasEmtyPayload){
field.setValue(null, FieldValueType.UInteger);
}else{
field.setValue((FileHandler.readInt(ebxFileBytes, seeker, order) & 0xffffffffL), FieldValueType.UInteger);
}
}else if(fieldDesc.getType() == (short) 0xc0fd){ //signed int
if (hasEmtyPayload){
field.setValue(null, FieldValueType.Integer);
}else{
field.setValue(FileHandler.readInt(ebxFileBytes, seeker), FieldValueType.Integer);
}
}else if (fieldDesc.getType() == (short) 0xc0ad){//BOOL
if (hasEmtyPayload){
field.setValue(null, FieldValueType.Bool);
}else{
byte b = FileHandler.readByte(ebxFileBytes, seeker);
if (b==0x00){
field.setValue(false, FieldValueType.Bool);
}else if (b==0x01){
field.setValue(true, FieldValueType.Bool);
}else{
field.setValue(null, FieldValueType.Bool);
System.err.println("Field is not a boolean! at "+seeker.getOffset());
}
}
}else if (fieldDesc.getType() == (short) 0xc0ed){//short
if (hasEmtyPayload){
field.setValue(null, FieldValueType.Short);
}else{
field.setValue(FileHandler.readShort(ebxFileBytes, seeker, order), FieldValueType.Short);
}
}else if (fieldDesc.getType() == (short) 0xc0cd){//BYTE
if (hasEmtyPayload){
field.setValue(null, FieldValueType.Byte);
}else{
field.setValue(FileHandler.readByte(ebxFileBytes, seeker), FieldValueType.Byte);
}
}else if(fieldDesc.getType() == (short) 0x0035){ // #guid
if (hasEmtyPayload){
field.setValue(null, FieldValueType.Guid);
}else{
int tempValue = FileHandler.readInt(ebxFileBytes, seeker);
//field.setValue(String.valueOf(tempValue)+"test", FieldValueType.Guid);
//return field;
// INTERNAL STARTS AT 0x1 (values needs to be substracted by 1 to optain the index)
// EXTERNAL STARTS AT 0x80 00 00 00 -> substract base value to optain index. (starts at 0)
if(((tempValue>>31)) == -1){
EBXExternalGUID guid = externalGUIDs[(tempValue & 0x7fffffff)];//same as (tempValue - 0x800000) ^__^
/*String fileGUIDName = null; -> Lets do that in the EBX TreeViewCellFactory
try{
if (Main.getGame().getEBXFileGUIDs()!=null){//DEBUG-
fileGUIDName = Main.getGame().getEBXFileGUIDs().get(guid.getFileGUID().toUpperCase());
}
}catch(NullPointerException e){
System.err.println("EBXFileGUID Database does not exist!");
}
if (fileGUIDName != null){
field.setValue(fileGUIDName+" "+guid.getInstanceGUID(), FieldValueType.ExternalGuid); //We need to convert the String later back to a FileGUID
}else{//}*/
field.setValue(guid.getFileGUID()+" "+guid.getInstanceGUID(), FieldValueType.ExternalGuid);
}else if (tempValue == 0x0){
field.setValue("*nullGUID*", FieldValueType.Guid);
}else{
internalGUIDFieldIndexs.add(fields.size()-1);//current field index ;)
//String test = FileHandler.bytesToHex(FileHandler.toBytes(tempValue, ByteOrder.BIG_ENDIAN));
//String intGuid = internalGUIDs.get(tempValue-1);
field.setValue(tempValue, FieldValueType.Guid); //set tempValue temp as value.
//the real value gets set after the instance loop.
//order seems to be different but not wrong.
}
}
}else{
System.err.println("(EBXLoader) Unknown field type: "+Integer.toHexString(fieldDesc.getType())+" File name: "+filePath);
field.setValue("*unknown field type*", FieldValueType.Unknown);
/*
try:
(typ,length)=numDict[fieldDesc.type]
num=self.unpack(typ,f.read(length))[0]
field.value=num
except:
print "Unknown field type: "+str(fieldDesc.type)+" File name: "+self.relPath
field.value="*unknown field type*"
*/
}
/*<END OF DECODE>*/
if (seeker.hasError()){
return null;
}
return field;
}
public String readString(byte[] fileArray, int offset) {
String tmp = "";
for (int i = 0; i < 1000; i++) {
byte[] b = readByte(fileArray, offset + i, 1);
if (b[0] != 0x0) {
String str;
try {
str = new String(b, "UTF-8");
tmp += str;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
i = 1500;
}
} else {
break;
}
}
return tmp;
}
public byte[] readByte(byte[] fileArray, int offset, int len) {
byte[] buffer = new byte[len];
for (int i = 0; i < len; i++) {
try{
buffer[i] = fileArray[offset + i];
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("Error, END OF FILE REACHED in EBXLoader!!");
buffer[i] = 0;
return null;
}
}
return buffer;
}
public byte[] readMagic(byte[] fileArray) {
return readByte(fileArray, 0, 4);
}
public String getFileGUID() {
return fileGUID;
}
// Constructor
public EBXLoader() {
/*
numDict.put(0xC12D, 8);
numDict.put(0xc0cd, 1);
numDict.put(0x0035, 4);
numDict.put(0xc10d, 4);
numDict.put(0xc14d, 8);
numDict.put(0xc0ad, 1);
numDict.put(0xc0fd, 4);
numDict.put(0xc0bd, 1);
numDict.put(0xc0ed, 2);
numDict.put(0xc0dd, 2);
numDict.put(0xc13d, 4);
*/
}
public ByteOrder getByteOrder() {
return order;
}
}