You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This is a binary patching and incremental downloading tool, similar to rsync or zsync. It aims to improve the large binary download processes that previously were served by robocopy (i.e. full packages produced by the build farm). The original code can be found in `//depot/usr/yuriy.odonnell/unsync`. This commit is a branch from the original location to preserve history. While the codebase is designed to be self-contained and does not depend on any engine libraries, it mostly follows the UE coding guidelines and can be built with UBT. Currently only Windows is supported, however the tool is expected to also work on Mac and Linux in the future. #rb Martin.Ridgers #preflight skip [CL 18993571 by Yuriy ODonnell in ue5-main branch]
335 lines
8.0 KiB
C++
335 lines
8.0 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "UnsyncBuffer.h"
|
|
#include "UnsyncUtil.h"
|
|
#include "UnsyncVarInt.h"
|
|
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
namespace unsync {
|
|
|
|
// Minimal partial implementation of Unreal Engine Compact Binary.
|
|
// (just barely enough to talk to Jupiter)
|
|
|
|
enum class EMiniCbFieldType : uint8
|
|
{
|
|
None = 0x00,
|
|
Null = 0x01,
|
|
Object = 0x02,
|
|
UniformObject = 0x03,
|
|
Array = 0x04,
|
|
UniformArray = 0x05,
|
|
Binary = 0x06,
|
|
String = 0x07,
|
|
IntegerPositive = 0x08,
|
|
IntegerNegative = 0x09,
|
|
Float32 = 0x0a,
|
|
Float64 = 0x0b,
|
|
BoolFalse = 0x0c,
|
|
BoolTrue = 0x0d,
|
|
ObjectAttachment = 0x0e,
|
|
BinaryAttachment = 0x0f,
|
|
Hash = 0x10,
|
|
Uuid = 0x11,
|
|
DateTime = 0x12,
|
|
TimeSpan = 0x13,
|
|
ObjectId = 0x14,
|
|
CustomById = 0x1e,
|
|
CustomByName = 0x1f,
|
|
|
|
// Flags
|
|
Reserved = 0x20,
|
|
HasFieldType = 0x40,
|
|
HasFieldName = 0x80,
|
|
};
|
|
|
|
inline bool
|
|
HasName(EMiniCbFieldType Type)
|
|
{
|
|
return (uint8(Type) & uint8(EMiniCbFieldType::HasFieldName)) != 0;
|
|
}
|
|
|
|
struct FMiniCbPayloadSize
|
|
{
|
|
uint64 DataSize = 0;
|
|
uint64 HeaderSize = 0;
|
|
};
|
|
|
|
inline FMiniCbPayloadSize
|
|
GetPayloadSize(EMiniCbFieldType Type, const uint8* Payload = nullptr)
|
|
{
|
|
switch (Type)
|
|
{
|
|
default:
|
|
UNSYNC_FATAL(L"Field type %d is not implemented", uint8(Type));
|
|
return {0, 0};
|
|
case EMiniCbFieldType::None:
|
|
return {0, 0};
|
|
case EMiniCbFieldType::Null:
|
|
return {1, 0};
|
|
case EMiniCbFieldType::Hash:
|
|
case EMiniCbFieldType::BinaryAttachment:
|
|
return {20, 0};
|
|
case EMiniCbFieldType::Object:
|
|
case EMiniCbFieldType::UniformObject:
|
|
case EMiniCbFieldType::Array:
|
|
case EMiniCbFieldType::UniformArray:
|
|
case EMiniCbFieldType::Binary:
|
|
case EMiniCbFieldType::String:
|
|
case EMiniCbFieldType::CustomByName:
|
|
case EMiniCbFieldType::CustomById:
|
|
{
|
|
uint32 SizeFieldByteCount = 0;
|
|
const uint64 PayloadSize = ReadVarUint(Payload, SizeFieldByteCount);
|
|
return {PayloadSize, SizeFieldByteCount};
|
|
}
|
|
}
|
|
}
|
|
|
|
struct FMiniCbWriter
|
|
{
|
|
static const uint64 MAX_OBJECT_HEADER_SIZE = 9 + 1; // maximum var-uint size + object type identifier
|
|
|
|
FMiniCbWriter()
|
|
{
|
|
// allocate some space for the header
|
|
Output.Resize(MAX_OBJECT_HEADER_SIZE);
|
|
memset(Output.Data(), 0, MAX_OBJECT_HEADER_SIZE);
|
|
}
|
|
|
|
void AddNull() { Output.PushBack(uint8(EMiniCbFieldType::Null)); }
|
|
|
|
void AddBinaryAttachment(FHash160 Hash, std::string_view Name = {}) // TODO: add field name
|
|
{
|
|
WriteFieldHeader(EMiniCbFieldType::BinaryAttachment, Name);
|
|
Output.Append(Hash.Data, sizeof(Hash.Data));
|
|
|
|
NumAttachments++;
|
|
}
|
|
|
|
void AddHash(FHash160 Hash, std::string_view Name = {}) // TODO: add field name
|
|
{
|
|
WriteFieldHeader(EMiniCbFieldType::Hash, Name);
|
|
Output.Append(Hash.Data, sizeof(Hash.Data));
|
|
}
|
|
|
|
void AddHashArray(std::span<FHash160> Hashes, std::string_view Name = {})
|
|
{
|
|
WriteUniformArrayHeader(EMiniCbFieldType::Hash, Hashes.size_bytes(), Hashes.size(), Name);
|
|
Output.Append((const uint8*)Hashes.data(), Hashes.size_bytes());
|
|
}
|
|
|
|
void AddBinaryAttachmentArray(std::span<FHash160> Hashes, std::string_view Name = {})
|
|
{
|
|
WriteUniformArrayHeader(EMiniCbFieldType::BinaryAttachment, Hashes.size_bytes(), Hashes.size(), Name);
|
|
Output.Append((const uint8*)Hashes.data(), Hashes.size_bytes());
|
|
}
|
|
|
|
void Finalize()
|
|
{
|
|
uint64 PayloadSize = Output.Size() - MAX_OBJECT_HEADER_SIZE;
|
|
uint32 SizeFieldByteCount = MeasureVarUint(PayloadSize);
|
|
HeaderOffset = MAX_OBJECT_HEADER_SIZE - SizeFieldByteCount - 1;
|
|
Output[HeaderOffset] = uint8(EMiniCbFieldType::Object);
|
|
uint32 WroteBytes = WriteVarUint(PayloadSize, &Output[HeaderOffset + 1]);
|
|
|
|
UNSYNC_ASSERT(WroteBytes == SizeFieldByteCount);
|
|
}
|
|
|
|
const uint8* Data() const
|
|
{
|
|
UNSYNC_ASSERT(HeaderOffset != ~0ull);
|
|
return Output.Data() + HeaderOffset;
|
|
}
|
|
const uint64 Size() const
|
|
{
|
|
UNSYNC_ASSERT(HeaderOffset != ~0ull);
|
|
return Output.Size() - HeaderOffset;
|
|
}
|
|
|
|
FBufferView GetBufferView() const { return FBufferView{Data(), Size()}; }
|
|
|
|
uint64 GetNumAttachments() const { return NumAttachments; }
|
|
|
|
private:
|
|
void WriteUint(uint64 V)
|
|
{
|
|
uint8 Buffer[16] = {};
|
|
uint32 EncodedSize = MeasureVarUint(V);
|
|
UNSYNC_ASSERT(EncodedSize <= sizeof(Buffer));
|
|
WriteVarUint(V, Buffer);
|
|
Output.Append(Buffer, EncodedSize);
|
|
}
|
|
|
|
void WriteString(std::string_view Str)
|
|
{
|
|
WriteUint(Str.length());
|
|
Output.Append((const uint8*)Str.data(), Str.length());
|
|
}
|
|
|
|
void WriteFieldHeader(EMiniCbFieldType Type, std::string_view Name = {})
|
|
{
|
|
uint8 FieldType = uint8(Type);
|
|
|
|
if (!Name.empty())
|
|
{
|
|
FieldType |= uint8(EMiniCbFieldType::HasFieldName);
|
|
}
|
|
|
|
Output.PushBack(FieldType);
|
|
|
|
if (!Name.empty())
|
|
{
|
|
WriteString(Name);
|
|
}
|
|
}
|
|
|
|
void WriteUniformArrayHeader(EMiniCbFieldType ElemType, uint64 SizeInBytes, uint64 ItemCount, std::string_view Name = {})
|
|
{
|
|
uint32 ItemCountSize = MeasureVarUint(ItemCount);
|
|
uint64 PayloadSize = SizeInBytes + ItemCountSize + 1; // full size of the field, including 1 byte for element type
|
|
|
|
WriteFieldHeader(EMiniCbFieldType::UniformArray, Name);
|
|
|
|
WriteUint(PayloadSize);
|
|
WriteUint(ItemCount);
|
|
|
|
Output.PushBack(uint8(ElemType));
|
|
}
|
|
|
|
FBuffer Output;
|
|
uint64 HeaderOffset = ~0ull;
|
|
uint64 NumAttachments = 0;
|
|
};
|
|
|
|
struct FMiniCbReader;
|
|
|
|
struct FMiniCbFieldView : FBufferView
|
|
{
|
|
EMiniCbFieldType Type = EMiniCbFieldType::None;
|
|
std::string_view Name;
|
|
|
|
uint64 UniformArrayByteCount = 0;
|
|
uint64 UniformArrayItemCount = 0;
|
|
EMiniCbFieldType UniformArrayItemType = EMiniCbFieldType::None;
|
|
|
|
FMiniCbFieldView Child();
|
|
|
|
bool IsValid() const { return Data != nullptr; }
|
|
|
|
template<typename T>
|
|
const T& GetValue() const
|
|
{
|
|
UNSYNC_ASSERT(IsValid());
|
|
// TODO: validate requested type against type field
|
|
return *reinterpret_cast<const T*>(Data);
|
|
}
|
|
|
|
template<typename T>
|
|
std::span<const T> GetUniformArray() const
|
|
{
|
|
UNSYNC_ASSERT(IsValid());
|
|
UNSYNC_ASSERT(Type == EMiniCbFieldType::UniformArray);
|
|
return std::span<const T>(reinterpret_cast<const T*>(Data), UniformArrayItemCount);
|
|
}
|
|
};
|
|
|
|
struct FMiniCbReader
|
|
{
|
|
FMiniCbReader(const uint8* InData, uint64 InSize) : Data(InData), Size(InSize), Cursor(InData) {}
|
|
FMiniCbReader(FBufferView View) : FMiniCbReader(View.Data, View.Size) {}
|
|
FMiniCbReader(const FMiniCbFieldView& InView) : FMiniCbReader(InView.Data, InView.Size) {}
|
|
|
|
FMiniCbFieldView Child()
|
|
{
|
|
FMiniCbFieldView Result = {};
|
|
|
|
if (Cursor >= (Data + Size))
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
uint8 RawId = *Cursor;
|
|
bool bHasName = (RawId & uint8(EMiniCbFieldType::HasFieldName)) != 0;
|
|
EMiniCbFieldType Id = EMiniCbFieldType(RawId & (uint8(EMiniCbFieldType::HasFieldName) - 1));
|
|
|
|
Cursor += 1;
|
|
|
|
if (bHasName)
|
|
{
|
|
uint32 NameHeaderSize = 0;
|
|
const uint64 NameSize = ReadVarUint(Cursor, NameHeaderSize);
|
|
|
|
Result.Name = std::string_view((const char*)Cursor + NameHeaderSize, NameSize);
|
|
Cursor += NameHeaderSize + NameSize;
|
|
}
|
|
|
|
Result.Type = Id;
|
|
|
|
switch (Id)
|
|
{
|
|
default:
|
|
UNSYNC_FATAL(L"Field type %d is not implemented", uint8(Id));
|
|
break;
|
|
case EMiniCbFieldType::None:
|
|
case EMiniCbFieldType::Null:
|
|
break;
|
|
case EMiniCbFieldType::Object:
|
|
case EMiniCbFieldType::BinaryAttachment:
|
|
case EMiniCbFieldType::Hash:
|
|
{
|
|
FMiniCbPayloadSize FieldSize = GetPayloadSize(Id, Cursor);
|
|
|
|
Result.Data = Cursor + FieldSize.HeaderSize;
|
|
Result.Size = FieldSize.DataSize;
|
|
|
|
Cursor += FieldSize.DataSize + FieldSize.HeaderSize;
|
|
|
|
break;
|
|
}
|
|
case EMiniCbFieldType::UniformArray:
|
|
{
|
|
uint32 VarUintSize = 0;
|
|
|
|
uint64 PayloadSize = ReadVarUint(Cursor, VarUintSize);
|
|
Cursor += VarUintSize;
|
|
|
|
Result.UniformArrayItemCount = ReadVarUint(Cursor, VarUintSize);
|
|
Cursor += VarUintSize;
|
|
|
|
Result.UniformArrayByteCount = PayloadSize - VarUintSize - 1;
|
|
|
|
Result.UniformArrayItemType = EMiniCbFieldType(*Cursor);
|
|
Cursor += 1;
|
|
|
|
Result.Data = Cursor;
|
|
Result.Size = Result.UniformArrayByteCount;
|
|
|
|
Cursor += Result.UniformArrayByteCount;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
const uint8* Data;
|
|
uint64 Size;
|
|
|
|
const uint8* Cursor = nullptr;
|
|
};
|
|
|
|
inline FMiniCbFieldView
|
|
FMiniCbFieldView::Child()
|
|
{
|
|
FMiniCbReader Reader(*this);
|
|
return Reader.Child();
|
|
}
|
|
|
|
} // namespace unsync
|