uox3/source/UOPData.cpp
2022-03-05 09:12:21 -05:00

319 lines
8.7 KiB
C++

// Copyright © 2021 Charles Kerr. All rights reserved.
// Created on: 6/1/21
#include "UOPData.hpp"
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <cstdio>
#include "zlib.h" // Not <> as we are using our local one
//+++++++++++++++++++++++++++++++++++++++++++++++++++
//
// Methods for UOPData
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++
namespace UO {
//=============================================================================
bool UOPData::checkUOPFormat(std::uint32_t format){
if (format != 0x50594D) {
return false ;
}
return true ;
}
//=============================================================================
bool UOPData::checkUOPVersion(std::uint32_t version){
if (version > 5) {
return false ;
}
return true ;
}
//=============================================================================
UOPData::UOPData() {
_mytype = invalid ;
}
//=============================================================================
std::string UOPData::buildHashString(const std::string& hashformat, std::size_t id) {
char buffer[200] ;
std::snprintf(buffer,200, hashformat.c_str(),id);
return std::string(buffer);
}
//=============================================================================
void UOPData::buildHashes(std::size_t maxhashes,const std::string& hashkey, const std::string &hashkey2) {
_chunk_id1.clear();
_chunk_id2.clear();
for (std::size_t i = 0 ; i < maxhashes ; ++i) {
auto hash = buildHashString(hashkey, i);
auto hashvalue = HashLittle2(hash);
_chunk_id1.insert_or_assign(hashvalue, static_cast<std::uint32_t>(i));
if (!hashkey2.empty()){
auto hash = buildHashString(hashkey2, i);
auto hashvalue = HashLittle2(hash);
_chunk_id2.insert_or_assign(hashvalue, static_cast<std::uint32_t>(i));
}
}
}
//=============================================================================
std::uint32_t UOPData::getChunkID(std::uint64_t hashid){
auto iter = _chunk_id1.find(hashid) ;
if (iter != _chunk_id1.end()){
return iter->second ;
}
else {
auto iter = _chunk_id2.find(hashid) ;
if (iter != _chunk_id2.end()){
return iter->second ;
}
}
throw std::out_of_range(std::string("UOP: No entry found for hash id: )" + std::to_string(hashid)));
}
//=============================================================================
UOPData::UTableEntry UOPData::readTableEntry(std::ifstream &input){
UTableEntry entry ;
input.read(reinterpret_cast<char*>(&entry.offset),8);
input.read(reinterpret_cast<char*>(&entry.header_length),4);
input.read(reinterpret_cast<char*>(&entry.compressed_length),4);
input.read(reinterpret_cast<char*>(&entry.decompressed_length),4);
input.read(reinterpret_cast<char*>(&entry.filehash),8);
input.read(reinterpret_cast<char*>(&entry.data_block_hash),4);
input.read(reinterpret_cast<char*>(&entry.compression),2);
return entry ;
}
//=============================================================================
std::vector<UOPData::UTableEntry> UOPData::gatherTableEntry(std::ifstream &input,std::uint32_t maxentry){
std::vector<UTableEntry> rvalue ;
rvalue.reserve(maxentry);
for (size_t i = 0; i<maxentry;i++){
rvalue.push_back(readTableEntry(input));
}
return rvalue ;
}
//============================================================================
bool UOPData::loadUOP(std::ifstream &input){
// Read the Header information
std::uint32_t uidata =0;
std::uint64_t ulldata = 0 ;
input.read(reinterpret_cast<char*>(&uidata), 4) ;
if (!checkUOPFormat(uidata)) {
return false ;
}
input.read(reinterpret_cast<char*>(&uidata), 4) ;
if (!checkUOPVersion(uidata)) {
return false ;
}
// Build the hashes (gumps take two hashes, since the number of padding changed)
buildHashes(_maxhashes, _hashformat,_hashformat2) ;
// hashes are built
//strip off the time format
input.read(reinterpret_cast<char*>(&uidata), 4) ;
// table address
input.read(reinterpret_cast<char*>(&ulldata), 8) ;
auto tableaddress = ulldata ;
input.seekg(tableaddress,std::ios::beg);
do {
//strip number of entries in this table
input.read(reinterpret_cast<char*>(&uidata), 4) ;
auto numentries = uidata ;
input.read(reinterpret_cast<char*>(&ulldata), 8) ;
tableaddress = ulldata ;
std::vector<unsigned char> sourcedata;
std::vector<unsigned char> destdata ;
auto entries = gatherTableEntry(input, numentries);
for (auto entry: entries) {
// For each entry, process it
// if the offset is zero, we skip it
if (entry.offset == 0){
continue ;
}
if (_mytype == multi) {
// We want to skip housing.bin
if ((entry.decompressed_length==0) || (entry.filehash == 0x126D1E99DDEDEE0A)) {
continue ;
}
}
// get the chunkid
std::uint32_t chunkid = 0 ;
try {
chunkid = getChunkID(entry.filehash) ;
}
catch(...) {
// We couldn't find the hash, so did we build enough of them?
break;
}
// we need to read in the data ;
input.seekg(entry.offset+entry.header_length,std::ios::beg);
sourcedata.clear();
sourcedata.resize(entry.compressed_length,0);
input.read(reinterpret_cast<char*>(sourcedata.data()),entry.compressed_length);
if (entry.compression == 1){
destdata.clear() ;
destdata.resize(entry.decompressed_length,0);
decompress(sourcedata, destdata) ;
sourcedata = destdata ;
}
processData(sourcedata, chunkid);
}
input.seekg(tableaddress,std::ios::beg);
}while( (tableaddress != 0) && input.good());
return true ;
}
//=============================================================================
void UOPData::processData(std::vector<unsigned char> &data, std::uint32_t chunkid){
}
//=============================================================================
bool UOPData::decompress(std::vector<unsigned char> &source, std::vector<unsigned char> &dest){
auto srcsize = static_cast<unsigned long>(source.size()) ;
auto destsize = static_cast<unsigned long>(dest.size());
auto status = uncompress2(dest.data(), &destsize, source.data(), &srcsize);
if (status != Z_OK){
return false ;
}
return true ;
}
//=============================================================================
std::uint64_t UOPData::HashLittle2(const std::string& s) {
std::uint32_t length = static_cast<std::uint32_t>(s.size()) ;
std::uint32_t a ;
std::uint32_t b ;
std::uint32_t c ;
c = 0xDEADBEEF + static_cast<std::uint32_t>(length) ;
a = c;
b = c ;
std::uint32_t k = 0 ;
std::uint32_t l = 0 ;
while (length > 12){
a += (s[k++]);
a += (s[k++] << 8);
a += (s[k++] << 16);
a += (s[k++] << 24);
b += (s[k++]);
b += (s[k++] << 8);
b += (s[k++] << 16);
b += (s[k++] << 24);
c += (s[k++]);
c += (s[k++] << 8);
c += (s[k++] << 16);
c += (s[k++] << 24);
a -= c; a ^= c << 4 | c >> 28; c += b;
b -= a; b ^= a << 6 | a >> 26; a += c;
c -= b; c ^= b << 8 | b >> 24; b += a;
a -= c; a ^= c << 16 | c >> 16; c += b;
b -= a; b ^= a << 19 | a >> 13; a += c;
c -= b; c ^= b << 4 | b >> 28; b += a;
length -= 12 ;
}
// Notice the lack of breaks! we actually want it to fall through
switch (length) {
case 12: {
l = k + 11;
c += (s[l] << 24);
}
[[fallthrough]];
case 11: {
l = k + 10;
c += (s[l] << 16);
}
[[fallthrough]];
case 10: {
l = k + 9;
c += (s[l] << 8);
}
[[fallthrough]];
case 9: {
l = k + 8;
c += (s[l]);
}
[[fallthrough]];
case 8: {
l = k + 7;
b += (s[l] << 24);
}
[[fallthrough]];
case 7: {
l = k + 6;
b += (s[l] << 16);
}
[[fallthrough]];
case 6: {
l = k + 5;
b += (s[l] << 8);
}
[[fallthrough]];
case 5: {
l = k + 4;
b += (s[l]);
}
[[fallthrough]];
case 4: {
l = k + 3;
a += (s[l] << 24);
}
[[fallthrough]];
case 3: {
l = k + 2;
a += (s[l] << 16);
}
[[fallthrough]];
case 2: {
l = k + 1;
a += (s[l] << 8);
}
[[fallthrough]];
case 1: {
a += (s[k]);
c ^= b; c -= b << 14 | b >> 18;
a ^= c; a -= c << 11 | c >> 21;
b ^= a; b -= a << 25 | a >> 7;
c ^= b; c -= b << 16 | b >> 16;
a ^= c; a -= c << 4 | c >> 28;
b ^= a; b -= a << 14 | a >> 18;
c ^= b; c -= b << 24 | b >> 8;
break;
}
default:
break;
}
return (static_cast<std::uint64_t>(b) << 32) | static_cast<std::uint64_t>(c) ;
}
//=============================================================================
std::uint32_t UOPData::HadAdler32(char* d, std::uintmax_t length ) {
std::uint32_t a = 1 ;
std::uint32_t b = 0 ;
for (std::uintmax_t i = 0 ; i < length; i++){
a = (a + (d[i] % 65521)) ;
b = (b + a) % 65521 ;
}
return (b<<16) | a ;
}
}