package tk.captainsplexx.Resource.TOC;
import java.io.File;
import java.nio.ByteOrder;
import java.util.ArrayList;
import tk.captainsplexx.Resource.FileHandler;
import tk.captainsplexx.Resource.FileSeeker;
import tk.captainsplexx.Resource.ResourceHandler.LinkBundleType;
import tk.captainsplexx.Resource.TOC.TocManager.TocEntryType;
import tk.captainsplexx.Resource.TOC.TocManager.TocFieldType;
public class TocCreator {
public static byte[] createTocFile(ConvertedTocFile cToc){
ArrayList<Byte> file = new ArrayList<>();
//Header 556Bytes == Type - Sig - Key
byte[] header = FileHandler.readFile("res/toc/header.hex");
for (byte b : header){file.add(b);}
TocEntry rootEntry = new TocEntry(TocEntryType.ORDINARY);
//Entries
if (!cToc.getTag().equals("")){
TocField tag = new TocField(cToc.getTag(), TocFieldType.GUID, "tag");
rootEntry.getFields().add(tag);
}
TocEntry bundleEntry = new TocEntry(TocEntryType.ORDINARY);
for (TocSBLink link : cToc.getBundles()){
TocEntry linkEntry = new TocEntry(TocEntryType.ORDINARY);
TocField fieldID = new TocField(link.getID(), TocFieldType.STRING, "id");
linkEntry.getFields().add(fieldID);
TocField fieldOffset = new TocField(link.getOffset(), TocFieldType.LONG, "offset");
linkEntry.getFields().add(fieldOffset);
TocField fieldSize = new TocField(link.getSize(), TocFieldType.INTEGER, "size");
linkEntry.getFields().add(fieldSize);
if (link.isBase()){
TocField base = new TocField(link.isBase(), TocFieldType.BOOL, "base");
linkEntry.getFields().add(base);
}
if (link.isDelta()){
TocField delta = new TocField(link.isDelta(), TocFieldType.BOOL, "delta");
linkEntry.getFields().add(delta);
}
TocField linkField = new TocField(linkEntry, TocFieldType.ENTRY, null);
bundleEntry.getFields().add(linkField);
}
TocField bundles = new TocField(bundleEntry, TocFieldType.LIST, "bundles");
rootEntry.getFields().add(bundles);
TocEntry chunkEntry = new TocEntry(TocEntryType.ORDINARY);
for (TocSBLink link : cToc.getChunks()){
TocEntry linkEntry = new TocEntry(TocEntryType.ORDINARY);
TocField fieldID = new TocField(link.getGuid(), TocFieldType.GUID, "id");
linkEntry.getFields().add(fieldID);
TocField fieldOffset = new TocField(link.getOffset(), TocFieldType.LONG, "offset");
linkEntry.getFields().add(fieldOffset);
TocField fieldSize = new TocField(link.getSize(), TocFieldType.INTEGER, "size");
linkEntry.getFields().add(fieldSize);
if (link.isBase()){
TocField base = new TocField(link.isBase(), TocFieldType.BOOL, "base");
linkEntry.getFields().add(base);
}
if (link.isDelta()){
TocField delta = new TocField(link.isDelta(), TocFieldType.BOOL, "delta");
linkEntry.getFields().add(delta);
}
TocField linkField = new TocField(linkEntry, TocFieldType.ENTRY, null);
chunkEntry.getFields().add(linkField);
}
TocField chunks = new TocField(chunkEntry, TocFieldType.LIST, "chunks");
rootEntry.getFields().add(chunks);
if (cToc.isCas()){
TocField cas = new TocField(cToc.isCas(), TocFieldType.BOOL, "cas");
rootEntry.getFields().add(cas);
}
if (!(cToc.getTotalSize() == -1)){
TocField totalsize = new TocField(cToc.getTotalSize(), TocFieldType.LONG, "totalSize");
rootEntry.getFields().add(totalsize);
}
if (cToc.getName()!=null){
TocField name = new TocField(cToc.getName(), TocFieldType.STRING, "name");
rootEntry.getFields().add(name);
}
if (cToc.alwaysEmitSuperBundle){
TocField emitSuperBundle = new TocField(cToc.isAlwaysEmitSuperBundle(), TocFieldType.BOOL, "alwaysEmitSuperbundle");
rootEntry.getFields().add(emitSuperBundle);
}
file.addAll(createEntry(rootEntry));
while(file.size()%16!=0){
file.add((byte) 0x0);
}
return FileHandler.toByteArray(file);
}
public static byte[] createSBpart(ConvertedSBpart part){
ArrayList<Byte> out = new ArrayList<>();
TocEntry rootEntry = new TocEntry(TocEntryType.ORDINARY);
TocField path = new TocField(part.getPath(), TocFieldType.STRING, "path");
rootEntry.getFields().add(path);
TocField magicSalt = new TocField(part.getMagicSalt(), TocFieldType.INTEGER, "magicSalt");
rootEntry.getFields().add(magicSalt);
//TEST-TOTALSIZE
long totalSize = 0;
//EBX
TocEntry ebxEntry = new TocEntry(TocEntryType.ORDINARY);
for (ResourceLink link : part.getEbx()){
TocEntry linkEntry = new TocEntry(TocEntryType.ORDINARY);
TocField name = new TocField(link.getName(), TocFieldType.STRING, "name");
linkEntry.getFields().add(name);
TocField sha1 = new TocField(link.getSha1(), TocFieldType.SHA1, "sha1");
linkEntry.getFields().add(sha1);
TocField size = new TocField(link.getSize(), TocFieldType.LONG, "size");
totalSize += link.getSize();
linkEntry.getFields().add(size);
TocField originalSize = new TocField(link.getOriginalSize(), TocFieldType.LONG, "originalSize");
linkEntry.getFields().add(originalSize);
if (link.getCasPatchType() != 0){
TocField casPatchType = new TocField(link.getCasPatchType(), TocFieldType.INTEGER, "casPatchType");
linkEntry.getFields().add(casPatchType);
}
if (link.getBaseSha1() != null){
TocField baseSha1 = new TocField(link.getBaseSha1(), TocFieldType.SHA1, "baseSha1");
linkEntry.getFields().add(baseSha1);
}
if (link.getDeltaSha1() != null){
TocField deltaSha1 = new TocField(link.getDeltaSha1(), TocFieldType.SHA1, "deltaSha1");
linkEntry.getFields().add(deltaSha1);
}
TocField linkField = new TocField(linkEntry, TocFieldType.ENTRY, null);
ebxEntry.getFields().add(linkField);
}
if (ebxEntry.getFields().size()>0){
TocField ebxs = new TocField(ebxEntry, TocFieldType.LIST, "ebx");
rootEntry.getFields().add(ebxs);
}
//DBX
TocEntry dbxEntry = new TocEntry(TocEntryType.ORDINARY);
for (ResourceLink link : part.getDbx()){
TocEntry linkEntry = new TocEntry(TocEntryType.ORDINARY);
TocField name = new TocField(link.getName(), TocFieldType.STRING, "name");
linkEntry.getFields().add(name);
TocField sha1 = new TocField(link.getSha1(), TocFieldType.SHA1, "sha1");
linkEntry.getFields().add(sha1);
TocField size = new TocField(link.getSize(), TocFieldType.LONG, "size");
totalSize += link.getSize();
linkEntry.getFields().add(size);
TocField originalSize = new TocField(link.getOriginalSize(), TocFieldType.LONG, "originalSize");
linkEntry.getFields().add(originalSize);
if (link.getCasPatchType() != 0){
TocField casPatchType = new TocField(link.getCasPatchType(), TocFieldType.INTEGER, "casPatchType");
linkEntry.getFields().add(casPatchType);
}
if (link.getBaseSha1() != null){
TocField baseSha1 = new TocField(link.getBaseSha1(), TocFieldType.SHA1, "baseSha1");
linkEntry.getFields().add(baseSha1);
}
if (link.getDeltaSha1() != null){
TocField deltaSha1 = new TocField(link.getDeltaSha1(), TocFieldType.SHA1, "deltaSha1");
linkEntry.getFields().add(deltaSha1);
}
TocField linkField = new TocField(linkEntry, TocFieldType.ENTRY, null);
dbxEntry.getFields().add(linkField);
}
if (dbxEntry.getFields().size()>0){
TocField dbxs = new TocField(dbxEntry, TocFieldType.LIST, "dbx");
rootEntry.getFields().add(dbxs);
}
//RES
TocEntry resEntry = new TocEntry(TocEntryType.ORDINARY);
for (ResourceLink link : part.getRes()){
TocEntry linkEntry = new TocEntry(TocEntryType.ORDINARY);
TocField name = new TocField(link.getName(), TocFieldType.STRING, "name");
linkEntry.getFields().add(name);
TocField sha1 = new TocField(link.getSha1(), TocFieldType.SHA1, "sha1");
linkEntry.getFields().add(sha1);
TocField size = new TocField(link.getSize(), TocFieldType.LONG, "size");
totalSize += link.getSize();
linkEntry.getFields().add(size);
TocField originalSize = new TocField(link.getOriginalSize(), TocFieldType.LONG, "originalSize");
linkEntry.getFields().add(originalSize);
//RES-SPEC
TocField resType = new TocField(link.getResType(), TocFieldType.INTEGER, "resType");
linkEntry.getFields().add(resType);
TocField resMeta = new TocField(link.getResMeta(), TocFieldType.RAW2, "resMeta");
linkEntry.getFields().add(resMeta);
TocField resRid = new TocField(link.getResRid(), TocFieldType.LONG, "resRid");
linkEntry.getFields().add(resRid);
if (link.getIdata() != null){
TocField idata = new TocField(link.getIdata(), TocFieldType.RAW2, "idata");
linkEntry.getFields().add(idata);
}
//DEFAULT
if (link.getCasPatchType() != 0){
TocField casPatchType = new TocField(link.getCasPatchType(), TocFieldType.INTEGER, "casPatchType");
linkEntry.getFields().add(casPatchType);
}
if (link.getBaseSha1() != null){
TocField baseSha1 = new TocField(link.getBaseSha1(), TocFieldType.SHA1, "baseSha1");
linkEntry.getFields().add(baseSha1);
}
if (link.getDeltaSha1() != null){
TocField deltaSha1 = new TocField(link.getDeltaSha1(), TocFieldType.SHA1, "deltaSha1");
linkEntry.getFields().add(deltaSha1);
}
TocField linkField = new TocField(linkEntry, TocFieldType.ENTRY, null);
resEntry.getFields().add(linkField);
}
if (resEntry.getFields().size()>0){
TocField ress = new TocField(resEntry, TocFieldType.LIST, "res");
rootEntry.getFields().add(ress);
}
//CHUNKS
TocEntry chunksEntry = new TocEntry(TocEntryType.ORDINARY);
for (ResourceLink link : part.getChunks()){
TocEntry linkEntry = new TocEntry(TocEntryType.ORDINARY);
TocField id = new TocField(link.getId(), TocFieldType.GUID, "id");
linkEntry.getFields().add(id);
TocField sha1 = new TocField(link.getSha1(), TocFieldType.SHA1, "sha1");
linkEntry.getFields().add(sha1);
TocField size = new TocField(link.getSize(), TocFieldType.LONG, "size");
totalSize += link.getSize();
linkEntry.getFields().add(size);
if (link.getOriginalSize()!=0){
TocField originalSize = new TocField(link.getOriginalSize(), TocFieldType.LONG, "originalSize");
linkEntry.getFields().add(originalSize);
}
if (link.getRangeStart()>=0){
TocField rangeStart = new TocField(link.getRangeStart(), TocFieldType.INTEGER, "rangeStart");
linkEntry.getFields().add(rangeStart);
}
if (link.getRangeEnd()>=0){
TocField rangeEnd = new TocField(link.getRangeEnd(), TocFieldType.INTEGER, "rangeEnd");
linkEntry.getFields().add(rangeEnd);
}
TocField logicalOffset = new TocField(link.getLogicalOffset(), TocFieldType.INTEGER, "logicalOffset");
linkEntry.getFields().add(logicalOffset);
TocField logicalSize = new TocField(link.getLogicalSize(), TocFieldType.INTEGER, "logicalSize");
linkEntry.getFields().add(logicalSize);
if (link.getCasPatchType() != 0){
TocField casPatchType = new TocField(link.getCasPatchType(), TocFieldType.INTEGER, "casPatchType");
linkEntry.getFields().add(casPatchType);
}
if (link.getBaseSha1() != null){
TocField baseSha1 = new TocField(link.getBaseSha1(), TocFieldType.SHA1, "baseSha1");
linkEntry.getFields().add(baseSha1);
}
if (link.getDeltaSha1() != null){
TocField deltaSha1 = new TocField(link.getDeltaSha1(), TocFieldType.SHA1, "deltaSha1");
linkEntry.getFields().add(deltaSha1);
}
TocField linkField = new TocField(linkEntry, TocFieldType.ENTRY, null);
chunksEntry.getFields().add(linkField);
}
if (chunksEntry.getFields().size()>0){
TocField chunks = new TocField(chunksEntry, TocFieldType.LIST, "chunks");
rootEntry.getFields().add(chunks);
}
//CHUNKMETA
TocEntry chunkMetaEntry = new TocEntry(TocEntryType.ORDINARY);
for (ResourceLink link : part.getChunkMeta()){
TocEntry linkEntry = new TocEntry(TocEntryType.ORDINARY);
//CHUNKMETA-SPEC
TocField h32 = new TocField(link.getH32(), TocFieldType.INTEGER, "h32");
linkEntry.getFields().add(h32);
TocField meta = new TocField(link.getMeta(), TocFieldType.RAW, "meta");
linkEntry.getFields().add(meta);
if (link.getFirstMip()!=-1){
TocField firstMip = new TocField(link.getFirstMip(), TocFieldType.INTEGER, "firstMip");
linkEntry.getFields().add(firstMip);
}
//DEFAULT does it even exist here ?
if (link.getCasPatchType() != 0){
TocField casPatchType = new TocField(link.getCasPatchType(), TocFieldType.INTEGER, "casPatchType");
linkEntry.getFields().add(casPatchType);
}
if (link.getBaseSha1() != null){
TocField baseSha1 = new TocField(link.getBaseSha1(), TocFieldType.SHA1, "baseSha1");
linkEntry.getFields().add(baseSha1);
}
if (link.getDeltaSha1() != null){
TocField deltaSha1 = new TocField(link.getDeltaSha1(), TocFieldType.SHA1, "deltaSha1");
linkEntry.getFields().add(deltaSha1);
}
TocField linkField = new TocField(linkEntry, TocFieldType.ENTRY, null);
chunkMetaEntry.getFields().add(linkField);
}
if (chunkMetaEntry.getFields().size()>0){
TocField chunkMetas = new TocField(chunkMetaEntry, TocFieldType.LIST, "chunkMeta");
rootEntry.getFields().add(chunkMetas);
}
//FIELDS
TocField alignMembers = new TocField(part.isAlignMembers(), TocFieldType.BOOL, "alignMembers");
rootEntry.getFields().add(alignMembers);
TocField ridSupport = new TocField(part.isRidSupport(), TocFieldType.BOOL, "ridSupport");
rootEntry.getFields().add(ridSupport);
TocField storeCompressedSizes = new TocField(part.isStoreCompressedSizes(), TocFieldType.BOOL, "storeCompressedSizes");
rootEntry.getFields().add(storeCompressedSizes);
//Battlefield does not validate this value, but may needed for "INTERNAL-MEMORY-CALCULATIONS"!
TocField totalSizeF = new TocField(totalSize, TocFieldType.LONG, "totalSize");
//TocField totalSizeF = new TocField(part.getTotalSize(), TocFieldType.LONG, "totalSize");
rootEntry.getFields().add(totalSizeF);
TocField dbxTotalSize = new TocField(part.getDbxTotalSize(), TocFieldType.LONG, "dbxTotalSize");
rootEntry.getFields().add(dbxTotalSize);
//PAYLOAD
out.addAll(createEntry(rootEntry));
while(out.size()%16!=0){
out.add((byte) 0x0);
}
return FileHandler.toByteArray(out);
}
static ArrayList<Byte> createEntry(TocEntry tocE){
ArrayList<Byte> entry = new ArrayList<>();
switch(tocE.getType()){
case ORDINARY:
entry.add((byte) 0x82);
ArrayList<Byte> entryData = new ArrayList<Byte>();
for (TocField field : tocE.getFields()){
ArrayList<Byte> data = createField(field);
entryData.addAll(data);
}
entry.addAll(FileHandler.toLEB128List((entryData.size()+1) & 0xFFFFFFFF));
for (byte b : entryData){
entry.add(b);
}
entry.add((byte) 0x00);
break;
default:
System.err.println("Unknown type of Entry found in TocCreator :( "+tocE.getType());
return null;
}
return entry;
}
static ArrayList<Byte> createField(TocField field){
if (field.getType() == null){return null;}
ArrayList<Byte> data = new ArrayList<Byte>();
//name
String name = field.getName();
ArrayList<Byte> nameBytes = new ArrayList<Byte>();
for (byte b : name.getBytes()){
nameBytes.add(b);
}
nameBytes.add((byte) 0x00); //tailing null
//type
switch(field.getType()){
case BOOL:
data.add((byte) 0x06);
data.addAll(nameBytes);
boolean v = (boolean) field.getObj();
if (v){
data.add((byte) 0x01);
}else{
data.add((byte) 0x00);
}
break;
case GUID:
data.add((byte) 0x0F);
data.addAll(nameBytes);
byte[] guid = FileHandler.hexStringToByteArray(((String) field.getObj()).toUpperCase());
for (byte b : guid){
data.add(b);
}
break;
case INTEGER:
data.add((byte) 0x08);
data.addAll(nameBytes);
byte[] intB = FileHandler.toBytes((int) field.getObj(), ByteOrder.LITTLE_ENDIAN);
for (byte b : intB){
data.add(b);
}
break;
case LIST:
data.add((byte) 0x01);
data.addAll(nameBytes);
TocEntry entries = (TocEntry) field.getObj();
ArrayList<Byte> list = new ArrayList<Byte>();
for (TocField f : entries.getFields()){
if (f.getType() == TocFieldType.ENTRY){
TocEntry fieldEntry = (TocEntry) f.getObj();
ArrayList<Byte> entry = createEntry(fieldEntry);
list.addAll(entry);
}else{
ArrayList<Byte> entry = createField(f);
list.addAll(entry);
}
}
data.addAll(FileHandler.toLEB128List((list.size()+1) & 0xFFFFFFFF));
data.addAll(list);
data.add((byte) 0x00);
break;
case LONG:
data.add((byte) 0x09);
data.addAll(nameBytes);
byte[] longB = FileHandler.toBytes((long) field.getObj(), ByteOrder.LITTLE_ENDIAN);
for (byte b : longB){
data.add(b);
}
break;
case RAW:
data.add((byte) 0x02);
data.addAll(nameBytes);
byte[] raw = (byte[]) field.getObj();
data.addAll(FileHandler.toLEB128List(raw.length));
for (byte b : raw){
data.add(b);
}
break;
case RAW2:
data.add((byte) 0x13);
data.addAll(nameBytes);
byte[] raw2 = (byte[]) field.getObj();
if (raw2 == null){
System.err.println("NULL RAW2 FOUND :/");
break;
}
data.addAll(FileHandler.toLEB128List(raw2.length));
for (byte b : raw2){
data.add(b);
}
break;
case SHA1:
data.add((byte) 0x10);
data.addAll(nameBytes);
byte[] sha1 = FileHandler.hexStringToByteArray(((String) field.getObj()).toUpperCase());
for (byte b : sha1){
data.add(b);
}
break;
case STRING:
data.add((byte) 0x07);
data.addAll(nameBytes);
String value = (String) field.getObj();
if (value == null){
System.err.println("NULL STRING FOUND :/");
break;
}
data.addAll(FileHandler.toLEB128List((value.length()+1) & 0xFFFFFFFF));
//tailing null is also inculded!
for (byte b : value.getBytes()){
data.add(b);
}
data.add((byte) 0x00);
break;
case ENTRY:
System.err.println("ONLY USED FOR RECREATION, NORMALY THIS SHOULD NOT HAPPEN :(");
break;
default:
break;
}
return data;
}
public static boolean createModifiedSBFile(ConvertedTocFile toc, ConvertedSBpart newBundlePart, boolean isNew, String destination, boolean override){
//creates modi. sb file and updates toc file for that.
byte[] header2 = new byte[]{0x53, 0x70, 0x6C, 0x65, 0x78, 0x58, 0x5F,
0x4D, 0x6F, 0x64, 0x5F, 0x46, 0x69, 0x6C,
0x65, 0x21};
System.out.println("Creating ModifiedSBFile!");
FileSeeker seeker = new FileSeeker();
destination = FileHandler.normalizePath(destination);
File destFile = new File(destination);
if (destFile.exists()){
if (!override){
System.err.println("Can't create a modified SB file. File does already exists.");
return false;
}else{
System.out.println("File already exist. Using overriding on: "+destination);
destFile.delete();
}
}
destFile = null;//Not needed anymore, freeUp memory!
if (isNew){
TocSBLink link = new TocSBLink();
link.setID(newBundlePart.getPath());
link.setType(LinkBundleType.BUNDLES);
toc.getBundles().add(link);
}
FileHandler.writeFile(destination, header2, false);
seeker.seek(header2.length);
for (TocSBLink bundle : toc.getBundles()){
long currentOffset = seeker.getOffset();
if (bundle.getID().equals(newBundlePart.getPath())){
System.out.println("Bundle replace/add: "+bundle.getID());
byte[] newBundleBytes = createSBpart(newBundlePart);
if (newBundleBytes.length == 0){
System.err.println("zero length");
}
FileHandler.writeFile(destination, newBundleBytes, true);
bundle.setOffset(currentOffset);
bundle.setSize(newBundleBytes.length);
seeker.seek(newBundleBytes.length);
bundle.setBase(false);
bundle.setDelta(true);
}else{
if (bundle.isBase() && !bundle.isDelta()){
System.out.println("Bundle link: "+bundle.getID());
}else{
System.out.println("Bundle copy: "+bundle.getID());
boolean success = FileHandler.extendFileFromFile(bundle.getSbPath()/*.replace("/Updata/Patch/", "/")*/, bundle.getOffset(), bundle.getSize(), destination, seeker);
if (!success){
System.err.println("Abort: something went wrong while creating modified sb file :( (BUNDLES)");
return false;
}else{
bundle.setOffset(currentOffset);
}
}
}
}
for (TocSBLink chunk : toc.getChunks()){
long currentOffset = seeker.getOffset();
if (chunk.isBase() && !chunk.isDelta()){
System.out.println("Chunk link: "+chunk.getID());
}else{
System.out.println("Chunk copy: "+chunk.getID());
boolean success = FileHandler.extendFileFromFile(chunk.getSbPath(), chunk.getOffset(), chunk.getSizeLong(), destination, seeker);
if (!success){
System.err.println("Abort: something went wrong while creating modified sb file :( (CHUNKS)");
return false;
}else{
chunk.setOffset(currentOffset);
}
}
}
return true;
}
}