Add basic patch installation support
Right now it can only handle stuff like updating ffxivboot. It lacks indexed patch support, a lot of zipatch commands, etc.
This commit is contained in:
parent
ffb3350ddc
commit
e9c7223048
3 changed files with 302 additions and 1 deletions
|
@ -10,7 +10,8 @@ add_library(libxiv STATIC
|
||||||
src/compression.cpp
|
src/compression.cpp
|
||||||
src/exhparser.cpp
|
src/exhparser.cpp
|
||||||
src/exdparser.cpp
|
src/exdparser.cpp
|
||||||
src/installextract.cpp)
|
src/installextract.cpp
|
||||||
|
src/patch.cpp)
|
||||||
target_include_directories(libxiv PUBLIC include PRIVATE src)
|
target_include_directories(libxiv PUBLIC include PRIVATE src)
|
||||||
target_link_libraries(libxiv PUBLIC
|
target_link_libraries(libxiv PUBLIC
|
||||||
unshield
|
unshield
|
||||||
|
|
5
include/patch.h
Normal file
5
include/patch.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
void processPatch(const std::string_view path, const std::string_view installDirectory);
|
295
src/patch.cpp
Normal file
295
src/patch.cpp
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
#include "patch.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
|
#include "utility.h"
|
||||||
|
#include "compression.h"
|
||||||
|
|
||||||
|
// based off of https://xiv.dev/data-files/zipatch and some parts of xivquicklauncher
|
||||||
|
// since the docs are incomplete
|
||||||
|
// 24 bytes long
|
||||||
|
struct FHDRChunk {
|
||||||
|
uint16_t dum;
|
||||||
|
uint8_t version; // 3?
|
||||||
|
uint8_t dum2;
|
||||||
|
char name[4];
|
||||||
|
uint8_t dummy[8]; // TODO: what are these? xiv.dev says they're unused
|
||||||
|
uint32_t depotHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct APLYChunk {
|
||||||
|
uint32_t option;
|
||||||
|
uint32_t reserved;
|
||||||
|
uint32_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ADIRChunk {
|
||||||
|
uint32_t pathLength;
|
||||||
|
std::string path;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DELDChunk {
|
||||||
|
uint32_t pathLength;
|
||||||
|
std::string path;
|
||||||
|
};
|
||||||
|
|
||||||
|
void processPatch(const std::string_view path, const std::string_view installDirectory) {
|
||||||
|
FILE* file = fopen(path.data(), "rb");
|
||||||
|
if(file == nullptr) {
|
||||||
|
throw std::runtime_error("Failed to open patch file " + std::string(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check signature
|
||||||
|
fseek(file, 12, SEEK_SET);
|
||||||
|
|
||||||
|
// TODO: UGLY CODE
|
||||||
|
while(true) {
|
||||||
|
uint32_t chunksize;
|
||||||
|
fread(&chunksize, sizeof(uint32_t), 1, file);
|
||||||
|
endianSwap(&chunksize);
|
||||||
|
|
||||||
|
char chunkname[4];
|
||||||
|
fread(chunkname, 4, 1, file);
|
||||||
|
|
||||||
|
if(strncmp(chunkname, "FHDR", 4) == 0) {
|
||||||
|
fmt::print("FHDR\n");
|
||||||
|
|
||||||
|
FHDRChunk chunk;
|
||||||
|
fread(&chunk, sizeof(FHDRChunk), 1, file);
|
||||||
|
|
||||||
|
endianSwap(&chunk.version); // these still dont work lol
|
||||||
|
endianSwap(&chunk.depotHash);
|
||||||
|
|
||||||
|
if(strncmp(chunk.name, "HIST", 4) == 0)
|
||||||
|
fmt::print("HIST patch!\n");
|
||||||
|
else if(strncmp(chunk.name, "DIFF", 4) == 0)
|
||||||
|
fmt::print("DIFF patch!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strncmp(chunkname, "APLY", 4) == 0) {
|
||||||
|
fmt::print("APLY\n");
|
||||||
|
|
||||||
|
APLYChunk chunk;
|
||||||
|
fread(&chunk, sizeof(APLYChunk), 1, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strncmp(chunkname, "ADIR", 4) == 0) {
|
||||||
|
fmt::print("ADIR\n");
|
||||||
|
|
||||||
|
ADIRChunk chunk;
|
||||||
|
fread(&chunk.pathLength, sizeof(uint32_t), 1, file);
|
||||||
|
|
||||||
|
endianSwap(&chunk.pathLength);
|
||||||
|
|
||||||
|
chunk.path.resize(chunk.pathLength);
|
||||||
|
fread(chunk.path.data(), chunk.pathLength, 1, file);
|
||||||
|
|
||||||
|
fmt::print("ADIR {}\n", chunk.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strncmp(chunkname, "DELD", 4) == 0) {
|
||||||
|
fmt::print("DELD\n");
|
||||||
|
|
||||||
|
DELDChunk chunk;
|
||||||
|
fread(&chunk.pathLength, sizeof(uint32_t), 1, file);
|
||||||
|
|
||||||
|
endianSwap(&chunk.pathLength);
|
||||||
|
|
||||||
|
chunk.path.resize(chunk.pathLength);
|
||||||
|
fread(chunk.path.data(), chunk.pathLength, 1, file);
|
||||||
|
|
||||||
|
fmt::print("DELD {}\n", chunk.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strcmp(chunkname, "SQPK") == 0) {
|
||||||
|
fmt::print("SQPK\n");
|
||||||
|
|
||||||
|
int32_t hsize;
|
||||||
|
fread(&hsize, sizeof(int32_t), 1, file);
|
||||||
|
endianSwap(&hsize);
|
||||||
|
|
||||||
|
char opcode;
|
||||||
|
fread(&opcode, sizeof(char), 1, file);
|
||||||
|
|
||||||
|
switch(opcode) {
|
||||||
|
case 'A':
|
||||||
|
fmt::print("A\n");
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
fmt::print("D\n");
|
||||||
|
break;
|
||||||
|
case 'E':
|
||||||
|
fmt::print("E\n");
|
||||||
|
break;
|
||||||
|
case 'F': {
|
||||||
|
size_t size = hsize - 5;
|
||||||
|
size_t start = ftell(file);
|
||||||
|
|
||||||
|
char opcode;
|
||||||
|
fread(&opcode, sizeof(char), 1, file);
|
||||||
|
|
||||||
|
// alignment?
|
||||||
|
fseek(file, 2, SEEK_CUR);
|
||||||
|
|
||||||
|
int64_t fileOffset;
|
||||||
|
fread(&fileOffset, sizeof(int64_t), 1, file);
|
||||||
|
endianSwap(&fileOffset);
|
||||||
|
|
||||||
|
uint64_t fileSize;
|
||||||
|
fread(&fileSize, sizeof(uint64_t), 1, file);
|
||||||
|
endianSwap(&fileSize);
|
||||||
|
|
||||||
|
uint32_t pathLength;
|
||||||
|
fread(&pathLength, sizeof(uint32_t), 1, file);
|
||||||
|
endianSwap(&pathLength);
|
||||||
|
|
||||||
|
uint32_t expansionId;
|
||||||
|
fread(&expansionId, sizeof(uint16_t), 1, file);
|
||||||
|
endianSwap(&expansionId);
|
||||||
|
|
||||||
|
// wtf is this
|
||||||
|
fseek(file, 2, SEEK_CUR);
|
||||||
|
|
||||||
|
std::string path;
|
||||||
|
path.resize(pathLength);
|
||||||
|
fread(path.data(), pathLength, 1, file);
|
||||||
|
|
||||||
|
// TODO: implement R, D, and M
|
||||||
|
switch(opcode) {
|
||||||
|
case 'A':
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> data;
|
||||||
|
|
||||||
|
size_t currentOffset = ftell(file);
|
||||||
|
while(size - currentOffset + start > 0) {
|
||||||
|
struct BlockHeader {
|
||||||
|
int32_t size;
|
||||||
|
uint32_t dummy;
|
||||||
|
int32_t compressedLength; // < 32000 is uncompressed data
|
||||||
|
int32_t decompressedLength;
|
||||||
|
} header;
|
||||||
|
|
||||||
|
fread(&header, sizeof(BlockHeader), 1, file);
|
||||||
|
|
||||||
|
std::vector<uint8_t> localdata;
|
||||||
|
|
||||||
|
bool isCompressed = header.compressedLength != 32000;
|
||||||
|
int compressedBlockLengtrh = (int)(((isCompressed ? header.compressedLength : header.decompressedLength) + 143) & 0xFFFFFF80);
|
||||||
|
if(isCompressed) {
|
||||||
|
localdata.resize(header.decompressedLength);
|
||||||
|
|
||||||
|
std::vector<uint8_t> compressed_data;
|
||||||
|
compressed_data.resize(compressedBlockLengtrh - header.size);
|
||||||
|
fread(compressed_data.data(), compressed_data.size(), 1, file);
|
||||||
|
|
||||||
|
zlib::no_header_decompress(reinterpret_cast<uint8_t*>(compressed_data.data()),
|
||||||
|
compressed_data.size(),
|
||||||
|
reinterpret_cast<uint8_t*>(localdata.data()),
|
||||||
|
localdata.size());
|
||||||
|
} else {
|
||||||
|
localdata.resize(header.decompressedLength);
|
||||||
|
|
||||||
|
fread(localdata.data(), localdata.size(), 1, file);
|
||||||
|
|
||||||
|
fseek(file, compressedBlockLengtrh - header.size - header.decompressedLength, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.insert(data.end(), localdata.begin(), localdata.end());
|
||||||
|
currentOffset = ftell(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: create directory tree
|
||||||
|
std::string newFilename = fmt::format("{}/{}", installDirectory, path);
|
||||||
|
FILE* newFile = fopen(newFilename.c_str(), "w");
|
||||||
|
fwrite(data.data(), data.size(), 1, newFile);
|
||||||
|
fclose(newFile);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fmt::print("unhandled opcode {}\n", opcode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt::print("F\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
fmt::print("H\n");
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
fmt::print("I\n");
|
||||||
|
break;
|
||||||
|
case 'X': {
|
||||||
|
size_t size = hsize - 5;
|
||||||
|
size_t start = ftell(file);
|
||||||
|
|
||||||
|
char status;
|
||||||
|
fread(&status, sizeof(char), 1, file);
|
||||||
|
|
||||||
|
char version;
|
||||||
|
fread(&version, sizeof(char), 1, file);
|
||||||
|
|
||||||
|
fseek(file, 1, SEEK_CUR);
|
||||||
|
|
||||||
|
uint64_t installSize;
|
||||||
|
fread(&installSize, sizeof(uint64_t), 1, file);
|
||||||
|
endianSwap(&installSize);
|
||||||
|
|
||||||
|
int offset = size - (ftell(file) - start);
|
||||||
|
fseek(file, offset, SEEK_CUR);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'T': {
|
||||||
|
size_t size = hsize - 5;
|
||||||
|
size_t start = ftell(file);
|
||||||
|
|
||||||
|
// reserved
|
||||||
|
fseek(file, 3, SEEK_CUR);
|
||||||
|
|
||||||
|
uint16_t platform;
|
||||||
|
fread(&platform, sizeof(uint16_t), 1, file);
|
||||||
|
endianSwap(&platform);
|
||||||
|
|
||||||
|
int16_t region;
|
||||||
|
fread(®ion, sizeof(int16_t), 1, file);
|
||||||
|
endianSwap(®ion);
|
||||||
|
|
||||||
|
int16_t isDebug;
|
||||||
|
fread(&isDebug, sizeof(int16_t), 1, file);
|
||||||
|
endianSwap(&isDebug);
|
||||||
|
|
||||||
|
uint16_t version;
|
||||||
|
fread(&version, sizeof(uint16_t), 1, file);
|
||||||
|
endianSwap(&version);
|
||||||
|
|
||||||
|
uint64_t deletedDataSize;
|
||||||
|
fread(&version, sizeof(uint64_t), 1, file);
|
||||||
|
|
||||||
|
uint64_t seekCount;
|
||||||
|
fread(&seekCount, sizeof(uint64_t), 1, file);
|
||||||
|
|
||||||
|
int offset = size - (ftell(file) - start);
|
||||||
|
fseek(file, offset, SEEK_CUR);
|
||||||
|
int newOffset = ftell(file);
|
||||||
|
newOffset = newOffset;
|
||||||
|
fseek(file, 0, SEEK_CUR);
|
||||||
|
|
||||||
|
// TODO: check if these values actually correct?
|
||||||
|
fmt::print("T {} {} {} {} {} {}\n", platform, region, isDebug, version, deletedDataSize, seekCount);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strcmp(chunkname, "EOF_") == 0) {
|
||||||
|
fmt::print("EOF\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, sizeof(uint32_t), SEEK_CUR); // crc32, used for something?
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue