First pass idLib conversion from hex rays4.

This commit is contained in:
Justin Marshall
2026-08-08 13:54:26 -07:00
parent 3d01d735ce
commit 09a4cb91c3
120 changed files with 17718 additions and 0 deletions
+206
View File
@@ -0,0 +1,206 @@
#include "analysisclient.h"
#ifdef nullptr
#undef nullptr
#endif
#ifdef snprintf
#undef snprintf
#endif
#include <algorithm>
#include <cstdio>
#include <ctime>
idCVar mq_analysisReportRate(
"mq_analysisReportRate", "1", CVAR_INTEGER,
"Control the rate at which asserts and map stats get reported.");
idCVar mq_enable(
"mq_enable", "0", CVAR_BOOL,
"Enables the analysis client");
idAnalysisClient analysisClient;
namespace {
std::string CurrentTimestamp() {
std::time_t now = std::time(0);
std::tm utcTime = {};
gmtime_s(&utcTime, &now);
char buffer[32] = {};
std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", &utcTime);
return buffer;
}
std::string EventTimestamp(const char* timestamp) {
return timestamp == 0 || timestamp[0] == 0
? CurrentTimestamp() : std::string(timestamp);
}
int NextReportTime() {
const int seconds = std::max(1, mq_analysisReportRate.GetInteger());
return Sys_Milliseconds() + seconds * 1000;
}
} // namespace
idAnalysisClient::idAnalysisClient()
: idMQClientThread(), reportTime(0), channel(nullptr), currentMap(),
assertMutex(), mapLoadMutex(), viewNoteMutex(), pendingAsserts(),
pendingMapLoads(), pendingViewNotes() {
}
idAnalysisClient::~idAnalysisClient() {
StopThread(true);
}
void idAnalysisClient::StartMessageSystem() {
if (mq_enable.GetBool()) {
StartThread("AMQP idAnalysisClient >> Server");
}
}
void idAnalysisClient::StopMessageSystem() {
StopThread(true);
}
void idAnalysisClient::SetCurrentMap(const char* mapName) {
currentMap = mapName == nullptr ? "" : mapName;
}
void idAnalysisClient::QueueAssert(const idreports::AssertReport& report,
const char* timestamp) {
idScopedCriticalSection lock(assertMutex);
pendingEvent_t<idreports::AssertReport> event;
event.timestamp = EventTimestamp(timestamp);
event.message = report;
pendingAsserts.push_back(event);
}
void idAnalysisClient::QueueMapLoad(const idreports::MapReport& report,
const char* timestamp) {
idScopedCriticalSection lock(mapLoadMutex);
pendingEvent_t<idreports::MapReport> event;
event.timestamp = EventTimestamp(timestamp);
event.message = report;
pendingMapLoads.push_back(event);
}
void idAnalysisClient::QueueViewNote(const idreports::ViewNoteReport& report,
const char* timestamp) {
idScopedCriticalSection lock(viewNoteMutex);
pendingEvent_t<idreports::ViewNoteReport> event;
event.timestamp = EventTimestamp(timestamp);
event.message = report;
pendingViewNotes.push_back(event);
}
int idAnalysisClient::NumPendingAsserts() {
idScopedCriticalSection lock(assertMutex);
return static_cast<int>(pendingAsserts.size());
}
int idAnalysisClient::NumPendingMapLoads() {
idScopedCriticalSection lock(mapLoadMutex);
return static_cast<int>(pendingMapLoads.size());
}
int idAnalysisClient::NumPendingViewNotes() {
idScopedCriticalSection lock(viewNoteMutex);
return static_cast<int>(pendingViewNotes.size());
}
void idAnalysisClient::PreRun() {
channel = connection.GetChannel();
if (channel != nullptr) {
channel->ExchangeDeclare(idStr("idtech5"), idStr("topic"), true,
false);
}
reportTime = NextReportTime();
}
void PublishEvent(idreports::LogEvent& logEvent, const char* timestamp,
const idreports::LogEvent_Severity severity, const char* message,
google::protobuf::MessageLite& outgoing, const char* messageType,
idMQChannel* channel, const char* routingKey) {
logEvent.Clear();
logEvent.set_timestamp(timestamp == nullptr ? "" : timestamp);
logEvent.set_severity(severity);
logEvent.set_message(message == nullptr ? "" : message);
logEvent.set_datatype(messageType == nullptr ? "" : messageType);
std::string outgoingData;
outgoing.SerializePartialToString(&outgoingData);
logEvent.set_data(outgoingData.data(), outgoingData.size());
std::string envelope;
logEvent.SerializePartialToString(&envelope);
if (channel != nullptr) {
channel->BasicPublish(idStr("idtech5"),
idStr(routingKey == nullptr ? "" : routingKey), false, false,
envelope.data(), static_cast<unsigned int>(envelope.size()));
}
logEvent.Clear();
}
void idAnalysisClient::ThreadSlice() {
if (reportTime >= Sys_Milliseconds()) {
Sleep(25);
return;
}
std::vector<pendingEvent_t<idreports::AssertReport> > asserts;
std::vector<pendingEvent_t<idreports::MapReport> > mapLoads;
std::vector<pendingEvent_t<idreports::ViewNoteReport> > viewNotes;
{
idScopedCriticalSection lock(assertMutex);
asserts.swap(pendingAsserts);
}
{
idScopedCriticalSection lock(mapLoadMutex);
mapLoads.swap(pendingMapLoads);
}
{
idScopedCriticalSection lock(viewNoteMutex);
viewNotes.swap(pendingViewNotes);
}
idreports::LogEvent logEvent;
for (std::size_t index = 0; index < asserts.size(); ++index) {
char message[1200];
std::snprintf(message, sizeof(message), "Hit an assert in %s",
asserts[index].message.mapname().c_str());
PublishEvent(logEvent, asserts[index].timestamp.c_str(),
idreports::LogEvent_Severity_SEV_ERROR, message,
asserts[index].message, "AssertReport", channel,
"idtech5.events.assert_report");
}
for (std::size_t index = 0; index < mapLoads.size(); ++index) {
char message[1200];
std::snprintf(message, sizeof(message), "%s loaded the map %s",
mapLoads[index].message.username().c_str(),
mapLoads[index].message.mapname().c_str());
PublishEvent(logEvent, mapLoads[index].timestamp.c_str(),
idreports::LogEvent_Severity_SEV_WARNING, message,
mapLoads[index].message, "MapReport", channel,
"idtech5.events.map_load");
}
for (std::size_t index = 0; index < viewNotes.size(); ++index) {
char message[1200];
std::snprintf(message, sizeof(message),
"%s published a viewnote for %s",
viewNotes[index].message.username().c_str(),
viewNotes[index].message.mappath().c_str());
PublishEvent(logEvent, viewNotes[index].timestamp.c_str(),
idreports::LogEvent_Severity_SEV_INFO, message,
viewNotes[index].message, "ViewNoteReport", channel,
"idtech5.events.viewnote");
}
reportTime = NextReportTime();
Sleep(250);
}
void idAnalysisClient::OnThreadTerminate() {
channel = nullptr;
idLib::Printf("idAnalysisClient::OnThreadTerminate \n");
}
+96
View File
@@ -0,0 +1,96 @@
#pragma once
#include "idlib/networking/amqp/mqmessaging.h"
#include "idlib/networking/protocols/reports.pb.h"
#include <string>
#include <vector>
struct mapLoadEvent_t {
const char* timestamp;
idreports::MapReport message;
};
struct viewNoteEvent_t {
const char* timestamp;
idreports::ViewNoteReport message;
};
struct assertEvent_t {
const char* timestmap; // Original spelling retained for recovered ABI users.
idreports::AssertReport message;
};
struct idUniqueWarning {
idStr message;
unsigned int count;
};
struct viewNoteData_t {
idStr bugTitle;
idStr taskType;
idStr reproSteps;
idStr details;
idStr severity;
idStr priority;
idStr attachmentFilename;
idStr gameName;
bool isMultiplayer;
int buildNumberMajor;
int buildNumberMinor;
idStr vt_filePath;
idStr vt_filePathOverride;
idStr mapName;
};
class idAnalysisClient : public idMQClientThread {
public:
idAnalysisClient();
~idAnalysisClient() override;
void StartMessageSystem() override;
void StopMessageSystem() override;
void PreRun() override;
void ThreadSlice() override;
void OnThreadTerminate() override;
void SetCurrentMap(const char* mapName);
void QueueAssert(const idreports::AssertReport& report,
const char* timestamp = 0);
void QueueMapLoad(const idreports::MapReport& report,
const char* timestamp = 0);
void QueueViewNote(const idreports::ViewNoteReport& report,
const char* timestamp = 0);
int NumPendingAsserts();
int NumPendingMapLoads();
int NumPendingViewNotes();
int reportTime;
idMQChannel* channel;
idStrStatic<1024> currentMap;
idSysMutex assertMutex;
idSysMutex mapLoadMutex;
idSysMutex viewNoteMutex;
private:
template<class T>
struct pendingEvent_t {
std::string timestamp;
T message;
};
std::vector<pendingEvent_t<idreports::AssertReport> > pendingAsserts;
std::vector<pendingEvent_t<idreports::MapReport> > pendingMapLoads;
std::vector<pendingEvent_t<idreports::ViewNoteReport> > pendingViewNotes;
};
extern idCVar mq_enable;
extern idCVar mq_analysisReportRate;
extern idAnalysisClient analysisClient;
void PublishEvent(idreports::LogEvent& logEvent, const char* timestamp,
idreports::LogEvent_Severity severity, const char* message,
google::protobuf::MessageLite& outgoing, const char* messageType,
idMQChannel* channel, const char* routingKey);
+7
View File
@@ -0,0 +1,7 @@
#pragma once
// The complete allocator implementation survived in Doom 3 BFG's Heap.h.
// Tungsten split the same fixed-block and dynamic-block templates into this
// path; retain that source-level include path while using the vetted PC code.
#include "idlib/precompiled.h"
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "../math/vector.h"
// Minimal tungsten ABI facade used while the BFG idBounds implementation is
// still the baseline for the larger geometry subsystem.
class idBounds {
public:
idVec3 b[2];
const idVec3& operator[](const int index) const {
return b[index];
}
idVec3& operator[](const int index) {
return b[index];
}
};
static_assert(sizeof(idBounds) == 24, "Recovered idBounds layout changed");
+43
View File
@@ -0,0 +1,43 @@
#include "bounds2d.h"
bool idBounds2D::AddPoint(const idVec2& point) {
bool expanded = false;
if (point.x < bounds[0].x) {
bounds[0].x = point.x;
expanded = true;
}
if (point.x > bounds[1].x) {
bounds[1].x = point.x;
expanded = true;
}
if (point.y < bounds[0].y) {
bounds[0].y = point.y;
expanded = true;
}
if (point.y > bounds[1].y) {
bounds[1].y = point.y;
expanded = true;
}
return expanded;
}
bool idBounds2D::ContainsPoint(const idVec2& point) const {
return point.x >= bounds[0].x
&& point.x <= bounds[1].x
&& point.y >= bounds[0].y
&& point.y <= bounds[1].y;
}
bool idBounds2D::IntersectBounds(const idBounds2D& other) const {
if (&other == this) {
return true;
}
return other.bounds[0].x <= bounds[1].x
&& other.bounds[1].x >= bounds[0].x
&& other.bounds[0].y <= bounds[1].y
&& other.bounds[1].y >= bounds[0].y;
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "../math/vector.h"
// tungsten.exe.h type 13101.
class idBounds2D {
public:
idVec2 bounds[2];
bool AddPoint(const idVec2& point);
bool ContainsPoint(const idVec2& point) const;
bool IntersectBounds(const idBounds2D& other) const;
const idVec2& operator[](const int index) const {
return bounds[index];
}
idVec2& operator[](const int index) {
return bounds[index];
}
};
static_assert(sizeof(idBounds2D) == 16, "Recovered idBounds2D layout changed");
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include "bounds.h"
#include <algorithm>
#include <cstdint>
#include <limits>
class idBoundsShort {
public:
std::int16_t b[2][3];
void SetBounds(const idBounds& bounds) {
for (int side = 0; side < 2; ++side) {
for (int axis = 0; axis < 3; ++axis) {
const int integerValue = static_cast<int>(bounds[side][axis]);
b[side][axis] = static_cast<std::int16_t>(std::max(
static_cast<int>(std::numeric_limits<std::int16_t>::min()),
std::min(
static_cast<int>(std::numeric_limits<std::int16_t>::max()),
integerValue
)
));
}
}
}
idBounds ToBounds() const {
idBounds result;
for (int side = 0; side < 2; ++side) {
for (int axis = 0; axis < 3; ++axis) {
result[side][axis] = static_cast<float>(b[side][axis]);
}
}
return result;
}
bool IntersectsBounds(const idBoundsShort& other) const {
for (int axis = 0; axis < 3; ++axis) {
if (b[0][axis] > other.b[1][axis]
|| other.b[0][axis] > b[1][axis]) {
return false;
}
}
return true;
}
};
static_assert(sizeof(idBoundsShort) == 12, "idBoundsShort ABI changed");
+19
View File
@@ -0,0 +1,19 @@
#include "frustum.h"
void idFrustum::ToPoints(idVec3 points[8]) const {
const idVec3 nearCenter = origin + axis[0] * dNear;
const idVec3 nearLeft = axis[1] * (dLeft * dNear * invFar);
const idVec3 nearUp = axis[2] * (dUp * dNear * invFar);
points[0] = nearCenter + nearLeft + nearUp;
points[1] = nearCenter - nearLeft + nearUp;
points[2] = nearCenter - nearLeft - nearUp;
points[3] = nearCenter + nearLeft - nearUp;
const idVec3 farCenter = origin + axis[0] * dFar;
const idVec3 farLeft = axis[1] * dLeft;
const idVec3 farUp = axis[2] * dUp;
points[4] = farCenter + farLeft + farUp;
points[5] = farCenter - farLeft + farUp;
points[6] = farCenter - farLeft - farUp;
points[7] = farCenter + farLeft - farUp;
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "idlib/precompiled.h"
class idFrustum {
public:
idFrustum()
: origin(vec3_origin), axis(mat3_identity), dNear(0.0f), dFar(0.0f),
dLeft(0.0f), dUp(0.0f), invFar(0.0f) {
}
idFrustum(const idVec3& newOrigin, const idMat3& newAxis,
const float nearDistance, const float farDistance,
const float farLeft, const float farUp)
: origin(newOrigin), axis(newAxis), dNear(nearDistance),
dFar(farDistance), dLeft(farLeft), dUp(farUp),
invFar(farDistance != 0.0f ? 1.0f / farDistance : 0.0f) {
}
void ToPoints(idVec3 points[8]) const;
private:
idVec3 origin;
idMat3 axis;
float dNear;
float dFar;
float dLeft;
float dUp;
float invFar;
};
static_assert(sizeof(idFrustum) == 68, "Recovered idFrustum ABI changed");
+190
View File
@@ -0,0 +1,190 @@
#include "color.h"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
const idColor idColor::colorBlack(0.00f, 0.00f, 0.00f, 1.00f);
const idColor idColor::colorWhite(1.00f, 1.00f, 1.00f, 1.00f);
const idColor idColor::colorRed(1.00f, 0.00f, 0.00f, 1.00f);
const idColor idColor::colorGreen(0.00f, 1.00f, 0.00f, 1.00f);
const idColor idColor::colorBlue(0.00f, 0.00f, 1.00f, 1.00f);
const idColor idColor::colorYellow(1.00f, 1.00f, 0.00f, 1.00f);
const idColor idColor::colorMagenta(1.00f, 0.00f, 1.00f, 1.00f);
const idColor idColor::colorCyan(0.00f, 1.00f, 1.00f, 1.00f);
const idColor idColor::colorOrange(1.00f, 0.50f, 0.00f, 1.00f);
const idColor idColor::colorPurple(0.60f, 0.00f, 0.60f, 1.00f);
const idColor idColor::colorPink(0.73f, 0.40f, 0.48f, 1.00f);
const idColor idColor::colorBrown(0.40f, 0.35f, 0.08f, 1.00f);
const idColor idColor::colorLtGrey(0.75f, 0.75f, 0.75f, 1.00f);
const idColor idColor::colorMdGrey(0.50f, 0.50f, 0.50f, 1.00f);
const idColor idColor::colorDkGrey(0.25f, 0.25f, 0.25f, 1.00f);
const idColor idColor::colorDefault = idColor::colorWhite;
// These axis assignments look unusual but are explicit in the recovered
// dynamic initializers: X=blue, Y=red, Z=green.
const idColor idColor::colorXAxis = idColor::colorBlue;
const idColor idColor::colorYAxis = idColor::colorRed;
const idColor idColor::colorZAxis = idColor::colorGreen;
namespace {
constexpr int kStringBufferSize = 4096;
std::uint8_t FloatToByte(const float value) {
const int integer = static_cast<int>(value * 255.0f);
return static_cast<std::uint8_t>(std::max(0, std::min(255, integer)));
}
void SkipWhitespace(const char*& cursor) {
while (*cursor != '\0'
&& std::isspace(static_cast<unsigned char>(*cursor)) != 0) {
++cursor;
}
}
bool ParseFloat(const char*& cursor, float& value) {
SkipWhitespace(cursor);
char* end = nullptr;
value = std::strtof(cursor, &end);
if (end == cursor) {
return false;
}
cursor = end;
return true;
}
void AppendFloat(
char* destination,
const int destinationSize,
int& length,
const float value,
const int precision,
const bool addLeadingSpace
) {
char number[64];
const int safePrecision = std::max(0, std::min(9, precision));
std::snprintf(number, sizeof(number), "%.*f", safePrecision, value);
if (safePrecision > 0) {
int numberLength = static_cast<int>(std::strlen(number));
while (numberLength > 0 && number[numberLength - 1] == '0') {
number[--numberLength] = '\0';
}
if (numberLength > 0 && number[numberLength - 1] == '.') {
number[--numberLength] = '\0';
}
}
const int written = std::snprintf(
destination + length,
static_cast<std::size_t>(destinationSize - length),
addLeadingSpace ? " %s" : "%s",
number
);
if (written > 0) {
length = std::min(destinationSize - 1, length + written);
}
}
} // namespace
const char* idColor::ToString(const int precision, const bool parens) const {
static thread_local int bufferIndex = 0;
static thread_local char buffers[4][kStringBufferSize];
char values[kStringBufferSize];
values[0] = '\0';
int length = 0;
AppendFloat(values, kStringBufferSize, length, r, precision, false);
AppendFloat(values, kStringBufferSize, length, g, precision, true);
AppendFloat(values, kStringBufferSize, length, b, precision, true);
AppendFloat(values, kStringBufferSize, length, a, precision, true);
char* const result = buffers[bufferIndex];
bufferIndex = (bufferIndex + 1) & 3;
if (parens) {
std::snprintf(result, kStringBufferSize, "( %s )", values);
} else {
std::snprintf(result, kStringBufferSize, "%s", values);
}
return result;
}
void idColor::Lerp(
const idColor& from,
const idColor& to,
const float lerp
) {
if (lerp <= 0.0f) {
*this = from;
} else if (lerp >= 1.0f) {
*this = to;
} else {
r = from.r + (to.r - from.r) * lerp;
g = from.g + (to.g - from.g) * lerp;
b = from.b + (to.b - from.b) * lerp;
a = from.a + (to.a - from.a) * lerp;
}
}
std::uint32_t idColor::PackColor(const idVec4& color) {
const std::uint32_t red = FloatToByte(color.x);
const std::uint32_t green = FloatToByte(color.y);
const std::uint32_t blue = FloatToByte(color.z);
const std::uint32_t alpha = FloatToByte(color.w);
return red | (green << 8) | (blue << 16) | (alpha << 24);
}
void idColor::UnpackColor(
const std::uint32_t color,
idVec4& unpackedColor
) {
constexpr float kByteToFloat = 1.0f / 255.0f;
unpackedColor.Set(
static_cast<float>((color >> 0) & 255) * kByteToFloat,
static_cast<float>((color >> 8) & 255) * kByteToFloat,
static_cast<float>((color >> 16) & 255) * kByteToFloat,
static_cast<float>((color >> 24) & 255) * kByteToFloat
);
}
bool idColor::SetFromString(const char* str, const bool parseParens) {
if (str == nullptr) {
return false;
}
const char* cursor = str;
SkipWhitespace(cursor);
if (parseParens) {
if (*cursor != '(') {
return false;
}
++cursor;
}
idColor parsed;
if (!ParseFloat(cursor, parsed.r)
|| !ParseFloat(cursor, parsed.g)
|| !ParseFloat(cursor, parsed.b)
|| !ParseFloat(cursor, parsed.a)) {
return false;
}
SkipWhitespace(cursor);
if (parseParens) {
if (*cursor != ')') {
return false;
}
++cursor;
SkipWhitespace(cursor);
}
if (*cursor != '\0') {
return false;
}
*this = parsed;
return true;
}
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include "math/vector.h"
#include <cstdint>
// tungsten.exe.h type 12194.
class idColor {
public:
float r;
float g;
float b;
float a;
idColor() = default;
idColor(
const float red,
const float green,
const float blue,
const float alpha = 1.0f
)
: r(red)
, g(green)
, b(blue)
, a(alpha) {
}
const char* ToString(int precision, bool parens) const;
void Lerp(const idColor& from, const idColor& to, float lerp);
static std::uint32_t PackColor(const idVec4& color);
static void UnpackColor(std::uint32_t color, idVec4& unpackedColor);
bool SetFromString(const char* str, bool parseParens);
static const idColor colorBlack;
static const idColor colorWhite;
static const idColor colorRed;
static const idColor colorGreen;
static const idColor colorBlue;
static const idColor colorYellow;
static const idColor colorMagenta;
static const idColor colorCyan;
static const idColor colorOrange;
static const idColor colorPurple;
static const idColor colorPink;
static const idColor colorBrown;
static const idColor colorLtGrey;
static const idColor colorMdGrey;
static const idColor colorDkGrey;
static const idColor colorDefault;
static const idColor colorXAxis;
static const idColor colorYAxis;
static const idColor colorZAxis;
};
static_assert(sizeof(idColor) == 16, "Recovered idColor layout changed");
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#include <cstdint>
#include <cstdlib>
template<typename type>
class idAutoPtr {
public:
explicit idAutoPtr(type* pointer = nullptr)
: Pointee(pointer) {
}
~idAutoPtr() {
delete Pointee;
}
idAutoPtr(idAutoPtr&& other) noexcept
: Pointee(other.Release()) {
}
idAutoPtr& operator=(idAutoPtr&& other) noexcept {
if (this != &other) {
Reset(other.Release());
}
return *this;
}
idAutoPtr(const idAutoPtr&) = delete;
idAutoPtr& operator=(const idAutoPtr&) = delete;
type* Get() const { return Pointee; }
type* operator->() const { return Pointee; }
type& operator*() const { return *Pointee; }
explicit operator bool() const { return Pointee != nullptr; }
type* Release() {
type* const result = Pointee;
Pointee = nullptr;
return result;
}
void Reset(type* pointer = nullptr) {
if (Pointee != pointer) {
delete Pointee;
Pointee = pointer;
}
}
private:
type* Pointee;
};
template<typename type>
class idAutoPtr_Array {
public:
explicit idAutoPtr_Array(type* pointer = nullptr)
: Pointee(pointer) {
}
virtual ~idAutoPtr_Array() {
std::free(Pointee);
}
idAutoPtr_Array(const idAutoPtr_Array&) = delete;
idAutoPtr_Array& operator=(const idAutoPtr_Array&) = delete;
type* Get() const { return Pointee; }
type& operator[](const int index) const { return Pointee[index]; }
type* Release() {
type* const result = Pointee;
Pointee = nullptr;
return result;
}
private:
type* Pointee;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idAutoPtr<int>) == 4, "Recovered idAutoPtr ABI changed");
static_assert(sizeof(idAutoPtr_Array<int>) == 8,
"Recovered idAutoPtr_Array ABI changed");
#endif
@@ -0,0 +1,5 @@
#include "binaryheap.h"
// The recovered BinaryHeap.cpp contains only the testBinaryHeap console
// command. The reusable implementation was inline in BinaryHeap.h; its PC
// regression coverage lives in source/tests/idlib_containers_test.cpp.
+188
View File
@@ -0,0 +1,188 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <cstring>
template<typename nodeType, typename priorityType, priorityType LOWEST_VALUE>
class idBinaryHeap {
public:
struct idHeapNode {
nodeType node;
priorityType priority;
};
explicit idBinaryHeap(const int initialSize_ = 16)
: nodes(nullptr), curSize(0), initialSize(std::max(1, initialSize_)),
numNodes(0), ordered(true), externalBuffer(false) {
Allocate(initialSize);
}
idBinaryHeap(idHeapNode* const buffer, const int bufferSize)
: nodes(buffer), curSize(std::max(0, bufferSize - 1)),
initialSize(std::max(0, bufferSize - 1)), numNodes(0),
ordered(true), externalBuffer(true) {
if (nodes != nullptr && bufferSize > 0) {
nodes[0].priority = LOWEST_VALUE;
}
}
~idBinaryHeap() {
if (!externalBuffer) {
std::free(nodes);
}
}
idBinaryHeap(const idBinaryHeap&) = delete;
idBinaryHeap& operator=(const idBinaryHeap&) = delete;
bool Insert(const nodeType& node, const priorityType& priority) {
if (!ordered) {
return InsertUnsorted(node, priority);
}
if (!EnsureCapacity()) {
return false;
}
int hole = ++numNodes;
while (hole > 1 && priority < nodes[hole / 2].priority) {
nodes[hole] = nodes[hole / 2];
hole /= 2;
}
nodes[hole].node = node;
nodes[hole].priority = priority;
return true;
}
bool InsertUnsorted(const nodeType& node, const priorityType& priority) {
if (!EnsureCapacity()) {
return false;
}
++numNodes;
nodes[numNodes].node = node;
nodes[numNodes].priority = priority;
if (numNodes > 1 && priority < nodes[numNodes / 2].priority) {
ordered = false;
}
return true;
}
nodeType GetMin() {
SortHeap();
return numNodes == 0 ? nodeType() : nodes[1].node;
}
priorityType GetMinPriority() {
SortHeap();
return numNodes == 0 ? LOWEST_VALUE : nodes[1].priority;
}
nodeType RemoveMin() {
SortHeap();
if (numNodes == 0) {
return nodeType();
}
const nodeType result = nodes[1].node;
nodes[1] = nodes[numNodes--];
if (numNodes > 0) {
PercolateDown(1);
}
return result;
}
void SortHeap() {
if (ordered || numNodes < 2) {
ordered = true;
return;
}
for (int index = numNodes / 2; index > 0; --index) {
PercolateDown(index);
}
ordered = true;
}
void MakeEmpty() {
if (!externalBuffer && curSize != initialSize) {
std::free(nodes);
nodes = nullptr;
curSize = 0;
Allocate(initialSize);
}
numNodes = 0;
ordered = true;
if (nodes != nullptr) {
nodes[0].priority = LOWEST_VALUE;
}
}
int Num() const { return numNodes; }
bool IsEmpty() const { return numNodes == 0; }
private:
idHeapNode* nodes;
int curSize;
int initialSize;
int numNodes;
bool ordered;
bool externalBuffer;
bool Allocate(const int size) {
idHeapNode* const storage = static_cast<idHeapNode*>(
std::malloc(sizeof(idHeapNode) * static_cast<std::size_t>(size + 1))
);
if (storage == nullptr) {
return false;
}
nodes = storage;
curSize = size;
nodes[0].priority = LOWEST_VALUE;
return true;
}
bool Resize(const int newSize) {
if (externalBuffer || newSize <= curSize) {
return false;
}
idHeapNode* const replacement = static_cast<idHeapNode*>(
std::malloc(sizeof(idHeapNode) * static_cast<std::size_t>(newSize + 1))
);
if (replacement == nullptr) {
return false;
}
std::memcpy(replacement, nodes,
sizeof(idHeapNode) * static_cast<std::size_t>(numNodes + 1));
std::free(nodes);
nodes = replacement;
curSize = newSize;
return true;
}
bool EnsureCapacity() {
if (numNodes < curSize) {
return true;
}
return Resize(std::max(1, curSize * 2));
}
void PercolateDown(int hole) {
const idHeapNode value = nodes[hole];
while (hole * 2 <= numNodes) {
int child = hole * 2;
if (child != numNodes
&& nodes[child + 1].priority < nodes[child].priority) {
++child;
}
if (!(nodes[child].priority < value.priority)) {
break;
}
nodes[hole] = nodes[child];
hole = child;
}
nodes[hole] = value;
}
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idBinaryHeap<int, int, (-2147483647 - 1)>) == 20,
"Recovered idBinaryHeap ABI changed");
#endif
+102
View File
@@ -0,0 +1,102 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#include <cstring>
// Recovered from shared/idlib/containers/BitArray.h. The field order is
// intentionally kept identical to the 32-bit tungsten type.
class idBitArray {
public:
explicit idBitArray(const std::int16_t tag = 0)
: buffer(nullptr), bits(0), memTag(tag), ownsBuffer(false) {
}
idBitArray(unsigned char* storage, const unsigned int numBits,
const std::int16_t tag = 0)
: buffer(storage), bits(numBits), memTag(tag), ownsBuffer(false) {
Clear();
}
~idBitArray() {
Free();
}
idBitArray(const idBitArray&) = delete;
idBitArray& operator=(const idBitArray&) = delete;
bool Alloc(const unsigned int numBits) {
Free();
bits = numBits;
ownsBuffer = true;
const std::size_t bytes = ByteCount();
if (bytes == 0) {
return true;
}
buffer = static_cast<unsigned char*>(std::calloc(bytes, 1));
if (buffer == nullptr) {
bits = 0;
ownsBuffer = false;
return false;
}
return true;
}
void Clear() {
if (buffer != nullptr) {
std::memset(buffer, 0, ByteCount());
}
}
void Set(const unsigned int bitNum) {
if (bitNum < bits && buffer != nullptr) {
buffer[bitNum >> 3] |= static_cast<unsigned char>(1u << (bitNum & 7));
}
}
void Clear(const unsigned int bitNum) {
if (bitNum < bits && buffer != nullptr) {
buffer[bitNum >> 3] &= static_cast<unsigned char>(~(1u << (bitNum & 7)));
}
}
void Set(const unsigned int bitNum, const bool value) {
if (value) {
Set(bitNum);
} else {
Clear(bitNum);
}
}
bool Get(const unsigned int bitNum) const {
return bitNum < bits && buffer != nullptr
&& (buffer[bitNum >> 3] & (1u << (bitNum & 7))) != 0;
}
unsigned int Num() const {
return bits;
}
private:
unsigned char* buffer;
unsigned int bits;
std::int16_t memTag;
bool ownsBuffer;
std::size_t ByteCount() const {
return static_cast<std::size_t>((bits + 7u) >> 3);
}
void Free() {
if (buffer != nullptr && ownsBuffer) {
std::free(buffer);
}
buffer = nullptr;
bits = 0;
ownsBuffer = false;
}
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idBitArray) == 12, "Recovered idBitArray ABI changed");
#endif
+3
View File
@@ -0,0 +1,3 @@
// Tungsten's containers/BTree.cpp contains only the testBinaryTree console
// command. The reusable idBTree implementation is header-only and is supplied
// by the compiled Doom 3 BFG idlib/containers/BTree.h baseline.
+4
View File
@@ -0,0 +1,4 @@
// Tungsten's containers/List.cpp contains diagnostic console commands for the
// header-only idList and idArray implementations. The PC runtime templates are
// supplied by the compiled Doom 3 BFG idlib/containers/List.h baseline; the
// recovery suite exercises container behavior without registering engine CVars.
@@ -0,0 +1,85 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <new>
template<class T>
class idRecoveredList {
public:
explicit idRecoveredList(const int initialGranularity = 16)
: list(nullptr), num(0), size(0),
granularity(static_cast<std::int16_t>(initialGranularity)),
memTag(0), listStatic(0) {
}
~idRecoveredList() {
Clear(true);
}
idRecoveredList(const idRecoveredList&) = delete;
idRecoveredList& operator=(const idRecoveredList&) = delete;
bool Reserve(const int capacity) {
if (capacity <= size) return true;
const int step = granularity > 0 ? granularity : 16;
const int newSize = ((capacity + step - 1) / step) * step;
T* const replacement = static_cast<T*>(
std::malloc(sizeof(T) * static_cast<std::size_t>(newSize)));
if (replacement == nullptr) return false;
if (list != nullptr && num > 0) {
std::memcpy(replacement, list,
sizeof(T) * static_cast<std::size_t>(num));
}
std::free(list);
list = replacement;
size = newSize;
return true;
}
T* Alloc() {
if (!Reserve(num + 1)) return nullptr;
T* const result = list + num++;
std::memset(result, 0, sizeof(T));
return result;
}
bool Append(const T& value) {
T* const destination = Alloc();
if (destination == nullptr) return false;
*destination = value;
return true;
}
void Clear(const bool freeMemory = false) {
num = 0;
if (freeMemory) {
std::free(list);
list = nullptr;
size = 0;
}
}
int Num() const { return num; }
int Capacity() const { return size; }
T* Ptr() { return list; }
const T* Ptr() const { return list; }
T& operator[](const int index) { return list[index]; }
const T& operator[](const int index) const { return list[index]; }
private:
T* list;
int num;
int size;
std::int16_t granularity;
std::uint8_t memTag;
std::uint8_t listStatic;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idRecoveredList<int>) == 16,
"Recovered list ABI changed");
#endif
+99
View File
@@ -0,0 +1,99 @@
#pragma once
template<typename type>
class idSearch {
public:
virtual ~idSearch() = default;
virtual int Search(const type* base, unsigned int num,
const type& value) const = 0;
virtual int Search_FirstGreater(const type* base, int num,
const type& value) const = 0;
virtual int Search_FirstGreaterEqual(const type* base, int num,
const type& value) const = 0;
virtual int Search_LastLess(const type* base, int num,
const type& value) const = 0;
virtual int Search_LastLessEqual(const type* base, int num,
const type& value) const = 0;
};
template<typename type, typename derived>
class idSearch_Binary : public idSearch<type> {
public:
int Search(const type* base, const unsigned int num,
const type& value) const override {
if (base == nullptr || num == 0) {
return -1;
}
const int index = Search_LastLessEqual(base, static_cast<int>(num), value);
return Compare(base[index], value) == 0 ? index : -1;
}
int Search_FirstGreater(const type* base, const int num,
const type& value) const override {
int first = 0;
int count = num < 0 ? 0 : num;
while (count > 0) {
const int step = count / 2;
const int middle = first + step;
if (Compare(base[middle], value) <= 0) {
first = middle + 1;
count -= step + 1;
} else {
count = step;
}
}
return first;
}
int Search_FirstGreaterEqual(const type* base, const int num,
const type& value) const override {
int first = 0;
int count = num < 0 ? 0 : num;
while (count > 0) {
const int step = count / 2;
const int middle = first + step;
if (Compare(base[middle], value) < 0) {
first = middle + 1;
count -= step + 1;
} else {
count = step;
}
}
return first;
}
int Search_LastLess(const type* base, const int num,
const type& value) const override {
const int result = Search_FirstGreaterEqual(base, num, value) - 1;
return result < 0 ? 0 : result;
}
int Search_LastLessEqual(const type* base, const int num,
const type& value) const override {
const int result = Search_FirstGreater(base, num, value) - 1;
return result < 0 ? 0 : result;
}
private:
int Compare(const type& left, const type& right) const {
return static_cast<const derived*>(this)->Compare(left, right);
}
};
template<typename type>
class idSearch_BinaryDefault
: public idSearch_Binary<type, idSearch_BinaryDefault<type>> {
public:
int Compare(const type& left, const type& right) const {
if (left < right) {
return -1;
}
if (right < left) {
return 1;
}
return 0;
}
};
template<typename type>
using idSearch_DefaultCompare = idSearch_BinaryDefault<type>;
@@ -0,0 +1,101 @@
#include "autocomplete.h"
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <new>
namespace {
int ComparePrefixInsensitive(const char* left, const char* right,
const int count) {
const unsigned char* safeLeft = reinterpret_cast<const unsigned char*>(
left == nullptr ? "" : left
);
const unsigned char* safeRight = reinterpret_cast<const unsigned char*>(
right == nullptr ? "" : right
);
for (int index = 0; index < count; ++index) {
const int leftChar = std::tolower(safeLeft[index]);
const int rightChar = std::tolower(safeRight[index]);
if (leftChar != rightChar || safeLeft[index] == '\0'
|| safeRight[index] == '\0') {
return leftChar - rightChar;
}
}
return 0;
}
}
idAutoComplete::~idAutoComplete() {
ClearSuggestions();
}
void idAutoComplete::ClearSuggestions() {
if (suggestions.list != nullptr) {
for (int index = 0; index < suggestions.size; ++index) {
suggestions.list[index].~idStr();
}
std::free(suggestions.list);
}
suggestions.list = nullptr;
suggestions.num = 0;
suggestions.size = 0;
}
void idAutoComplete::AppendSuggestion(const idStr& suggestion) {
if (suggestions.num == suggestions.size) {
const int newSize = suggestions.size + suggestions.granularity;
idStr* const replacement = static_cast<idStr*>(
std::malloc(sizeof(idStr) * static_cast<std::size_t>(newSize))
);
if (replacement == nullptr) {
return;
}
for (int index = 0; index < newSize; ++index) {
new (&replacement[index]) idStr();
}
for (int index = 0; index < suggestions.num; ++index) {
replacement[index] = suggestions.list[index];
}
if (suggestions.list != nullptr) {
for (int index = 0; index < suggestions.size; ++index) {
suggestions.list[index].~idStr();
}
std::free(suggestions.list);
}
suggestions.list = replacement;
suggestions.size = newSize;
}
suggestions.list[suggestions.num++] = suggestion;
}
void idAutoComplete::Append(
const idStr& suggestion,
const int completingArg
) {
const int argumentIndex = completingArg < 0
? args.Argc() - 1
: completingArg;
const char* const partial = args.Argv(argumentIndex);
const int partialLength = static_cast<int>(std::strlen(partial));
if (partialLength > 0
&& ComparePrefixInsensitive(suggestion.c_str(), partial, partialLength) != 0) {
return;
}
if (args.Argc() == 1 && argumentIndex == 0) {
AppendSuggestion(suggestion);
return;
}
idStr completed;
for (int index = 0; index < argumentIndex; ++index) {
completed.Append(args.Argv(index));
completed.Append(' ');
}
completed.Append(suggestion);
AppendSuggestion(completed);
}
@@ -0,0 +1,69 @@
#pragma once
#include "../text/cmdargs.h"
#include "../text/str.h"
#include <cstdint>
struct idAutoCompleteStringList {
idStr* list;
int num;
int size;
std::int16_t granularity;
std::uint8_t memTag;
std::uint8_t listStatic;
};
static_assert(
sizeof(idAutoCompleteStringList) == 16,
"Recovered idList<idStr, 5> layout changed"
);
// tungsten.exe.h type 12218. idCmdArgs and idList retain their recovered
// Win32 layouts through the active BFG-compatible core container layer.
class idAutoComplete {
public:
idAutoComplete()
: matchLength(0)
, currentIndex(-1)
, suggestions{nullptr, 0, 0, 16, 5, 0} {
}
~idAutoComplete();
void Append(const idStr& suggestion, int completingArg = -1);
const char* GetArg(const int index) const {
return args.Argv(index);
}
void Clear() {
matchLength = 0;
currentIndex = -1;
args.Clear();
ClearSuggestions();
}
void SetArgs(const idCmdArgs& newArgs) {
args = newArgs;
}
int GetNumSuggestions() const {
return suggestions.num;
}
const idStr& GetSuggestion(const int index) const {
return suggestions.list[index];
}
private:
int matchLength;
int currentIndex;
idCmdArgs args;
idAutoCompleteStringList suggestions;
void AppendSuggestion(const idStr& suggestion);
void ClearSuggestions();
};
static_assert(sizeof(idAutoComplete) == 2332, "idAutoComplete ABI changed");
@@ -0,0 +1,244 @@
#include "file_metrics.h"
#include "idlib/sys/sys_networking.h"
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
namespace {
idTCP metricsTCP;
unsigned char* sendQueue = nullptr;
int sendQueueSize = 0;
int sendIndex = 0;
int pendingIndex = 0;
char metricsServer[256] = "127.0.0.1";
unsigned short metricsPort = 8012;
int initialRetryTime = 1000;
int maximumRetryTime = 60000;
int retryTime = 0;
std::uint64_t timeoutWait = 0;
std::mutex metricsMutex;
std::uint64_t Milliseconds() {
using namespace std::chrono;
return static_cast<std::uint64_t>(duration_cast<milliseconds>(
steady_clock::now().time_since_epoch()).count());
}
void PutUInt32LE(unsigned char* output, const unsigned int value) {
output[0] = static_cast<unsigned char>(value);
output[1] = static_cast<unsigned char>(value >> 8);
output[2] = static_cast<unsigned char>(value >> 16);
output[3] = static_cast<unsigned char>(value >> 24);
}
int StringBytes(const char* text) {
return static_cast<int>(std::strlen(text == nullptr ? "" : text)) + 1;
}
} // namespace
idFile_Metrics::idFile_Metrics(const char* streamName)
: uniqID(0), name(streamName == nullptr ? "" : streamName), bytesSent(0) {
}
idFile_Metrics::~idFile_Metrics() {
CloseMetricsStream(name.c_str());
}
const char* idFile_Metrics::GetFullPath() const {
const netadr_t address = metricsTCP.GetAddress();
if (metricsTCP.IsOpen()) {
fullpath.Format("metrics://%u.%u.%u.%u:%u/%s",
static_cast<unsigned int>(address.ip[0]),
static_cast<unsigned int>(address.ip[1]),
static_cast<unsigned int>(address.ip[2]),
static_cast<unsigned int>(address.ip[3]),
static_cast<unsigned int>(address.port), name.c_str());
} else {
fullpath.Format("metrics://%s:%u/%s", metricsServer,
static_cast<unsigned int>(metricsPort), name.c_str());
}
return fullpath.c_str();
}
int idFile_Metrics::Read(void*, int) {
return 0;
}
int idFile_Metrics::Write(const void* buffer, const int len) {
const int written = WriteInternal(name.c_str(), buffer, len);
bytesSent += written;
return written;
}
int idFile_Metrics::Seek(long, fsOrigin_t) {
return -1;
}
void idFile_Metrics::Flush() {
std::lock_guard<std::mutex> lock(metricsMutex);
bool queueTraffic = false;
FlushBufferedWrites(queueTraffic);
}
void idFile_Metrics::ForceFlush() {
Flush();
}
idFile_Metrics* idFile_Metrics::OpenMetricsStream(const char* streamName) {
return new idFile_Metrics(streamName);
}
void idFile_Metrics::CloseMetricsStream(const char* streamName) {
std::lock_guard<std::mutex> lock(metricsMutex);
if (!metricsTCP.IsOpen()) return;
bool queueTraffic = false;
FlushBufferedWrites(queueTraffic);
static const char control[] = "CONTROL";
static const char close[] = "CLOSE";
const char* const safeName = streamName == nullptr ? "" : streamName;
const int payloadSize = static_cast<int>(sizeof(control) + sizeof(close))
+ StringBytes(safeName);
unsigned char prefix[4] = {};
PutUInt32LE(prefix, static_cast<unsigned int>(payloadSize));
BufferedWriteInternal(queueTraffic, prefix, sizeof(prefix));
BufferedWriteInternal(queueTraffic, control, sizeof(control));
BufferedWriteInternal(queueTraffic, close, sizeof(close));
BufferedWriteInternal(queueTraffic, safeName, StringBytes(safeName));
if (!queueTraffic) FlushBufferedWrites(queueTraffic);
}
void idFile_Metrics::ConfigureServer(const char* host, const unsigned short port,
const int initialRetryMilliseconds,
const int maximumRetryMilliseconds) {
std::lock_guard<std::mutex> lock(metricsMutex);
metricsTCP.Close();
const char* const safeHost = host == nullptr || *host == '\0'
? "127.0.0.1" : host;
std::strncpy(metricsServer, safeHost, sizeof(metricsServer) - 1);
metricsServer[sizeof(metricsServer) - 1] = '\0';
metricsPort = port;
initialRetryTime = std::max(1, initialRetryMilliseconds);
maximumRetryTime = std::max(initialRetryTime, maximumRetryMilliseconds);
retryTime = 0;
timeoutWait = 0;
sendIndex = pendingIndex = 0;
}
void idFile_Metrics::ShutdownTransport() {
std::lock_guard<std::mutex> lock(metricsMutex);
metricsTCP.Close();
std::free(sendQueue);
sendQueue = nullptr;
sendQueueSize = sendIndex = pendingIndex = 0;
retryTime = 0;
timeoutWait = 0;
}
bool idFile_Metrics::EnsureConnection() {
if (metricsTCP.IsOpen()) return true;
if (Milliseconds() < timeoutWait || metricsPort == 0) return false;
// A blocking connect gives the PC port a definite connected state. All
// subsequent writes retain the non-blocking short-write queue semantics.
if (metricsTCP.Connect(metricsServer, metricsPort, false, true, true)) {
retryTime = 0;
timeoutWait = 0;
return true;
}
retryTime = retryTime == 0 ? initialRetryTime
: std::min(maximumRetryTime, retryTime * 2);
timeoutWait = Milliseconds() + static_cast<std::uint64_t>(retryTime);
return false;
}
void idFile_Metrics::WriteToQueue(const void* buffer, const int len) {
if (buffer == nullptr || len <= 0) return;
const int retained = pendingIndex - sendIndex;
const int required = retained + len;
if (required > sendQueueSize) {
int newSize = std::max(1024, sendQueueSize);
while (newSize < required) newSize *= 2;
unsigned char* const replacement = static_cast<unsigned char*>(
std::malloc(static_cast<std::size_t>(newSize)));
if (replacement == nullptr) return;
if (retained > 0) {
std::memcpy(replacement, sendQueue + sendIndex,
static_cast<std::size_t>(retained));
}
std::free(sendQueue);
sendQueue = replacement;
sendQueueSize = newSize;
sendIndex = 0;
pendingIndex = retained;
} else if (sendIndex > 0 && pendingIndex + len > sendQueueSize) {
if (retained > 0) {
std::memmove(sendQueue, sendQueue + sendIndex,
static_cast<std::size_t>(retained));
}
sendIndex = 0;
pendingIndex = retained;
}
std::memcpy(sendQueue + pendingIndex, buffer, static_cast<std::size_t>(len));
pendingIndex += len;
}
void idFile_Metrics::BufferedWriteInternal(bool& queueTraffic,
const void* buffer, const int len) {
if (buffer == nullptr || len <= 0) return;
if (queueTraffic) {
WriteToQueue(buffer, len);
return;
}
const int written = metricsTCP.Write(buffer, len);
const int validWritten = std::max(0, written);
if (written != len) {
queueTraffic = true;
WriteToQueue(static_cast<const unsigned char*>(buffer) + validWritten,
len - validWritten);
}
}
void idFile_Metrics::FlushBufferedWrites(bool& queueTraffic) {
if (!metricsTCP.IsOpen() || sendIndex >= pendingIndex) {
if (sendIndex >= pendingIndex) sendIndex = pendingIndex = 0;
return;
}
const int available = pendingIndex - sendIndex;
const int written = metricsTCP.Write(sendQueue + sendIndex, available);
if (written > 0) sendIndex += written;
if (written < available) queueTraffic = true;
if (sendIndex >= pendingIndex) sendIndex = pendingIndex = 0;
}
void idFile_Metrics::WriteFrame(bool& queueTraffic, const char* streamName,
const void* buffer, const int len) {
const char* const safeName = streamName == nullptr ? "" : streamName;
const int nameBytes = StringBytes(safeName);
unsigned char prefix[4] = {};
PutUInt32LE(prefix, static_cast<unsigned int>(nameBytes + len));
BufferedWriteInternal(queueTraffic, prefix, sizeof(prefix));
BufferedWriteInternal(queueTraffic, safeName, nameBytes);
BufferedWriteInternal(queueTraffic, buffer, len);
}
int idFile_Metrics::WriteInternal(const char* streamName,
const void* buffer, const int len) {
if (buffer == nullptr || len <= 0) return 0;
std::lock_guard<std::mutex> lock(metricsMutex);
if (!EnsureConnection()) return 0;
bool queueTraffic = false;
FlushBufferedWrites(queueTraffic);
WriteFrame(queueTraffic, streamName, buffer, len);
return len;
}
@@ -0,0 +1,60 @@
#pragma once
// The recovery target still uses Doom 3 BFG's portable idFile implementation
// as its base file layer. idFile_Metrics retains the tungsten data layout and
// supplies the additional network-backed stream behavior recovered from the
// Xbox 360 executable.
#include "idlib/precompiled.h"
// BFG emulates the C++11 keyword for its original compiler. Recovery sources
// use the real C++14 keyword and the standard library.
#ifdef nullptr
#undef nullptr
#endif
class idFile_Metrics : public idFile {
public:
explicit idFile_Metrics(const char* streamName);
~idFile_Metrics() override;
const char* GetName() const override { return name.c_str(); }
const char* GetFullPath() const override;
int Read(void* buffer, int len) override;
int Write(const void* buffer, int len) override;
int Length() const override { return bytesSent; }
int Tell() const override { return bytesSent; }
int Seek(long offset, fsOrigin_t origin) override;
void Flush() override;
void ForceFlush() override;
static idFile_Metrics* OpenMetricsStream(const char* streamName);
static void CloseMetricsStream(const char* streamName);
// PC recovery controls. The original values came from metrics_* CVars.
static void ConfigureServer(const char* host, unsigned short port,
int initialRetryMilliseconds = 1000,
int maximumRetryMilliseconds = 60000);
static void ShutdownTransport();
private:
static int WriteInternal(const char* streamName, const void* buffer, int len);
static void WriteToQueue(const void* buffer, int len);
static void BufferedWriteInternal(bool& queueTraffic,
const void* buffer, int len);
static void FlushBufferedWrites(bool& queueTraffic);
static bool EnsureConnection();
static void WriteFrame(bool& queueTraffic, const char* streamName,
const void* buffer, int len);
// idTech 5 added uniqID to idFile; BFG's portable base predates it. Keep
// the field in the same derived-object position to preserve tungsten ABI.
unsigned int uniqID;
idStr name;
mutable idStr fullpath;
int bytesSent;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idFile_Metrics) == 76,
"Recovered idFile_Metrics ABI changed");
#endif
+279
View File
@@ -0,0 +1,279 @@
#include "file_mtp.h"
#include "idlib/sys/sys_networking.h"
#include <algorithm>
#include <cstring>
#include <mutex>
#include <vector>
namespace {
idTCP mtpTCP;
char mtpServer[256] = "10.4.20.180";
unsigned short mtpPort = 2769;
int mtpWriteSize = 1400;
int mtpTimeout = 1000;
std::mutex mtpMutex;
void PutUInt16BE(unsigned char* output, const unsigned int value) {
output[0] = static_cast<unsigned char>(value >> 8);
output[1] = static_cast<unsigned char>(value);
}
void PutUInt32BE(unsigned char* output, const unsigned int value) {
output[0] = static_cast<unsigned char>(value >> 24);
output[1] = static_cast<unsigned char>(value >> 16);
output[2] = static_cast<unsigned char>(value >> 8);
output[3] = static_cast<unsigned char>(value);
}
void PutUInt64BE(unsigned char* output, const std::uint64_t value) {
PutUInt32BE(output, static_cast<unsigned int>(value >> 32));
PutUInt32BE(output + 4, static_cast<unsigned int>(value));
}
unsigned int GetUInt32BE(const unsigned char* input) {
return (static_cast<unsigned int>(input[0]) << 24)
| (static_cast<unsigned int>(input[1]) << 16)
| (static_cast<unsigned int>(input[2]) << 8)
| static_cast<unsigned int>(input[3]);
}
std::uint64_t GetUInt64BE(const unsigned char* input) {
return (static_cast<std::uint64_t>(GetUInt32BE(input)) << 32)
| GetUInt32BE(input + 4);
}
} // namespace
idFile_MTP::idFile_MTP()
: uniqID(0), position(0), mode(MTP_FS_READ), length(0),
timestamp(static_cast<unsigned int>(-1)) {
}
idFile_MTP::~idFile_MTP() = default;
void idFile_MTP::ConfigureServer(const char* host, const unsigned short port,
const int writeSize, const int timeoutMilliseconds) {
std::lock_guard<std::mutex> lock(mtpMutex);
mtpTCP.Close();
const char* const safeHost = host == nullptr || *host == '\0'
? "127.0.0.1" : host;
std::strncpy(mtpServer, safeHost, sizeof(mtpServer) - 1);
mtpServer[sizeof(mtpServer) - 1] = '\0';
mtpPort = port;
mtpWriteSize = std::max(1, writeSize);
mtpTimeout = std::max(1, timeoutMilliseconds);
}
void idFile_MTP::ShutdownTransport() {
std::lock_guard<std::mutex> lock(mtpMutex);
mtpTCP.Close();
}
bool idFile_MTP::EnsureConnection() {
return mtpTCP.IsOpen()
|| mtpTCP.Connect(mtpServer, mtpPort, false, true, false);
}
bool idFile_MTP::SendRequest(const std::uint64_t offset,
const unsigned int requestLength, const operation_t operation,
const char* filename) {
if (!EnsureConnection()) return false;
const char* const safeName = filename == nullptr ? "" : filename;
const std::size_t filenameLength = std::min<std::size_t>(
std::strlen(safeName), 0xFFFFu);
unsigned char request[16] = {};
PutUInt64BE(request, offset);
PutUInt32BE(request + 8, requestLength);
PutUInt16BE(request + 12, static_cast<unsigned int>(filenameLength));
PutUInt16BE(request + 14, operation);
if (mtpTCP.WriteBlocking(request, sizeof(request), mtpTimeout)
!= sizeof(request)) {
mtpTCP.Close();
return false;
}
if (filenameLength > 0 && mtpTCP.WriteBlocking(safeName,
static_cast<int>(filenameLength), mtpTimeout)
!= static_cast<int>(filenameLength)) {
mtpTCP.Close();
return false;
}
return true;
}
bool idFile_MTP::Open(const char* filename, const mtpFileMode_t openMode) {
const char* const safeName = filename == nullptr ? "" : filename;
name = safeName;
fullPath.Format("MTP:%s", safeName);
mode = openMode;
position = 0;
std::lock_guard<std::mutex> lock(mtpMutex);
if (!SendRequest(0, static_cast<unsigned int>(openMode), OP_OPEN,
name.c_str())) return false;
unsigned char stats[16] = {};
if (mtpTCP.ReadBlocking(stats, sizeof(stats), mtpTimeout)
!= sizeof(stats)) {
mtpTCP.Close();
return false;
}
length = GetUInt64BE(stats);
timestamp = GetUInt32BE(stats + 8);
if (timestamp == static_cast<unsigned int>(-1)) return false;
if (mode == MTP_FS_APPEND) position = length;
return true;
}
int idFile_MTP::Read(void* buffer, const int len) {
return ReadOfs(static_cast<std::int64_t>(position), buffer, len);
}
int idFile_MTP::ReadOfs(const std::int64_t offset, void* buffer, const int len) {
if (buffer == nullptr || len <= 0 || offset < 0
|| (mode != MTP_FS_READ && mode != MTP_FS_READ_WRITE
&& mode != MTP_FS_READ_NO_BUFFERING)) return 0;
std::lock_guard<std::mutex> lock(mtpMutex);
for (int attempt = 0; attempt < 5; ++attempt) {
if (!SendRequest(static_cast<std::uint64_t>(offset),
static_cast<unsigned int>(len), OP_READ, name.c_str())) {
mtpTCP.Close();
continue;
}
int total = 0;
bool failed = false;
while (total < len) {
unsigned char sizeBytes[4] = {};
if (mtpTCP.ReadBlocking(sizeBytes, sizeof(sizeBytes), mtpTimeout)
!= sizeof(sizeBytes)) {
failed = true;
break;
}
const unsigned int chunk = GetUInt32BE(sizeBytes);
if (chunk == 0) break;
if (chunk > static_cast<unsigned int>(len - total)
|| mtpTCP.ReadBlocking(static_cast<unsigned char*>(buffer)
+ total, static_cast<int>(chunk), mtpTimeout)
!= static_cast<int>(chunk)) {
failed = true;
break;
}
total += static_cast<int>(chunk);
}
if (!failed) {
position = static_cast<std::uint64_t>(offset) + total;
return total;
}
mtpTCP.Close();
}
return 0;
}
int idFile_MTP::Write(const void* buffer, const int len) {
return WriteOfs(static_cast<std::int64_t>(position), buffer, len);
}
int idFile_MTP::WriteOfs(const std::int64_t offset, const void* buffer,
const int len) {
if (buffer == nullptr || len <= 0 || offset < 0
|| (mode != MTP_FS_WRITE && mode != MTP_FS_READ_WRITE
&& mode != MTP_FS_APPEND)) return 0;
std::lock_guard<std::mutex> lock(mtpMutex);
int total = 0;
while (total < len) {
const int chunk = std::min(mtpWriteSize, len - total);
if (!SendRequest(static_cast<std::uint64_t>(offset) + total,
static_cast<unsigned int>(chunk), OP_WRITE, name.c_str())
|| mtpTCP.WriteBlocking(static_cast<const unsigned char*>(buffer)
+ total, chunk, mtpTimeout) != chunk) {
mtpTCP.Close();
break;
}
unsigned char countBytes[4] = {};
if (mtpTCP.ReadBlocking(countBytes, sizeof(countBytes), mtpTimeout)
!= sizeof(countBytes)) {
mtpTCP.Close();
break;
}
const int accepted = static_cast<int>(GetUInt32BE(countBytes));
if (accepted <= 0 || accepted > chunk) {
mtpTCP.Close();
break;
}
total += accepted;
if (accepted != chunk) break;
}
position = static_cast<std::uint64_t>(offset) + total;
length = std::max(length, position);
return total;
}
void idFile_MTP::SetLength(const unsigned int len) {
std::lock_guard<std::mutex> lock(mtpMutex);
if (SendRequest(0, len, OP_SET_LENGTH, name.c_str())) {
length = len;
if (position > length) position = length;
}
}
int idFile_MTP::Length() const {
return length > 0x7FFFFFFFu ? 0x7FFFFFFF : static_cast<int>(length);
}
int idFile_MTP::Tell() const {
return position > 0x7FFFFFFFu ? 0x7FFFFFFF : static_cast<int>(position);
}
int idFile_MTP::Seek(const long offset, const fsOrigin_t origin) {
std::int64_t target = 0;
if (origin == FS_SEEK_CUR) {
target = static_cast<std::int64_t>(position) + offset;
} else if (origin == FS_SEEK_END) {
target = static_cast<std::int64_t>(length) + offset;
} else if (origin == FS_SEEK_SET) {
target = offset;
} else {
return -1;
}
if (target < 0) return -1;
position = static_cast<std::uint64_t>(target);
return 0;
}
bool idFile_MTP::List(const char* directory, const char* extension,
idList<idStr>& list) {
const char* const safeDirectory = directory == nullptr ? "" : directory;
const char* const safeExtension = extension == nullptr ? "" : extension;
fullPath.Format("%s\\*%s", safeDirectory, safeExtension);
name = fullPath;
std::lock_guard<std::mutex> lock(mtpMutex);
if (!SendRequest(0, 0, OP_LIST, name.c_str())) return false;
unsigned char lengthBytes[4] = {};
if (mtpTCP.ReadBlocking(lengthBytes, sizeof(lengthBytes), mtpTimeout)
!= sizeof(lengthBytes)) {
mtpTCP.Close();
return false;
}
const unsigned int byteCount = GetUInt32BE(lengthBytes);
if (byteCount > 16u * 1024u * 1024u) return false;
std::vector<char> entries(byteCount);
if (byteCount > 0 && mtpTCP.ReadBlocking(entries.data(),
static_cast<int>(byteCount), mtpTimeout)
!= static_cast<int>(byteCount)) {
mtpTCP.Close();
return false;
}
unsigned int cursor = 0;
while (cursor < byteCount) {
const char* const entry = entries.data() + cursor;
const std::size_t remaining = byteCount - cursor;
const std::size_t entryLength = strnlen(entry, remaining);
if (entryLength == remaining) return false;
list.Append(idStr(entry));
cursor += static_cast<unsigned int>(entryLength + 1);
}
return true;
}
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include "idlib/precompiled.h"
#ifdef nullptr
#undef nullptr
#endif
#include <cstdint>
enum mtpFileMode_t {
MTP_FS_READ = 0,
MTP_FS_WRITE = 1,
MTP_FS_READ_WRITE = 2,
MTP_FS_READ_NO_BUFFERING = 3,
MTP_FS_APPEND = 4
};
class idFile_MTP : public idFile {
public:
idFile_MTP();
~idFile_MTP() override;
const char* GetName() const override { return name.c_str(); }
const char* GetFullPath() const override { return fullPath.c_str(); }
int Read(void* buffer, int len) override;
int Write(const void* buffer, int len) override;
int Length() const override;
ID_TIME_T Timestamp() const override { return timestamp; }
int Tell() const override;
int Seek(long offset, fsOrigin_t origin) override;
bool Open(const char* filename, mtpFileMode_t openMode);
int ReadOfs(std::int64_t offset, void* buffer, int len);
int WriteOfs(std::int64_t offset, const void* buffer, int len);
void SetLength(unsigned int len);
std::uint64_t Length64() const { return length; }
std::uint64_t Tell64() const { return position; }
bool List(const char* directory, const char* extension,
idList<idStr>& list);
static void ConfigureServer(const char* host,
unsigned short port = 2769, int writeSize = 1400,
int timeoutMilliseconds = 1000);
static void ShutdownTransport();
private:
enum operation_t : unsigned short {
OP_READ = 0,
OP_LIST = 1,
OP_WRITE = 2,
OP_SET_LENGTH = 3,
OP_OPEN = 4
};
static bool EnsureConnection();
static bool SendRequest(std::uint64_t offset, unsigned int requestLength,
operation_t operation, const char* filename);
// BFG's idFile predates the recovered idTech 5 uniqID field.
unsigned int uniqID;
idStr name;
idStr fullPath;
std::uint64_t position;
mtpFileMode_t mode;
std::uint64_t length;
unsigned int timestamp;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idFile_MTP) == 104,
"Recovered idFile_MTP ABI changed");
#endif
+413
View File
@@ -0,0 +1,413 @@
#include "file_nfs.h"
#include <Windows.h>
#include <algorithm>
#include <cctype>
#include <climits>
#include <cstring>
#include <mutex>
#include <string>
#include <vector>
namespace {
struct MountEntry {
std::string alias;
std::wstring root;
bool readOnly;
};
struct FileState {
HANDLE handle = INVALID_HANDLE_VALUE;
std::mutex mutex;
std::wstring nativePath;
~FileState() {
if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle);
}
};
std::mutex mountsMutex;
std::vector<MountEntry> mounts;
std::string Lower(std::string text) {
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return text;
}
std::wstring Wide(const char* text) {
if (text == nullptr || *text == 0) return std::wstring();
int amount = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
text, -1, nullptr, 0);
UINT codePage = CP_UTF8;
DWORD flags = MB_ERR_INVALID_CHARS;
if (amount == 0) {
codePage = CP_ACP;
flags = 0;
amount = MultiByteToWideChar(codePage, flags, text, -1, nullptr, 0);
}
if (amount <= 0) return std::wstring();
std::wstring result(static_cast<std::size_t>(amount), L'\0');
MultiByteToWideChar(codePage, flags, text, -1, &result[0], amount);
result.resize(static_cast<std::size_t>(amount - 1));
return result;
}
std::wstring FullPath(const std::wstring& path) {
const DWORD required = GetFullPathNameW(path.c_str(), 0, nullptr, nullptr);
if (required == 0) return std::wstring();
std::wstring result(static_cast<std::size_t>(required), L'\0');
const DWORD amount = GetFullPathNameW(path.c_str(), required,
&result[0], nullptr);
if (amount == 0 || amount >= required) return std::wstring();
result.resize(amount);
while (result.size() > 3
&& (result.back() == L'\\' || result.back() == L'/')) {
result.pop_back();
}
return result;
}
bool PathStartsWith(const std::wstring& path, const std::wstring& root) {
if (path.size() < root.size()) return false;
if (_wcsnicmp(path.c_str(), root.c_str(), root.size()) != 0) return false;
return path.size() == root.size() || path[root.size()] == L'\\'
|| path[root.size()] == L'/';
}
bool Resolve(const char* nfsPath, std::wstring& nativePath,
bool* readOnly = nullptr, std::string* aliasResult = nullptr) {
std::string path = nfsPath == nullptr ? "" : nfsPath;
if (path.size() >= 4 && Lower(path.substr(0, 4)) == "nfs:") {
path.erase(0, 4);
}
std::replace(path.begin(), path.end(), '\\', '/');
while (!path.empty() && path.front() == '/') path.erase(path.begin());
const std::size_t slash = path.find('/');
const std::string alias = Lower(path.substr(0, slash));
const std::string relative = slash == std::string::npos
? std::string() : path.substr(slash + 1);
if (alias.empty()) return false;
std::lock_guard<std::mutex> lock(mountsMutex);
for (const MountEntry& mount : mounts) {
if (mount.alias != alias) continue;
std::wstring candidate = mount.root;
if (!relative.empty()) {
candidate.push_back(L'\\');
std::wstring tail = Wide(relative.c_str());
std::replace(tail.begin(), tail.end(), L'/', L'\\');
candidate += tail;
}
candidate = FullPath(candidate);
if (candidate.empty() || !PathStartsWith(candidate, mount.root)) {
return false;
}
nativePath = candidate;
if (readOnly != nullptr) *readOnly = mount.readOnly;
if (aliasResult != nullptr) *aliasResult = alias;
return true;
}
return false;
}
bool CreateDirectories(const std::wstring& path, const bool includeLast) {
std::wstring target = path;
if (!includeLast) {
const std::size_t slash = target.find_last_of(L"\\/");
if (slash == std::wstring::npos) return true;
target.resize(slash);
}
const std::size_t rootEnd = target.size() >= 3 && target[1] == L':' ? 3 : 0;
for (std::size_t index = rootEnd; index <= target.size(); ++index) {
if (index != target.size() && target[index] != L'\\'
&& target[index] != L'/') continue;
const std::wstring part = target.substr(0, index);
if (part.empty()) continue;
if (!CreateDirectoryW(part.c_str(), nullptr)) {
const DWORD error = GetLastError();
if (error != ERROR_ALREADY_EXISTS) return false;
}
}
return true;
}
bool RefreshMetadata(FileState& state, std::uint64_t& size,
unsigned int& timestamp) {
LARGE_INTEGER length{};
FILETIME writeTime{};
if (!GetFileSizeEx(state.handle, &length)
|| !GetFileTime(state.handle, nullptr, nullptr, &writeTime)) return false;
size = static_cast<std::uint64_t>(length.QuadPart);
ULARGE_INTEGER ticks{};
ticks.LowPart = writeTime.dwLowDateTime;
ticks.HighPart = writeTime.dwHighDateTime;
constexpr std::uint64_t epoch = 116444736000000000ULL;
timestamp = ticks.QuadPart > epoch
? static_cast<unsigned int>((ticks.QuadPart - epoch) / 10000000ULL) : 0;
return true;
}
bool HasExtension(const std::wstring& name, const char* extension) {
if (extension == nullptr || *extension == 0
|| std::strcmp(extension, "*") == 0) return true;
const std::wstring suffix = Wide(extension);
return name.size() >= suffix.size()
&& _wcsicmp(name.c_str() + name.size() - suffix.size(),
suffix.c_str()) == 0;
}
} // namespace
idFile_Nfs::idFile_Nfs()
: uniqID(0), openRemote(false), openPadding{}, fh{},
fullPath("invalid"), mode(NFS_FS_READ), position(0), size(0),
timeStamp(0), demandSeek(false), seekPadding{}, nfsClient(nullptr),
ro(false) {}
idFile_Nfs::~idFile_Nfs() {
delete static_cast<FileState*>(nfsClient);
nfsClient = nullptr;
openRemote = false;
}
bool idFile_Nfs::Mount(const char* alias, const char* windowsRoot,
const bool readOnly) {
std::string normalized = Lower(alias == nullptr ? "" : alias);
while (!normalized.empty() && normalized.front() == '/') {
normalized.erase(normalized.begin());
}
while (!normalized.empty() && normalized.back() == '/') normalized.pop_back();
if (normalized.empty() || normalized.find('/') != std::string::npos
|| normalized.find('\\') != std::string::npos) return false;
const std::wstring root = FullPath(Wide(windowsRoot));
if (root.empty()) return false;
const DWORD attributes = GetFileAttributesW(root.c_str());
if (attributes == INVALID_FILE_ATTRIBUTES
|| (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) return false;
std::lock_guard<std::mutex> lock(mountsMutex);
for (MountEntry& mount : mounts) {
if (mount.alias == normalized) {
mount.root = root;
mount.readOnly = readOnly;
return true;
}
}
mounts.push_back({normalized, root, readOnly});
return true;
}
void idFile_Nfs::UnmountAll() {
std::lock_guard<std::mutex> lock(mountsMutex);
mounts.clear();
}
bool idFile_Nfs::Open(const char* path, const nfsFileMode_t openMode,
bool create, const bool createPath) {
delete static_cast<FileState*>(nfsClient);
nfsClient = nullptr;
openRemote = false;
std::wstring native;
bool mountReadOnly = false;
if (!Resolve(path, native, &mountReadOnly)) return false;
const bool wantsWrite = openMode == NFS_FS_WRITE
|| openMode == NFS_FS_READ_WRITE || openMode == NFS_FS_APPEND;
if (mountReadOnly && wantsWrite) return false;
if (openMode == NFS_FS_WRITE || openMode == NFS_FS_APPEND) create = true;
if (createPath && create && !CreateDirectories(native, false)) return false;
DWORD access = GENERIC_READ;
DWORD disposition = OPEN_EXISTING;
if (openMode == NFS_FS_WRITE) {
access = GENERIC_WRITE | GENERIC_READ;
disposition = CREATE_ALWAYS;
} else if (openMode == NFS_FS_READ_WRITE) {
access = GENERIC_WRITE | GENERIC_READ;
disposition = create ? OPEN_ALWAYS : OPEN_EXISTING;
} else if (openMode == NFS_FS_APPEND) {
access = GENERIC_WRITE | GENERIC_READ;
disposition = OPEN_ALWAYS;
}
FileState* const state = new FileState;
state->nativePath = native;
state->handle = CreateFileW(native.c_str(), access,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
if (state->handle == INVALID_HANDLE_VALUE) {
delete state;
return false;
}
nfsClient = state;
openRemote = true;
ro = mountReadOnly;
mode = openMode;
fullPath = path == nullptr ? "" : path;
if (!RefreshMetadata(*state, size, timeStamp)) {
delete state;
nfsClient = nullptr;
openRemote = false;
return false;
}
position = openMode == NFS_FS_APPEND ? size : 0;
return true;
}
int idFile_Nfs::ReadOfs(const std::int64_t offset, void* buffer,
const int len) {
FileState* const state = static_cast<FileState*>(nfsClient);
if (state == nullptr || buffer == nullptr || len <= 0 || offset < 0) return 0;
std::lock_guard<std::mutex> lock(state->mutex);
LARGE_INTEGER saved{}, target{};
target.QuadPart = offset;
if (!SetFilePointerEx(state->handle, {}, &saved, FILE_CURRENT)
|| !SetFilePointerEx(state->handle, target, nullptr, FILE_BEGIN)) return 0;
DWORD amount = 0;
const BOOL success = ReadFile(state->handle, buffer,
static_cast<DWORD>(len), &amount, nullptr);
SetFilePointerEx(state->handle, saved, nullptr, FILE_BEGIN);
return success ? static_cast<int>(amount) : 0;
}
int idFile_Nfs::WriteOfs(const std::int64_t offset, const void* buffer,
const int len) {
FileState* const state = static_cast<FileState*>(nfsClient);
if (state == nullptr || ro || buffer == nullptr || len <= 0 || offset < 0) return 0;
std::lock_guard<std::mutex> lock(state->mutex);
LARGE_INTEGER saved{}, target{};
target.QuadPart = offset;
if (!SetFilePointerEx(state->handle, {}, &saved, FILE_CURRENT)
|| !SetFilePointerEx(state->handle, target, nullptr, FILE_BEGIN)) return 0;
DWORD amount = 0;
const BOOL success = WriteFile(state->handle, buffer,
static_cast<DWORD>(len), &amount, nullptr);
SetFilePointerEx(state->handle, saved, nullptr, FILE_BEGIN);
if (success) {
size = std::max(size, static_cast<std::uint64_t>(offset) + amount);
RefreshMetadata(*state, size, timeStamp);
}
return success ? static_cast<int>(amount) : 0;
}
int idFile_Nfs::Read(void* buffer, const int len) {
const int amount = ReadOfs(static_cast<std::int64_t>(position), buffer, len);
position += static_cast<std::uint64_t>(amount);
return amount;
}
int idFile_Nfs::Write(const void* buffer, const int len) {
if (mode == NFS_FS_APPEND) position = size;
const int amount = WriteOfs(static_cast<std::int64_t>(position), buffer, len);
position += static_cast<std::uint64_t>(amount);
return amount;
}
int idFile_Nfs::Length() const {
return size > static_cast<std::uint64_t>(INT_MAX) ? INT_MAX
: static_cast<int>(size);
}
int idFile_Nfs::Tell() const {
return position > static_cast<std::uint64_t>(INT_MAX) ? INT_MAX
: static_cast<int>(position);
}
int idFile_Nfs::Seek(const long offset, const fsOrigin_t origin) {
return Seek64(offset, origin);
}
int idFile_Nfs::Seek64(const std::int64_t offset, const fsOrigin_t origin) {
std::int64_t base = 0;
if (origin == FS_SEEK_CUR) base = static_cast<std::int64_t>(position);
else if (origin == FS_SEEK_END) base = static_cast<std::int64_t>(size);
const std::int64_t next = base + offset;
if (next < 0) return -1;
position = static_cast<std::uint64_t>(next);
demandSeek = true;
return 0;
}
void idFile_Nfs::SetLength(const unsigned int len) { SetLength64(len); }
bool idFile_Nfs::SetLength64(const std::uint64_t len) {
FileState* const state = static_cast<FileState*>(nfsClient);
if (state == nullptr || ro || len > static_cast<std::uint64_t>(INT64_MAX)) return false;
std::lock_guard<std::mutex> lock(state->mutex);
LARGE_INTEGER saved{}, target{};
target.QuadPart = static_cast<LONGLONG>(len);
if (!SetFilePointerEx(state->handle, {}, &saved, FILE_CURRENT)
|| !SetFilePointerEx(state->handle, target, nullptr, FILE_BEGIN)
|| !SetEndOfFile(state->handle)) return false;
SetFilePointerEx(state->handle, saved, nullptr, FILE_BEGIN);
size = len;
if (position > size) position = size;
RefreshMetadata(*state, size, timeStamp);
return true;
}
void idFile_Nfs::Flush() {
FileState* const state = static_cast<FileState*>(nfsClient);
if (state != nullptr && !ro) FlushFileBuffers(state->handle);
}
void idFile_Nfs::ForceFlush() { Flush(); }
bool idFile_Nfs::CreateOsPath(const char* path) {
std::wstring native;
bool readOnly = false;
return Resolve(path, native, &readOnly) && !readOnly
&& CreateDirectories(native, false);
}
int idFile_Nfs::ListFiles(const char* path, const char* extension,
idList<idStr>& list) {
list.Clear();
std::wstring native;
if (!Resolve(path, native)) return 0;
std::wstring query = native;
if (!query.empty() && query.back() != L'\\') query.push_back(L'\\');
query.push_back(L'*');
WIN32_FIND_DATAW data{};
HANDLE find = FindFirstFileW(query.c_str(), &data);
if (find == INVALID_HANDLE_VALUE) return 0;
do {
const std::wstring name = data.cFileName;
if (name == L"." || name == L".." || !HasExtension(name, extension)) continue;
const int amount = WideCharToMultiByte(CP_UTF8, 0, name.c_str(), -1,
nullptr, 0, nullptr, nullptr);
if (amount <= 1) continue;
std::string utf8(static_cast<std::size_t>(amount), '\0');
WideCharToMultiByte(CP_UTF8, 0, name.c_str(), -1, &utf8[0], amount,
nullptr, nullptr);
utf8.resize(static_cast<std::size_t>(amount - 1));
list.Append(idStr(utf8.c_str()));
} while (FindNextFileW(find, &data));
FindClose(find);
return list.Num();
}
bool idFile_Nfs::RemoveFile(const char* path) {
std::wstring native;
bool readOnly = false;
if (!Resolve(path, native, &readOnly) || readOnly) return false;
const DWORD attributes = GetFileAttributesW(native.c_str());
if (attributes == INVALID_FILE_ATTRIBUTES) return false;
return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0
? RemoveDirectoryW(native.c_str()) != FALSE
: DeleteFileW(native.c_str()) != FALSE;
}
bool idFile_Nfs::RenameFile(const char* oldPath, const char* newPath) {
std::wstring oldNative, newNative;
bool oldReadOnly = false, newReadOnly = false;
std::string oldAlias, newAlias;
if (!Resolve(oldPath, oldNative, &oldReadOnly, &oldAlias)
|| !Resolve(newPath, newNative, &newReadOnly, &newAlias)
|| oldReadOnly || newReadOnly || oldAlias != newAlias) return false;
return MoveFileExW(oldNative.c_str(), newNative.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != FALSE;
}
+79
View File
@@ -0,0 +1,79 @@
#pragma once
#include "idlib/precompiled.h"
#ifdef nullptr
#undef nullptr
#endif
#ifdef strcmp
#undef strcmp
#endif
#include <cstdint>
enum nfsFileMode_t {
NFS_FS_READ = 0,
NFS_FS_WRITE = 1,
NFS_FS_READ_WRITE = 2,
NFS_FS_READ_NO_BUFFERING = 3,
NFS_FS_APPEND = 4
};
class idFile_Nfs : public idFile {
public:
idFile_Nfs();
~idFile_Nfs() override;
const char* GetName() const override { return fullPath.c_str(); }
const char* GetFullPath() const override { return fullPath.c_str(); }
int Read(void* buffer, int len) override;
int Write(const void* buffer, int len) override;
int Length() const override;
ID_TIME_T Timestamp() const override { return timeStamp; }
int Tell() const override;
int Seek(long offset, fsOrigin_t origin) override;
void Flush() override;
void ForceFlush() override;
bool Open(const char* path, nfsFileMode_t openMode, bool create = false,
bool createPath = false);
int ReadOfs(std::int64_t offset, void* buffer, int len);
int WriteOfs(std::int64_t offset, const void* buffer, int len);
void SetLength(unsigned int len);
bool SetLength64(std::uint64_t len);
std::uint64_t Length64() const { return size; }
std::uint64_t Tell64() const { return position; }
int Seek64(std::int64_t offset, fsOrigin_t origin);
bool IsReadOnly() const { return ro; }
static bool Mount(const char* alias, const char* windowsRoot,
bool readOnly = false);
static void UnmountAll();
static bool CreateOsPath(const char* path);
static int ListFiles(const char* path, const char* extension,
idList<idStr>& list);
static bool RemoveFile(const char* path);
static bool RenameFile(const char* oldPath, const char* newPath);
private:
struct NfsInternalFh { std::uint32_t pad[38]; };
unsigned int uniqID;
bool openRemote;
unsigned char openPadding[3];
NfsInternalFh fh;
idStr fullPath;
nfsFileMode_t mode;
std::uint64_t position;
std::uint64_t size;
unsigned int timeStamp;
bool demandSeek;
unsigned char seekPadding[3];
void* nfsClient;
bool ro;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idFile_Nfs) == 232,
"Recovered idFile_Nfs ABI changed");
#endif
@@ -0,0 +1,13 @@
#pragma once
// The portable permanent-file implementation currently comes from the BFG
// framework baseline. This compatibility include restores the original
// shared/idlib path used by tungsten sources while its wider idTech 5 layout
// is introduced incrementally at the filesystem boundary.
#include "idlib/precompiled.h"
#ifdef nullptr
#undef nullptr
#endif
inline int idFilePermanentSectorSize(const idFile_Permanent&) { return 1; }
@@ -0,0 +1,247 @@
#include "warningsfilter.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <new>
#include <regex>
#include <string>
namespace {
std::mutex filterMutex;
struct filterConfig_t {
const mgWarningsFilter* owner;
bool enabled;
};
filterConfig_t filterConfigs[8] = {};
bool& EnabledFor(const mgWarningsFilter* owner) {
for (filterConfig_t& config : filterConfigs) {
if (config.owner == owner) return config.enabled;
}
for (filterConfig_t& config : filterConfigs) {
if (config.owner == nullptr) {
config.owner = owner;
config.enabled = true;
return config.enabled;
}
}
return filterConfigs[0].enabled;
}
bool DecodeJSONString(const char* objectBegin, const char* objectEnd,
const char* key, std::string& result) {
const std::string needle = std::string("\"") + key + "\"";
const char* keyPosition = std::search(objectBegin, objectEnd,
needle.begin(), needle.end());
if (keyPosition == objectEnd) return false;
const char* cursor = keyPosition + needle.size();
while (cursor < objectEnd && *cursor != ':') ++cursor;
if (cursor == objectEnd) return false;
while (++cursor < objectEnd && (*cursor == ' ' || *cursor == '\t'
|| *cursor == '\r' || *cursor == '\n')) {}
if (cursor == objectEnd || *cursor != '"') return false;
++cursor;
result.clear();
while (cursor < objectEnd && *cursor != '"') {
if (*cursor == '\\' && cursor + 1 < objectEnd) {
++cursor;
switch (*cursor) {
case 'n': result.push_back('\n'); break;
case 'r': result.push_back('\r'); break;
case 't': result.push_back('\t'); break;
default: result.push_back(*cursor); break;
}
} else {
result.push_back(*cursor);
}
++cursor;
}
return cursor < objectEnd;
}
} // namespace
warningfilter_t::warningfilter_t() : reg{} {}
warningfilter_t::warningfilter_t(const warningfilter_t& other)
: pattern(other.pattern), owner(other.owner), reg{} {
if (other.reg.value != nullptr) {
try {
reg.value = new std::regex(pattern.c_str(),
std::regex_constants::extended);
} catch (const std::regex_error&) {
reg.value = nullptr;
}
}
}
warningfilter_t::~warningfilter_t() {
delete static_cast<std::regex*>(reg.value);
}
warningfilter_t& warningfilter_t::operator=(const warningfilter_t& other) {
if (this == &other) return *this;
delete static_cast<std::regex*>(reg.value);
reg = {};
pattern = other.pattern;
owner = other.owner;
if (other.reg.value != nullptr) {
try {
reg.value = new std::regex(pattern.c_str(),
std::regex_constants::extended);
} catch (const std::regex_error&) {
reg.value = nullptr;
}
}
return *this;
}
mgWarningsFilter::mgWarningsFilter() : filters{}, init(false) {
filters.granularity = 16;
EnabledFor(this) = true;
}
mgWarningsFilter::~mgWarningsFilter() {
Clear();
for (filterConfig_t& config : filterConfigs) {
if (config.owner == this) config = {};
}
}
bool mgWarningsFilter::EnsureCapacity(const int amount) {
if (amount <= filters.size) return true;
int newSize = filters.size == 0 ? filters.granularity : filters.size;
while (newSize < amount) newSize += filters.granularity;
warningfilter_t* const replacement = new (std::nothrow)
warningfilter_t[newSize];
if (replacement == nullptr) return false;
for (int index = 0; index < filters.num; ++index) {
replacement[index] = filters.list[index];
}
delete[] filters.list;
filters.list = replacement;
filters.size = newSize;
return true;
}
bool mgWarningsFilter::AddFilter(const char* patternText,
const char* ownerText) {
if (patternText == nullptr || *patternText == '\0') return false;
std::regex* compiled = nullptr;
try {
compiled = new std::regex(patternText, std::regex_constants::extended);
} catch (const std::regex_error&) {
return false;
}
std::lock_guard<std::mutex> lock(filterMutex);
if (!EnsureCapacity(filters.num + 1)) {
delete compiled;
return false;
}
warningfilter_t& filter = filters.list[filters.num++];
filter.pattern = patternText;
filter.owner = ownerText == nullptr ? "" : ownerText;
filter.reg.re_nsub = 0;
filter.reg.value = compiled;
init = true;
return true;
}
int mgWarningsFilter::RemoveOwner(const char* ownerText) {
const char* const safeOwner = ownerText == nullptr ? "" : ownerText;
std::lock_guard<std::mutex> lock(filterMutex);
int removed = 0;
for (int index = 0; index < filters.num;) {
if (std::strcmp(filters.list[index].owner.c_str(), safeOwner) != 0) {
++index;
continue;
}
for (int move = index + 1; move < filters.num; ++move) {
filters.list[move - 1] = filters.list[move];
}
filters.list[filters.num - 1] = warningfilter_t();
--filters.num;
++removed;
}
return removed;
}
void mgWarningsFilter::Clear() {
std::lock_guard<std::mutex> lock(filterMutex);
delete[] filters.list;
filters.list = nullptr;
filters.num = 0;
filters.size = 0;
init = false;
}
bool mgWarningsFilter::IsFiltered(const char* warning, idStr* matchedOwner) const {
if (warning == nullptr || !EnabledFor(this)) return false;
std::lock_guard<std::mutex> lock(filterMutex);
for (int index = 0; index < filters.num; ++index) {
const warningfilter_t& filter = filters.list[index];
const std::regex* const expression =
static_cast<const std::regex*>(filter.reg.value);
if (expression != nullptr && std::regex_search(warning, *expression)) {
if (matchedOwner != nullptr) *matchedOwner = filter.owner;
return true;
}
}
return false;
}
const warningfilter_t* mgWarningsFilter::GetFilter(const int index) const {
return index >= 0 && index < filters.num ? filters.list + index : nullptr;
}
void mgWarningsFilter::SetEnabled(const bool value) {
EnabledFor(this) = value;
}
bool mgWarningsFilter::IsEnabled() const {
return EnabledFor(this);
}
int mgWarningsFilter::LoadJSONText(const char* json) {
if (json == nullptr) return 0;
int loaded = 0;
const char* cursor = json;
while ((cursor = std::strchr(cursor, '{')) != nullptr) {
const char* const end = std::strchr(cursor + 1, '}');
if (end == nullptr) break;
std::string patternText;
std::string ownerText;
if (DecodeJSONString(cursor, end, "pattern", patternText)) {
DecodeJSONString(cursor, end, "owner", ownerText);
if (AddFilter(patternText.c_str(), ownerText.c_str())) ++loaded;
}
cursor = end + 1;
}
return loaded;
}
bool mgWarningsFilter::LoadJSONFile(const char* filename) {
if (filename == nullptr) return false;
FILE* file = nullptr;
if (fopen_s(&file, filename, "rb") != 0 || file == nullptr) return false;
std::fseek(file, 0, SEEK_END);
const long length = std::ftell(file);
std::fseek(file, 0, SEEK_SET);
if (length < 0 || length > 16 * 1024 * 1024) {
std::fclose(file);
return false;
}
std::string contents(static_cast<std::size_t>(length), '\0');
const std::size_t read = contents.empty() ? 0
: std::fread(&contents[0], 1, contents.size(), file);
std::fclose(file);
return read == contents.size() && LoadJSONText(contents.c_str()) > 0;
}
mgWarningsFilter warningsFilter;
@@ -0,0 +1,66 @@
#pragma once
#include "idlib/text/str.h"
#include <cstdint>
struct warningfilter_regex_t {
unsigned int re_nsub;
void* value;
};
struct warningfilter_t {
warningfilter_t();
warningfilter_t(const warningfilter_t& other);
~warningfilter_t();
warningfilter_t& operator=(const warningfilter_t& other);
idStr pattern;
idStr owner;
warningfilter_regex_t reg;
};
class mgWarningsFilter {
public:
mgWarningsFilter();
~mgWarningsFilter();
bool AddFilter(const char* pattern, const char* owner = "");
int RemoveOwner(const char* owner);
void Clear();
bool IsFiltered(const char* warning, idStr* owner = nullptr) const;
bool Filter(const char* warning) const { return IsFiltered(warning); }
bool LoadJSONFile(const char* filename);
int LoadJSONText(const char* json);
int NumFilters() const { return filters.num; }
const warningfilter_t* GetFilter(int index) const;
void SetEnabled(bool value);
bool IsEnabled() const;
private:
struct recoveredFilterList_t {
warningfilter_t* list;
int num;
int size;
short granularity;
unsigned char memTag;
unsigned char listStatic;
} filters;
bool init;
// Enabled is kept out of the recovered object layout in the .cpp sidecar.
bool EnsureCapacity(int amount);
};
extern mgWarningsFilter warningsFilter;
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(warningfilter_regex_t) == 8,
"Recovered regex_t facade ABI changed");
static_assert(sizeof(warningfilter_t) == 72,
"Recovered warningfilter_t ABI changed");
static_assert(sizeof(mgWarningsFilter) == 20,
"Recovered mgWarningsFilter ABI changed");
#endif
+227
View File
@@ -0,0 +1,227 @@
#include "idlib/geometry/geometry.h"
#include <algorithm>
#include <cmath>
#include <limits>
namespace {
constexpr float kSmallestNormal = std::numeric_limits<float>::min();
constexpr float kParallelEpsilonSqr = 0.0001f;
float ClampUnit(const float value) {
return std::max(0.0f, std::min(1.0f, value));
}
float Cross2D(const idVec2& left, const idVec2& right) {
return left.x * right.y - left.y * right.x;
}
idVec2 Subtract2D(const idVec2& left, const idVec2& right) {
return idVec2(left.x - right.x, left.y - right.y);
}
idVec3 NormalizeSafely(const idVec3& vector) {
const float lengthSqr = vector.LengthSqr();
if (lengthSqr < kSmallestNormal) {
return idVec3(0.0f, 0.0f, 0.0f);
}
return vector * (1.0f / std::sqrt(lengthSqr));
}
} // namespace
float idGeometry::PositionOnLineSegment(
const idVec3& point,
const idVec3& start,
const idVec3& end
) {
const idVec3 segment = end - start;
const float lengthSqr = segment.LengthSqr();
if (lengthSqr < kSmallestNormal) {
return 0.0f;
}
return (point - start).Dot(segment) / lengthSqr;
}
bool idGeometry::ClosestPointOnLineSegment(
const idVec3& point,
const idVec3& start,
const idVec3& end,
idVec3& closest
) {
const idVec3 segment = end - start;
const float lengthSqr = segment.LengthSqr();
if (lengthSqr < kSmallestNormal) {
closest = start;
return false;
}
const float position = (point - start).Dot(segment) / lengthSqr;
if (position < 0.0f) {
closest = start;
return false;
}
if (position > 1.0f) {
closest = end;
return false;
}
closest = start + segment * position;
return true;
}
void idGeometry::ClosestPointOnLine(
const idVec3& point,
const idVec3& start,
const idVec3& dir,
idVec3& closest
) {
closest = start + dir * (point - start).Dot(dir);
}
idVec3 idGeometry::TriangleNormal(
const idVec3& a,
const idVec3& b,
const idVec3& c
) {
// The recovered winding is intentionally (c-a) x (b-a), opposite the
// common formulation. Renderer-facing callers depend on that sign.
return NormalizeSafely((c - a).Cross(b - a));
}
bool idGeometry::IntersectRayWithLineSegment2D(
const idVec2& rayStart,
const idVec2& rayDir,
const idVec2& segStart,
const idVec2& segEnd,
float& dist
) {
const idVec2 segment = Subtract2D(segEnd, segStart);
const idVec2 offset = Subtract2D(segStart, rayStart);
const float denominator = Cross2D(rayDir, segment);
if (denominator * denominator > kParallelEpsilonSqr) {
const float inverseDenominator = 1.0f / denominator;
const float segmentPosition = -Cross2D(rayDir, offset)
* inverseDenominator;
if (segmentPosition < 0.0f || segmentPosition > 1.0f) {
return false;
}
const float rayPosition = Cross2D(offset, segment)
* inverseDenominator;
if (rayPosition < 0.0f) {
return false;
}
dist = rayPosition;
return true;
}
if (Cross2D(rayDir, offset) * Cross2D(rayDir, offset)
> kParallelEpsilonSqr) {
return false;
}
// The PPC output is difficult to read in this collinear branch. This is
// the equivalent geometric result: the nearest forward overlap measured
// in ray parameter units. Keep it isolated for later binary trace checks.
const float rayLengthSqr = rayDir.x * rayDir.x + rayDir.y * rayDir.y;
if (rayLengthSqr < kSmallestNormal) {
return false;
}
const idVec2 endOffset = Subtract2D(segEnd, rayStart);
const float startPosition = (offset.x * rayDir.x + offset.y * rayDir.y)
/ rayLengthSqr;
const float endPosition = (endOffset.x * rayDir.x + endOffset.y * rayDir.y)
/ rayLengthSqr;
const float nearPosition = std::min(startPosition, endPosition);
const float farPosition = std::max(startPosition, endPosition);
if (farPosition < 0.0f) {
return false;
}
dist = std::max(0.0f, nearPosition);
return true;
}
float idGeometry::SquarePointLineSegmentDistance(
const idVec3& point,
const idVec3& start,
const idVec3& end
) {
const idVec3 segment = end - start;
const float lengthSqr = segment.LengthSqr();
if (lengthSqr < 0.01f) {
return (point - start).LengthSqr();
}
const float position = ClampUnit((point - start).Dot(segment) / lengthSqr);
return (point - (start + segment * position)).LengthSqr();
}
void idGeometry::SegmentSegmentClosestPoints(
const idVec3& start1,
const idVec3& end1,
const idVec3& start2,
const idVec3& end2,
idVec3& out1,
idVec3& out2,
float& t1,
float& t2,
const bool clampTValues
) {
const idVec3 direction1 = end1 - start1;
const idVec3 direction2 = end2 - start2;
const idVec3 offset = start1 - start2;
const float length1Sqr = direction1.LengthSqr();
const float length2Sqr = direction2.LengthSqr();
const float directionsDot = direction1.Dot(direction2);
const float offsetDot1 = direction1.Dot(offset);
const float offsetDot2 = direction2.Dot(offset);
const float denominator = length1Sqr * length2Sqr
- directionsDot * directionsDot;
if (length1Sqr < kSmallestNormal
|| length2Sqr < kSmallestNormal
|| denominator < kSmallestNormal) {
out1 = start1;
out2 = start2;
t1 = 1.0f;
t2 = 1.0f;
return;
}
t1 = (directionsDot * offsetDot2 - offsetDot1 * length2Sqr)
/ denominator;
if (clampTValues) {
t1 = ClampUnit(t1);
}
t2 = (t1 * directionsDot + offsetDot2) / length2Sqr;
if (clampTValues) {
t2 = ClampUnit(t2);
}
out1 = start1 + direction1 * t1;
out2 = start2 + direction2 * t2;
}
idVec3 idGeometry::FindNearestPerpendicular(
const idVec3& input,
const idVec3& up,
const idVec3& hint
) {
idVec3 perpendicular = input.Cross(up);
if (perpendicular.Dot(hint) < 0.0f) {
perpendicular = -perpendicular;
}
return NormalizeSafely(perpendicular);
}
float idGeometry::AreaOfTriangle(
const idVec3& a,
const idVec3& b,
const idVec3& c
) {
return 0.5f * (b - a).Cross(c - a).Length();
}
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include "../math/vector.h"
class idGeometry {
public:
static float PositionOnLineSegment(
const idVec3& point,
const idVec3& start,
const idVec3& end
);
static bool ClosestPointOnLineSegment(
const idVec3& point,
const idVec3& start,
const idVec3& end,
idVec3& closest
);
static void ClosestPointOnLine(
const idVec3& point,
const idVec3& start,
const idVec3& dir,
idVec3& closest
);
static idVec3 TriangleNormal(
const idVec3& a,
const idVec3& b,
const idVec3& c
);
static bool IntersectRayWithLineSegment2D(
const idVec2& rayStart,
const idVec2& rayDir,
const idVec2& segStart,
const idVec2& segEnd,
float& dist
);
static float SquarePointLineSegmentDistance(
const idVec3& point,
const idVec3& start,
const idVec3& end
);
static void SegmentSegmentClosestPoints(
const idVec3& start1,
const idVec3& end1,
const idVec3& start2,
const idVec3& end2,
idVec3& out1,
idVec3& out2,
float& t1,
float& t2,
bool clampTValues
);
static idVec3 FindNearestPerpendicular(
const idVec3& input,
const idVec3& up,
const idVec3& hint
);
static float AreaOfTriangle(
const idVec3& a,
const idVec3& b,
const idVec3& c
);
};
@@ -0,0 +1,16 @@
#include "screenrect.h"
void idScreenRect::Clear() {
x1 = 32000;
y1 = 32000;
x2 = -32000;
y2 = -32000;
}
void idScreenRect::Zero() {
x1 = 0;
y1 = 0;
x2 = 0;
y2 = 0;
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
// tungsten.exe.h type 12913. Unlike Doom 3 BFG's idScreenRect, this idTech 5
// build uses four 32-bit coordinates and does not store depth bounds here.
class idScreenRect {
public:
int x1;
int y1;
int x2;
int y2;
void Clear();
void Zero();
bool IsEmpty() const {
return x1 > x2 || y1 > y2;
}
};
static_assert(sizeof(idScreenRect) == 16, "Recovered idScreenRect layout changed");
+58
View File
@@ -0,0 +1,58 @@
#include "crc16.h"
#include <array>
#include <cassert>
#include <cstddef>
namespace {
using CrcTable = std::array<std::uint16_t, 256>;
CrcTable BuildCrcTable() {
CrcTable table = {};
for (std::size_t index = 0; index < table.size(); ++index) {
std::uint16_t value = static_cast<std::uint16_t>(index << 8);
for (int bit = 0; bit < 8; ++bit) {
value = (value & 0x8000u) != 0
? static_cast<std::uint16_t>((value << 1) ^ 0x1021u)
: static_cast<std::uint16_t>(value << 1);
}
table[index] = value;
}
return table;
}
const CrcTable& GetCrcTable() {
static const CrcTable table = BuildCrcTable();
return table;
}
} // namespace
void CRC16_UpdateChecksum(
std::uint16_t& crcValue,
const void* data,
const int length
) {
assert(length >= 0);
assert(data != nullptr || length == 0);
if (length <= 0) {
return;
}
const CrcTable& table = GetCrcTable();
const auto* bytes = static_cast<const std::uint8_t*>(data);
for (int index = 0; index < length; ++index) {
const std::uint8_t tableIndex = static_cast<std::uint8_t>(
(crcValue >> 8) ^ bytes[index]
);
crcValue = static_cast<std::uint16_t>(
table[tableIndex] ^ static_cast<std::uint16_t>(crcValue << 8)
);
}
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
// Recovered from ?CRC16_UpdateChecksum@@YAXAAGPBXH@Z at 0x82F2BE00.
// The reference parameter and const void pointer match the decorated PDB name;
// Hex-Rays displayed the reference as a pointer in the raw 360 dump.
void CRC16_UpdateChecksum(
std::uint16_t& crcValue,
const void* data,
int length
);
+137
View File
@@ -0,0 +1,137 @@
#include "murmur.h"
#include <cassert>
namespace {
constexpr std::uint32_t MURMUR_MULTIPLIER = 0x5BD1E995u;
std::uint32_t ReadLittleEndian32(const std::uint8_t* data) {
return static_cast<std::uint32_t>(data[0])
| (static_cast<std::uint32_t>(data[1]) << 8)
| (static_cast<std::uint32_t>(data[2]) << 16)
| (static_cast<std::uint32_t>(data[3]) << 24);
}
std::uint32_t ReadBigEndian32(const std::uint8_t* data) {
return (static_cast<std::uint32_t>(data[0]) << 24)
| (static_cast<std::uint32_t>(data[1]) << 16)
| (static_cast<std::uint32_t>(data[2]) << 8)
| static_cast<std::uint32_t>(data[3]);
}
std::uint32_t MixWord(std::uint32_t value) {
value *= MURMUR_MULTIPLIER;
value ^= value >> 24;
value *= MURMUR_MULTIPLIER;
return value;
}
} // namespace
std::uint32_t MurMur32_HashData(
const void* key,
const int length,
const std::uint32_t seed
) {
assert(length >= 0);
assert(key != nullptr || length == 0);
if (length < 0 || (key == nullptr && length != 0)) {
return 0;
}
const auto* data = static_cast<const std::uint8_t*>(key);
int remaining = length;
std::uint32_t hash = seed ^ static_cast<std::uint32_t>(length);
while (remaining >= 4) {
const std::uint32_t word = MixWord(ReadLittleEndian32(data));
hash = (hash * MURMUR_MULTIPLIER) ^ word;
data += 4;
remaining -= 4;
}
switch (remaining) {
case 3:
hash ^= static_cast<std::uint32_t>(data[2]) << 16;
// Fall through.
case 2:
hash ^= static_cast<std::uint32_t>(data[1]) << 8;
// Fall through.
case 1:
hash ^= data[0];
hash *= MURMUR_MULTIPLIER;
break;
default:
break;
}
hash ^= hash >> 13;
hash *= MURMUR_MULTIPLIER;
hash ^= hash >> 15;
return hash;
}
std::uint64_t MurMur64_HashData(
const void* key,
const int length,
const std::uint32_t seed
) {
assert(length >= 0);
assert(key != nullptr || length == 0);
if (length < 0 || (key == nullptr && length != 0)) {
return 0;
}
const auto* data = static_cast<const std::uint8_t*>(key);
int remaining = length;
std::uint32_t hash1 = seed ^ static_cast<std::uint32_t>(length);
std::uint32_t hash2 = 0;
// The recovered 360 function loads full words directly on big-endian PPC,
// but constructs its one-to-three-byte tail explicitly. Preserve that byte
// behavior so hashes continue to match the supplied Xbox resource data.
while (remaining >= 8) {
hash1 = (hash1 * MURMUR_MULTIPLIER)
^ MixWord(ReadBigEndian32(data));
hash2 = (hash2 * MURMUR_MULTIPLIER)
^ MixWord(ReadBigEndian32(data + 4));
data += 8;
remaining -= 8;
}
if (remaining >= 4) {
hash1 = (hash1 * MURMUR_MULTIPLIER)
^ MixWord(ReadBigEndian32(data));
data += 4;
remaining -= 4;
}
switch (remaining) {
case 3:
hash2 ^= static_cast<std::uint32_t>(data[2]) << 16;
// Fall through.
case 2:
hash2 ^= static_cast<std::uint32_t>(data[1]) << 8;
// Fall through.
case 1:
hash2 ^= data[0];
hash2 *= MURMUR_MULTIPLIER;
break;
default:
break;
}
hash1 ^= hash2 >> 18;
hash1 *= MURMUR_MULTIPLIER;
hash2 ^= hash1 >> 22;
hash2 *= MURMUR_MULTIPLIER;
hash1 ^= hash2 >> 17;
hash1 *= MURMUR_MULTIPLIER;
hash2 ^= hash1 >> 19;
hash2 *= MURMUR_MULTIPLIER;
return (static_cast<std::uint64_t>(hash1) << 32) | hash2;
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
// PDB signatures:
// ?MurMur32_HashData@@YAIPBXHI@Z
// ?MurMur64_HashData@@YA_KPBXHI@Z
std::uint32_t MurMur32_HashData(
const void* key,
int length,
std::uint32_t seed
);
std::uint64_t MurMur64_HashData(
const void* key,
int length,
std::uint32_t seed
);
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <type_traits>
template<typename valueType, typename invalidType>
class idIndex {
public:
idIndex()
: value(static_cast<valueType>(-1)) {
}
explicit idIndex(const valueType index)
: value(index) {
}
bool IsValid() const {
return value != static_cast<valueType>(-1);
}
void Invalidate() {
value = static_cast<valueType>(-1);
}
valueType Get() const {
return value;
}
operator valueType() const {
return value;
}
bool operator==(const idIndex& other) const { return value == other.value; }
bool operator!=(const idIndex& other) const { return value != other.value; }
bool operator<(const idIndex& other) const { return value < other.value; }
private:
valueType value;
};
enum class idRecoveredInvalidIndex : int { invalid = -1 };
static_assert(sizeof(idIndex<short, idRecoveredInvalidIndex>) == sizeof(short),
"Recovered idIndex ABI changed");
+200
View File
@@ -0,0 +1,200 @@
#include "lib_print.h"
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
namespace {
struct warningInfo_t { const char* type; const char* name; };
std::recursive_mutex printMutex;
idPrintListener* listeners = nullptr;
std::array<warningInfo_t, 32> warningInfo{};
int numWarningInfo = 0;
idLibPrint::fatalErrorHandler_t fatalErrorHandler = nullptr;
const std::thread::id mainThread = std::this_thread::get_id();
std::string RemoveColors(const std::string& source) {
std::string result;
result.reserve(source.size());
for (std::size_t index = 0; index < source.size(); ++index) {
if (source[index] == '^' && index + 1 < source.size()
&& source[index + 1] >= '0' && source[index + 1] <= '9') {
++index;
} else {
result.push_back(source[index]);
}
}
return result;
}
std::string Format(const char* format, va_list args) {
char buffer[4096] = {};
const int amount = std::vsnprintf(buffer, sizeof(buffer),
format == nullptr ? "" : format, args);
if (amount < 0) {
buffer[sizeof(buffer) - 2] = '\n';
buffer[sizeof(buffer) - 1] = '\0';
}
return buffer;
}
const char* Prefix(const printSeverity_t severity) {
switch (severity) {
case SEV_WARNING: return "^3WARNING: ^1";
case SEV_ERROR: return "^3ERROR: ^1";
case SEV_FATAL: return "^3FATAL ERROR: ^1";
default: return "";
}
}
void ThrowMessage(const printSeverity_t severity, const std::string& message) {
throw idRecoveredPrintException(RemoveColors(message).c_str(),
severity == SEV_FATAL);
}
} // namespace
idPrintListener::idPrintListener()
: next(nullptr), wantColor(true), threadSafe(false),
minSeverity(SEV_PRINT) {}
idPrintListener::~idPrintListener() { UnRegisterPrintListener(); }
void idPrintListener::RegisterPrintListener() {
idLibPrint::RegisterPrintListener(this);
}
void idPrintListener::UnRegisterPrintListener() {
idLibPrint::UnRegisterPrintListener(this);
}
bool idLibPrint::PushWarningInfo(const char* type, const char* name) {
std::lock_guard<std::recursive_mutex> lock(printMutex);
if (numWarningInfo >= static_cast<int>(warningInfo.size())) return false;
warningInfo[static_cast<std::size_t>(numWarningInfo++)] = {type, name};
return true;
}
void idLibPrint::PopWarningInfo() {
std::lock_guard<std::recursive_mutex> lock(printMutex);
if (numWarningInfo > 0) --numWarningInfo;
}
void idLibPrint::RegisterFatalErrorHandler(fatalErrorHandler_t handler) {
std::lock_guard<std::recursive_mutex> lock(printMutex);
fatalErrorHandler = handler;
}
void idLibPrint::RegisterPrintListener(idPrintListener* listener) {
if (listener == nullptr) return;
std::lock_guard<std::recursive_mutex> lock(printMutex);
for (idPrintListener* current = listeners; current != nullptr;
current = current->next) {
if (current == listener) return;
}
listener->next = listeners;
listeners = listener;
}
void idLibPrint::UnRegisterPrintListener(idPrintListener* listener) {
if (listener == nullptr) return;
std::lock_guard<std::recursive_mutex> lock(printMutex);
idPrintListener** link = &listeners;
while (*link != nullptr && *link != listener) link = &(*link)->next;
if (*link == listener) {
*link = listener->next;
listener->next = nullptr;
}
}
void idLibPrint::Dispatch(const printSeverity_t severity, const char* format,
va_list args) {
va_list copy;
va_copy(copy, args);
std::string message = Prefix(severity);
message += Format(format, copy);
va_end(copy);
std::lock_guard<std::recursive_mutex> lock(printMutex);
if (severity >= SEV_WARNING) {
for (int index = numWarningInfo - 1; index >= 0; --index) {
message += index == numWarningInfo - 1
? " ^7while loading " : " ^8from ";
message += warningInfo[static_cast<std::size_t>(index)].type == nullptr
? "" : warningInfo[static_cast<std::size_t>(index)].type;
message.push_back(' ');
message += warningInfo[static_cast<std::size_t>(index)].name == nullptr
? "" : warningInfo[static_cast<std::size_t>(index)].name;
}
message.push_back('\n');
}
const bool onMainThread = std::this_thread::get_id() == mainThread;
bool delivered = false;
for (idPrintListener* listener = listeners; listener != nullptr;
listener = listener->next) {
if (listener->wantColor && severity >= listener->minSeverity
&& (onMainThread || listener->threadSafe)) {
listener->Print(message.c_str());
delivered = true;
}
}
const std::string plain = RemoveColors(message);
for (idPrintListener* listener = listeners; listener != nullptr;
listener = listener->next) {
if (!listener->wantColor && severity >= listener->minSeverity
&& (onMainThread || listener->threadSafe)) {
listener->Print(plain.c_str());
delivered = true;
}
}
if (!delivered) std::fputs(plain.c_str(), stderr);
if (severity == SEV_FATAL && fatalErrorHandler != nullptr) {
fatalErrorHandler(plain.c_str());
}
if (severity >= SEV_ERROR) ThrowMessage(severity, message);
}
void idLibPrint::Debugf(const char* format, ...) {
va_list args; va_start(args, format); Dispatch(SEV_DEBUG, format, args); va_end(args);
}
void idLibPrint::Printf(const char* format, ...) {
va_list args; va_start(args, format); Dispatch(SEV_PRINT, format, args); va_end(args);
}
void idLibPrint::PrintfIf(const bool condition, const char* format, ...) {
if (!condition) return;
va_list args; va_start(args, format); Dispatch(SEV_PRINT, format, args); va_end(args);
}
void idLibPrint::VPrintf(const char* format, va_list args) {
Dispatch(SEV_PRINT, format, args);
}
void idLibPrint::Warning(const char* format, ...) {
va_list args; va_start(args, format); Dispatch(SEV_WARNING, format, args); va_end(args);
}
void idLibPrint::WarningIf(const bool condition, const char* format, ...) {
if (!condition) return;
va_list args; va_start(args, format); Dispatch(SEV_WARNING, format, args); va_end(args);
}
void idLibPrint::Error(const char* format, ...) {
va_list args; va_start(args, format); Dispatch(SEV_ERROR, format, args); va_end(args);
std::terminate();
}
void idLibPrint::FatalError(const char* format, ...) {
va_list args; va_start(args, format); Dispatch(SEV_FATAL, format, args); va_end(args);
std::terminate();
}
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <cstdarg>
#include <stdexcept>
enum printSeverity_t {
SEV_DEBUG = 0,
SEV_PRINT = 1,
SEV_WARNING = 2,
SEV_ERROR = 3,
SEV_FATAL = 4
};
class idPrintListener {
public:
idPrintListener();
virtual ~idPrintListener();
virtual void Print(const char* text) = 0;
void RegisterPrintListener();
void UnRegisterPrintListener();
idPrintListener* next;
bool wantColor;
bool threadSafe;
printSeverity_t minSeverity;
};
class idRecoveredPrintException : public std::runtime_error {
public:
idRecoveredPrintException(const char* message, bool fatal)
: std::runtime_error(message == nullptr ? "" : message),
fatalError(fatal) {}
bool IsFatal() const { return fatalError; }
private:
bool fatalError;
};
// PC-side extension for the listener/context facilities added after the BFG
// idLib interface. The six common Printf/Warning/Error entry points continue
// to be supplied by the BFG baseline until the engine-wide Lib.h is replaced.
class idLibPrint {
public:
using fatalErrorHandler_t = void (*)(const char*);
static bool PushWarningInfo(const char* type, const char* name);
static void PopWarningInfo();
static void RegisterFatalErrorHandler(fatalErrorHandler_t handler);
static void Debugf(const char* format, ...);
static void Printf(const char* format, ...);
static void PrintfIf(bool condition, const char* format, ...);
static void VPrintf(const char* format, va_list args);
static void Warning(const char* format, ...);
static void WarningIf(bool condition, const char* format, ...);
[[noreturn]] static void Error(const char* format, ...);
[[noreturn]] static void FatalError(const char* format, ...);
static void Dispatch(printSeverity_t severity, const char* format,
va_list args);
static void RegisterPrintListener(idPrintListener* listener);
static void UnRegisterPrintListener(idPrintListener* listener);
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idPrintListener) == 16,
"Recovered idPrintListener layout changed");
#endif
+321
View File
@@ -0,0 +1,321 @@
#include "lookuptable.h"
#include <algorithm>
#include <cmath>
namespace {
int PositiveModulo(const int value, const int modulus) {
const int remainder = value % modulus;
return remainder < 0 ? remainder + modulus : remainder;
}
int FloorDivide(const int value, const int divisor) {
int quotient = value / divisor;
if (value < 0 && value % divisor != 0) {
--quotient;
}
return quotient;
}
} // namespace
void idCatmullRomSpline<float>::Sort() {
for (int end = numKnots - 1; end > 0; --end) {
int maximumIndex = 0;
for (int index = 1; index <= end; ++index) {
if (times[index] > times[maximumIndex]) {
maximumIndex = index;
}
}
std::swap(times[maximumIndex], times[end]);
std::swap(values[maximumIndex], values[end]);
}
changed = true;
}
void idCatmullRomSpline<float>::Normalize(const float totalTime) {
if (numKnots < 1 || times[numKnots - 1] == 0.0f) {
return;
}
const float scale = totalTime / times[numKnots - 1];
for (int index = 0; index < numKnots; ++index) {
times[index] *= scale;
}
changed = true;
}
float idCatmullRomSpline<float>::ClampedTime(const float time) const {
if (boundaryType == CLAMPED) {
if (time < times[0]) {
return times[0];
}
if (time >= times[numKnots - 1]) {
return times[numKnots - 1];
}
return time;
}
if (boundaryType == CLOSED) {
const float period = times[numKnots - 1] + closeTime;
if (period == 0.0f) {
return times[0];
}
return time - std::floor(time / period) * period;
}
return time;
}
int idCatmullRomSpline<float>::IndexForTime(
const float time,
const bool fastSearch
) const {
if (fastSearch && currentIndex >= 0 && currentIndex <= numKnots) {
if (currentIndex == 0) {
if (time <= times[0]) {
return 0;
}
} else if (currentIndex == numKnots) {
if (time > times[numKnots - 1]) {
return currentIndex;
}
} else if (time > times[currentIndex - 1]
&& time <= times[currentIndex]) {
return currentIndex;
} else if (currentIndex + 1 <= numKnots
&& time > times[currentIndex]
&& (currentIndex + 1 == numKnots
|| time <= times[currentIndex + 1])) {
++currentIndex;
return currentIndex;
}
}
int low = 0;
int high = numKnots;
while (low < high) {
const int middle = low + (high - low) / 2;
if (time <= times[middle]) {
high = middle;
} else {
low = middle + 1;
}
}
if (fastSearch) {
currentIndex = low;
}
return low;
}
float idCatmullRomSpline<float>::TimeForIndex(const int index) const {
const int lastIndex = numKnots - 1;
if (index >= 0 && index <= lastIndex) {
return times[index];
}
if (boundaryType == CLOSED) {
const float period = times[lastIndex] + closeTime;
const int cycle = FloorDivide(index, numKnots);
return static_cast<float>(cycle) * period
+ times[PositiveModulo(index, numKnots)];
}
if (index < 0) {
return times[0]
+ static_cast<float>(index) * (times[1] - times[0]);
}
return times[lastIndex]
+ static_cast<float>(index - lastIndex)
* (times[lastIndex] - times[lastIndex - 1]);
}
float idCatmullRomSpline<float>::ValueForIndex(const int index) const {
const int lastIndex = numKnots - 1;
if (index >= 0 && index <= lastIndex) {
return values[index];
}
if (boundaryType == CLOSED) {
return values[PositiveModulo(index, numKnots)];
}
if (boundaryType == CLAMPED) {
return index < 0 ? values[0] : values[lastIndex];
}
if (index < 0) {
return values[0]
+ static_cast<float>(index) * (values[1] - values[0]);
}
return values[lastIndex]
+ static_cast<float>(index - lastIndex)
* (values[lastIndex] - values[lastIndex - 1]);
}
void idCatmullRomSpline<float>::Basis(
const int index,
const float time,
float basisValues[4]
) const {
const float intervalStart = TimeForIndex(index);
const float intervalEnd = TimeForIndex(index + 1);
const float intervalLength = intervalEnd - intervalStart;
const float s = intervalLength == 0.0f
? 0.0f
: (time - intervalStart) / intervalLength;
basisValues[0] = 0.5f * (((2.0f - s) * s - 1.0f) * s);
basisValues[1] = 0.5f * ((((3.0f * s - 5.0f) * s) * s) + 2.0f);
basisValues[2] = 0.5f * ((((4.0f - 3.0f * s) * s + 1.0f) * s));
basisValues[3] = 0.5f * ((s - 1.0f) * s * s);
}
float idCatmullRomSpline<float>::GetCurrentValue(
const float time,
const bool fastSearch
) const {
if (numKnots == 1) {
return values[0];
}
const float adjustedTime = ClampedTime(time);
const int upperIndex = IndexForTime(adjustedTime, fastSearch);
const int basisIndex = upperIndex - 1;
float basisValues[4];
Basis(basisIndex, adjustedTime, basisValues);
float result = 0.0f;
for (int index = 0; index < 4; ++index) {
result += ValueForIndex(basisIndex - 1 + index)
* basisValues[index];
}
return result;
}
idLookupTable::idLookupTable()
: clamp(false)
, snap(false)
, spline(false)
, minimum(0.0f)
, maximum(1.0f)
, values{} {
values.numKnots = 0;
values.currentIndex = -1;
values.changed = true;
values.boundaryType = idCatmullRomSpline<float>::CLOSED;
values.closeTime = 1.0f;
}
void idLookupTable::SetSnap(const bool enabled) {
snap = enabled;
if (spline && enabled) {
spline = false;
}
}
void idLookupTable::SetSpline(const bool enabled) {
spline = enabled;
if (enabled && snap) {
snap = false;
}
}
void idLookupTable::Finalize() {
values.Sort();
if (values.numKnots > 1 && values.times[values.numKnots - 1] > 1.0f) {
const float totalTime = clamp
? static_cast<float>(values.numKnots - 1)
: 1.0f;
values.Normalize(totalTime);
}
if (values.numKnots > 0) {
values.closeTime = 1.0f - values.times[values.numKnots - 1];
} else {
values.closeTime = 1.0f;
}
values.changed = true;
}
void idLookupTable::SetClamp(const bool enabled) {
clamp = enabled;
values.changed = true;
values.boundaryType = enabled
? idCatmullRomSpline<float>::CLAMPED
: idCatmullRomSpline<float>::CLOSED;
}
void idLookupTable::AddValue(const float time, const float value) {
if (values.numKnots >= 64) {
return;
}
values.times[values.numKnots] = time;
values.values[values.numKnots] = value;
++values.numKnots;
values.changed = true;
}
void idLookupTable::Clear() {
values.numKnots = 0;
values.currentIndex = -1;
values.changed = true;
minimum = 0.0f;
maximum = 1.0f;
clamp = false;
snap = false;
spline = false;
values.boundaryType = idCatmullRomSpline<float>::CLOSED;
values.closeTime = 1.0f;
}
float idLookupTable::TableLookupNormalized(
const float time,
const bool fastSearch
) const {
if (values.numKnots == 0) {
return 0.0f;
}
if (values.numKnots == 1) {
return values.values[0];
}
if (spline) {
return values.GetCurrentValue(time, fastSearch);
}
if (clamp) {
if (time <= values.times[0]) {
return values.values[0];
}
if (time >= values.times[values.numKnots - 1]) {
return values.values[values.numKnots - 1];
}
}
const float adjustedTime = values.ClampedTime(time);
const int upperIndex = values.IndexForTime(adjustedTime, fastSearch);
if (snap) {
return values.ValueForIndex(upperIndex - 1);
}
const float lowerTime = values.TimeForIndex(upperIndex - 1);
const float upperTime = values.TimeForIndex(upperIndex);
if (lowerTime == upperTime) {
return 0.0f;
}
const float lowerValue = values.ValueForIndex(upperIndex - 1);
const float upperValue = values.ValueForIndex(upperIndex);
const float fraction = (adjustedTime - lowerTime)
/ (upperTime - lowerTime);
return lowerValue + (upperValue - lowerValue) * fraction;
}
float idLookupTable::TableLookup(
const float time,
const bool fastSearch
) const {
return minimum + (maximum - minimum)
* TableLookupNormalized(time, fastSearch);
}
+61
View File
@@ -0,0 +1,61 @@
#pragma once
template<typename Type>
class idCatmullRomSpline;
template<>
class idCatmullRomSpline<float> {
public:
enum boundary_t {
FREE = 0,
CLAMPED = 1,
CLOSED = 2
};
float times[64];
float values[64];
int numKnots;
mutable int currentIndex;
bool changed;
boundary_t boundaryType;
float closeTime;
void Sort();
void Normalize(float totalTime);
float ClampedTime(float time) const;
int IndexForTime(float time, bool fastSearch) const;
float TimeForIndex(int index) const;
float ValueForIndex(int index) const;
void Basis(int index, float time, float basisValues[4]) const;
float GetCurrentValue(float time, bool fastSearch) const;
};
static_assert(
sizeof(idCatmullRomSpline<float>) == 532,
"Recovered idCatmullRomSpline<float> layout changed"
);
class idLookupTable {
public:
idLookupTable();
void SetSnap(bool enabled);
void SetSpline(bool enabled);
void Finalize();
void SetClamp(bool enabled);
void AddValue(float time, float value);
void Clear();
float TableLookupNormalized(float time, bool fastSearch) const;
float TableLookup(float time, bool fastSearch) const;
private:
bool clamp;
bool snap;
bool spline;
float minimum;
float maximum;
idCatmullRomSpline<float> values;
};
static_assert(sizeof(idLookupTable) == 544, "idLookupTable ABI changed");
+89
View File
@@ -0,0 +1,89 @@
#pragma once
#include <cstdint>
class idBoundedIntBase {
public:
virtual ~idBoundedIntBase() = default;
virtual void SetValue(int value) = 0;
virtual int GetValue() const = 0;
};
template<int MIN_VALUE, int MAX_VALUE>
class idBoundedInt final : public idBoundedIntBase {
public:
explicit idBoundedInt(const int initialValue = MIN_VALUE)
: value(MIN_VALUE) {
SetValue(initialValue);
}
void SetValue(const int newValue) override {
value = newValue < MIN_VALUE ? MIN_VALUE
: (newValue > MAX_VALUE ? MAX_VALUE : newValue);
}
int GetValue() const override {
return value;
}
operator int() const {
return value;
}
private:
int value;
};
class idBoundedFloatBase {
public:
virtual ~idBoundedFloatBase() = default;
virtual void SetValue(float value) = 0;
virtual float GetValue() const = 0;
};
// The original uses four integral template arguments so floating-point bounds
// remain legal in the C++03-era source. Only <0,0,1,0> occurs in tungsten;
// the second and fourth arguments represent the fractional decimal component.
template<int MIN_WHOLE, int MIN_FRACTION, int MAX_WHOLE, int MAX_FRACTION>
class idBoundedFloat final : public idBoundedFloatBase {
public:
explicit idBoundedFloat(const float initialValue = Minimum())
: value(Minimum()) {
SetValue(initialValue);
}
void SetValue(const float newValue) override {
value = newValue < Minimum() ? Minimum()
: (newValue > Maximum() ? Maximum() : newValue);
}
float GetValue() const override {
return value;
}
operator float() const {
return value;
}
private:
float value;
static constexpr float Fraction(const int digits) {
return static_cast<float>(digits) / 1000.0f;
}
static constexpr float Minimum() {
return static_cast<float>(MIN_WHOLE) + Fraction(MIN_FRACTION);
}
static constexpr float Maximum() {
return static_cast<float>(MAX_WHOLE) + Fraction(MAX_FRACTION);
}
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idBoundedInt<0, 4>) == 8,
"Recovered idBoundedInt ABI changed");
static_assert(sizeof(idBoundedFloat<0, 0, 1, 0>) == 8,
"Recovered idBoundedFloat ABI changed");
#endif
+53
View File
@@ -0,0 +1,53 @@
#include "decay.h"
#include <cmath>
idParametricDecay::idParametricDecay()
: delta(0.0f)
, linear(0.0f)
, t0(0.0f)
, tdelta(0.0f)
, lambda(0.0f) {
}
void idParametricDecay::Init(
const float newDelta,
const float newLinear,
const float newT0,
const float newTDelta,
const float newLambda
) {
delta = newDelta;
linear = newLinear;
t0 = newT0;
tdelta = newTDelta;
lambda = newLambda;
}
void idParametricDecay::SetTZero(const float newT0) {
t0 = newT0;
}
void idParametricDecay::SetDelta(const float newDelta) {
delta = newDelta;
}
float idParametricDecay::Evaluate(const float t) const {
if (t < t0) {
return delta;
}
const float elapsed = t - t0;
if (elapsed > tdelta) {
return 0.0f;
}
const float normalizedTime = elapsed / tdelta;
const float exponential = std::pow(
0.5f,
elapsed / (lambda * tdelta)
);
return ((1.0f - linear) * exponential
+ (1.0f - normalizedTime) * linear) * delta;
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
// tungsten.exe.h type 12849.
class idParametricDecay {
public:
idParametricDecay();
void Init(float delta, float linear, float t0, float tdelta, float lambda);
void SetTZero(float t0);
void SetDelta(float delta);
float Evaluate(float t) const;
private:
float delta;
float linear;
float t0;
float tdelta;
float lambda;
};
static_assert(sizeof(idParametricDecay) == 20,
"Recovered idParametricDecay layout changed");
+87
View File
@@ -0,0 +1,87 @@
#pragma once
#include <cmath>
class idFader {
public:
enum type_t {
FADE_LINEAR = 0,
FADE_SINE = 1,
FADE_INVERSE_SINE = 2
};
idFader(const type_t fadeType = FADE_LINEAR, const float initialValue = 0.0f)
: type(fadeType), startTime(0), duration(0),
startValue(initialValue), endValue(initialValue) {
}
float GetValue(const int time) const {
switch (type) {
case FADE_SINE: return GetSine(time);
case FADE_INVERSE_SINE: return GetInverseSine(time);
default: return GetLinear(time);
}
}
float GetLinear(const int time) const {
return Interpolate(time, LinearFraction(time));
}
float GetSine(const int time) const {
const float fraction = LinearFraction(time);
const float shaped = std::sin(fraction * 1.5707963267948966f);
return Interpolate(time, shaped);
}
float GetInverseSine(const int time) const {
const float fraction = LinearFraction(time);
const float shaped = 1.0f + std::sin(
fraction * 1.5707963267948966f + 4.71238898038469f
);
return Interpolate(time, shaped);
}
void FadeTowards(const float newEndValue, const int time,
const int newDuration) {
startValue = GetValue(time);
startTime = time;
endValue = newEndValue;
duration = newDuration < 0 ? 0 : newDuration;
}
void SetType(const type_t fadeType) {
type = fadeType;
}
private:
type_t type;
int startTime;
int duration;
float startValue;
float endValue;
float LinearFraction(const int time) const {
if (time <= startTime) {
return 0.0f;
}
if (duration <= 0) {
return 1.0f;
}
if (time >= startTime + duration) {
return 1.0f;
}
return static_cast<float>(time - startTime) / static_cast<float>(duration);
}
float Interpolate(const int time, const float fraction) const {
if (time < startTime) {
return startValue;
}
if (duration <= 0 || time >= startTime + duration) {
return endValue;
}
return startValue + fraction * (endValue - startValue);
}
};
static_assert(sizeof(idFader) == 20, "Recovered idFader ABI changed");
+82
View File
@@ -0,0 +1,82 @@
#pragma once
#include "idlib/precompiled.h"
class idMat3x4 {
public:
idMat3x4() {
Identity();
}
idMat3x4(const idMat3& rotation, const idVec3& translation) {
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
mat[row * 4 + column] = rotation[column][row];
}
mat[row * 4 + 3] = translation[row];
}
}
void Identity() {
for (int index = 0; index < 12; ++index) {
mat[index] = 0.0f;
}
mat[0] = mat[5] = mat[10] = 1.0f;
}
void Transform(idVec3& result, const idVec3& value) const {
result.x = value.x * mat[0] + value.y * mat[1]
+ value.z * mat[2] + mat[3];
result.y = value.x * mat[4] + value.y * mat[5]
+ value.z * mat[6] + mat[7];
result.z = value.x * mat[8] + value.y * mat[9]
+ value.z * mat[10] + mat[11];
}
void Rotate(idMat3& result, const idMat3& value) const {
for (int column = 0; column < 3; ++column) {
result[column].x = mat[0] * value[column].x
+ mat[1] * value[column].y + mat[2] * value[column].z;
result[column].y = mat[4] * value[column].x
+ mat[5] * value[column].y + mat[6] * value[column].z;
result[column].z = mat[8] * value[column].x
+ mat[9] * value[column].y + mat[10] * value[column].z;
}
}
void Invert() {
const float old[12] = {
mat[0], mat[1], mat[2], mat[3],
mat[4], mat[5], mat[6], mat[7],
mat[8], mat[9], mat[10], mat[11]
};
mat[0] = old[0]; mat[1] = old[4]; mat[2] = old[8];
mat[4] = old[1]; mat[5] = old[5]; mat[6] = old[9];
mat[8] = old[2]; mat[9] = old[6]; mat[10] = old[10];
mat[3] = -(mat[0] * old[3] + mat[1] * old[7] + mat[2] * old[11]);
mat[7] = -(mat[4] * old[3] + mat[5] * old[7] + mat[6] * old[11]);
mat[11] = -(mat[8] * old[3] + mat[9] * old[7] + mat[10] * old[11]);
}
void LeftTransposeMultiply(const idMat3& value) {
float old[12];
for (int index = 0; index < 12; ++index) {
old[index] = mat[index];
}
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 4; ++column) {
mat[row * 4 + column] = value[0][row] * old[column]
+ value[1][row] * old[4 + column]
+ value[2][row] * old[8 + column];
}
}
}
float* ToFloatPtr() { return mat; }
const float* ToFloatPtr() const { return mat; }
private:
float mat[12];
};
static_assert(sizeof(idMat3x4) == 48, "Recovered idMat3x4 ABI changed");
+31
View File
@@ -0,0 +1,31 @@
#include "mathlib.h"
#include <cstdint>
int InterleaveBits(const int x, const int y) {
const std::uint32_t xBits = static_cast<std::uint32_t>(x);
const std::uint32_t yBits = static_cast<std::uint32_t>(y);
std::uint32_t interleaved = 0;
for (int bit = 0; bit < 16; ++bit) {
interleaved |= ((xBits >> bit) & 1u) << (bit * 2);
interleaved |= ((yBits >> bit) & 1u) << (bit * 2 + 1);
}
return static_cast<int>(interleaved);
}
void DeInterleaveBits(const int bits, int& x, int& y) {
const std::uint32_t interleaved = static_cast<std::uint32_t>(bits);
std::uint32_t xBits = 0;
std::uint32_t yBits = 0;
for (int bit = 0; bit < 16; ++bit) {
xBits |= ((interleaved >> (bit * 2)) & 1u) << bit;
yBits |= ((interleaved >> (bit * 2 + 1)) & 1u) << bit;
}
x = static_cast<int>(xBits);
y = static_cast<int>(yBits);
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
// Recovered PDB signatures:
// ?InterleaveBits@@YAHHH@Z
// ?DeInterleaveBits@@YAXHAAH0@Z
int InterleaveBits(int x, int y);
void DeInterleaveBits(int bits, int& x, int& y);
+391
View File
@@ -0,0 +1,391 @@
#include "spatialmat.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <malloc.h>
namespace {
constexpr std::size_t SPATIAL_MAT_FLOATS =
idSpatialMat::MAX_ROWS * idSpatialMat::ROW_STRIDE;
constexpr std::size_t SPATIAL_MAT_BYTES =
SPATIAL_MAT_FLOATS * sizeof(float);
float* AllocSpatialMat() {
return static_cast<float*>(_aligned_malloc(SPATIAL_MAT_BYTES, 16));
}
bool IsValidSize(const int rows, const int columns) {
return rows >= 0 && rows <= idSpatialMat::MAX_ROWS
&& columns >= 0 && columns <= idSpatialMat::MAX_COLUMNS;
}
} // namespace
idSpatialMat::idSpatialMat()
: numRows(0), numColumns(0), allocatedRows(0), mat(nullptr) {
}
idSpatialMat::idSpatialMat(const int rows, const int columns)
: idSpatialMat() {
SetSize(rows, columns);
}
idSpatialMat::idSpatialMat(const idSpatialMat& other)
: idSpatialMat() {
*this = other;
}
idSpatialMat::~idSpatialMat() {
if (mat != nullptr && allocatedRows > 0) {
_aligned_free(mat);
}
}
idSpatialMat& idSpatialMat::operator=(const idSpatialMat& other) {
if (this == &other) {
return *this;
}
SetSize(other.numRows, other.numColumns);
if (mat != nullptr && other.mat != nullptr) {
std::memcpy(mat, other.mat, SPATIAL_MAT_BYTES);
}
return *this;
}
void idSpatialMat::SetSize(const int rows, const int columns) {
if (!IsValidSize(rows, columns)) {
numRows = 0;
numColumns = 0;
return;
}
if (mat == nullptr) {
mat = AllocSpatialMat();
if (mat == nullptr) {
numRows = 0;
numColumns = 0;
allocatedRows = 0;
return;
}
allocatedRows = MAX_ROWS;
std::memset(mat, 0, SPATIAL_MAT_BYTES);
}
numRows = rows;
numColumns = columns;
ClearPadding();
}
void idSpatialMat::ChangeNumRows(const int rows) {
if (rows < 0 || rows > MAX_ROWS || mat == nullptr) {
return;
}
if (rows != numRows) {
const int firstClearedRow = std::min(rows, numRows);
const int rowCount = std::max(rows, numRows) - firstClearedRow;
if (rowCount > 0) {
std::memset(mat + firstClearedRow * ROW_STRIDE, 0,
static_cast<std::size_t>(rowCount * ROW_STRIDE) * sizeof(float));
}
numRows = rows;
}
}
void idSpatialMat::Zero(const int rows, const int columns) {
SetSize(rows, columns);
Zero();
}
void idSpatialMat::Zero() {
if (mat != nullptr) {
std::memset(mat, 0, SPATIAL_MAT_BYTES);
}
}
void idSpatialMat::Set(const idMat3& m1, const idMat3& m2) {
SetSize(3, 6);
if (mat == nullptr) {
return;
}
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
(*this)(row, column) = m1[row][column];
(*this)(row, column + 3) = m2[row][column];
}
}
}
void idSpatialMat::Set(const idMat3& m1, const idMat3& m2,
const idMat3& m3, const idMat3& m4) {
SetSize(6, 6);
if (mat == nullptr) {
return;
}
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
(*this)(row, column) = m1[row][column];
(*this)(row, column + 3) = m2[row][column];
(*this)(row + 3, column) = m3[row][column];
(*this)(row + 3, column + 3) = m4[row][column];
}
}
}
void idSpatialMat::SetData(const int rows, const int columns, float* data) {
if (!IsValidSize(rows, columns) || data == nullptr) {
return;
}
if (mat != nullptr && allocatedRows > 0) {
_aligned_free(mat);
}
numRows = rows;
numColumns = columns;
allocatedRows = -MAX_ROWS;
mat = data;
ClearPadding();
}
void idSpatialMat::ClearPadding() {
if (mat == nullptr) {
return;
}
for (int row = 0; row < MAX_ROWS; ++row) {
const int first = row < numRows ? numColumns : 0;
std::fill(mat + row * ROW_STRIDE + first,
mat + (row + 1) * ROW_STRIDE, 0.0f);
}
}
void idSpatialMat::Negate() {
if (mat == nullptr) {
return;
}
for (int row = 0; row < numRows; ++row) {
for (int column = 0; column < ROW_STRIDE; ++column) {
(*this)[row][column] = -(*this)[row][column];
}
}
}
void idSpatialMat::Transpose(idSpatialMat& dst) const {
float values[MAX_ROWS][MAX_COLUMNS] = {};
for (int row = 0; row < numRows; ++row) {
for (int column = 0; column < numColumns; ++column) {
values[column][row] = (*this)(row, column);
}
}
dst.Zero(numColumns, numRows);
for (int row = 0; row < numColumns; ++row) {
for (int column = 0; column < numRows; ++column) {
dst(row, column) = values[row][column];
}
}
}
void idSpatialMat::Subtract(const idSpatialMat& other) {
if (mat == nullptr || other.mat == nullptr
|| numRows != other.numRows || numColumns != other.numColumns) {
return;
}
for (int index = 0; index < MAX_ROWS * ROW_STRIDE; ++index) {
mat[index] -= other.mat[index];
}
}
void idSpatialMat::Multiply(idSpatialVec& dst, const idSpatialVec& vec) const {
float result[MAX_ROWS] = {};
const int terms = std::min(numColumns, vec.GetSize());
for (int row = 0; row < numRows; ++row) {
for (int column = 0; column < terms; ++column) {
result[row] += (*this)(row, column) * vec[column];
}
}
dst.SetSize(numRows);
for (int row = 0; row < numRows; ++row) {
dst[row] = result[row];
}
}
void idSpatialMat::MultiplyAdd(idSpatialVec& dst, const idSpatialVec& vec) const {
if (dst.GetSize() < numRows) {
dst.SetSize(numRows);
}
const int terms = std::min(numColumns, vec.GetSize());
for (int row = 0; row < numRows; ++row) {
float value = 0.0f;
for (int column = 0; column < terms; ++column) {
value += (*this)(row, column) * vec[column];
}
dst[row] += value;
}
}
void idSpatialMat::MultiplySub(idSpatialVec& dst, const idSpatialVec& vec) const {
if (dst.GetSize() < numRows) {
dst.SetSize(numRows);
}
const int terms = std::min(numColumns, vec.GetSize());
for (int row = 0; row < numRows; ++row) {
float value = 0.0f;
for (int column = 0; column < terms; ++column) {
value += (*this)(row, column) * vec[column];
}
dst[row] -= value;
}
}
void idSpatialMat::TransposeMultiplyAdd(idSpatialVec& dst,
const idSpatialVec& vec) const {
if (dst.GetSize() < numColumns) {
dst.SetSize(numColumns);
}
const int terms = std::min(numRows, vec.GetSize());
for (int column = 0; column < numColumns; ++column) {
float value = 0.0f;
for (int row = 0; row < terms; ++row) {
value += (*this)(row, column) * vec[row];
}
dst[column] += value;
}
}
void idSpatialMat::TransposeMultiplySub(idSpatialVec& dst,
const idSpatialVec& vec) const {
if (dst.GetSize() < numColumns) {
dst.SetSize(numColumns);
}
const int terms = std::min(numRows, vec.GetSize());
for (int column = 0; column < numColumns; ++column) {
float value = 0.0f;
for (int row = 0; row < terms; ++row) {
value += (*this)(row, column) * vec[row];
}
dst[column] -= value;
}
}
void idSpatialMat::Multiply(idSpatialMat& dst,
const idSpatialMat& other) const {
if (numColumns != other.numRows) {
dst.Zero(0, 0);
return;
}
float values[MAX_ROWS][MAX_COLUMNS] = {};
for (int row = 0; row < numRows; ++row) {
for (int column = 0; column < other.numColumns; ++column) {
for (int term = 0; term < numColumns; ++term) {
values[row][column] +=
(*this)(row, term) * other(term, column);
}
}
}
dst.Zero(numRows, other.numColumns);
for (int row = 0; row < numRows; ++row) {
for (int column = 0; column < other.numColumns; ++column) {
dst(row, column) = values[row][column];
}
}
}
void idSpatialMat::TransposeMultiply(idSpatialMat& dst,
const idSpatialMat& other) const {
if (numRows != other.numRows) {
dst.Zero(0, 0);
return;
}
float values[MAX_ROWS][MAX_COLUMNS] = {};
for (int row = 0; row < numColumns; ++row) {
for (int column = 0; column < other.numColumns; ++column) {
for (int term = 0; term < numRows; ++term) {
values[row][column] +=
(*this)(term, row) * other(term, column);
}
}
}
dst.Zero(numColumns, other.numColumns);
for (int row = 0; row < numColumns; ++row) {
for (int column = 0; column < other.numColumns; ++column) {
dst(row, column) = values[row][column];
}
}
}
bool idSpatialMat::Inverse(idSpatialMat& dst) const {
if (numRows != numColumns || numRows < 1 || numRows > MAX_ROWS) {
return false;
}
switch (numRows) {
case 1: return Inverse1x1(dst);
case 2: return Inverse2x2(dst);
case 3: return Inverse3x3(dst);
case 4: return Inverse4x4(dst);
case 5: return Inverse5x5(dst);
case 6: return Inverse6x6(dst);
default: return false;
}
}
bool idSpatialMat::InverseNxN(idSpatialMat& dst, const int dimension) const {
double work[MAX_ROWS][MAX_ROWS * 2] = {};
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
work[row][column] = (*this)(row, column);
}
work[row][dimension + row] = 1.0;
}
for (int pivotColumn = 0; pivotColumn < dimension; ++pivotColumn) {
int pivotRow = pivotColumn;
for (int row = pivotColumn + 1; row < dimension; ++row) {
if (std::fabs(work[row][pivotColumn])
> std::fabs(work[pivotRow][pivotColumn])) {
pivotRow = row;
}
}
if (std::fabs(work[pivotRow][pivotColumn]) < 1.0e-14) {
return false;
}
if (pivotRow != pivotColumn) {
for (int column = 0; column < dimension * 2; ++column) {
std::swap(work[pivotRow][column], work[pivotColumn][column]);
}
}
const double reciprocal = 1.0 / work[pivotColumn][pivotColumn];
for (int column = 0; column < dimension * 2; ++column) {
work[pivotColumn][column] *= reciprocal;
}
for (int row = 0; row < dimension; ++row) {
if (row == pivotColumn) {
continue;
}
const double scale = work[row][pivotColumn];
for (int column = 0; column < dimension * 2; ++column) {
work[row][column] -= scale * work[pivotColumn][column];
}
}
}
dst.Zero(dimension, dimension);
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
dst(row, column) = static_cast<float>(work[row][dimension + column]);
}
}
return true;
}
bool idSpatialMat::Inverse1x1(idSpatialMat& dst) const { return InverseNxN(dst, 1); }
bool idSpatialMat::Inverse2x2(idSpatialMat& dst) const { return InverseNxN(dst, 2); }
bool idSpatialMat::Inverse3x3(idSpatialMat& dst) const { return InverseNxN(dst, 3); }
bool idSpatialMat::Inverse4x4(idSpatialMat& dst) const { return InverseNxN(dst, 4); }
bool idSpatialMat::Inverse5x5(idSpatialMat& dst) const { return InverseNxN(dst, 5); }
bool idSpatialMat::Inverse6x6(idSpatialMat& dst) const { return InverseNxN(dst, 6); }
idSpatialVec idSpatialMat::SubSpatialVec(const int row) const {
idSpatialVec result;
if (mat != nullptr && row >= 0 && row < numRows) {
result.SetData(MAX_COLUMNS, mat + row * ROW_STRIDE);
}
return result;
}
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include "idlib/precompiled.h"
#include "spatialvec.h"
// Tungsten stores every spatial matrix in a six-row, eight-float-stride slab.
// The two padding floats per row are intentional: the Xenon implementation
// loads complete VMX vectors from each half-row.
class idSpatialMat {
public:
static const int MAX_ROWS = 6;
static const int MAX_COLUMNS = 6;
static const int ROW_STRIDE = 8;
idSpatialMat();
idSpatialMat(int rows, int columns);
idSpatialMat(const idSpatialMat& other);
~idSpatialMat();
idSpatialMat& operator=(const idSpatialMat& other);
void SetSize(int rows, int columns);
void ChangeNumRows(int rows);
void Zero(int rows, int columns);
void Zero();
void Set(const idMat3& m1, const idMat3& m2);
void Set(const idMat3& m1, const idMat3& m2,
const idMat3& m3, const idMat3& m4);
void SetData(int rows, int columns, float* data);
void Negate();
void Transpose(idSpatialMat& dst) const;
void Subtract(const idSpatialMat& other);
void Multiply(idSpatialVec& dst, const idSpatialVec& vec) const;
void MultiplyAdd(idSpatialVec& dst, const idSpatialVec& vec) const;
void MultiplySub(idSpatialVec& dst, const idSpatialVec& vec) const;
void TransposeMultiplyAdd(idSpatialVec& dst, const idSpatialVec& vec) const;
void TransposeMultiplySub(idSpatialVec& dst, const idSpatialVec& vec) const;
void Multiply(idSpatialMat& dst, const idSpatialMat& other) const;
void TransposeMultiply(idSpatialMat& dst, const idSpatialMat& other) const;
bool Inverse(idSpatialMat& dst) const;
idSpatialVec SubSpatialVec(int row) const;
int GetNumRows() const { return numRows; }
int GetNumColumns() const { return numColumns; }
int GetAllocatedRows() const { return allocatedRows; }
float* ToFloatPtr() { return mat; }
const float* ToFloatPtr() const { return mat; }
float* operator[](int row) { return mat + row * ROW_STRIDE; }
const float* operator[](int row) const { return mat + row * ROW_STRIDE; }
float& operator()(int row, int column) { return mat[row * ROW_STRIDE + column]; }
float operator()(int row, int column) const { return mat[row * ROW_STRIDE + column]; }
private:
bool InverseNxN(idSpatialMat& dst, int dimension) const;
bool Inverse1x1(idSpatialMat& dst) const;
bool Inverse2x2(idSpatialMat& dst) const;
bool Inverse3x3(idSpatialMat& dst) const;
bool Inverse4x4(idSpatialMat& dst) const;
bool Inverse5x5(idSpatialMat& dst) const;
bool Inverse6x6(idSpatialMat& dst) const;
void ClearPadding();
int numRows;
int numColumns;
int allocatedRows;
float* mat;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idSpatialMat) == 16, "Recovered idSpatialMat ABI changed");
#endif
+153
View File
@@ -0,0 +1,153 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
class idSpatialVec {
public:
idSpatialVec()
: size(0), allocated(0), p(nullptr) {
}
explicit idSpatialVec(const int length)
: idSpatialVec() {
SetSize(length);
}
idSpatialVec(const idSpatialVec& other)
: idSpatialVec() {
SetSize(other.size);
if (p != nullptr && other.p != nullptr) {
std::memcpy(p, other.p, sizeof(float) * other.size);
}
}
idSpatialVec(idSpatialVec&& other) noexcept
: size(other.size), allocated(other.allocated), p(other.p) {
other.size = 0;
other.allocated = 0;
other.p = nullptr;
}
~idSpatialVec() {
if (allocated > 0) {
std::free(p);
}
}
idSpatialVec& operator=(const idSpatialVec& other) {
if (this != &other) {
SetSize(other.size);
if (p != nullptr && other.p != nullptr) {
std::memcpy(p, other.p, sizeof(float) * other.size);
}
}
return *this;
}
idSpatialVec& operator=(idSpatialVec&& other) noexcept {
if (this != &other) {
if (allocated > 0) {
std::free(p);
}
size = other.size;
allocated = other.allocated;
p = other.p;
other.size = 0;
other.allocated = 0;
other.p = nullptr;
}
return *this;
}
void SetData(const int length, float* data) {
if (allocated > 0) {
std::free(p);
}
p = data;
size = static_cast<std::int16_t>(std::max(0, length));
allocated = static_cast<std::int16_t>(-std::max(8, length));
if (p != nullptr) {
for (int index = size; index < -allocated; ++index) {
p[index] = 0.0f;
}
}
}
bool SetSize(const int newSize) {
if (newSize < 0 || newSize > 32767) {
return false;
}
const int capacity = allocated < 0 ? -allocated : allocated;
if (p == nullptr || newSize > capacity) {
const int newCapacity = std::max(8, (newSize + 7) & ~7);
float* const replacement = static_cast<float*>(
std::calloc(static_cast<std::size_t>(newCapacity), sizeof(float))
);
if (replacement == nullptr) {
return false;
}
if (p != nullptr) {
std::memcpy(replacement, p,
sizeof(float) * static_cast<std::size_t>(std::min<int>(size, newSize)));
}
if (allocated > 0) {
std::free(p);
}
p = replacement;
allocated = static_cast<std::int16_t>(newCapacity);
} else if (newSize > size) {
std::memset(p + size, 0,
sizeof(float) * static_cast<std::size_t>(newSize - size));
}
size = static_cast<std::int16_t>(newSize);
return true;
}
void ChangeSize(const int newSize) {
SetSize(newSize);
}
void Zero() {
if (p != nullptr) {
std::memset(p, 0, sizeof(float) * static_cast<std::size_t>(size));
}
}
void Clamp(const float minimum, const float maximum) {
for (int index = 0; index < size; ++index) {
p[index] = std::max(minimum, std::min(maximum, p[index]));
}
}
float LengthSqr() const {
float sum = 0.0f;
for (int index = 0; index < size; ++index) {
sum += p[index] * p[index];
}
return sum;
}
float Length() const {
return std::sqrt(LengthSqr());
}
int GetSize() const { return size; }
float* ToFloatPtr() { return p; }
const float* ToFloatPtr() const { return p; }
float& operator[](const int index) { return p[index]; }
float operator[](const int index) const { return p[index]; }
private:
std::int16_t size;
std::int16_t allocated;
float* p;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idSpatialVec) == 8, "Recovered idSpatialVec ABI changed");
#endif
+117
View File
@@ -0,0 +1,117 @@
#pragma once
#include "vector.h"
#include <algorithm>
#include <cmath>
template<typename vectorType>
struct idSpringDimension;
template<> struct idSpringDimension<idVec1> { static constexpr int value = 1; };
template<> struct idSpringDimension<idVec2> { static constexpr int value = 2; };
template<> struct idSpringDimension<idVec3> { static constexpr int value = 3; };
template<typename vectorType>
class idSpring {
public:
idSpring()
: maxSpeed(0.0f), hasPMax(false), hasPMin(false),
k(1.0f), c(2.0f), m(1.0f), restLength(0.0f) {
p0.Zero();
p1.Zero();
vel.Zero();
pMin.Zero();
pMax.Zero();
}
void SetConstants(float springConstant, const float dampingConstant) {
k = std::min(10000.0f, std::max(0.0f, springConstant));
c = dampingConstant < 0.0f ? 2.0f * std::sqrt(m * k)
: dampingConstant;
}
void SetMass(const float mass) { m = mass > 0.0f ? mass : 1.0f; }
void SetRestLength(const float length) { restLength = std::max(0.0f, length); }
void SetMaxSpeed(const float speed) { maxSpeed = speed; }
void SetAnchor(const vectorType& anchor) { p0 = anchor; }
void SetPosition(const vectorType& position) { p1 = position; }
void SetVelocity(const vectorType& velocity) { vel = velocity; }
void SetMinimum(const vectorType& minimum) { pMin = minimum; hasPMin = true; }
void SetMaximum(const vectorType& maximum) { pMax = maximum; hasPMax = true; }
void ClearMinimum() { hasPMin = false; }
void ClearMaximum() { hasPMax = false; }
const vectorType& GetPosition() const { return p1; }
const vectorType& GetVelocity() const { return vel; }
void Update(float deltaTime) {
while (deltaTime > 0.0f) {
const float step = std::min(deltaTime, 0.0085f);
deltaTime -= step;
float distanceSquared = 0.0f;
float difference[idSpringDimension<vectorType>::value];
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
difference[index] = p1[index] - p0[index];
distanceSquared += difference[index] * difference[index];
}
const float distance = std::sqrt(distanceSquared);
const float inverseDistance = distance > 0.00001f ? 1.0f / distance : 0.0f;
const float springForce = -(distance - restLength) * k;
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
const float force = difference[index] * inverseDistance * springForce
- vel[index] * c;
vel[index] += (force / m) * step;
}
float speedSquared = 0.0f;
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
speedSquared += vel[index] * vel[index];
}
const float speed = std::sqrt(speedSquared);
if (maxSpeed > 0.0f && speed > maxSpeed) {
const float scale = maxSpeed / speed;
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
vel[index] *= scale;
}
}
if (speed < 0.00001f) {
vel.Zero();
}
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
p1[index] += vel[index] * step;
}
if (distance < 0.00001f) {
p1 = p0;
}
}
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
if (hasPMin) {
p1[index] = std::max(p1[index], pMin[index]);
}
if (hasPMax) {
p1[index] = std::min(p1[index], pMax[index]);
}
}
}
private:
vectorType p0;
vectorType p1;
vectorType vel;
float maxSpeed;
vectorType pMin;
vectorType pMax;
bool hasPMax;
bool hasPMin;
float k;
float c;
float m;
float restLength;
};
static_assert(sizeof(idSpring<idVec1>) == 44, "Recovered idSpring<idVec1> ABI changed");
static_assert(sizeof(idSpring<idVec2>) == 64, "Recovered idSpring<idVec2> ABI changed");
static_assert(sizeof(idSpring<idVec3>) == 84, "Recovered idSpring<idVec3> ABI changed");
+329
View File
@@ -0,0 +1,329 @@
#pragma once
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <cstring>
class idVec1 {
public:
float x;
idVec1() = default;
explicit idVec1(const float newX) : x(newX) {}
void Zero() { x = 0.0f; }
float operator[](const int) const { return x; }
float& operator[](const int) { return x; }
};
static_assert(sizeof(idVec1) == 4, "Recovered idVec1 layout changed");
// Minimal recovered ABI surface for tungsten's idVec2. More vector operations
// will move here as their out-of-line idTech 5 implementations are activated.
class idVec2 {
public:
float x;
float y;
idVec2() = default;
idVec2(const float newX, const float newY)
: x(newX)
, y(newY) {
}
void Set(const float newX, const float newY) {
x = newX;
y = newY;
}
void Zero() {
x = 0.0f;
y = 0.0f;
}
float operator[](const int index) const {
assert(index >= 0 && index < 2);
return (&x)[index];
}
float& operator[](const int index) {
assert(index >= 0 && index < 2);
return (&x)[index];
}
};
static_assert(sizeof(idVec2) == 8, "Recovered idVec2 layout changed");
// Minimal recovered ABI surface for tungsten's idVec3. The class deliberately
// stays a three-float POD layout; Xbox-only SIMD assumptions belong in the PC
// portability layer rather than in this type.
class idVec3 {
public:
float x;
float y;
float z;
idVec3() = default;
idVec3(const float newX, const float newY, const float newZ)
: x(newX)
, y(newY)
, z(newZ) {
}
void Set(const float newX, const float newY, const float newZ) {
x = newX;
y = newY;
z = newZ;
}
void Zero() {
x = 0.0f;
y = 0.0f;
z = 0.0f;
}
float operator[](const int index) const {
assert(index >= 0 && index < 3);
return (&x)[index];
}
float& operator[](const int index) {
assert(index >= 0 && index < 3);
return (&x)[index];
}
idVec3 operator-() const {
return idVec3(-x, -y, -z);
}
idVec3 operator+(const idVec3& other) const {
return idVec3(x + other.x, y + other.y, z + other.z);
}
idVec3 operator-(const idVec3& other) const {
return idVec3(x - other.x, y - other.y, z - other.z);
}
idVec3 operator*(const float scale) const {
return idVec3(x * scale, y * scale, z * scale);
}
float Dot(const idVec3& other) const {
return x * other.x + y * other.y + z * other.z;
}
idVec3 Cross(const idVec3& other) const {
return idVec3(
y * other.z - z * other.y,
z * other.x - x * other.z,
x * other.y - y * other.x
);
}
float LengthSqr() const {
return Dot(*this);
}
float Length() const {
return std::sqrt(LengthSqr());
}
};
static_assert(sizeof(idVec3) == 12, "Recovered idVec3 layout changed");
class idMat3 {
public:
idVec3 mat[3];
idMat3() = default;
explicit idMat3(float diagonal) {
mat[0].Set(diagonal, 0.0f, 0.0f);
mat[1].Set(0.0f, diagonal, 0.0f);
mat[2].Set(0.0f, 0.0f, diagonal);
}
idVec3& operator[](const int index) { return mat[index]; }
const idVec3& operator[](const int index) const { return mat[index]; }
};
static_assert(sizeof(idMat3) == 36, "Recovered idMat3 layout changed");
class idVec4 {
public:
float x;
float y;
float z;
float w;
idVec4() = default;
idVec4(
const float newX,
const float newY,
const float newZ,
const float newW
)
: x(newX)
, y(newY)
, z(newZ)
, w(newW) {
}
void Set(
const float newX,
const float newY,
const float newZ,
const float newW
) {
x = newX;
y = newY;
z = newZ;
w = newW;
}
float operator[](const int index) const {
assert(index >= 0 && index < 4);
return (&x)[index];
}
float& operator[](const int index) {
assert(index >= 0 && index < 4);
return (&x)[index];
}
};
static_assert(sizeof(idVec4) == 16, "Recovered idVec4 layout changed");
class idAngles {
public:
float pitch;
float yaw;
float roll;
idAngles() = default;
idAngles(const float newPitch, const float newYaw, const float newRoll)
: pitch(newPitch), yaw(newYaw), roll(newRoll) {
}
float operator[](const int index) const { return (&pitch)[index]; }
float& operator[](const int index) { return (&pitch)[index]; }
};
static_assert(sizeof(idAngles) == 12, "Recovered idAngles layout changed");
class idQuat {
public:
float x;
float y;
float z;
float w;
idQuat() = default;
idQuat(const float newX, const float newY, const float newZ, const float newW)
: x(newX), y(newY), z(newZ), w(newW) {
}
float operator[](const int index) const { return (&x)[index]; }
float& operator[](const int index) { return (&x)[index]; }
};
static_assert(sizeof(idQuat) == 16, "Recovered idQuat layout changed");
// The Xbox 360 type-information stream serializes the dynamic math types by
// their three/four-field facades. Keep these definitions allocation-simple on
// the standalone recovery targets; the complete idLib target uses BFG's
// layout-compatible implementations.
class idVecX {
public:
idVecX() : size(0), alloced(0), p(nullptr) {}
explicit idVecX(const int newSize) : idVecX() { SetSize(newSize); }
idVecX(const idVecX& other) : idVecX() {
SetSize(other.size);
if (size > 0) std::memcpy(p, other.p, sizeof(float) * size);
}
~idVecX() { std::free(p); }
idVecX& operator=(const idVecX& other) {
if (this != &other) {
SetSize(other.size);
if (size > 0) std::memcpy(p, other.p, sizeof(float) * size);
}
return *this;
}
void SetSize(const int newSize) {
const int safeSize = newSize > 0 ? newSize : 0;
if (safeSize > alloced) {
float* const replacement = static_cast<float*>(
std::realloc(p, sizeof(float) * safeSize));
if (replacement == nullptr) return;
p = replacement;
alloced = safeSize;
}
size = safeSize;
}
int GetSize() const { return size; }
float& operator[](const int index) { return p[index]; }
float operator[](const int index) const { return p[index]; }
private:
int size;
int alloced;
float* p;
};
static_assert(sizeof(idVecX) == 12, "Recovered idVecX layout changed");
class idMatX {
public:
idMatX() : numRows(0), numColumns(0), alloced(0), mat(nullptr) {}
idMatX(const int rows, const int columns) : idMatX() {
SetSize(rows, columns);
}
idMatX(const idMatX& other) : idMatX() {
SetSize(other.numRows, other.numColumns);
const int count = numRows * numColumns;
if (count > 0) std::memcpy(mat, other.mat, sizeof(float) * count);
}
~idMatX() { std::free(mat); }
idMatX& operator=(const idMatX& other) {
if (this != &other) {
SetSize(other.numRows, other.numColumns);
const int count = numRows * numColumns;
if (count > 0) std::memcpy(mat, other.mat, sizeof(float) * count);
}
return *this;
}
void SetSize(const int rows, const int columns) {
const int safeRows = rows > 0 ? rows : 0;
const int safeColumns = columns > 0 ? columns : 0;
const int count = safeRows * safeColumns;
if (count > alloced) {
float* const replacement = static_cast<float*>(
std::realloc(mat, sizeof(float) * count));
if (replacement == nullptr) return;
mat = replacement;
alloced = count;
}
numRows = safeRows;
numColumns = safeColumns;
}
int GetNumRows() const { return numRows; }
int GetNumColumns() const { return numColumns; }
float* operator[](const int row) { return mat + row * numColumns; }
const float* operator[](const int row) const {
return mat + row * numColumns;
}
private:
int numRows;
int numColumns;
int alloced;
float* mat;
};
static_assert(sizeof(idMatX) == 16, "Recovered idMatX layout changed");
@@ -0,0 +1,84 @@
#include "metricrecord.h"
#include "metricsframework.h"
#include <cstdio>
#include <cstring>
namespace {
FILE* MetricStream(idMetricFile* metricFile) {
return metricFile == nullptr ? nullptr
: static_cast<FILE*>(metricFile->fileHandle);
}
void WriteU32(FILE* stream, const std::uint32_t value) {
unsigned char bytes[4] = {
static_cast<unsigned char>(value >> 0),
static_cast<unsigned char>(value >> 8),
static_cast<unsigned char>(value >> 16),
static_cast<unsigned char>(value >> 24)
};
std::fwrite(bytes, 1, sizeof(bytes), stream);
}
void WriteU64(FILE* stream, const std::uint64_t value) {
WriteU32(stream, static_cast<std::uint32_t>(value));
WriteU32(stream, static_cast<std::uint32_t>(value >> 32));
}
void WriteString(FILE* stream, const char* text) {
const char* const safeText = text == nullptr ? "" : text;
const std::uint32_t length = static_cast<std::uint32_t>(std::strlen(safeText));
WriteU32(stream, length);
if (length > 0) std::fwrite(safeText, 1, length, stream);
}
} // namespace
idMetricRecord::idMetricRecord(const idStr& recordName)
: name(recordName), baseName(recordName) {
}
void idMetricRecord::WriteHeader(idMetricFile* metricFile) {
FILE* const stream = MetricStream(metricFile);
if (stream == nullptr) return;
WriteU32(stream, 1);
WriteString(stream, "entryTime");
WriteU32(stream, 0);
}
void idMetricRecord::SerializeEntry(idMetricFile* metricFile) {
FILE* const stream = MetricStream(metricFile);
if (stream == nullptr) return;
const std::uint64_t now = metricsFrameworkLocal.GetCurrentSystemTime();
WriteU32(stream, static_cast<std::uint32_t>(now - metricFile->startTime));
}
void idMetricRecord::AppendToName(const idStr& suffix) {
name = baseName;
name.Append(suffix);
}
void idMetricFile::WriteFileInfo(const idStr& metricName) {
FILE* const stream = MetricStream(this);
if (stream == nullptr) return;
startTime = metricsFrameworkLocal.GetCurrentSystemTime();
WriteString(stream, "IDMETRIC");
WriteU32(stream, 1);
WriteString(stream, metricName.c_str());
WriteU64(stream, startTime);
}
void idMetricRecord::Serialize() {
if (!metricsFrameworkLocal.IsRecording()) return;
idMetricFile* const metricFile = metricsFrameworkLocal.GetFileHandle(name);
if (metricFile == nullptr || metricFile->fileHandle == nullptr) return;
if (!metricFile->headerWritten) {
metricFile->headerWritten = true;
metricFile->WriteFileInfo(name);
WriteHeader(metricFile);
}
SerializeEntry(metricFile);
}
@@ -0,0 +1,38 @@
#pragma once
#include "idlib/text/str.h"
#include <cstdint>
struct idMetricFile {
idMetricFile() : fileHandle(nullptr), headerWritten(false), startTime(0) {}
void WriteFileInfo(const idStr& metricName);
void* fileHandle;
bool headerWritten;
std::uint64_t startTime;
};
class idMetricRecord {
public:
explicit idMetricRecord(const idStr& recordName);
virtual ~idMetricRecord() = default;
virtual void WriteHeader(idMetricFile* metricFile);
virtual void SerializeEntry(idMetricFile* metricFile);
void AppendToName(const idStr& suffix);
void Serialize();
const idStr& GetName() const { return name; }
protected:
idStr name;
idStr baseName;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idMetricFile) == 16, "Recovered idMetricFile ABI changed");
static_assert(sizeof(idMetricRecord) == 68,
"Recovered idMetricRecord ABI changed");
#endif
+83
View File
@@ -0,0 +1,83 @@
#include "metrics.h"
#include "metricsframework.h"
#include <cmath>
#include <cstdio>
#include <cstring>
namespace {
FILE* MetricStream(idMetricFile* metricFile) {
return metricFile == nullptr ? nullptr
: static_cast<FILE*>(metricFile->fileHandle);
}
void WriteU32(FILE* stream, const std::uint32_t value) {
std::fwrite(&value, 1, sizeof(value), stream);
}
void WriteFloat(FILE* stream, const float value) {
std::fwrite(&value, 1, sizeof(value), stream);
}
void WriteString(FILE* stream, const char* text) {
const std::uint32_t length = static_cast<std::uint32_t>(std::strlen(text));
WriteU32(stream, length);
std::fwrite(text, 1, length, stream);
}
} // namespace
idGaugeMetric::idGaugeMetric(const idStr& shortName, const idStr&)
: idMetricRecord(shortName), count(0), min(0.0f), max(0.0f), mean(0.0f),
mean2(0.0f), m2(0.0f), handle(-1), lastPushFrame(0), gameState(-1) {
}
bool idGaugeMetric::CheckPushState() {
const int pushFrame = metricsFrameworkLocal.GetPushFrame();
if (pushFrame <= lastPushFrame || count == 0) return false;
Serialize();
lastPushFrame = pushFrame;
count = 0;
min = max = mean = mean2 = m2 = 0.0f;
return true;
}
void idGaugeMetric::Log(const float value, const bool skipPush) {
if (count == 0) min = max = value;
else {
if (value < min) min = value;
if (value > max) max = value;
}
const float delta = value - mean;
++count;
mean += delta / static_cast<float>(count);
m2 += delta * (value - mean);
mean2 += value * value;
if (!skipPush) CheckPushState();
}
void idGaugeMetric::WriteHeader(idMetricFile* metricFile) {
FILE* const stream = MetricStream(metricFile);
if (stream == nullptr) return;
idMetricRecord::WriteHeader(metricFile);
WriteU32(stream, 5);
const char* const names[] = { "count", "min", "max", "mean", "stdDev" };
for (int index = 0; index < 5; ++index) {
WriteString(stream, names[index]);
WriteU32(stream, index == 0 ? 0u : 1u);
}
}
void idGaugeMetric::SerializeEntry(idMetricFile* metricFile) {
FILE* const stream = MetricStream(metricFile);
if (stream == nullptr) return;
idMetricRecord::SerializeEntry(metricFile);
WriteU32(stream, static_cast<std::uint32_t>(count));
WriteFloat(stream, min);
WriteFloat(stream, max);
WriteFloat(stream, mean);
WriteFloat(stream, count > 0 ? std::sqrt(mean2 / count) : 0.0f);
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "metricrecord.h"
class idGaugeMetric : public idMetricRecord {
public:
idGaugeMetric(const idStr& shortName, const idStr& description = idStr());
bool CheckPushState();
void Log(float value, bool skipPush = false);
void WriteHeader(idMetricFile* metricFile) override;
void SerializeEntry(idMetricFile* metricFile) override;
int GetCount() const { return count; }
float GetMin() const { return min; }
float GetMax() const { return max; }
float GetMean() const { return mean; }
private:
int count;
float min;
float max;
float mean;
float mean2;
float m2;
int handle;
int lastPushFrame;
int gameState;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idGaugeMetric) == 104,
"Recovered idGaugeMetric ABI changed");
#endif
@@ -0,0 +1,201 @@
#include "metricsframework.h"
#include "idlib/sys/sys_time.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
namespace {
struct metricSlot_t {
idMetricsFramework* owner;
char name[128];
char path[MAX_PATH];
idMetricFile file;
};
struct frameworkConfig_t {
idMetricsFramework* owner;
char directory[MAX_PATH];
int heartbeatMilliseconds;
};
metricSlot_t metricSlots[64] = {};
frameworkConfig_t frameworkConfigs[8] = {};
frameworkConfig_t& ConfigFor(idMetricsFramework* owner) {
for (frameworkConfig_t& config : frameworkConfigs) {
if (config.owner == owner) return config;
}
for (frameworkConfig_t& config : frameworkConfigs) {
if (config.owner == nullptr) {
config.owner = owner;
std::strcpy(config.directory, "metrics");
config.heartbeatMilliseconds = 250;
return config;
}
}
return frameworkConfigs[0];
}
void WriteU32(FILE* stream, const std::uint32_t value) {
std::fwrite(&value, 1, sizeof(value), stream);
}
void WriteString(FILE* stream, const char* text) {
const std::uint32_t length = static_cast<std::uint32_t>(std::strlen(text));
WriteU32(stream, length);
std::fwrite(text, 1, length, stream);
}
void SanitizeName(const char* source, char* destination, const int capacity) {
int out = 0;
for (const char* cursor = source; *cursor != '\0' && out + 1 < capacity;
++cursor) {
const unsigned char value = static_cast<unsigned char>(*cursor);
destination[out++] = (value >= 'a' && value <= 'z')
|| (value >= 'A' && value <= 'Z')
|| (value >= '0' && value <= '9') || value == '-' || value == '_'
? static_cast<char>(value) : '_';
}
destination[out] = '\0';
}
} // namespace
idMetricsFramework metricsFrameworkLocal;
idMetricsFramework::MachineInfo::MachineInfo()
: idMetricRecord(idStr("MachineInfo")) {
}
void idMetricsFramework::MachineInfo::WriteHeader(idMetricFile* metricFile) {
FILE* const stream = metricFile == nullptr ? nullptr
: static_cast<FILE*>(metricFile->fileHandle);
if (stream == nullptr) return;
idMetricRecord::WriteHeader(metricFile);
WriteU32(stream, 1);
WriteString(stream, "BuildInfo");
WriteU32(stream, 6);
}
void idMetricsFramework::MachineInfo::SerializeEntry(idMetricFile* metricFile) {
FILE* const stream = metricFile == nullptr ? nullptr
: static_cast<FILE*>(metricFile->fileHandle);
if (stream == nullptr) return;
idMetricRecord::SerializeEntry(metricFile);
WriteString(stream, "tech5-recovery-pc");
}
idMetricsFramework::idMetricsFramework()
: currentPushTime(0), currentWriteTime(0), currentPushFrame(0),
lastCheckedPushFrame(0), currentState(0), baseHandle(0),
isRecording(false), fileTable{nullptr, 256, 0, 255}, stateStream() {
ConfigFor(this);
}
idMetricsFramework::~idMetricsFramework() {
MetricsStop();
for (frameworkConfig_t& config : frameworkConfigs) {
if (config.owner == this) {
std::memset(&config, 0, sizeof(config));
break;
}
}
}
std::uint64_t idMetricsFramework::GetCurrentSystemTime() const {
constexpr std::uint64_t WINDOWS_TO_UNIX_100NS = 116444736000000000ULL;
const std::uint64_t systemTime = Sys_CurrentSystemTime();
return systemTime >= WINDOWS_TO_UNIX_100NS
? (systemTime - WINDOWS_TO_UNIX_100NS) / 10000ULL : systemTime / 10000ULL;
}
int idMetricsFramework::GetPushFrame() {
const std::uint64_t now = GetCurrentSystemTime();
const int heartbeat = ConfigFor(this).heartbeatMilliseconds;
++lastCheckedPushFrame;
if (currentPushTime == 0 || now - currentPushTime >=
static_cast<std::uint64_t>(std::max(1, heartbeat))) {
currentPushTime = now;
currentPushFrame = lastCheckedPushFrame;
}
return currentPushFrame;
}
void idMetricsFramework::SetOutputDirectory(const char* directory) {
frameworkConfig_t& config = ConfigFor(this);
std::strncpy(config.directory,
directory == nullptr || directory[0] == '\0' ? "metrics" : directory,
sizeof(config.directory) - 1);
config.directory[sizeof(config.directory) - 1] = '\0';
}
void idMetricsFramework::SetHeartbeatMilliseconds(const int milliseconds) {
ConfigFor(this).heartbeatMilliseconds = std::max(1, milliseconds);
}
void idMetricsFramework::MetricsRecord() {
isRecording = true;
MachineInfo machineInfo;
machineInfo.Serialize();
}
void idMetricsFramework::MetricsStop() {
if (!isRecording && fileTable.numEntries == 0) return;
for (metricSlot_t& slot : metricSlots) {
if (slot.owner == this) {
if (slot.file.fileHandle != nullptr) {
std::fclose(static_cast<FILE*>(slot.file.fileHandle));
}
slot = metricSlot_t{};
}
}
fileTable.numEntries = 0;
isRecording = false;
++baseHandle;
}
void idMetricsFramework::OpenMetricFile(const idStr& filename,
idMetricFile& metricFile) {
frameworkConfig_t& config = ConfigFor(this);
CreateDirectoryA(config.directory, nullptr);
char safeName[128] = {};
SanitizeName(filename.c_str(), safeName, sizeof(safeName));
char path[MAX_PATH] = {};
std::snprintf(path, sizeof(path), "%s\\%s.metric", config.directory,
safeName[0] == '\0' ? "unnamed" : safeName);
FILE* stream = nullptr;
if (fopen_s(&stream, path, "wb") == 0) metricFile.fileHandle = stream;
}
idMetricFile* idMetricsFramework::GetFileHandle(const idStr& filename) {
for (metricSlot_t& slot : metricSlots) {
if (slot.owner == this && std::strcmp(slot.name, filename.c_str()) == 0) {
return &slot.file;
}
}
for (metricSlot_t& slot : metricSlots) {
if (slot.owner == nullptr) {
slot.owner = this;
std::strncpy(slot.name, filename.c_str(), sizeof(slot.name) - 1);
OpenMetricFile(filename, slot.file);
if (slot.file.fileHandle == nullptr) {
slot = metricSlot_t{};
return nullptr;
}
++fileTable.numEntries;
return &slot.file;
}
}
return nullptr;
}
@@ -0,0 +1,54 @@
#pragma once
#include "metricrecord.h"
class idMetricsFramework {
public:
class MachineInfo : public idMetricRecord {
public:
MachineInfo();
void WriteHeader(idMetricFile* metricFile) override;
void SerializeEntry(idMetricFile* metricFile) override;
};
idMetricsFramework();
~idMetricsFramework();
std::uint64_t GetCurrentSystemTime() const;
int GetPushFrame();
void MetricsRecord();
void MetricsStop();
idMetricFile* GetFileHandle(const idStr& filename);
bool IsRecording() const { return isRecording; }
void SetOutputDirectory(const char* directory);
void SetHeartbeatMilliseconds(int milliseconds);
private:
void OpenMetricFile(const idStr& filename, idMetricFile& metricFile);
std::uint64_t currentPushTime;
std::uint64_t currentWriteTime;
int currentPushFrame;
int lastCheckedPushFrame;
int currentState;
int baseHandle;
bool isRecording;
struct recoveredHashTable_t {
void** heads;
int tableSize;
int numEntries;
int tableSizeMask;
} fileTable;
idMetricFile stateStream;
};
extern idMetricsFramework metricsFrameworkLocal;
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idMetricsFramework::MachineInfo) == 68,
"Recovered MachineInfo ABI changed");
static_assert(sizeof(idMetricsFramework) == 72,
"Recovered idMetricsFramework ABI changed");
#endif
+200
View File
@@ -0,0 +1,200 @@
#include "plog.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
namespace {
std::int64_t ClockTicks() {
LARGE_INTEGER counter = {};
QueryPerformanceCounter(&counter);
return counter.QuadPart;
}
double TicksPerMillisecond() {
static double value = 0.0;
if (value == 0.0) {
LARGE_INTEGER frequency = {};
QueryPerformanceFrequency(&frequency);
value = static_cast<double>(frequency.QuadPart) / 1000.0;
}
return value;
}
float TicksToMilliseconds(const std::int64_t ticks) {
return static_cast<float>(static_cast<double>(ticks) / TicksPerMillisecond());
}
} // namespace
idPLog pLog;
idPLog::idPLog()
: treeEntries(64), logEntries(64), lastEntry(0), overHeadTicks(-1),
groupMask(0) {
Clear();
}
void idPLog::Clear() {
treeEntries.Clear();
logEntries.Clear();
logEntry_t* const root = logEntries.Alloc();
if (root != nullptr) {
root->label = "root";
root->parent = 0;
root->totalTicks = 0;
}
lastEntry = 0;
}
void idPLog::EnsureEntries() {
logEntries.Reserve(4096);
treeEntries.Reserve(4096);
}
std::int64_t idPLog::GetOverHeadTicks() {
if (overHeadTicks >= 0) return overHeadTicks;
std::int64_t best = INT64_MAX;
for (int pass = 0; pass < 4; ++pass) {
const std::int64_t start = ClockTicks();
for (int index = 0; index < 1000; ++index) {
const std::int64_t end = ClockTicks();
(void)end;
}
best = std::min(best, ClockTicks() - start);
}
overHeadTicks = static_cast<int>(best / 1000);
return overHeadTicks;
}
void idPLog::SubtractOverhead() {
const std::int64_t overhead = GetOverHeadTicks();
for (int index = 1; index < logEntries.Num(); ++index) {
const int parent = logEntries[index].parent;
if (parent >= 0 && parent < logEntries.Num()) {
logEntries[parent].totalTicks = std::max<std::int64_t>(0,
logEntries[parent].totalTicks - overhead);
}
}
}
float idPLog::BuildLogData(const float thresholdMS,
const std::int64_t adjustByTicks) {
treeEntries.Clear();
treeEntry_t* const root = treeEntries.Alloc();
if (root == nullptr) return 0.0f;
root->label = "root";
root->depth = 0;
root->numHits = 1;
root->parent = -1;
root->firstChild = -1;
root->nextChild = -1;
for (int index = 1; index < logEntries.Num(); ++index) {
const logEntry_t& source = logEntries[index];
int treeParent = 0;
if (source.parent > 0 && source.parent < index) {
const char* const parentLabel = logEntries[source.parent].label;
for (int candidate = treeEntries.Num() - 1; candidate > 0; --candidate) {
if (treeEntries[candidate].label == parentLabel
|| std::strcmp(treeEntries[candidate].label, parentLabel) == 0) {
treeParent = candidate;
break;
}
}
}
int destination = -1;
for (int candidate = 1; candidate < treeEntries.Num(); ++candidate) {
if (treeEntries[candidate].parent == treeParent
&& (treeEntries[candidate].label == source.label
|| std::strcmp(treeEntries[candidate].label, source.label) == 0)) {
destination = candidate;
break;
}
}
if (destination < 0) {
treeEntry_t* const entry = treeEntries.Alloc();
if (entry == nullptr) break;
destination = treeEntries.Num() - 1;
entry->label = source.label;
entry->depth = treeEntries[treeParent].depth + 1;
entry->parent = treeParent;
entry->firstChild = -1;
entry->nextChild = treeEntries[treeParent].firstChild;
treeEntries[treeParent].firstChild = destination;
}
treeEntry_t& entry = treeEntries[destination];
const std::int64_t adjusted = std::max<std::int64_t>(0,
source.totalTicks - adjustByTicks);
entry.totalTicks += adjusted;
++entry.numHits;
root->totalTicks += adjusted;
}
if (thresholdMS > 0.0f) {
// Keep the recovered table intact; the threshold only controls output.
}
return TicksToMilliseconds(root->totalTicks);
}
void idPLog::ShowUniqueEntries(const float thresholdMS,
const std::int64_t adjustByTicks) {
BuildLogData(thresholdMS, adjustByTicks);
for (int index = 1; index < treeEntries.Num(); ++index) {
const treeEntry_t& entry = treeEntries[index];
const float milliseconds = TicksToMilliseconds(entry.totalTicks);
if (milliseconds >= thresholdMS) {
std::printf("%-40s %6d %10.3f ms\n", entry.label,
entry.numHits, milliseconds);
}
}
}
void idPLog::ShowCallGraph(const float thresholdMS,
const std::int64_t adjustByTicks) {
BuildLogData(thresholdMS, adjustByTicks);
for (int index = 1; index < treeEntries.Num(); ++index) {
const treeEntry_t& entry = treeEntries[index];
const float milliseconds = TicksToMilliseconds(entry.totalTicks);
if (milliseconds >= thresholdMS) {
std::printf("%*s%s: %.3f ms (%d)\n", entry.depth * 2, "",
entry.label, milliseconds, entry.numHits);
}
}
}
idPLogScope::idPLogScope(idPLog& log, const std::uint64_t mask,
const char* label)
: logIndex(-1), pLog(&log) {
if (!log.IsGroupEnabled(mask)) return;
idPLog::logEntry_t* const entry = log.logEntries.Alloc();
if (entry == nullptr) return;
entry->label = label == nullptr ? "<unnamed>" : label;
entry->parent = log.lastEntry;
entry->totalTicks = ClockTicks();
logIndex = log.logEntries.Num() - 1;
log.lastEntry = logIndex;
}
idPLogScope::~idPLogScope() {
End();
}
void idPLogScope::End(std::int64_t* totalTicks) {
if (logIndex < 0 || pLog == nullptr) return;
idPLog::logEntry_t& entry = pLog->logEntries[logIndex];
entry.totalTicks = ClockTicks() - entry.totalTicks;
if (totalTicks != nullptr) *totalTicks = entry.totalTicks;
pLog->lastEntry = entry.parent;
logIndex = -1;
}
+81
View File
@@ -0,0 +1,81 @@
#pragma once
#include "idlib/containers/recoveredlist.h"
#include <cstdint>
#pragma pack(push, 4)
class idPLog {
public:
struct treeEntry_t {
const char* label;
int depth;
int numHits;
std::int64_t totalTicks;
int parent;
int firstChild;
int nextChild;
bool allocationOccured;
};
struct logEntry_t {
const char* label;
int parent;
std::int64_t totalTicks;
};
idPLog();
void Clear();
void EnsureEntries();
void SetGroupMask(std::uint64_t mask) { groupMask = mask; }
void EnableGroups(std::uint64_t mask) { groupMask |= mask; }
void DisableGroups(std::uint64_t mask) { groupMask &= ~mask; }
std::uint64_t GetGroupMask() const { return groupMask; }
bool IsGroupEnabled(std::uint64_t mask) const { return (groupMask & mask) != 0; }
std::int64_t GetOverHeadTicks();
void SubtractOverhead();
float BuildLogData(float thresholdMS = 0.0f,
std::int64_t adjustByTicks = 0);
void ShowUniqueEntries(float thresholdMS = 0.0f,
std::int64_t adjustByTicks = 0);
void ShowCallGraph(float thresholdMS = 0.0f,
std::int64_t adjustByTicks = 0);
int NumLogEntries() const { return logEntries.Num(); }
int NumTreeEntries() const { return treeEntries.Num(); }
const logEntry_t& GetLogEntry(int index) const { return logEntries[index]; }
private:
friend class idPLogScope;
idRecoveredList<treeEntry_t> treeEntries;
idRecoveredList<logEntry_t> logEntries;
int lastEntry;
int overHeadTicks;
std::uint64_t groupMask;
};
class idPLogScope {
public:
idPLogScope(idPLog& log, std::uint64_t groupMask, const char* label);
~idPLogScope();
void End(std::int64_t* totalTicks = nullptr);
private:
int logIndex;
idPLog* pLog;
};
#pragma pack(pop)
extern idPLog pLog;
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idPLog::treeEntry_t) == 36,
"Recovered idPLog tree entry ABI changed");
static_assert(sizeof(idPLog::logEntry_t) == 16,
"Recovered idPLog log entry ABI changed");
static_assert(sizeof(idPLog) == 48, "Recovered idPLog ABI changed");
static_assert(sizeof(idPLogScope) == 8, "Recovered idPLogScope ABI changed");
#endif
@@ -0,0 +1,98 @@
#include "mqmessaging.h"
#ifdef nullptr
#undef nullptr
#endif
#ifdef snprintf
#undef snprintf
#endif
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <ctime>
idMQGraphiteClient::idMQGraphiteClient(const idStr& exchangeName,
const idStr& metricPrefix)
: idMQClientThread(), reportTime(0), channel(nullptr), outgoingEvents(),
exchange(exchangeName), prefix(metricPrefix), threadMutex() {
}
idMQGraphiteClient::~idMQGraphiteClient() {
StopThread(true);
for (int index = 0; index < outgoingEvents.Num(); ++index) {
std::free(const_cast<char*>(outgoingEvents[index].eventName));
}
}
void idMQGraphiteClient::StartMessageSystem() {
StartThread("AMQP Graphite Client");
}
void idMQGraphiteClient::PreRun() {
channel = connection.GetChannel();
if (channel != nullptr) {
channel->ExchangeDeclare(exchange, idStr("topic"), true, false);
}
reportTime = static_cast<int>(GetTickCount()) + 10000;
}
void idMQGraphiteClient::OnThreadTerminate() { channel = nullptr; }
void idMQGraphiteClient::LogEvent(const idStr& eventName,
const float duration) {
idScopedCriticalSection lock(threadMutex);
for (int index = 0; index < outgoingEvents.Num(); ++index) {
graphiteEvent_t& event = outgoingEvents[index];
if (_stricmp(event.eventName, eventName.c_str()) == 0) {
++event.count;
event.duration += duration;
event.mean += (duration - event.mean) / event.count;
event.mean2 += duration * duration;
return;
}
}
graphiteEvent_t event = {};
event.eventName = _strdup(eventName.c_str());
event.count = 1;
event.duration = duration;
event.mean = duration;
event.mean2 = duration * duration;
outgoingEvents.Append(event);
}
void idMQGraphiteClient::ThreadSlice() {
if (static_cast<int>(GetTickCount()) < reportTime) {
Sleep(10);
return;
}
idMQList<graphiteEvent_t> events;
{
idScopedCriticalSection lock(threadMutex);
events = outgoingEvents;
outgoingEvents.Clear();
}
const long long timestamp = static_cast<long long>(std::time(nullptr));
static const char* const names[] = { "rate", "avg", "std" };
for (int index = 0; index < events.Num(); ++index) {
const graphiteEvent_t& event = events[index];
const float values[] = {
event.duration,
event.mean,
event.count == 0 ? 0.0f
: std::sqrt(event.mean2 / static_cast<float>(event.count))
};
for (int metric = 0; metric < 3; ++metric) {
char line[2048];
const int amount = std::snprintf(line, sizeof(line),
"%s.%s.%s %g %lld", prefix.c_str(), event.eventName,
names[metric], values[metric], timestamp);
if (channel != nullptr && amount > 0) {
channel->BasicPublish(exchange, prefix, false, false, line,
static_cast<unsigned int>(amount));
}
}
std::free(const_cast<char*>(event.eventName));
}
reportTime = static_cast<int>(GetTickCount()) + 10000;
}
@@ -0,0 +1,58 @@
#include "mqmessaging.h"
idMQCommand::idMQCommand()
: method(nullptr), contentHeader(nullptr), complete(false), contentBody(16),
remainingBodyBytes(0) {
}
idMQCommand::~idMQCommand() = default;
void idMQCommand::Reset() {
delete method;
delete contentHeader;
method = nullptr;
contentHeader = nullptr;
complete = false;
contentBody.Clear();
remainingBodyBytes = 0;
}
idMQCommandBuilder::idMQCommandBuilder()
: state(MQ_CMDSTATE_NEED_METHOD), remainingBytes(0) {
}
MQErrors_t idMQCommandBuilder::HandleFrame(idMQCommand& command,
idMQFrame& frame) {
if (state == MQ_CMDSTATE_NEED_METHOD) {
if (frame.type != AMQP_FRAME_METHOD) return MQERROR_INVALID_FRAME;
command.method = idMQChannel::DecodeMethod(frame);
if (command.method == nullptr) return MQERROR_UNEXPECTED_METHOD;
state = command.method->HasContent()
? MQ_CMDSTATE_NEED_HEADER : MQ_CMDSTATE_COMPLETE;
} else if (state == MQ_CMDSTATE_NEED_HEADER) {
if (frame.type != AMQP_FRAME_HEADER) return MQERROR_INVALID_FRAME;
command.contentHeader = idMQChannel::DecodeContentHeader(frame);
if (command.contentHeader == nullptr) return MQERROR_UNEXPECTED_CLASS;
command.remainingBodyBytes = command.contentHeader->bodyLength;
state = command.remainingBodyBytes == 0
? MQ_CMDSTATE_COMPLETE : MQ_CMDSTATE_NEED_BODY;
} else if (state == MQ_CMDSTATE_NEED_BODY) {
if (frame.type != AMQP_FRAME_BODY) return MQERROR_INVALID_FRAME;
if (static_cast<std::uint64_t>(frame.data.Num())
> command.remainingBodyBytes) return MQERROR_FRAME_OVERFLOW;
for (int index = 0; index < frame.data.Num(); ++index) {
command.contentBody.Append(frame.data[index]);
}
command.remainingBodyBytes -= frame.data.Num();
if (command.remainingBodyBytes == 0) state = MQ_CMDSTATE_COMPLETE;
} else {
return MQERROR_UNKNOWN;
}
command.complete = state == MQ_CMDSTATE_COMPLETE;
if (command.complete) {
state = MQ_CMDSTATE_NEED_METHOD;
remainingBytes = 0;
}
return MQERROR_NONE;
}
@@ -0,0 +1,94 @@
#include "mqcommon.h"
#ifdef nullptr
#undef nullptr
#endif
#include <algorithm>
#include <cstring>
namespace {
unsigned short Swap16(const unsigned short value) {
return static_cast<unsigned short>((value >> 8) | (value << 8));
}
unsigned int Swap32(const unsigned int value) {
return ((value & 0x000000FFu) << 24)
| ((value & 0x0000FF00u) << 8)
| ((value & 0x00FF0000u) >> 8)
| ((value & 0xFF000000u) >> 24);
}
} // namespace
amqpEndpoint_t::amqpEndpoint_t()
: host("localhost"), port(5672), username("guest"), password("guest"),
vhost("/"), channelMax(0), frameMax(0), heartbeat(0),
nonBlocking(true), silent(true), minorVersion(0), majorVersion(8) {
}
idMQTCP::idMQTCP() = default;
idMQTCP::~idMQTCP() { Close(); }
bool idMQTCP::Connect(const char* host, const unsigned short port,
const bool nonBlocking, const bool silent) {
return tcp.Connect(host, port, nonBlocking, silent, false);
}
void idMQTCP::Close() { tcp.Close(); }
bool idMQTCP::IsOpen() const { return tcp.IsOpen(); }
int idMQTCP::Read(void* data, const int size, const bool blocking,
const int timeoutMS) {
return blocking ? tcp.ReadBlocking(data, size, timeoutMS)
: tcp.Read(data, size);
}
int idMQTCP::Write(const void* data, const int size, const bool blocking,
const int timeoutMS) {
return blocking ? tcp.WriteBlocking(data, size, timeoutMS)
: tcp.Write(data, size);
}
int idMQTCP::ReadByte(unsigned char& value, const bool blocking,
const int timeoutMS) {
return Read(&value, sizeof(value), blocking, timeoutMS);
}
int idMQTCP::ReadUInt16(unsigned short& value, const bool blocking,
const int timeoutMS) {
const int amount = Read(&value, sizeof(value), blocking, timeoutMS);
if (amount == sizeof(value)) value = Swap16(value);
return amount;
}
int idMQTCP::ReadUInt32(unsigned int& value, const bool blocking,
const int timeoutMS) {
const int amount = Read(&value, sizeof(value), blocking, timeoutMS);
if (amount == sizeof(value)) value = Swap32(value);
return amount;
}
idMQBuffer::idMQBuffer() : body(), readPos(0) {}
void idMQBuffer::Clear() {
body.Clear();
readPos = 0;
}
void idMQBuffer::WriteData(const void* source, const int size) {
if (source == nullptr || size <= 0) return;
const unsigned char* bytes = static_cast<const unsigned char*>(source);
for (int index = 0; index < size; ++index) body.Append(bytes[index]);
}
void idMQBuffer::WriteUInt16(unsigned short value, const bool bigEndian) {
if (bigEndian) value = Swap16(value);
WriteData(&value, sizeof(value));
}
void idMQBuffer::WriteUInt32(unsigned int value, const bool bigEndian) {
if (bigEndian) value = Swap32(value);
WriteData(&value, sizeof(value));
}
@@ -0,0 +1,494 @@
#pragma once
// Recovered AMQP 0-8 surface used by tungsten's telemetry clients. The wire
// protocol remains AMQP compatible; only the old Xbox socket/thread plumbing
// is replaced by the portable idTCP and Win32 primitives already in idLib.
#include "idlib/precompiled.h"
#include "idlib/sys/sys_networking.h"
#ifdef nullptr
#undef nullptr
#endif
#ifdef _stricmp
#undef _stricmp
#endif
#include <cstdint>
#include <cstring>
#include <new>
template<class T>
class idMQList {
public:
explicit idMQList(int initialGranularity = 16)
: list(nullptr), num(0), size(0),
granularity(static_cast<short>(initialGranularity)), memTag(5),
listStatic(0) {}
idMQList(const idMQList& other) : idMQList(other.granularity) {
*this = other;
}
~idMQList() { delete[] list; }
idMQList& operator=(const idMQList& other) {
if (this == &other) return *this;
SetNum(other.num);
for (int index = 0; index < num; ++index) list[index] = other[index];
return *this;
}
void Clear() { num = 0; }
int Num() const { return num; }
T* Ptr() { return list; }
const T* Ptr() const { return list; }
T& operator[](int index) { return list[index]; }
const T& operator[](int index) const { return list[index]; }
void SetNum(int newNum) {
if (newNum > size) Resize(newNum);
if (newNum >= 0 && newNum <= size) num = newNum;
}
int Append(const T& value) {
if (num == size && !Resize(num + 1)) return -1;
list[num] = value;
return num++;
}
bool RemoveIndex(int index) {
if (index < 0 || index >= num) return false;
for (int i = index; i + 1 < num; ++i) list[i] = list[i + 1];
--num;
return true;
}
private:
bool Resize(int required) {
const int step = granularity > 0 ? granularity : 16;
const int newSize = ((required + step - 1) / step) * step;
T* replacement = new (std::nothrow) T[newSize];
if (replacement == nullptr) return false;
for (int index = 0; index < num; ++index) replacement[index] = list[index];
delete[] list;
list = replacement;
size = newSize;
return true;
}
T* list;
int num;
int size;
short granularity;
unsigned char memTag;
unsigned char listStatic;
};
class idMQKeyValue {
public:
idMQKeyValue() : key(), value() {}
idMQKeyValue(const char* keyText, const char* valueText)
: key(keyText), value(valueText) {}
const idStr& GetKey() const { return key; }
const idStr& GetValue() const { return value; }
idStr key;
idStr value;
};
class idMQTable {
public:
idMQTable() : args(), hash{} {}
void Clear() { args.Clear(); }
int GetNumKeyVals() const { return args.Num(); }
const idMQKeyValue* GetKeyVal(int index) const {
return index >= 0 && index < args.Num() ? &args[index] : nullptr;
}
void Set(const char* key, const char* value) {
const char* safeKey = key == nullptr ? "" : key;
for (int index = 0; index < args.Num(); ++index) {
if (_stricmp(args[index].key.c_str(), safeKey) == 0) {
args[index].value = value == nullptr ? "" : value;
return;
}
}
args.Append(idMQKeyValue(safeKey, value == nullptr ? "" : value));
}
const char* GetString(const char* key, const char* defaultValue = "") const {
const char* safeKey = key == nullptr ? "" : key;
for (int index = 0; index < args.Num(); ++index) {
if (_stricmp(args[index].key.c_str(), safeKey) == 0) {
return args[index].value.c_str();
}
}
return defaultValue;
}
private:
idMQList<idMQKeyValue> args;
std::uint32_t hash[8];
};
enum MQErrors_t {
MQERROR_NONE = 0,
MQERROR_UNKNOWN,
MQERROR_INVALID_FRAME,
MQERROR_FRAME_OVERFLOW,
MQERROR_SESSION_NOT_FOUND,
MQERROR_STREAM_ERROR,
MQERROR_CONNECTION_START,
MQERROR_BAD_SPEC_VERSION,
MQERROR_AUTH_FAILED,
MQERROR_UNEXPECTED_CLASS,
MQERROR_UNEXPECTED_METHOD,
MQERROR_SERVER_CLOSED
};
enum MQAssemblerState_t {
MQ_CMDSTATE_NEED_METHOD = 0,
MQ_CMDSTATE_NEED_HEADER,
MQ_CMDSTATE_NEED_BODY,
MQ_CMDSTATE_COMPLETE
};
enum amqpClassId_t {
AMQP_CLASS_CONNECTION = 10,
AMQP_CLASS_CHANNEL = 20,
AMQP_CLASS_ACCESS = 30,
AMQP_CLASS_EXCHANGE = 40,
AMQP_CLASS_QUEUE = 50,
AMQP_CLASS_BASIC = 60
};
enum amqpFrameType_t {
AMQP_FRAME_METHOD = 1,
AMQP_FRAME_HEADER = 2,
AMQP_FRAME_BODY = 3,
AMQP_FRAME_HEARTBEAT = 8,
AMQP_FRAME_END = 0xCE
};
struct amqpEndpoint_t {
amqpEndpoint_t();
idStr host;
unsigned short port;
idStr username;
idStr password;
idStr vhost;
unsigned short channelMax;
unsigned short frameMax;
unsigned short heartbeat;
bool nonBlocking;
bool silent;
int minorVersion;
int majorVersion;
};
struct amqpShutdownReason_t {
amqpShutdownReason_t()
: replyCode(0), cId(0), mId(0), error(MQERROR_NONE), replyText("") {}
unsigned short replyCode;
unsigned short cId;
unsigned short mId;
MQErrors_t error;
const char* replyText;
};
class idMQFrame;
class idMQMethod {
public:
virtual ~idMQMethod() = default;
virtual int GetClassId() const = 0;
virtual int GetMethodId() const = 0;
virtual bool HasContent() const { return false; }
virtual bool IsAsync() const { return false; }
virtual void Populate(idMQFrame& frame) = 0;
virtual void ToFrame(idMQFrame& frame) = 0;
};
class idMQFrame {
public:
idMQFrame();
idMQFrame(unsigned char frameType, unsigned short channelNumber);
void Clear();
void ReadData(void* destination, int length);
void WriteData(const void* source, int length);
unsigned char ReadByte();
unsigned short ReadUInt16();
unsigned int ReadUInt32();
std::uint64_t ReadUInt64();
bool ReadBool();
idStr ReadShortString();
void ReadString(idMQList<unsigned char>& value);
void ReadTable(idMQTable& value);
bool ReadPropertyPresence();
void FinalizeReadPresence();
void WriteByte(unsigned char value);
void WriteUInt16(unsigned short value);
void WriteUInt32(unsigned int value);
void WriteUInt64(std::uint64_t value);
void WriteBool(bool value);
void WriteShortString(const idStr& value);
void WriteString(const idMQList<unsigned char>& value);
void WriteTable(const idMQTable& value);
void WritePropertyPresence(bool present);
void FinalizeWritePresence();
void FlushWriteBitBuffer();
void FinalizeFrame();
int ReadFromStream(class idMQTCP& stream);
int WriteToStream(class idMQTCP& stream);
unsigned char type;
unsigned short channel;
idMQList<unsigned char> data;
int readPos;
bool locked;
bool resetReadBitBuffer;
unsigned char readBitBuffer;
unsigned int readBitMask;
bool flushWriteBitBuffer;
unsigned char writeBitBuffer;
unsigned int writeBitMask;
unsigned short readPresenceBuffer;
unsigned short readPresencePosition;
unsigned short writePresenceBuffer;
unsigned short writePresencePosition;
};
class idMQTCP {
public:
idMQTCP();
~idMQTCP();
bool Connect(const char* host, unsigned short port, bool nonBlocking,
bool silent);
void Close();
bool IsOpen() const;
int Read(void* data, int size, bool blocking = true,
int timeoutMS = 5000);
int Write(const void* data, int size, bool blocking = true,
int timeoutMS = 5000);
int ReadByte(unsigned char& value, bool blocking = true,
int timeoutMS = 5000);
int ReadUInt16(unsigned short& value, bool blocking = true,
int timeoutMS = 5000);
int ReadUInt32(unsigned int& value, bool blocking = true,
int timeoutMS = 5000);
idTCP tcp;
};
class idMQContentHeader {
public:
idMQContentHeader() : bodyLength(0) {}
virtual ~idMQContentHeader() = default;
virtual int GetProtocolId() const = 0;
virtual void Populate(idMQFrame& frame) = 0;
virtual void ToFrame(idMQFrame& frame) = 0;
std::uint64_t bodyLength;
};
class idMQBuffer {
public:
idMQBuffer();
void Clear();
void WriteData(const void* data, int size);
void WriteUInt16(unsigned short value, bool bigEndian = true);
void WriteUInt32(unsigned int value, bool bigEndian = true);
idMQList<unsigned char> body;
int readPos;
};
class idMQFrameHandler {
public:
explicit idMQFrameHandler(const amqpEndpoint_t& endpointValue);
~idMQFrameHandler();
bool Connect();
void Close();
int SendFrame(idMQFrame& frame);
int ReadFrame(idMQFrame& frame);
bool SendHeader();
amqpEndpoint_t endpoint;
idSysMutex readLock;
idSysMutex writeLock;
idMQTCP tcp;
};
template<class T>
class idDeferredResult {
public:
idDeferredResult() : value(), filled(false), signal(false) {}
void SetValue(const T& newValue) {
value = newValue;
filled = true;
signal.Raise();
}
bool GetValue(T& result) {
if (!signal.Wait(5000) || !filled) return false;
result = value;
filled = false;
return true;
}
bool GetValue() {
if (!signal.Wait(5000) || !filled) return false;
filled = false;
return true;
}
void Clear() {
filled = false;
signal.Clear();
}
T value;
bool filled;
idSysSignal signal;
};
class idMQCommand {
public:
idMQCommand();
~idMQCommand();
void Reset();
idMQMethod* method;
idMQContentHeader* contentHeader;
bool complete;
idMQList<unsigned char> contentBody;
std::uint64_t remainingBodyBytes;
};
class idMQCommandBuilder {
public:
idMQCommandBuilder();
MQErrors_t HandleFrame(idMQCommand& command, idMQFrame& frame);
MQAssemblerState_t state;
std::uint64_t remainingBytes;
};
class idMQChannel;
class idMQSessionManager;
class idMQConnection {
public:
idMQConnection();
~idMQConnection();
void Init(amqpEndpoint_t& endpoint);
bool Connect();
void Close();
void Close(const char* reason, MQErrors_t error);
void Close(amqpShutdownReason_t& reason);
void SendFrame(idMQFrame& frame);
idMQChannel* GetChannel();
unsigned int frameMaxSize;
idMQList<idStr> knownHosts;
idMQFrameHandler* framer;
idMQSessionManager* sessionManager;
class idMQSession* sessionZero;
volatile bool terminateConnection;
volatile bool connectionTerminated;
volatile bool isClosing;
unsigned int threadHandle;
amqpShutdownReason_t shutdownReason;
private:
bool OpenCommunications();
void CreateThread();
void MessageThread();
static unsigned int StaticThread(void* connection);
};
class idMQSession {
public:
idMQSession(int channelNumber, idMQConnection* connection);
~idMQSession();
void Init(bool connectionChannel = false);
void Close();
void Close(const char* reason, MQErrors_t error,
bool notifyServer = true);
void HandleFrame(idMQFrame& frame);
void SendCommand(idMQCommand& command);
idMQConnection* connection;
idMQCommandBuilder cmdBuilder;
idMQChannel* protocol;
idMQCommand currentCommand;
int channelNumber;
bool isOpen;
};
class idMQSessionManager {
public:
explicit idMQSessionManager(idMQConnection* connection);
~idMQSessionManager();
void Init(int maximumSessions);
void Shutdown();
idMQSession* CreateSession();
idMQSession* Lookup(int channelNumber);
bool initialized;
idMQConnection* connection;
int maxSessions;
idMQList<idMQSession*> sessionList;
};
class idMQClientThread {
public:
idMQClientThread();
virtual ~idMQClientThread();
virtual void StartMessageSystem() { StartThread("AMQP Client"); }
virtual void StopMessageSystem() { StopThread(true); }
virtual void PreRun() {}
virtual void ThreadSlice() { Sys_Yield(); }
virtual void OnThreadTerminate() {}
void StartThread(const char* threadName);
void StopThread(bool waitForStop);
unsigned int handle;
volatile bool signalQuit;
volatile bool terminated;
idMQConnection connection;
int retryTime;
protected:
static void Connect(idMQClientThread* thread);
static unsigned int Thread(void* thread);
};
class idMQClient : public idMQClientThread {};
class idMQGraphiteClient : public idMQClientThread {
public:
struct graphiteEvent_t {
const char* eventName;
int count;
float duration;
float mean;
float mean2;
};
idMQGraphiteClient(const idStr& exchangeName, const idStr& metricPrefix);
~idMQGraphiteClient() override;
void StartMessageSystem() override;
void PreRun() override;
void ThreadSlice() override;
void OnThreadTerminate() override;
void LogEvent(const idStr& eventName, float duration);
int reportTime;
idMQChannel* channel;
idMQList<graphiteEvent_t> outgoingEvents;
idStr exchange;
idStr prefix;
idSysMutex threadMutex;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idMQTCP) == 20, "Recovered idMQTCP ABI changed");
static_assert(sizeof(idMQList<unsigned char>) == 16,
"Recovered AMQP list ABI changed");
static_assert(sizeof(idMQTable) == 48, "Recovered AMQP table ABI changed");
static_assert(sizeof(idMQBuffer) == 20, "Recovered idMQBuffer ABI changed");
static_assert(sizeof(idMQMethod) == 4, "Recovered idMQMethod ABI changed");
static_assert(sizeof(idMQContentHeader) == 16,
"Recovered idMQContentHeader ABI changed");
#endif
@@ -0,0 +1,181 @@
#include "mqmessaging.h"
#ifdef nullptr
#undef nullptr
#endif
#include <cstring>
idMQConnection::idMQConnection()
: frameMaxSize(131072), knownHosts(), framer(nullptr),
sessionManager(nullptr), sessionZero(nullptr), terminateConnection(false),
connectionTerminated(true), isClosing(false), threadHandle(0),
shutdownReason() {
}
idMQConnection::~idMQConnection() {
Close();
delete sessionZero;
delete sessionManager;
delete framer;
}
void idMQConnection::Init(amqpEndpoint_t& endpoint) {
Close();
delete sessionZero;
delete sessionManager;
delete framer;
framer = new idMQFrameHandler(endpoint);
sessionManager = new idMQSessionManager(this);
sessionZero = new idMQSession(0, this);
sessionZero->Init(true);
frameMaxSize = endpoint.frameMax == 0 ? 131072 : endpoint.frameMax;
shutdownReason = amqpShutdownReason_t();
}
void idMQConnection::CreateThread() {
terminateConnection = false;
connectionTerminated = false;
threadHandle = static_cast<unsigned int>(Sys_CreateThread(
StaticThread, this, THREAD_LOWEST, "AMQP Connection", CORE_ANY,
0x20000, false));
}
unsigned int idMQConnection::StaticThread(void* value) {
static_cast<idMQConnection*>(value)->MessageThread();
return 0;
}
void idMQConnection::MessageThread() {
while (!terminateConnection && framer != nullptr && framer->tcp.IsOpen()) {
idMQFrame frame;
if (framer->ReadFrame(frame) < 0) break;
idMQSession* session = frame.channel == 0 ? sessionZero
: sessionManager == nullptr ? nullptr
: sessionManager->Lookup(frame.channel);
if (session == nullptr) {
shutdownReason.error = MQERROR_SESSION_NOT_FOUND;
shutdownReason.replyText = "AMQP session not found";
break;
}
session->HandleFrame(frame);
}
connectionTerminated = true;
}
bool idMQConnection::OpenCommunications() {
if (framer == nullptr || sessionZero == nullptr || sessionZero->protocol == nullptr
|| !framer->SendHeader()) return false;
AMQPConnectionStart start;
if (!sessionZero->protocol->deferredConnectionStart.GetValue(start)) {
shutdownReason.error = MQERROR_CONNECTION_START;
shutdownReason.replyText = "Unable to get ConnectionStart details";
return false;
}
if (start.versionMajor != 8 || start.versionMinor != 0) {
shutdownReason.error = MQERROR_BAD_SPEC_VERSION;
shutdownReason.replyText = "AMQP protocol version mismatch";
return false;
}
idMQList<unsigned char> response(16);
response.Append(0);
const char* username = framer->endpoint.username.c_str();
for (int i = 0; username[i] != 0; ++i) response.Append(username[i]);
response.Append(0);
const char* password = framer->endpoint.password.c_str();
for (int i = 0; password[i] != 0; ++i) response.Append(password[i]);
AMQPConnectionTune tune;
sessionZero->protocol->ConnectionStartOk(tune, idStr("PLAIN"), response,
idStr("en_US"));
if (tune.frameMax == 0 && tune.channelMax == 0 && tune.heartbeat == 0) {
shutdownReason.error = MQERROR_AUTH_FAILED;
shutdownReason.replyText = "AMQP authentication/tuning failed";
return false;
}
const unsigned short channelMax = framer->endpoint.channelMax == 0
? tune.channelMax : framer->endpoint.channelMax;
const unsigned int frameMax = framer->endpoint.frameMax == 0
? tune.frameMax : framer->endpoint.frameMax;
const unsigned short heartbeat = framer->endpoint.heartbeat == 0
? tune.heartbeat : framer->endpoint.heartbeat;
frameMaxSize = frameMax == 0 ? 131072 : frameMax;
sessionManager->Init(channelMax);
sessionZero->protocol->ConnectionTuneOk(channelMax, frameMax, heartbeat);
idStr hosts;
sessionZero->protocol->ConnectionOpen(hosts, framer->endpoint.vhost,
idStr(""), false);
if (connectionTerminated) return false;
knownHosts.Clear();
const char* cursor = hosts.c_str();
while (*cursor != 0) {
while (*cursor == ' ') ++cursor;
const char* begin = cursor;
while (*cursor != 0 && *cursor != ' ') ++cursor;
if (cursor != begin) {
idStr host;
host.Append(begin, static_cast<int>(cursor - begin));
knownHosts.Append(host);
}
}
return true;
}
bool idMQConnection::Connect() {
if (framer == nullptr || !framer->Connect()) return false;
CreateThread();
if (!OpenCommunications()) {
Close(shutdownReason);
return false;
}
connectionTerminated = false;
return true;
}
void idMQConnection::SendFrame(idMQFrame& frame) {
if (framer == nullptr || framer->SendFrame(frame) < 0) {
shutdownReason.error = MQERROR_STREAM_ERROR;
shutdownReason.replyText = "AMQP stream write failed";
connectionTerminated = true;
}
}
idMQChannel* idMQConnection::GetChannel() {
if (sessionManager == nullptr || connectionTerminated) return nullptr;
idMQSession* session = sessionManager->CreateSession();
return session == nullptr ? nullptr : session->protocol;
}
void idMQConnection::Close(const char* reason, const MQErrors_t error) {
amqpShutdownReason_t value;
value.error = error;
value.replyText = reason == nullptr ? "" : reason;
Close(value);
}
void idMQConnection::Close(amqpShutdownReason_t& reason) {
if (isClosing) return;
isClosing = true;
shutdownReason = reason;
if (!connectionTerminated && sessionZero != nullptr
&& sessionZero->protocol != nullptr && reason.error == MQERROR_NONE) {
sessionZero->protocol->ConnectionClose(200, idStr("Goodbye"), 0, 0);
}
terminateConnection = true;
if (framer != nullptr) framer->Close();
if (threadHandle != 0
&& Sys_GetCurrentThreadID() != static_cast<uintptr_t>(threadHandle)) {
Sys_WaitForThread(static_cast<uintptr_t>(threadHandle));
Sys_DestroyThread(static_cast<uintptr_t>(threadHandle));
}
threadHandle = 0;
if (sessionManager != nullptr) sessionManager->Shutdown();
connectionTerminated = true;
isClosing = false;
}
void idMQConnection::Close() {
amqpShutdownReason_t reason;
Close(reason);
}
@@ -0,0 +1,31 @@
#pragma once
#include "mqcommon.h"
class AMQPBasicProperties;
class idMQConsumer {
public:
idMQConsumer() : consumerTag(), isConsuming(false), mutex() {}
virtual ~idMQConsumer() = default;
virtual void ProcessBasicConsumeOk(const idStr& tag) {
idScopedCriticalSection lock(mutex);
consumerTag = tag;
isConsuming = true;
}
virtual void ProcessBasicCancel(const idStr&) {
idScopedCriticalSection lock(mutex);
isConsuming = false;
}
virtual void ProcessBasicCancelOk(const idStr& tag) {
ProcessBasicCancel(tag);
}
virtual void ProcessBasicDeliver(const idStr& consumerTag,
std::uint64_t deliveryTag, bool redelivered, const idStr& exchange,
const idStr& routingKey, const AMQPBasicProperties* properties,
const idMQList<unsigned char>& body) = 0;
idStr consumerTag;
bool isConsuming;
idSysMutex mutex;
};
@@ -0,0 +1,330 @@
#include "mqcommon.h"
#ifdef nullptr
#undef nullptr
#endif
#ifdef snprintf
#undef snprintf
#endif
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
namespace {
void StoreUInt32BE(unsigned char* destination, const unsigned int value) {
destination[0] = static_cast<unsigned char>(value >> 24);
destination[1] = static_cast<unsigned char>(value >> 16);
destination[2] = static_cast<unsigned char>(value >> 8);
destination[3] = static_cast<unsigned char>(value);
}
} // namespace
idMQFrame::idMQFrame()
: type(0), channel(0), data(), readPos(0), locked(false),
resetReadBitBuffer(true), readBitBuffer(0), readBitMask(0),
flushWriteBitBuffer(false), writeBitBuffer(0), writeBitMask(1),
readPresenceBuffer(0), readPresencePosition(0), writePresenceBuffer(0),
writePresencePosition(0) {
}
idMQFrame::idMQFrame(const unsigned char frameType,
const unsigned short channelNumber)
: idMQFrame() {
type = frameType;
channel = channelNumber;
}
void idMQFrame::Clear() {
type = 0;
channel = 0;
data.Clear();
readPos = 0;
locked = false;
resetReadBitBuffer = true;
readBitBuffer = 0;
readBitMask = 0;
flushWriteBitBuffer = false;
writeBitBuffer = 0;
writeBitMask = 1;
readPresenceBuffer = readPresencePosition = 0;
writePresenceBuffer = writePresencePosition = 0;
}
void idMQFrame::ReadData(void* destination, const int length) {
resetReadBitBuffer = true;
if (destination == nullptr || length <= 0) return;
const int available = std::max(0, data.Num() - readPos);
const int amount = std::min(length, available);
if (amount > 0) std::memcpy(destination, data.Ptr() + readPos, amount);
if (amount < length) {
std::memset(static_cast<unsigned char*>(destination) + amount, 0,
length - amount);
}
readPos += amount;
}
void idMQFrame::FlushWriteBitBuffer() {
if (!flushWriteBitBuffer) return;
data.Append(writeBitBuffer);
flushWriteBitBuffer = false;
writeBitBuffer = 0;
writeBitMask = 1;
}
void idMQFrame::WriteData(const void* source, const int length) {
FlushWriteBitBuffer();
if (source == nullptr || length <= 0) return;
const unsigned char* bytes = static_cast<const unsigned char*>(source);
for (int index = 0; index < length; ++index) data.Append(bytes[index]);
}
unsigned char idMQFrame::ReadByte() {
unsigned char value = 0;
ReadData(&value, sizeof(value));
return value;
}
unsigned short idMQFrame::ReadUInt16() {
unsigned char bytes[2] = {};
ReadData(bytes, sizeof(bytes));
return static_cast<unsigned short>((bytes[0] << 8) | bytes[1]);
}
unsigned int idMQFrame::ReadUInt32() {
unsigned char bytes[4] = {};
ReadData(bytes, sizeof(bytes));
return (static_cast<unsigned int>(bytes[0]) << 24)
| (static_cast<unsigned int>(bytes[1]) << 16)
| (static_cast<unsigned int>(bytes[2]) << 8)
| static_cast<unsigned int>(bytes[3]);
}
std::uint64_t idMQFrame::ReadUInt64() {
const std::uint64_t high = ReadUInt32();
const std::uint64_t low = ReadUInt32();
return (high << 32) | low;
}
bool idMQFrame::ReadBool() {
if (resetReadBitBuffer || readBitMask == 0 || readBitMask > 0x80) {
if (readPos >= data.Num()) return false;
readBitBuffer = data[readPos++];
readBitMask = 1;
resetReadBitBuffer = false;
}
const bool result = (readBitBuffer & readBitMask) != 0;
readBitMask <<= 1;
return result;
}
idStr idMQFrame::ReadShortString() {
const int length = ReadByte();
idStr result;
for (int index = 0; index < length && readPos < data.Num(); ++index) {
result.Append(static_cast<char>(data[readPos++]));
}
resetReadBitBuffer = true;
return result;
}
void idMQFrame::ReadString(idMQList<unsigned char>& value) {
const unsigned int length = ReadUInt32();
value.Clear();
for (unsigned int index = 0; index < length && readPos < data.Num();
++index) value.Append(data[readPos++]);
}
void idMQFrame::WriteByte(const unsigned char value) {
WriteData(&value, sizeof(value));
}
void idMQFrame::WriteUInt16(const unsigned short value) {
const unsigned char bytes[2] = {
static_cast<unsigned char>(value >> 8),
static_cast<unsigned char>(value)
};
WriteData(bytes, sizeof(bytes));
}
void idMQFrame::WriteUInt32(const unsigned int value) {
unsigned char bytes[4];
StoreUInt32BE(bytes, value);
WriteData(bytes, sizeof(bytes));
}
void idMQFrame::WriteUInt64(const std::uint64_t value) {
WriteUInt32(static_cast<unsigned int>(value >> 32));
WriteUInt32(static_cast<unsigned int>(value));
}
void idMQFrame::WriteBool(const bool value) {
if (writeBitMask == 0 || writeBitMask > 0x80) FlushWriteBitBuffer();
if (value) writeBitBuffer |= static_cast<unsigned char>(writeBitMask);
flushWriteBitBuffer = true;
writeBitMask <<= 1;
}
void idMQFrame::WriteShortString(const idStr& value) {
const int length = std::min(value.Length(), 255);
WriteByte(static_cast<unsigned char>(length));
WriteData(value.c_str(), length);
}
void idMQFrame::WriteString(const idMQList<unsigned char>& value) {
WriteUInt32(value.Num());
if (value.Num() > 0) WriteData(value.Ptr(), value.Num());
}
void idMQFrame::WriteTable(const idMQTable& value) {
FlushWriteBitBuffer();
const int lengthOffset = data.Num();
WriteUInt32(0);
const int tableStart = data.Num();
for (int index = 0; index < value.GetNumKeyVals(); ++index) {
const idMQKeyValue* entry = value.GetKeyVal(index);
WriteShortString(entry->GetKey());
WriteByte('S');
const idStr& text = entry->GetValue();
WriteUInt32(text.Length());
WriteData(text.c_str(), text.Length());
}
StoreUInt32BE(data.Ptr() + lengthOffset, data.Num() - tableStart);
}
void idMQFrame::ReadTable(idMQTable& value) {
value.Clear();
const unsigned int length = ReadUInt32();
const int end = std::min(data.Num(), readPos + static_cast<int>(length));
while (readPos < end) {
const idStr key = ReadShortString();
const unsigned char fieldType = ReadByte();
idStr text;
if (fieldType == 'S') {
const unsigned int stringLength = ReadUInt32();
for (unsigned int i = 0; i < stringLength && readPos < end; ++i) {
text.Append(static_cast<char>(data[readPos++]));
}
} else if (fieldType == 't') {
text = ReadByte() != 0 ? "1" : "0";
} else if (fieldType == 'I') {
char buffer[32];
std::snprintf(buffer, sizeof(buffer), "%d",
static_cast<int>(ReadUInt32()));
text = buffer;
} else if (fieldType == 'l') {
char buffer[32];
std::snprintf(buffer, sizeof(buffer), "%llu",
static_cast<unsigned long long>(ReadUInt64()));
text = buffer;
} else if (fieldType == 'V') {
text.Clear();
} else {
// Unknown table values cannot be sized safely without the full
// AMQP type grammar, so consume the remaining declared table.
readPos = end;
break;
}
value.Set(key.c_str(), text.c_str());
}
readPos = end;
resetReadBitBuffer = true;
}
void idMQFrame::WritePropertyPresence(const bool present) {
if (writePresencePosition < 15 && present) {
writePresenceBuffer |= static_cast<unsigned short>(
0x8000u >> writePresencePosition);
}
++writePresencePosition;
}
void idMQFrame::FinalizeWritePresence() {
WriteUInt16(writePresenceBuffer);
writePresenceBuffer = 0;
writePresencePosition = 0;
}
bool idMQFrame::ReadPropertyPresence() {
if (readPresencePosition == 0) readPresenceBuffer = ReadUInt16();
const bool result = readPresencePosition < 15
&& (readPresenceBuffer & (0x8000u >> readPresencePosition)) != 0;
++readPresencePosition;
return result;
}
void idMQFrame::FinalizeReadPresence() {
readPresenceBuffer = 0;
readPresencePosition = 0;
}
void idMQFrame::FinalizeFrame() { FlushWriteBitBuffer(); }
int idMQFrame::ReadFromStream(idMQTCP& stream) {
Clear();
unsigned int payloadSize = 0;
if (stream.ReadByte(type) != 1 || stream.ReadUInt16(channel) != 2
|| stream.ReadUInt32(payloadSize) != 4) return -1;
if (payloadSize > 128u * 1024u * 1024u) return -1;
data.SetNum(static_cast<int>(payloadSize));
if (payloadSize != 0 && stream.Read(data.Ptr(), payloadSize, true, 5000)
!= static_cast<int>(payloadSize)) return -1;
unsigned char frameEnd = 0;
if (stream.ReadByte(frameEnd) != 1 || frameEnd != AMQP_FRAME_END) return -1;
return static_cast<int>(payloadSize) + 8;
}
int idMQFrame::WriteToStream(idMQTCP& stream) {
FinalizeFrame();
unsigned char header[7] = {
type,
static_cast<unsigned char>(channel >> 8),
static_cast<unsigned char>(channel),
0, 0, 0, 0
};
StoreUInt32BE(header + 3, data.Num());
if (stream.Write(header, sizeof(header), true, 5000) != sizeof(header)) {
return -1;
}
if (data.Num() > 0 && stream.Write(data.Ptr(), data.Num(), true, 5000)
!= data.Num()) return -1;
const unsigned char frameEnd = AMQP_FRAME_END;
if (stream.Write(&frameEnd, 1, true, 5000) != 1) return -1;
return data.Num() + 8;
}
idMQFrameHandler::idMQFrameHandler(const amqpEndpoint_t& endpointValue)
: endpoint(endpointValue), readLock(), writeLock(), tcp() {
}
idMQFrameHandler::~idMQFrameHandler() { Close(); }
bool idMQFrameHandler::Connect() {
return tcp.Connect(endpoint.host.c_str(), endpoint.port,
endpoint.nonBlocking, endpoint.silent);
}
void idMQFrameHandler::Close() { tcp.Close(); }
int idMQFrameHandler::SendFrame(idMQFrame& frame) {
idScopedCriticalSection lock(writeLock);
return frame.WriteToStream(tcp);
}
int idMQFrameHandler::ReadFrame(idMQFrame& frame) {
idScopedCriticalSection lock(readLock);
return frame.ReadFromStream(tcp);
}
bool idMQFrameHandler::SendHeader() {
const unsigned char protocol[8] = {
'A', 'M', 'Q', 'P', 1, 1,
static_cast<unsigned char>(endpoint.majorVersion),
static_cast<unsigned char>(endpoint.minorVersion)
};
return tcp.Write(protocol, sizeof(protocol), true, 5000)
== sizeof(protocol);
}
@@ -0,0 +1,506 @@
#include "mqmessaging.h"
#ifdef nullptr
#undef nullptr
#endif
#define EMPTY_METHOD(name) \
void name::Populate(idMQFrame&) {} \
void name::ToFrame(idMQFrame&) {}
void AMQPConnectionStart::Populate(idMQFrame& frame) {
versionMajor = frame.ReadByte();
versionMinor = frame.ReadByte();
frame.ReadTable(serverProperties);
frame.ReadString(mechanisms);
frame.ReadString(locales);
}
void AMQPConnectionStart::ToFrame(idMQFrame& frame) {
frame.WriteByte(versionMajor);
frame.WriteByte(versionMinor);
frame.WriteTable(serverProperties);
frame.WriteString(mechanisms);
frame.WriteString(locales);
}
void AMQPConnectionStartOk::Populate(idMQFrame& frame) {
frame.ReadTable(clientProperties);
mechanism = frame.ReadShortString();
frame.ReadString(response);
locale = frame.ReadShortString();
}
void AMQPConnectionStartOk::ToFrame(idMQFrame& frame) {
frame.WriteTable(clientProperties);
frame.WriteShortString(mechanism);
frame.WriteString(response);
frame.WriteShortString(locale);
}
void AMQPConnectionSecure::Populate(idMQFrame& frame) {
frame.ReadString(challenge);
}
void AMQPConnectionSecure::ToFrame(idMQFrame& frame) {
frame.WriteString(challenge);
}
void AMQPConnectionSecureOk::Populate(idMQFrame& frame) {
frame.ReadString(response);
}
void AMQPConnectionSecureOk::ToFrame(idMQFrame& frame) {
frame.WriteString(response);
}
void AMQPConnectionTune::Populate(idMQFrame& frame) {
channelMax = frame.ReadUInt16();
frameMax = frame.ReadUInt32();
heartbeat = frame.ReadUInt16();
}
void AMQPConnectionTune::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(channelMax);
frame.WriteUInt32(frameMax);
frame.WriteUInt16(heartbeat);
}
void AMQPConnectionTuneOk::Populate(idMQFrame& frame) {
channelMax = frame.ReadUInt16();
frameMax = frame.ReadUInt32();
heartbeat = frame.ReadUInt16();
}
void AMQPConnectionTuneOk::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(channelMax);
frame.WriteUInt32(frameMax);
frame.WriteUInt16(heartbeat);
}
void AMQPConnectionOpen::Populate(idMQFrame& frame) {
virtualHost = frame.ReadShortString();
capabilities = frame.ReadShortString();
insist = frame.ReadBool();
}
void AMQPConnectionOpen::ToFrame(idMQFrame& frame) {
frame.WriteShortString(virtualHost);
frame.WriteShortString(capabilities);
frame.WriteBool(insist);
}
void AMQPConnectionOpenOk::Populate(idMQFrame& frame) {
knownHosts = frame.ReadShortString();
}
void AMQPConnectionOpenOk::ToFrame(idMQFrame& frame) {
frame.WriteShortString(knownHosts);
}
void AMQPConnectionRedirect::Populate(idMQFrame& frame) {
host = frame.ReadShortString();
knownHosts = frame.ReadShortString();
}
void AMQPConnectionRedirect::ToFrame(idMQFrame& frame) {
frame.WriteShortString(host);
frame.WriteShortString(knownHosts);
}
void AMQPConnectionClose::Populate(idMQFrame& frame) {
replyCode = frame.ReadUInt16();
replyText = frame.ReadShortString();
cId = frame.ReadUInt16();
mId = frame.ReadUInt16();
}
void AMQPConnectionClose::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(replyCode);
frame.WriteShortString(replyText);
frame.WriteUInt16(cId);
frame.WriteUInt16(mId);
}
EMPTY_METHOD(AMQPConnectionCloseOk)
void AMQPChannelOpen::Populate(idMQFrame& frame) {
oob = frame.ReadShortString();
}
void AMQPChannelOpen::ToFrame(idMQFrame& frame) {
frame.WriteShortString(oob);
}
EMPTY_METHOD(AMQPChannelOpenOk)
void AMQPChannelFlow::Populate(idMQFrame& frame) { active = frame.ReadBool(); }
void AMQPChannelFlow::ToFrame(idMQFrame& frame) { frame.WriteBool(active); }
void AMQPChannelFlowOk::Populate(idMQFrame& frame) { active = frame.ReadBool(); }
void AMQPChannelFlowOk::ToFrame(idMQFrame& frame) { frame.WriteBool(active); }
void AMQPChannelAlert::Populate(idMQFrame& frame) {
replyCode = frame.ReadUInt16();
replyText = frame.ReadShortString();
frame.ReadTable(details);
}
void AMQPChannelAlert::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(replyCode);
frame.WriteShortString(replyText);
frame.WriteTable(details);
}
void AMQPChannelClose::Populate(idMQFrame& frame) {
replyCode = frame.ReadUInt16();
replyText = frame.ReadShortString();
cId = frame.ReadUInt16();
mId = frame.ReadUInt16();
}
void AMQPChannelClose::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(replyCode);
frame.WriteShortString(replyText);
frame.WriteUInt16(cId);
frame.WriteUInt16(mId);
}
EMPTY_METHOD(AMQPChannelCloseOk)
void AMQPExchangeDeclare::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
exchange = frame.ReadShortString();
type = frame.ReadShortString();
passive = frame.ReadBool();
durable = frame.ReadBool();
autoDelete = frame.ReadBool();
isInternal = frame.ReadBool();
noWait = frame.ReadBool();
frame.ReadTable(arguments);
}
void AMQPExchangeDeclare::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(exchange);
frame.WriteShortString(type);
frame.WriteBool(passive);
frame.WriteBool(durable);
frame.WriteBool(autoDelete);
frame.WriteBool(isInternal);
frame.WriteBool(noWait);
frame.WriteTable(arguments);
}
EMPTY_METHOD(AMQPExchangeDeclareOk)
void AMQPExchangeDelete::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
exchange = frame.ReadShortString();
unused = frame.ReadBool();
noWait = frame.ReadBool();
}
void AMQPExchangeDelete::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(exchange);
frame.WriteBool(unused);
frame.WriteBool(noWait);
}
EMPTY_METHOD(AMQPExchangeDeleteOk)
void AMQPQueueDeclare::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
queue = frame.ReadShortString();
passive = frame.ReadBool();
durable = frame.ReadBool();
exclusive = frame.ReadBool();
autoDelete = frame.ReadBool();
noWait = frame.ReadBool();
frame.ReadTable(arguments);
}
void AMQPQueueDeclare::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(queue);
frame.WriteBool(passive);
frame.WriteBool(durable);
frame.WriteBool(exclusive);
frame.WriteBool(autoDelete);
frame.WriteBool(noWait);
frame.WriteTable(arguments);
}
void AMQPQueueDeclareOk::Populate(idMQFrame& frame) {
queue = frame.ReadShortString();
messageCount = frame.ReadUInt32();
consumerCount = frame.ReadUInt32();
}
void AMQPQueueDeclareOk::ToFrame(idMQFrame& frame) {
frame.WriteShortString(queue);
frame.WriteUInt32(messageCount);
frame.WriteUInt32(consumerCount);
}
void AMQPQueueBind::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
queue = frame.ReadShortString();
exchange = frame.ReadShortString();
routingKey = frame.ReadShortString();
noWait = frame.ReadBool();
frame.ReadTable(arguments);
}
void AMQPQueueBind::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(queue);
frame.WriteShortString(exchange);
frame.WriteShortString(routingKey);
frame.WriteBool(noWait);
frame.WriteTable(arguments);
}
EMPTY_METHOD(AMQPQueueBindOk)
void AMQPQueueUnbind::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
queue = frame.ReadShortString();
exchange = frame.ReadShortString();
routingKey = frame.ReadShortString();
frame.ReadTable(arguments);
}
void AMQPQueueUnbind::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(queue);
frame.WriteShortString(exchange);
frame.WriteShortString(routingKey);
frame.WriteTable(arguments);
}
EMPTY_METHOD(AMQPQueueUnbindOk)
void AMQPQueuePurge::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
queue = frame.ReadShortString();
noWait = frame.ReadBool();
}
void AMQPQueuePurge::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(queue);
frame.WriteBool(noWait);
}
void AMQPQueuePurgeOk::Populate(idMQFrame& frame) {
messageCount = frame.ReadUInt32();
}
void AMQPQueuePurgeOk::ToFrame(idMQFrame& frame) {
frame.WriteUInt32(messageCount);
}
void AMQPQueueDelete::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
queue = frame.ReadShortString();
unused = frame.ReadBool();
empty = frame.ReadBool();
noWait = frame.ReadBool();
}
void AMQPQueueDelete::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(queue);
frame.WriteBool(unused);
frame.WriteBool(empty);
frame.WriteBool(noWait);
}
void AMQPQueueDeleteOk::Populate(idMQFrame& frame) {
messageCount = frame.ReadUInt32();
}
void AMQPQueueDeleteOk::ToFrame(idMQFrame& frame) {
frame.WriteUInt32(messageCount);
}
void AMQPBasicQos::Populate(idMQFrame& frame) {
prefetchSize = frame.ReadUInt32();
prefetchCount = frame.ReadUInt16();
isGlobal = frame.ReadBool();
}
void AMQPBasicQos::ToFrame(idMQFrame& frame) {
frame.WriteUInt32(prefetchSize);
frame.WriteUInt16(prefetchCount);
frame.WriteBool(isGlobal);
}
EMPTY_METHOD(AMQPBasicQosOk)
void AMQPBasicConsume::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
queue = frame.ReadShortString();
consumerTag = frame.ReadShortString();
noLocal = frame.ReadBool();
noAck = frame.ReadBool();
exclusive = frame.ReadBool();
noWait = frame.ReadBool();
}
void AMQPBasicConsume::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(queue);
frame.WriteShortString(consumerTag);
frame.WriteBool(noLocal);
frame.WriteBool(noAck);
frame.WriteBool(exclusive);
frame.WriteBool(noWait);
}
void AMQPBasicConsumeOk::Populate(idMQFrame& frame) {
consumerTag = frame.ReadShortString();
}
void AMQPBasicConsumeOk::ToFrame(idMQFrame& frame) {
frame.WriteShortString(consumerTag);
}
void AMQPBasicCancel::Populate(idMQFrame& frame) {
consumerTag = frame.ReadShortString();
noWait = frame.ReadBool();
}
void AMQPBasicCancel::ToFrame(idMQFrame& frame) {
frame.WriteShortString(consumerTag);
frame.WriteBool(noWait);
}
void AMQPBasicCancelOk::Populate(idMQFrame& frame) {
consumerTag = frame.ReadShortString();
}
void AMQPBasicCancelOk::ToFrame(idMQFrame& frame) {
frame.WriteShortString(consumerTag);
}
void AMQPBasicPublish::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
exchange = frame.ReadShortString();
routingKey = frame.ReadShortString();
mandatory = frame.ReadBool();
immediate = frame.ReadBool();
}
void AMQPBasicPublish::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(exchange);
frame.WriteShortString(routingKey);
frame.WriteBool(mandatory);
frame.WriteBool(immediate);
}
void AMQPBasicReturn::Populate(idMQFrame& frame) {
replyCode = frame.ReadUInt16();
replyText = frame.ReadShortString();
exchange = frame.ReadShortString();
routingKey = frame.ReadShortString();
}
void AMQPBasicReturn::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(replyCode);
frame.WriteShortString(replyText);
frame.WriteShortString(exchange);
frame.WriteShortString(routingKey);
}
void AMQPBasicDeliver::Populate(idMQFrame& frame) {
consumerTag = frame.ReadShortString();
deliveryTag = frame.ReadUInt64();
redelivered = frame.ReadBool();
exchange = frame.ReadShortString();
routingKey = frame.ReadShortString();
}
void AMQPBasicDeliver::ToFrame(idMQFrame& frame) {
frame.WriteShortString(consumerTag);
frame.WriteUInt64(deliveryTag);
frame.WriteBool(redelivered);
frame.WriteShortString(exchange);
frame.WriteShortString(routingKey);
}
void AMQPBasicGet::Populate(idMQFrame& frame) {
ticket = frame.ReadUInt16();
queue = frame.ReadShortString();
noAck = frame.ReadBool();
}
void AMQPBasicGet::ToFrame(idMQFrame& frame) {
frame.WriteUInt16(ticket);
frame.WriteShortString(queue);
frame.WriteBool(noAck);
}
void AMQPBasicGetOk::Populate(idMQFrame& frame) {
deliveryTag = frame.ReadUInt64();
redelivered = frame.ReadBool();
exchange = frame.ReadShortString();
routingKey = frame.ReadShortString();
messageCount = frame.ReadUInt32();
}
void AMQPBasicGetOk::ToFrame(idMQFrame& frame) {
frame.WriteUInt64(deliveryTag);
frame.WriteBool(redelivered);
frame.WriteShortString(exchange);
frame.WriteShortString(routingKey);
frame.WriteUInt32(messageCount);
}
void AMQPBasicGetEmpty::Populate(idMQFrame& frame) {
clusterId = frame.ReadShortString();
}
void AMQPBasicGetEmpty::ToFrame(idMQFrame& frame) {
frame.WriteShortString(clusterId);
}
void AMQPBasicAck::Populate(idMQFrame& frame) {
deliveryTag = frame.ReadUInt64();
multiple = frame.ReadBool();
}
void AMQPBasicAck::ToFrame(idMQFrame& frame) {
frame.WriteUInt64(deliveryTag);
frame.WriteBool(multiple);
}
void AMQPBasicReject::Populate(idMQFrame& frame) {
deliveryTag = frame.ReadUInt64();
requeue = frame.ReadBool();
}
void AMQPBasicReject::ToFrame(idMQFrame& frame) {
frame.WriteUInt64(deliveryTag);
frame.WriteBool(requeue);
}
void AMQPBasicRecoverAsync::Populate(idMQFrame& frame) {
requeue = frame.ReadBool();
}
void AMQPBasicRecoverAsync::ToFrame(idMQFrame& frame) {
frame.WriteBool(requeue);
}
void AMQPBasicRecover::Populate(idMQFrame& frame) {
requeue = frame.ReadBool();
}
void AMQPBasicRecover::ToFrame(idMQFrame& frame) {
frame.WriteBool(requeue);
}
EMPTY_METHOD(AMQPBasicRecoverOk)
AMQPBasicProperties::AMQPBasicProperties()
: idMQContentHeader(), contentType(), contentEncoding(), headers(),
deliveryMode(0), priority(0), correlationId(), replyTo(), expiration(),
messageId(), timestamp(0), type(), userId(), appId(), clusterId(),
b_contentType(false), b_contentEncoding(false), b_headers(false),
b_deliveryMode(false), b_priority(false), b_correlationId(false),
b_replyTo(false), b_expiration(false), b_messageId(false),
b_timestamp(false), b_type(false), b_userId(false), b_appId(false),
b_clusterId(false) {
}
void AMQPBasicProperties::Populate(idMQFrame& frame) {
b_contentType = frame.ReadPropertyPresence();
b_contentEncoding = frame.ReadPropertyPresence();
b_headers = frame.ReadPropertyPresence();
b_deliveryMode = frame.ReadPropertyPresence();
b_priority = frame.ReadPropertyPresence();
b_correlationId = frame.ReadPropertyPresence();
b_replyTo = frame.ReadPropertyPresence();
b_expiration = frame.ReadPropertyPresence();
b_messageId = frame.ReadPropertyPresence();
b_timestamp = frame.ReadPropertyPresence();
b_type = frame.ReadPropertyPresence();
b_userId = frame.ReadPropertyPresence();
b_appId = frame.ReadPropertyPresence();
b_clusterId = frame.ReadPropertyPresence();
frame.FinalizeReadPresence();
if (b_contentType) contentType = frame.ReadShortString();
if (b_contentEncoding) contentEncoding = frame.ReadShortString();
if (b_headers) frame.ReadTable(headers);
if (b_deliveryMode) deliveryMode = frame.ReadByte();
if (b_priority) priority = frame.ReadByte();
if (b_correlationId) correlationId = frame.ReadShortString();
if (b_replyTo) replyTo = frame.ReadShortString();
if (b_expiration) expiration = frame.ReadShortString();
if (b_messageId) messageId = frame.ReadShortString();
if (b_timestamp) timestamp = frame.ReadUInt64();
if (b_type) type = frame.ReadShortString();
if (b_userId) userId = frame.ReadShortString();
if (b_appId) appId = frame.ReadShortString();
if (b_clusterId) clusterId = frame.ReadShortString();
}
void AMQPBasicProperties::ToFrame(idMQFrame& frame) {
frame.WritePropertyPresence(b_contentType);
frame.WritePropertyPresence(b_contentEncoding);
frame.WritePropertyPresence(b_headers);
frame.WritePropertyPresence(b_deliveryMode);
frame.WritePropertyPresence(b_priority);
frame.WritePropertyPresence(b_correlationId);
frame.WritePropertyPresence(b_replyTo);
frame.WritePropertyPresence(b_expiration);
frame.WritePropertyPresence(b_messageId);
frame.WritePropertyPresence(b_timestamp);
frame.WritePropertyPresence(b_type);
frame.WritePropertyPresence(b_userId);
frame.WritePropertyPresence(b_appId);
frame.WritePropertyPresence(b_clusterId);
frame.FinalizeWritePresence();
if (b_contentType) frame.WriteShortString(contentType);
if (b_contentEncoding) frame.WriteShortString(contentEncoding);
if (b_headers) frame.WriteTable(headers);
if (b_deliveryMode) frame.WriteByte(deliveryMode);
if (b_priority) frame.WriteByte(priority);
if (b_correlationId) frame.WriteShortString(correlationId);
if (b_replyTo) frame.WriteShortString(replyTo);
if (b_expiration) frame.WriteShortString(expiration);
if (b_messageId) frame.WriteShortString(messageId);
if (b_timestamp) frame.WriteUInt64(timestamp);
if (b_type) frame.WriteShortString(type);
if (b_userId) frame.WriteShortString(userId);
if (b_appId) frame.WriteShortString(appId);
if (b_clusterId) frame.WriteShortString(clusterId);
}
#undef EMPTY_METHOD
@@ -0,0 +1,597 @@
#pragma once
#include "mqcommon.h"
enum amqpConnectionMethodId_t {
AMQP_METHOD_CONNECTION_START = 10,
AMQP_METHOD_CONNECTION_STARTOK = 11,
AMQP_METHOD_CONNECTION_SECURE = 20,
AMQP_METHOD_CONNECTION_SECUREOK = 21,
AMQP_METHOD_CONNECTION_TUNE = 30,
AMQP_METHOD_CONNECTION_TUNEOK = 31,
AMQP_METHOD_CONNECTION_OPEN = 40,
AMQP_METHOD_CONNECTION_OPENOK = 41,
AMQP_METHOD_CONNECTION_REDIRECT = 50,
AMQP_METHOD_CONNECTION_CLOSE = 60,
AMQP_METHOD_CONNECTION_CLOSEOK = 61
};
enum amqpChannelMethodId_t {
AMQP_METHOD_CHANNEL_OPEN = 10,
AMQP_METHOD_CHANNEL_OPENOK = 11,
AMQP_METHOD_CHANNEL_FLOW = 20,
AMQP_METHOD_CHANNEL_FLOWOK = 21,
AMQP_METHOD_CHANNEL_ALERT = 30,
AMQP_METHOD_CHANNEL_CLOSE = 40,
AMQP_METHOD_CHANNEL_CLOSEOK = 41
};
enum amqpExchangeMethodId_t {
AMQP_METHOD_EXCHANGE_DECLARE = 10,
AMQP_METHOD_EXCHANGE_DECLAREOK = 11,
AMQP_METHOD_EXCHANGE_DELETE = 20,
AMQP_METHOD_EXCHANGE_DELETEOK = 21
};
enum amqpQueueMethodId_t {
AMQP_METHOD_QUEUE_DECLARE = 10,
AMQP_METHOD_QUEUE_DECLAREOK = 11,
AMQP_METHOD_QUEUE_BIND = 20,
AMQP_METHOD_QUEUE_BINDOK = 21,
AMQP_METHOD_QUEUE_PURGE = 30,
AMQP_METHOD_QUEUE_PURGEOK = 31,
AMQP_METHOD_QUEUE_DELETE = 40,
AMQP_METHOD_QUEUE_DELETEOK = 41,
AMQP_METHOD_QUEUE_UNBIND = 50,
AMQP_METHOD_QUEUE_UNBINDOK = 51
};
enum amqpBasicMethodId_t {
AMQP_METHOD_BASIC_QOS = 10,
AMQP_METHOD_BASIC_QOSOK = 11,
AMQP_METHOD_BASIC_CONSUME = 20,
AMQP_METHOD_BASIC_CONSUMEOK = 21,
AMQP_METHOD_BASIC_CANCEL = 30,
AMQP_METHOD_BASIC_CANCELOK = 31,
AMQP_METHOD_BASIC_PUBLISH = 40,
AMQP_METHOD_BASIC_RETURN = 50,
AMQP_METHOD_BASIC_DELIVER = 60,
AMQP_METHOD_BASIC_GET = 70,
AMQP_METHOD_BASIC_GETOK = 71,
AMQP_METHOD_BASIC_GETEMPTY = 72,
AMQP_METHOD_BASIC_ACK = 80,
AMQP_METHOD_BASIC_REJECT = 90,
AMQP_METHOD_BASIC_RECOVERASYNC = 100,
AMQP_METHOD_BASIC_RECOVER = 110,
AMQP_METHOD_BASIC_RECOVEROK = 111
};
#define ID_AMQP_METHOD_META(classIdValue, methodIdValue, contentValue, asyncValue) \
int GetClassId() const override { return classIdValue; } \
int GetMethodId() const override { return methodIdValue; } \
bool HasContent() const override { return contentValue; } \
bool IsAsync() const override { return asyncValue; } \
void Populate(idMQFrame& frame) override; \
void ToFrame(idMQFrame& frame) override
class AMQPConnectionStart : public idMQMethod {
public:
AMQPConnectionStart() : versionMajor(0), versionMinor(0) {}
ID_AMQP_METHOD_META(10, 10, false, true);
unsigned char versionMajor;
unsigned char versionMinor;
idMQTable serverProperties;
idMQList<unsigned char> mechanisms;
idMQList<unsigned char> locales;
};
class AMQPConnectionStartOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(10, 11, false, false);
idMQTable clientProperties;
idStr mechanism;
idMQList<unsigned char> response;
idStr locale;
};
class AMQPConnectionSecure : public idMQMethod {
public:
ID_AMQP_METHOD_META(10, 20, false, true);
idMQList<unsigned char> challenge;
};
class AMQPConnectionSecureOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(10, 21, false, false);
idMQList<unsigned char> response;
};
class AMQPConnectionTune : public idMQMethod {
public:
AMQPConnectionTune() : channelMax(0), frameMax(0), heartbeat(0) {}
ID_AMQP_METHOD_META(10, 30, false, true);
unsigned short channelMax;
unsigned int frameMax;
unsigned short heartbeat;
};
class AMQPConnectionTuneOk : public idMQMethod {
public:
AMQPConnectionTuneOk() : channelMax(0), frameMax(0), heartbeat(0) {}
ID_AMQP_METHOD_META(10, 31, false, false);
unsigned short channelMax;
unsigned int frameMax;
unsigned short heartbeat;
};
class AMQPConnectionOpen : public idMQMethod {
public:
AMQPConnectionOpen() : insist(false) {}
ID_AMQP_METHOD_META(10, 40, false, false);
idStr virtualHost;
idStr capabilities;
bool insist;
};
class AMQPConnectionOpenOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(10, 41, false, true);
idStr knownHosts;
};
class AMQPConnectionRedirect : public idMQMethod {
public:
ID_AMQP_METHOD_META(10, 50, false, true);
idStr host;
idStr knownHosts;
};
class AMQPConnectionClose : public idMQMethod {
public:
AMQPConnectionClose() : replyCode(0), cId(0), mId(0) {}
ID_AMQP_METHOD_META(10, 60, false, true);
unsigned short replyCode;
idStr replyText;
unsigned short cId;
unsigned short mId;
};
class AMQPConnectionCloseOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(10, 61, false, true);
};
class AMQPChannelOpen : public idMQMethod {
public:
ID_AMQP_METHOD_META(20, 10, false, false);
idStr oob;
};
class AMQPChannelOpenOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(20, 11, false, true);
};
class AMQPChannelFlow : public idMQMethod {
public:
AMQPChannelFlow() : active(false) {}
ID_AMQP_METHOD_META(20, 20, false, true);
bool active;
};
class AMQPChannelFlowOk : public idMQMethod {
public:
AMQPChannelFlowOk() : active(false) {}
ID_AMQP_METHOD_META(20, 21, false, true);
bool active;
};
class AMQPChannelAlert : public idMQMethod {
public:
AMQPChannelAlert() : replyCode(0) {}
ID_AMQP_METHOD_META(20, 30, false, true);
unsigned short replyCode;
idStr replyText;
idMQTable details;
};
class AMQPChannelClose : public idMQMethod {
public:
AMQPChannelClose() : replyCode(0), cId(0), mId(0) {}
ID_AMQP_METHOD_META(20, 40, false, true);
unsigned short replyCode;
idStr replyText;
unsigned short cId;
unsigned short mId;
};
class AMQPChannelCloseOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(20, 41, false, true);
};
class AMQPExchangeDeclare : public idMQMethod {
public:
AMQPExchangeDeclare() : ticket(0), passive(false), durable(false),
autoDelete(false), isInternal(false), noWait(false) {}
ID_AMQP_METHOD_META(40, 10, false, false);
unsigned short ticket;
idStr exchange;
idStr type;
bool passive;
bool durable;
bool autoDelete;
bool isInternal;
bool noWait;
idMQTable arguments;
};
class AMQPExchangeDeclareOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(40, 11, false, true);
};
class AMQPExchangeDelete : public idMQMethod {
public:
AMQPExchangeDelete() : ticket(0), unused(false), noWait(false) {}
ID_AMQP_METHOD_META(40, 20, false, false);
unsigned short ticket;
idStr exchange;
bool unused;
bool noWait;
};
class AMQPExchangeDeleteOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(40, 21, false, true);
};
class AMQPQueueDeclare : public idMQMethod {
public:
AMQPQueueDeclare() : ticket(0), passive(false), durable(false),
exclusive(false), autoDelete(false), noWait(false) {}
ID_AMQP_METHOD_META(50, 10, false, false);
unsigned short ticket;
idStr queue;
bool passive;
bool durable;
bool exclusive;
bool autoDelete;
bool noWait;
idMQTable arguments;
};
class AMQPQueueDeclareOk : public idMQMethod {
public:
AMQPQueueDeclareOk() : messageCount(0), consumerCount(0) {}
ID_AMQP_METHOD_META(50, 11, false, true);
idStr queue;
unsigned int messageCount;
unsigned int consumerCount;
};
class AMQPQueueBind : public idMQMethod {
public:
AMQPQueueBind() : ticket(0), noWait(false) {}
ID_AMQP_METHOD_META(50, 20, false, false);
unsigned short ticket;
idStr queue;
idStr exchange;
idStr routingKey;
bool noWait;
idMQTable arguments;
};
class AMQPQueueBindOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(50, 21, false, true);
};
class AMQPQueueUnbind : public idMQMethod {
public:
AMQPQueueUnbind() : ticket(0) {}
ID_AMQP_METHOD_META(50, 50, false, false);
unsigned short ticket;
idStr queue;
idStr exchange;
idStr routingKey;
idMQTable arguments;
};
class AMQPQueueUnbindOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(50, 51, false, true);
};
class AMQPQueuePurge : public idMQMethod {
public:
AMQPQueuePurge() : ticket(0), noWait(false) {}
ID_AMQP_METHOD_META(50, 30, false, false);
unsigned short ticket;
idStr queue;
bool noWait;
};
class AMQPQueuePurgeOk : public idMQMethod {
public:
AMQPQueuePurgeOk() : messageCount(0) {}
ID_AMQP_METHOD_META(50, 31, false, true);
unsigned int messageCount;
};
class AMQPQueueDelete : public idMQMethod {
public:
AMQPQueueDelete() : ticket(0), unused(false), empty(false), noWait(false) {}
ID_AMQP_METHOD_META(50, 40, false, false);
unsigned short ticket;
idStr queue;
bool unused;
bool empty;
bool noWait;
};
class AMQPQueueDeleteOk : public idMQMethod {
public:
AMQPQueueDeleteOk() : messageCount(0) {}
ID_AMQP_METHOD_META(50, 41, false, true);
unsigned int messageCount;
};
class AMQPBasicQos : public idMQMethod {
public:
AMQPBasicQos() : prefetchSize(0), prefetchCount(0), isGlobal(false) {}
ID_AMQP_METHOD_META(60, 10, false, false);
unsigned int prefetchSize;
unsigned short prefetchCount;
bool isGlobal;
};
class AMQPBasicQosOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(60, 11, false, true);
};
class AMQPBasicConsume : public idMQMethod {
public:
AMQPBasicConsume() : ticket(0), noLocal(false), noAck(false),
exclusive(false), noWait(false) {}
ID_AMQP_METHOD_META(60, 20, false, false);
unsigned short ticket;
idStr queue;
idStr consumerTag;
bool noLocal;
bool noAck;
bool exclusive;
bool noWait;
};
class AMQPBasicConsumeOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(60, 21, false, true);
idStr consumerTag;
};
class AMQPBasicCancel : public idMQMethod {
public:
AMQPBasicCancel() : noWait(false) {}
ID_AMQP_METHOD_META(60, 30, false, false);
idStr consumerTag;
bool noWait;
};
class AMQPBasicCancelOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(60, 31, false, true);
idStr consumerTag;
};
class AMQPBasicPublish : public idMQMethod {
public:
AMQPBasicPublish() : ticket(0), mandatory(false), immediate(false) {}
ID_AMQP_METHOD_META(60, 40, true, false);
unsigned short ticket;
idStr exchange;
idStr routingKey;
bool mandatory;
bool immediate;
};
class AMQPBasicReturn : public idMQMethod {
public:
AMQPBasicReturn() : replyCode(0) {}
ID_AMQP_METHOD_META(60, 50, true, true);
unsigned short replyCode;
idStr replyText;
idStr exchange;
idStr routingKey;
};
class AMQPBasicDeliver : public idMQMethod {
public:
AMQPBasicDeliver() : deliveryTag(0), redelivered(false) {}
ID_AMQP_METHOD_META(60, 60, true, true);
idStr consumerTag;
std::uint64_t deliveryTag;
bool redelivered;
idStr exchange;
idStr routingKey;
};
class AMQPBasicGet : public idMQMethod {
public:
AMQPBasicGet() : ticket(0), noAck(false) {}
ID_AMQP_METHOD_META(60, 70, false, false);
unsigned short ticket;
idStr queue;
bool noAck;
};
class AMQPBasicGetOk : public idMQMethod {
public:
AMQPBasicGetOk() : deliveryTag(0), redelivered(false), messageCount(0) {}
ID_AMQP_METHOD_META(60, 71, true, true);
std::uint64_t deliveryTag;
bool redelivered;
idStr exchange;
idStr routingKey;
unsigned int messageCount;
};
class AMQPBasicGetEmpty : public idMQMethod {
public:
ID_AMQP_METHOD_META(60, 72, false, true);
idStr clusterId;
};
class AMQPBasicAck : public idMQMethod {
public:
AMQPBasicAck() : deliveryTag(0), multiple(false) {}
ID_AMQP_METHOD_META(60, 80, false, true);
std::uint64_t deliveryTag;
bool multiple;
};
class AMQPBasicReject : public idMQMethod {
public:
AMQPBasicReject() : deliveryTag(0), requeue(false) {}
ID_AMQP_METHOD_META(60, 90, false, true);
std::uint64_t deliveryTag;
bool requeue;
};
class AMQPBasicRecoverAsync : public idMQMethod {
public:
AMQPBasicRecoverAsync() : requeue(false) {}
ID_AMQP_METHOD_META(60, 100, false, false);
bool requeue;
};
class AMQPBasicRecover : public idMQMethod {
public:
AMQPBasicRecover() : requeue(false) {}
ID_AMQP_METHOD_META(60, 110, false, false);
bool requeue;
};
class AMQPBasicRecoverOk : public idMQMethod {
public:
ID_AMQP_METHOD_META(60, 111, false, true);
};
class AMQPBasicProperties : public idMQContentHeader {
public:
AMQPBasicProperties();
int GetProtocolId() const override { return AMQP_CLASS_BASIC; }
void Populate(idMQFrame& frame) override;
void ToFrame(idMQFrame& frame) override;
idStr contentType;
idStr contentEncoding;
idMQTable headers;
unsigned char deliveryMode;
unsigned char priority;
idStr correlationId;
idStr replyTo;
idStr expiration;
idStr messageId;
std::uint64_t timestamp;
idStr type;
idStr userId;
idStr appId;
idStr clusterId;
bool b_contentType;
bool b_contentEncoding;
bool b_headers;
bool b_deliveryMode;
bool b_priority;
bool b_correlationId;
bool b_replyTo;
bool b_expiration;
bool b_messageId;
bool b_timestamp;
bool b_type;
bool b_userId;
bool b_appId;
bool b_clusterId;
};
class idMQConsumer;
struct amqpBasicGetResultOk_t {
amqpBasicGetResultOk_t()
: deliveryTag(0), redelivered(false), exchange(), routingKey(),
messageCount(0), header(nullptr), body() {}
amqpBasicGetResultOk_t(const amqpBasicGetResultOk_t& other);
amqpBasicGetResultOk_t& operator=(const amqpBasicGetResultOk_t& other);
~amqpBasicGetResultOk_t();
std::uint64_t deliveryTag;
bool redelivered;
idStr exchange;
idStr routingKey;
unsigned int messageCount;
AMQPBasicProperties* header;
idMQList<unsigned char> body;
};
class idMQChannel {
public:
explicit idMQChannel(idMQSession* session);
~idMQChannel();
void Init();
void Close();
MQErrors_t HandleCommand(idMQCommand& command);
static idMQMethod* DecodeMethod(idMQFrame& frame);
static idMQContentHeader* DecodeContentHeader(idMQFrame& frame);
void ConnectionStartOk(AMQPConnectionTune& tune,
const idStr& mechanism, const idMQList<unsigned char>& response,
const idStr& locale);
void ConnectionTuneOk(unsigned short channelMax, unsigned int frameMax,
unsigned short heartbeat);
void ConnectionOpen(idStr& knownHosts, const idStr& vhost,
const idStr& capabilities, bool insist);
void ConnectionClose(unsigned short replyCode, const idStr& replyText,
unsigned short classId, unsigned short methodId);
void ChannelOpen(const idStr& outOfBand = idStr(""));
void ExchangeDeclare(const idStr& exchange, const idStr& type,
bool durable);
void ExchangeDeclare(const idStr& exchange, const idStr& type,
bool durable, bool autoDelete);
void BasicPublish(const idStr& exchange, const idStr& routingKey,
const idMQList<unsigned char>& body);
void BasicPublish(const idStr& exchange, const idStr& routingKey,
bool mandatory, bool immediate, const void* data, unsigned int size);
idMQSession* session;
idSysMutex protocolWait;
idDeferredResult<AMQPConnectionStart> deferredConnectionStart;
idDeferredResult<AMQPConnectionTune> deferredConnectionTune;
idDeferredResult<AMQPConnectionOpenOk> deferredConnectionOpenOk;
idDeferredResult<AMQPChannelOpenOk> deferredChannelOpenOk;
idDeferredResult<AMQPChannelFlow> deferredChannelFlow;
idDeferredResult<AMQPChannelFlowOk> deferredChannelFlowOk;
idDeferredResult<AMQPExchangeDeclareOk> deferredExchangeDeclareOk;
idDeferredResult<AMQPExchangeDeleteOk> deferredExchangeDeleteOk;
idDeferredResult<AMQPQueueDeclareOk> deferredQueueDeclareOk;
idDeferredResult<AMQPQueueBindOk> deferredQueueBindOk;
idDeferredResult<AMQPQueuePurgeOk> deferredQueuePurgeOk;
idDeferredResult<AMQPQueueDeleteOk> deferredQueueDeleteOk;
idDeferredResult<AMQPBasicConsumeOk> deferredBasicConsumeOk;
idDeferredResult<AMQPBasicCancelOk> deferredBasicCancelOk;
idDeferredResult<amqpBasicGetResultOk_t> deferredBasicGetResultOk;
idDeferredResult<AMQPBasicRecoverOk> deferredAMQPBasicRecoverOk;
idMQList<idMQConsumer*> consumers;
private:
void ExchangeDeclareInternal(const idStr& exchange, const idStr& type,
bool passive, bool durable, bool autoDelete, bool internal,
bool noWait);
void BasicPublishInternal(const idStr& exchange, const idStr& routingKey,
bool mandatory, bool immediate,
const idMQList<unsigned char>& body);
idMQConsumer* LookupConsumer(const idStr& tag);
bool RemoveConsumer(const idStr& tag);
};
#undef ID_AMQP_METHOD_META
@@ -0,0 +1,421 @@
#include "mqmessaging.h"
#include "mqconsumer.h"
#ifdef nullptr
#undef nullptr
#endif
#include <cstring>
namespace {
template<class T>
idMQMethod* Decode(idMQFrame& frame) {
T* value = new T();
value->Populate(frame);
return value;
}
void SendMethod(idMQSession* session, idMQMethod& method) {
if (session == nullptr) return;
idMQCommand command;
command.method = &method;
session->SendCommand(command);
}
} // namespace
amqpBasicGetResultOk_t::amqpBasicGetResultOk_t(
const amqpBasicGetResultOk_t& other)
: amqpBasicGetResultOk_t() {
*this = other;
}
amqpBasicGetResultOk_t& amqpBasicGetResultOk_t::operator=(
const amqpBasicGetResultOk_t& other) {
if (this == &other) return *this;
deliveryTag = other.deliveryTag;
redelivered = other.redelivered;
exchange = other.exchange;
routingKey = other.routingKey;
messageCount = other.messageCount;
delete header;
header = other.header == nullptr ? nullptr
: new AMQPBasicProperties(*other.header);
body = other.body;
return *this;
}
amqpBasicGetResultOk_t::~amqpBasicGetResultOk_t() { delete header; }
idMQChannel::idMQChannel(idMQSession* sessionValue)
: session(sessionValue), protocolWait(), deferredConnectionStart(),
deferredConnectionTune(), deferredConnectionOpenOk(),
deferredChannelOpenOk(), deferredChannelFlow(), deferredChannelFlowOk(),
deferredExchangeDeclareOk(), deferredExchangeDeleteOk(),
deferredQueueDeclareOk(), deferredQueueBindOk(), deferredQueuePurgeOk(),
deferredQueueDeleteOk(), deferredBasicConsumeOk(),
deferredBasicCancelOk(), deferredBasicGetResultOk(),
deferredAMQPBasicRecoverOk(), consumers() {
}
idMQChannel::~idMQChannel() = default;
void idMQChannel::Init() {
deferredConnectionStart.Clear();
deferredConnectionTune.Clear();
deferredConnectionOpenOk.Clear();
deferredChannelOpenOk.Clear();
}
idMQMethod* idMQChannel::DecodeMethod(idMQFrame& frame) {
const int classId = frame.ReadUInt16();
const int methodId = frame.ReadUInt16();
switch (classId) {
case AMQP_CLASS_CONNECTION:
switch (methodId) {
case 10: return Decode<AMQPConnectionStart>(frame);
case 11: return Decode<AMQPConnectionStartOk>(frame);
case 20: return Decode<AMQPConnectionSecure>(frame);
case 21: return Decode<AMQPConnectionSecureOk>(frame);
case 30: return Decode<AMQPConnectionTune>(frame);
case 31: return Decode<AMQPConnectionTuneOk>(frame);
case 40: return Decode<AMQPConnectionOpen>(frame);
case 41: return Decode<AMQPConnectionOpenOk>(frame);
case 50: return Decode<AMQPConnectionRedirect>(frame);
case 60: return Decode<AMQPConnectionClose>(frame);
case 61: return Decode<AMQPConnectionCloseOk>(frame);
}
break;
case AMQP_CLASS_CHANNEL:
switch (methodId) {
case 10: return Decode<AMQPChannelOpen>(frame);
case 11: return Decode<AMQPChannelOpenOk>(frame);
case 20: return Decode<AMQPChannelFlow>(frame);
case 21: return Decode<AMQPChannelFlowOk>(frame);
case 30: return Decode<AMQPChannelAlert>(frame);
case 40: return Decode<AMQPChannelClose>(frame);
case 41: return Decode<AMQPChannelCloseOk>(frame);
}
break;
case AMQP_CLASS_EXCHANGE:
switch (methodId) {
case 10: return Decode<AMQPExchangeDeclare>(frame);
case 11: return Decode<AMQPExchangeDeclareOk>(frame);
case 20: return Decode<AMQPExchangeDelete>(frame);
case 21: return Decode<AMQPExchangeDeleteOk>(frame);
}
break;
case AMQP_CLASS_QUEUE:
switch (methodId) {
case 10: return Decode<AMQPQueueDeclare>(frame);
case 11: return Decode<AMQPQueueDeclareOk>(frame);
case 20: return Decode<AMQPQueueBind>(frame);
case 21: return Decode<AMQPQueueBindOk>(frame);
case 30: return Decode<AMQPQueuePurge>(frame);
case 31: return Decode<AMQPQueuePurgeOk>(frame);
case 40: return Decode<AMQPQueueDelete>(frame);
case 41: return Decode<AMQPQueueDeleteOk>(frame);
case 50: return Decode<AMQPQueueUnbind>(frame);
case 51: return Decode<AMQPQueueUnbindOk>(frame);
}
break;
case AMQP_CLASS_BASIC:
switch (methodId) {
case 10: return Decode<AMQPBasicQos>(frame);
case 11: return Decode<AMQPBasicQosOk>(frame);
case 20: return Decode<AMQPBasicConsume>(frame);
case 21: return Decode<AMQPBasicConsumeOk>(frame);
case 30: return Decode<AMQPBasicCancel>(frame);
case 31: return Decode<AMQPBasicCancelOk>(frame);
case 40: return Decode<AMQPBasicPublish>(frame);
case 50: return Decode<AMQPBasicReturn>(frame);
case 60: return Decode<AMQPBasicDeliver>(frame);
case 70: return Decode<AMQPBasicGet>(frame);
case 71: return Decode<AMQPBasicGetOk>(frame);
case 72: return Decode<AMQPBasicGetEmpty>(frame);
case 80: return Decode<AMQPBasicAck>(frame);
case 90: return Decode<AMQPBasicReject>(frame);
case 100: return Decode<AMQPBasicRecoverAsync>(frame);
case 110: return Decode<AMQPBasicRecover>(frame);
case 111: return Decode<AMQPBasicRecoverOk>(frame);
}
break;
}
return nullptr;
}
idMQContentHeader* idMQChannel::DecodeContentHeader(idMQFrame& frame) {
const int classId = frame.ReadUInt16();
frame.ReadUInt16(); // weight, reserved and always zero
const std::uint64_t bodyLength = frame.ReadUInt64();
if (classId != AMQP_CLASS_BASIC) return nullptr;
AMQPBasicProperties* header = new AMQPBasicProperties();
header->bodyLength = bodyLength;
header->Populate(frame);
return header;
}
idMQConsumer* idMQChannel::LookupConsumer(const idStr& tag) {
for (int index = 0; index < consumers.Num(); ++index) {
if (consumers[index] != nullptr
&& _stricmp(consumers[index]->consumerTag.c_str(),
tag.c_str()) == 0) {
return consumers[index];
}
}
return nullptr;
}
bool idMQChannel::RemoveConsumer(const idStr& tag) {
for (int index = 0; index < consumers.Num(); ++index) {
if (consumers[index] != nullptr
&& _stricmp(consumers[index]->consumerTag.c_str(),
tag.c_str()) == 0) {
return consumers.RemoveIndex(index);
}
}
return false;
}
MQErrors_t idMQChannel::HandleCommand(idMQCommand& command) {
if (command.method == nullptr) return MQERROR_UNEXPECTED_METHOD;
const int classId = command.method->GetClassId();
const int methodId = command.method->GetMethodId();
if (classId == AMQP_CLASS_CONNECTION) {
switch (methodId) {
case 10: deferredConnectionStart.SetValue(
*static_cast<AMQPConnectionStart*>(command.method)); break;
case 30: deferredConnectionTune.SetValue(
*static_cast<AMQPConnectionTune*>(command.method)); break;
case 41: deferredConnectionOpenOk.SetValue(
*static_cast<AMQPConnectionOpenOk*>(command.method)); break;
case 60: {
AMQPConnectionCloseOk response;
SendMethod(session, response);
return MQERROR_SERVER_CLOSED;
}
case 61: break;
default: return MQERROR_UNEXPECTED_METHOD;
}
return MQERROR_NONE;
}
if (classId == AMQP_CLASS_CHANNEL) {
switch (methodId) {
case 11: deferredChannelOpenOk.SetValue(
*static_cast<AMQPChannelOpenOk*>(command.method)); break;
case 20: {
AMQPChannelFlow& flow = *static_cast<AMQPChannelFlow*>(command.method);
deferredChannelFlow.SetValue(flow);
AMQPChannelFlowOk response;
response.active = flow.active;
SendMethod(session, response);
break;
}
case 21: deferredChannelFlowOk.SetValue(
*static_cast<AMQPChannelFlowOk*>(command.method)); break;
case 40: {
AMQPChannelCloseOk response;
SendMethod(session, response);
return MQERROR_SERVER_CLOSED;
}
case 41: break;
default: return MQERROR_UNEXPECTED_METHOD;
}
return MQERROR_NONE;
}
if (classId == AMQP_CLASS_EXCHANGE) {
if (methodId == 11) deferredExchangeDeclareOk.SetValue(
*static_cast<AMQPExchangeDeclareOk*>(command.method));
else if (methodId == 21) deferredExchangeDeleteOk.SetValue(
*static_cast<AMQPExchangeDeleteOk*>(command.method));
else return MQERROR_UNEXPECTED_METHOD;
return MQERROR_NONE;
}
if (classId == AMQP_CLASS_QUEUE) {
switch (methodId) {
case 11: deferredQueueDeclareOk.SetValue(
*static_cast<AMQPQueueDeclareOk*>(command.method)); break;
case 21: deferredQueueBindOk.SetValue(
*static_cast<AMQPQueueBindOk*>(command.method)); break;
case 31: deferredQueuePurgeOk.SetValue(
*static_cast<AMQPQueuePurgeOk*>(command.method)); break;
case 41: deferredQueueDeleteOk.SetValue(
*static_cast<AMQPQueueDeleteOk*>(command.method)); break;
default: return MQERROR_UNEXPECTED_METHOD;
}
return MQERROR_NONE;
}
if (classId == AMQP_CLASS_BASIC) {
switch (methodId) {
case 21: {
AMQPBasicConsumeOk& value =
*static_cast<AMQPBasicConsumeOk*>(command.method);
deferredBasicConsumeOk.SetValue(value);
idMQConsumer* consumer = LookupConsumer(value.consumerTag);
if (consumer != nullptr) consumer->ProcessBasicConsumeOk(
value.consumerTag);
break;
}
case 30: {
AMQPBasicCancel& value =
*static_cast<AMQPBasicCancel*>(command.method);
idMQConsumer* consumer = LookupConsumer(value.consumerTag);
if (consumer != nullptr) consumer->ProcessBasicCancel(
value.consumerTag);
RemoveConsumer(value.consumerTag);
break;
}
case 31: deferredBasicCancelOk.SetValue(
*static_cast<AMQPBasicCancelOk*>(command.method)); break;
case 60: {
AMQPBasicDeliver& value =
*static_cast<AMQPBasicDeliver*>(command.method);
idMQConsumer* consumer = LookupConsumer(value.consumerTag);
if (consumer != nullptr) consumer->ProcessBasicDeliver(
value.consumerTag, value.deliveryTag, value.redelivered,
value.exchange, value.routingKey,
static_cast<AMQPBasicProperties*>(command.contentHeader),
command.contentBody);
break;
}
case 71: {
AMQPBasicGetOk& value =
*static_cast<AMQPBasicGetOk*>(command.method);
amqpBasicGetResultOk_t result;
result.deliveryTag = value.deliveryTag;
result.redelivered = value.redelivered;
result.exchange = value.exchange;
result.routingKey = value.routingKey;
result.messageCount = value.messageCount;
result.header = command.contentHeader == nullptr ? nullptr
: new AMQPBasicProperties(
*static_cast<AMQPBasicProperties*>(command.contentHeader));
result.body = command.contentBody;
deferredBasicGetResultOk.SetValue(result);
break;
}
case 111: deferredAMQPBasicRecoverOk.SetValue(
*static_cast<AMQPBasicRecoverOk*>(command.method)); break;
case 50: case 72: case 80: case 90: break;
default: return MQERROR_UNEXPECTED_METHOD;
}
return MQERROR_NONE;
}
return MQERROR_UNEXPECTED_CLASS;
}
void idMQChannel::ConnectionStartOk(AMQPConnectionTune& tune,
const idStr& mechanism, const idMQList<unsigned char>& response,
const idStr& locale) {
idScopedCriticalSection lock(protocolWait);
AMQPConnectionStartOk request;
request.mechanism = mechanism;
request.response = response;
request.locale = locale;
SendMethod(session, request);
deferredConnectionTune.GetValue(tune);
}
void idMQChannel::ConnectionTuneOk(const unsigned short channelMax,
const unsigned int frameMax, const unsigned short heartbeat) {
AMQPConnectionTuneOk request;
request.channelMax = channelMax;
request.frameMax = frameMax;
request.heartbeat = heartbeat;
SendMethod(session, request);
}
void idMQChannel::ConnectionOpen(idStr& knownHosts, const idStr& vhost,
const idStr& capabilities, const bool insist) {
idScopedCriticalSection lock(protocolWait);
AMQPConnectionOpen request;
request.virtualHost = vhost;
request.capabilities = capabilities;
request.insist = insist;
SendMethod(session, request);
AMQPConnectionOpenOk result;
if (deferredConnectionOpenOk.GetValue(result)) knownHosts = result.knownHosts;
}
void idMQChannel::ConnectionClose(const unsigned short replyCode,
const idStr& replyText, const unsigned short classId,
const unsigned short methodId) {
AMQPConnectionClose request;
request.replyCode = replyCode;
request.replyText = replyText;
request.cId = classId;
request.mId = methodId;
SendMethod(session, request);
}
void idMQChannel::ChannelOpen(const idStr& outOfBand) {
idScopedCriticalSection lock(protocolWait);
AMQPChannelOpen request;
request.oob = outOfBand;
SendMethod(session, request);
deferredChannelOpenOk.GetValue();
}
void idMQChannel::ExchangeDeclareInternal(const idStr& exchange,
const idStr& type, const bool passive, const bool durable,
const bool autoDelete, const bool internal, const bool noWait) {
idScopedCriticalSection lock(protocolWait);
AMQPExchangeDeclare request;
request.exchange = exchange;
request.type = type;
request.passive = passive;
request.durable = durable;
request.autoDelete = autoDelete;
request.isInternal = internal;
request.noWait = noWait;
SendMethod(session, request);
if (!noWait) deferredExchangeDeclareOk.GetValue();
}
void idMQChannel::ExchangeDeclare(const idStr& exchange, const idStr& type,
const bool durable) {
ExchangeDeclareInternal(exchange, type, false, durable, false, false,
false);
}
void idMQChannel::ExchangeDeclare(const idStr& exchange, const idStr& type,
const bool durable, const bool autoDelete) {
ExchangeDeclareInternal(exchange, type, false, durable, autoDelete,
false, false);
}
void idMQChannel::BasicPublishInternal(const idStr& exchange,
const idStr& routingKey, const bool mandatory, const bool immediate,
const idMQList<unsigned char>& body) {
AMQPBasicPublish method;
method.exchange = exchange;
method.routingKey = routingKey;
method.mandatory = mandatory;
method.immediate = immediate;
AMQPBasicProperties properties;
properties.bodyLength = body.Num();
idMQCommand command;
command.method = &method;
command.contentHeader = &properties;
command.contentBody = body;
session->SendCommand(command);
}
void idMQChannel::BasicPublish(const idStr& exchange,
const idStr& routingKey, const idMQList<unsigned char>& body) {
BasicPublishInternal(exchange, routingKey, false, false, body);
}
void idMQChannel::BasicPublish(const idStr& exchange,
const idStr& routingKey, const bool mandatory, const bool immediate,
const void* data, const unsigned int size) {
idMQList<unsigned char> body(16);
const unsigned char* bytes = static_cast<const unsigned char*>(data);
for (unsigned int index = 0; index < size; ++index) body.Append(bytes[index]);
BasicPublishInternal(exchange, routingKey, mandatory, immediate, body);
}
void idMQChannel::Close() {
if (session != nullptr) session->Close();
}
@@ -0,0 +1,127 @@
#include "mqmessaging.h"
#include <algorithm>
idMQSession::idMQSession(const int number, idMQConnection* owner)
: connection(owner), cmdBuilder(), protocol(nullptr), currentCommand(),
channelNumber(number), isOpen(false) {
}
idMQSession::~idMQSession() {
currentCommand.Reset();
delete protocol;
}
void idMQSession::Init(const bool connectionChannel) {
if (protocol == nullptr) protocol = new idMQChannel(this);
protocol->Init();
isOpen = true;
if (!connectionChannel) protocol->ChannelOpen();
}
void idMQSession::Close() {
Close("client shutdown", MQERROR_NONE, true);
}
void idMQSession::Close(const char*, const MQErrors_t, const bool notifyServer) {
if (!isOpen) return;
if (notifyServer && channelNumber != 0 && protocol != nullptr) {
AMQPChannelClose request;
request.replyCode = 200;
request.replyText = "Goodbye";
idMQCommand command;
command.method = &request;
SendCommand(command);
}
isOpen = false;
}
void idMQSession::HandleFrame(idMQFrame& frame) {
const MQErrors_t error = cmdBuilder.HandleFrame(currentCommand, frame);
if (error != MQERROR_NONE) {
Close("invalid frame", error, false);
currentCommand.Reset();
return;
}
if (currentCommand.complete) {
const MQErrors_t result = protocol == nullptr
? MQERROR_UNKNOWN : protocol->HandleCommand(currentCommand);
if (result != MQERROR_NONE && result != MQERROR_SERVER_CLOSED) {
Close("command handling failed", result, false);
}
currentCommand.Reset();
}
}
void idMQSession::SendCommand(idMQCommand& command) {
if (connection == nullptr || command.method == nullptr) return;
idMQFrame methodFrame(AMQP_FRAME_METHOD,
static_cast<unsigned short>(channelNumber));
methodFrame.WriteUInt16(command.method->GetClassId());
methodFrame.WriteUInt16(command.method->GetMethodId());
command.method->ToFrame(methodFrame);
connection->SendFrame(methodFrame);
if (!command.method->HasContent() || command.contentHeader == nullptr) return;
command.contentHeader->bodyLength = command.contentBody.Num();
idMQFrame headerFrame(AMQP_FRAME_HEADER,
static_cast<unsigned short>(channelNumber));
headerFrame.WriteUInt16(command.contentHeader->GetProtocolId());
headerFrame.WriteUInt16(0);
headerFrame.WriteUInt64(command.contentHeader->bodyLength);
command.contentHeader->ToFrame(headerFrame);
connection->SendFrame(headerFrame);
const unsigned int maximum = connection->frameMaxSize > 8
? connection->frameMaxSize - 8 : 131064;
int offset = 0;
while (offset < command.contentBody.Num()) {
const int amount = std::min(command.contentBody.Num() - offset,
static_cast<int>(maximum));
idMQFrame bodyFrame(AMQP_FRAME_BODY,
static_cast<unsigned short>(channelNumber));
bodyFrame.WriteData(command.contentBody.Ptr() + offset, amount);
connection->SendFrame(bodyFrame);
offset += amount;
}
}
idMQSessionManager::idMQSessionManager(idMQConnection* owner)
: initialized(false), connection(owner), maxSessions(0), sessionList() {
}
idMQSessionManager::~idMQSessionManager() { Shutdown(); }
void idMQSessionManager::Init(const int maximumSessions) {
Shutdown();
maxSessions = maximumSessions <= 0 ? 65535 : maximumSessions;
initialized = true;
}
void idMQSessionManager::Shutdown() {
for (int index = 0; index < sessionList.Num(); ++index) {
delete sessionList[index];
}
sessionList.Clear();
initialized = false;
}
idMQSession* idMQSessionManager::CreateSession() {
if (!initialized || sessionList.Num() >= maxSessions) return nullptr;
int number = 1;
while (Lookup(number) != nullptr) ++number;
idMQSession* session = new idMQSession(number, connection);
sessionList.Append(session);
session->Init(false);
return session;
}
idMQSession* idMQSessionManager::Lookup(const int number) {
for (int index = 0; index < sessionList.Num(); ++index) {
if (sessionList[index] != nullptr
&& sessionList[index]->channelNumber == number) {
return sessionList[index];
}
}
return nullptr;
}
@@ -0,0 +1,67 @@
#include "mqcommon.h"
#ifdef nullptr
#undef nullptr
#endif
idMQClientThread::idMQClientThread()
: handle(0), signalQuit(false), terminated(true), connection(),
retryTime(0) {
}
idMQClientThread::~idMQClientThread() { StopThread(true); }
void idMQClientThread::StopThread(const bool waitForStop) {
signalQuit = true;
connection.Close();
if (waitForStop && handle != 0) {
Sys_WaitForThread(static_cast<uintptr_t>(handle));
Sys_DestroyThread(static_cast<uintptr_t>(handle));
handle = 0;
}
}
void idMQClientThread::Connect(idMQClientThread* thread) {
if (thread == nullptr) return;
amqpEndpoint_t endpoint;
thread->connection.Init(endpoint);
int delay = 1000;
for (int attempt = 0; attempt < 4 && !thread->signalQuit; ++attempt) {
if (thread->connection.Connect()) {
thread->retryTime = 0;
return;
}
thread->retryTime = static_cast<int>(GetTickCount()) + delay;
const int end = thread->retryTime;
while (!thread->signalQuit
&& static_cast<int>(GetTickCount()) < end) Sleep(25);
delay += 1000;
}
thread->signalQuit = true;
}
unsigned int idMQClientThread::Thread(void* value) {
idMQClientThread* thread = static_cast<idMQClientThread*>(value);
Connect(thread);
if (!thread->signalQuit) thread->PreRun();
while (!thread->signalQuit) {
thread->ThreadSlice();
if (thread->connection.connectionTerminated && !thread->signalQuit) {
Connect(thread);
if (!thread->signalQuit) thread->PreRun();
}
}
thread->connection.Close();
thread->OnThreadTerminate();
thread->terminated = true;
return 0;
}
void idMQClientThread::StartThread(const char* threadName) {
if (handle != 0) return;
signalQuit = false;
terminated = false;
handle = static_cast<unsigned int>(Sys_CreateThread(Thread, this,
THREAD_LOWEST, threadName == nullptr ? "AMQP Client" : threadName,
CORE_ANY, 0x20000, false));
}
@@ -0,0 +1,584 @@
#include "reports.pb.h"
#include <algorithm>
#include <cstring>
#include <limits>
namespace {
void WriteVarint(std::string& output, std::uint64_t value) {
while (value >= 0x80) {
output.push_back(static_cast<char>((value & 0x7f) | 0x80));
value >>= 7;
}
output.push_back(static_cast<char>(value));
}
void WriteTag(std::string& output, const int number, const int wireType) {
WriteVarint(output, (static_cast<std::uint64_t>(number) << 3) | wireType);
}
void WriteLengthDelimited(std::string& output, const int number,
const std::string& value) {
WriteTag(output, number, 2);
WriteVarint(output, value.size());
output.append(value);
}
bool ReadVarint(const unsigned char*& cursor, const unsigned char* end,
std::uint64_t& value) {
value = 0;
for (int shift = 0; shift < 70 && cursor < end; shift += 7) {
const unsigned char byteValue = *cursor++;
if (shift < 64) value |= static_cast<std::uint64_t>(byteValue & 0x7f)
<< shift;
if ((byteValue & 0x80) == 0) return shift < 64 || byteValue <= 1;
}
return false;
}
bool ReadLength(const unsigned char*& cursor, const unsigned char* end,
const unsigned char*& fieldEnd) {
std::uint64_t size = 0;
if (!ReadVarint(cursor, end, size)
|| size > static_cast<std::uint64_t>(end - cursor)) return false;
fieldEnd = cursor + static_cast<std::size_t>(size);
return true;
}
bool SkipField(const int wireType, const unsigned char*& cursor,
const unsigned char* end) {
std::uint64_t ignored = 0;
const unsigned char* fieldEnd = 0;
switch (wireType) {
case 0: return ReadVarint(cursor, end, ignored);
case 1:
if (end - cursor < 8) return false;
cursor += 8;
return true;
case 2:
if (!ReadLength(cursor, end, fieldEnd)) return false;
cursor = fieldEnd;
return true;
case 5:
if (end - cursor < 4) return false;
cursor += 4;
return true;
default: return false;
}
}
std::shared_ptr<google::protobuf::MessageLite> CloneMessage(
const std::shared_ptr<google::protobuf::MessageLite>& source) {
if (!source) return std::shared_ptr<google::protobuf::MessageLite>();
std::shared_ptr<google::protobuf::MessageLite> result(source->New());
std::string data;
if (!source->SerializePartialToString(&data)
|| !result->ParseFromString(data)) {
return std::shared_ptr<google::protobuf::MessageLite>();
}
return result;
}
} // namespace
namespace google {
namespace protobuf {
bool MessageLite::SerializeToArray(void* data, const int size) const {
return IsInitialized() && SerializePartialToArray(data, size);
}
bool MessageLite::SerializePartialToString(std::string* output) const {
if (output == 0) return false;
const int size = ByteSize();
output->resize(size);
return size == 0 || SerializePartialToArray(&(*output)[0], size);
}
bool MessageLite::SerializeToString(std::string* output) const {
return IsInitialized() && SerializePartialToString(output);
}
std::string MessageLite::SerializeAsString() const {
std::string output;
SerializeToString(&output);
return output;
}
bool MessageLite::ParseFromString(const std::string& input) {
return ParseFromArray(input.data(), static_cast<int>(input.size()));
}
} // namespace protobuf
} // namespace google
namespace idreports {
bool LogEvent_Severity_IsValid(const int value) {
return value == 80 || value == 70 || value == 60 || value == 50
|| value == 40 || value == 30 || value == 20 || value == 10
|| value == 0;
}
const char* LogEvent_Severity_Name(const LogEvent_Severity value) {
switch (value) {
case LogEvent_Severity_SEV_EMERGENCY: return "SEV_EMERGENCY";
case LogEvent_Severity_SEV_ALERT: return "SEV_ALERT";
case LogEvent_Severity_SEV_CRITICAL: return "SEV_CRITICAL";
case LogEvent_Severity_SEV_ERROR: return "SEV_ERROR";
case LogEvent_Severity_SEV_WARNING: return "SEV_WARNING";
case LogEvent_Severity_SEV_NOTICE: return "SEV_NOTICE";
case LogEvent_Severity_SEV_INFO: return "SEV_INFO";
case LogEvent_Severity_SEV_DEBUG: return "SEV_DEBUG";
case LogEvent_Severity_SEV_TRASH: return "SEV_TRASH";
default: return "UNKNOWN";
}
}
bool TargetPlatform_IsValid(const int value) {
return value >= TARGET_WIN32 && value <= TARGET_LINUX;
}
const char* TargetPlatform_Name(const TargetPlatform value) {
static const char* const names[] = {
"TARGET_WIN32", "TARGET_X64", "TARGET_XBOX360", "TARGET_PS3",
"TARGET_DURANGO", "TARGET_WIIU", "TARGET_IPHONE", "TARGET_OSX",
"TARGET_LINUX"
};
return TargetPlatform_IsValid(value) ? names[value] : "UNKNOWN";
}
namespace internal {
FieldValue::FieldValue()
: kind(FIELD_UNKNOWN), integer(0), real(0.0f), text(), message(),
strings(), integers(), messages() {
}
ReportMessage::ReportMessage() : fields_() {
}
ReportMessage::ReportMessage(const ReportMessage& other) : fields_() {
MergeFrom(other);
}
ReportMessage& ReportMessage::operator=(const ReportMessage& other) {
if (this != &other) CopyFrom(other);
return *this;
}
ReportMessage::~ReportMessage() {
}
void ReportMessage::Clear() {
fields_.clear();
}
bool ReportMessage::HasField(const int number) const {
return fields_.find(number) != fields_.end();
}
void ReportMessage::ClearField(const int number) {
fields_.erase(number);
}
std::uint64_t ReportMessage::GetInteger(const int number) const {
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() ? 0 : found->second.integer;
}
void ReportMessage::SetInteger(const int number, const FieldKind kind,
const std::uint64_t value) {
FieldValue& field = fields_[number];
field.kind = kind;
field.integer = value;
}
float ReportMessage::GetFloat(const int number) const {
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() ? 0.0f : found->second.real;
}
void ReportMessage::SetFloat(const int number, const float value) {
FieldValue& field = fields_[number];
field.kind = FIELD_FLOAT;
field.real = value;
}
const std::string& ReportMessage::GetString(const int number) const {
static const std::string empty;
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() ? empty : found->second.text;
}
std::string* ReportMessage::MutableString(const int number) {
FieldValue& field = fields_[number];
field.kind = FIELD_STRING;
return &field.text;
}
std::string* ReportMessage::ReleaseString(const int number) {
const std::map<int, FieldValue>::iterator found = fields_.find(number);
if (found == fields_.end()) return 0;
std::string* result = new std::string(found->second.text);
fields_.erase(found);
return result;
}
void ReportMessage::SetString(const int number, const char* value,
const std::size_t size) {
FieldValue& field = fields_[number];
field.kind = FIELD_STRING;
field.text.assign(value == 0 ? "" : value, value == 0 ? 0 : size);
}
int ReportMessage::RepeatedIntegerSize(const int number) const {
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() ? 0
: static_cast<int>(found->second.integers.size());
}
std::uint64_t ReportMessage::GetRepeatedInteger(const int number,
const int index) const {
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() || index < 0
|| index >= static_cast<int>(found->second.integers.size())
? 0 : found->second.integers[index];
}
void ReportMessage::SetRepeatedInteger(const int number, const int index,
const FieldKind kind, const std::uint64_t value) {
FieldValue& field = fields_[number];
field.kind = kind;
if (index >= 0 && index < static_cast<int>(field.integers.size())) {
field.integers[index] = value;
}
}
void ReportMessage::AddRepeatedInteger(const int number, const FieldKind kind,
const std::uint64_t value) {
FieldValue& field = fields_[number];
field.kind = kind;
field.integers.push_back(value);
}
int ReportMessage::RepeatedStringSize(const int number) const {
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() ? 0
: static_cast<int>(found->second.strings.size());
}
const std::string& ReportMessage::GetRepeatedString(const int number,
const int index) const {
static const std::string empty;
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() || index < 0
|| index >= static_cast<int>(found->second.strings.size())
? empty : found->second.strings[index];
}
std::string* ReportMessage::MutableRepeatedString(const int number,
const int index) {
const std::map<int, FieldValue>::iterator found = fields_.find(number);
return found == fields_.end() || index < 0
|| index >= static_cast<int>(found->second.strings.size())
? 0 : &found->second.strings[index];
}
std::string* ReportMessage::AddRepeatedString(const int number) {
FieldValue& field = fields_[number];
field.kind = FIELD_REPEATED_STRING;
field.strings.push_back(std::string());
return &field.strings.back();
}
void ReportMessage::SetRepeatedString(const int number, const int index,
const char* value, const std::size_t size) {
std::string* item = MutableRepeatedString(number, index);
if (item != 0) item->assign(value == 0 ? "" : value,
value == 0 ? 0 : size);
}
int ReportMessage::RepeatedMessageSize(const int number) const {
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() ? 0
: static_cast<int>(found->second.messages.size());
}
const google::protobuf::MessageLite* ReportMessage::GetRepeatedMessage(
const int number, const int index) const {
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() || index < 0
|| index >= static_cast<int>(found->second.messages.size())
? 0 : found->second.messages[index].get();
}
google::protobuf::MessageLite* ReportMessage::MutableRepeatedMessage(
const int number, const int index) {
const std::map<int, FieldValue>::iterator found = fields_.find(number);
return found == fields_.end() || index < 0
|| index >= static_cast<int>(found->second.messages.size())
? 0 : found->second.messages[index].get();
}
google::protobuf::MessageLite* ReportMessage::AddRepeatedMessage(
const int number) {
std::shared_ptr<google::protobuf::MessageLite> value(
NewMessageForField(number));
if (!value) return 0;
FieldValue& field = fields_[number];
field.kind = FIELD_REPEATED_MESSAGE;
field.messages.push_back(value);
return value.get();
}
const google::protobuf::MessageLite* ReportMessage::GetMessage(
const int number) const {
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
return found == fields_.end() ? 0 : found->second.message.get();
}
google::protobuf::MessageLite* ReportMessage::MutableMessage(const int number) {
FieldValue& field = fields_[number];
field.kind = FIELD_MESSAGE;
if (!field.message) field.message.reset(NewMessageForField(number));
return field.message.get();
}
google::protobuf::MessageLite* ReportMessage::ReleaseMessage(const int number) {
const std::map<int, FieldValue>::iterator found = fields_.find(number);
if (found == fields_.end() || !found->second.message) return 0;
google::protobuf::MessageLite* result = found->second.message->New();
std::string data;
found->second.message->SerializePartialToString(&data);
result->ParseFromString(data);
fields_.erase(found);
return result;
}
void ReportMessage::SetAllocatedMessage(const int number,
google::protobuf::MessageLite* value) {
if (value == 0) {
ClearField(number);
return;
}
FieldValue& field = fields_[number];
field.kind = FIELD_MESSAGE;
field.message.reset(value);
}
google::protobuf::MessageLite* ReportMessage::NewMessageForField(int) const {
return 0;
}
bool ReportMessage::IsInitialized() const {
if (!HasRequiredFields()) return false;
for (std::map<int, FieldValue>::const_iterator field = fields_.begin();
field != fields_.end(); ++field) {
if (field->second.message && !field->second.message->IsInitialized()) {
return false;
}
for (std::size_t index = 0; index < field->second.messages.size();
++index) {
if (field->second.messages[index]
&& !field->second.messages[index]->IsInitialized()) {
return false;
}
}
}
return true;
}
bool ReportMessage::Serialize(std::string& output) const {
output.clear();
for (std::map<int, FieldValue>::const_iterator iterator = fields_.begin();
iterator != fields_.end(); ++iterator) {
const int number = iterator->first;
const FieldValue& field = iterator->second;
switch (field.kind) {
case FIELD_STRING:
WriteLengthDelimited(output, number, field.text);
break;
case FIELD_UINT32:
case FIELD_UINT64:
case FIELD_INT32:
case FIELD_INT64:
case FIELD_ENUM:
case FIELD_BOOL:
WriteTag(output, number, 0);
WriteVarint(output, field.integer);
break;
case FIELD_FLOAT: {
WriteTag(output, number, 5);
std::uint32_t bits = 0;
std::memcpy(&bits, &field.real, sizeof(bits));
output.push_back(static_cast<char>(bits));
output.push_back(static_cast<char>(bits >> 8));
output.push_back(static_cast<char>(bits >> 16));
output.push_back(static_cast<char>(bits >> 24));
break;
}
case FIELD_MESSAGE:
if (field.message) {
std::string nested;
field.message->SerializePartialToString(&nested);
WriteLengthDelimited(output, number, nested);
}
break;
case FIELD_REPEATED_STRING:
for (std::size_t index = 0; index < field.strings.size(); ++index) {
WriteLengthDelimited(output, number, field.strings[index]);
}
break;
case FIELD_REPEATED_UINT32:
case FIELD_REPEATED_UINT64:
for (std::size_t index = 0; index < field.integers.size(); ++index) {
WriteTag(output, number, 0);
WriteVarint(output, field.integers[index]);
}
break;
case FIELD_REPEATED_MESSAGE:
for (std::size_t index = 0; index < field.messages.size(); ++index) {
if (field.messages[index]) {
std::string nested;
field.messages[index]->SerializePartialToString(&nested);
WriteLengthDelimited(output, number, nested);
}
}
break;
default: break;
}
}
return true;
}
int ReportMessage::ByteSize() const {
std::string output;
Serialize(output);
return static_cast<int>(output.size());
}
bool ReportMessage::SerializePartialToArray(void* data, const int size) const {
if (size < 0 || (data == 0 && size != 0)) return false;
std::string output;
Serialize(output);
if (size < static_cast<int>(output.size())) return false;
if (!output.empty()) std::memcpy(data, output.data(), output.size());
return true;
}
bool ReportMessage::ParseFromArray(const void* data, const int size) {
if (size < 0 || (data == 0 && size != 0)) return false;
Clear();
const unsigned char* cursor = static_cast<const unsigned char*>(data);
const unsigned char* const end = cursor + size;
while (cursor < end) {
std::uint64_t tag = 0;
if (!ReadVarint(cursor, end, tag) || tag == 0) return false;
const int number = static_cast<int>(tag >> 3);
const int wireType = static_cast<int>(tag & 7);
const FieldKind kind = FieldKindForNumber(number);
if (kind == FIELD_UNKNOWN) {
if (!SkipField(wireType, cursor, end)) return false;
continue;
}
std::uint64_t integer = 0;
const unsigned char* fieldEnd = 0;
if (kind == FIELD_STRING || kind == FIELD_REPEATED_STRING
|| kind == FIELD_MESSAGE || kind == FIELD_REPEATED_MESSAGE) {
if (wireType != 2 || !ReadLength(cursor, end, fieldEnd)) return false;
if (kind == FIELD_STRING) {
SetString(number, reinterpret_cast<const char*>(cursor),
static_cast<std::size_t>(fieldEnd - cursor));
} else if (kind == FIELD_REPEATED_STRING) {
std::string* item = AddRepeatedString(number);
item->assign(reinterpret_cast<const char*>(cursor),
static_cast<std::size_t>(fieldEnd - cursor));
} else {
google::protobuf::MessageLite* nested = kind == FIELD_MESSAGE
? MutableMessage(number) : AddRepeatedMessage(number);
if (nested == 0 || !nested->ParseFromArray(cursor,
static_cast<int>(fieldEnd - cursor))) return false;
}
cursor = fieldEnd;
} else if (kind == FIELD_FLOAT) {
if (wireType != 5 || end - cursor < 4) return false;
const std::uint32_t bits = static_cast<std::uint32_t>(cursor[0])
| (static_cast<std::uint32_t>(cursor[1]) << 8)
| (static_cast<std::uint32_t>(cursor[2]) << 16)
| (static_cast<std::uint32_t>(cursor[3]) << 24);
float value = 0.0f;
std::memcpy(&value, &bits, sizeof(value));
SetFloat(number, value);
cursor += 4;
} else if ((kind == FIELD_REPEATED_UINT32
|| kind == FIELD_REPEATED_UINT64) && wireType == 2) {
if (!ReadLength(cursor, end, fieldEnd)) return false;
while (cursor < fieldEnd) {
if (!ReadVarint(cursor, fieldEnd, integer)) return false;
AddRepeatedInteger(number, kind, integer);
}
} else {
if (wireType != 0 || !ReadVarint(cursor, end, integer)) return false;
if (kind == FIELD_REPEATED_UINT32
|| kind == FIELD_REPEATED_UINT64) {
AddRepeatedInteger(number, kind, integer);
} else {
SetInteger(number, kind, integer);
}
}
}
return true;
}
void ReportMessage::CopyFrom(const google::protobuf::MessageLite& other) {
Clear();
MergeFrom(other);
}
void ReportMessage::MergeFrom(const google::protobuf::MessageLite& other) {
const ReportMessage* source = dynamic_cast<const ReportMessage*>(&other);
if (source == 0 || source == this || source->GetTypeName() != GetTypeName()) {
return;
}
for (std::map<int, FieldValue>::const_iterator iterator =
source->fields_.begin(); iterator != source->fields_.end(); ++iterator) {
const FieldValue& incoming = iterator->second;
FieldValue& destination = fields_[iterator->first];
if (incoming.kind == FIELD_REPEATED_STRING) {
destination.kind = incoming.kind;
destination.strings.insert(destination.strings.end(),
incoming.strings.begin(), incoming.strings.end());
} else if (incoming.kind == FIELD_REPEATED_UINT32
|| incoming.kind == FIELD_REPEATED_UINT64) {
destination.kind = incoming.kind;
destination.integers.insert(destination.integers.end(),
incoming.integers.begin(), incoming.integers.end());
} else if (incoming.kind == FIELD_REPEATED_MESSAGE) {
destination.kind = incoming.kind;
for (std::size_t index = 0; index < incoming.messages.size(); ++index) {
destination.messages.push_back(CloneMessage(incoming.messages[index]));
}
} else {
destination = incoming;
if (incoming.message) destination.message = CloneMessage(incoming.message);
}
}
}
void ReportMessage::Swap(ReportMessage* other) {
if (other != 0) fields_.swap(other->fields_);
}
} // namespace internal
void protobuf_AddDesc_reports_2eproto() {
}
void protobuf_ShutdownFile_reports_2eproto() {
}
} // namespace idreports
@@ -0,0 +1,646 @@
#pragma once
// Reconstructed from the protobuf 2.4 generated code in the Tungsten Xbox 360
// image. This keeps the original protobuf-lite surface and wire format while
// avoiding a dependency on the obsolete 2.4 runtime in the Windows port.
#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace google {
namespace protobuf {
class MessageLite {
public:
virtual ~MessageLite() {}
virtual std::string GetTypeName() const = 0;
virtual MessageLite* New() const = 0;
virtual void Clear() = 0;
virtual bool IsInitialized() const = 0;
virtual int ByteSize() const = 0;
virtual int GetCachedSize() const { return ByteSize(); }
virtual bool SerializePartialToArray(void* data, int size) const = 0;
virtual bool ParseFromArray(const void* data, int size) = 0;
bool SerializeToArray(void* data, int size) const;
bool SerializePartialToString(std::string* output) const;
bool SerializeToString(std::string* output) const;
std::string SerializeAsString() const;
bool ParseFromString(const std::string& input);
};
} // namespace protobuf
} // namespace google
namespace idreports {
enum LogEvent_Severity {
LogEvent_Severity_SEV_EMERGENCY = 80,
LogEvent_Severity_SEV_ALERT = 70,
LogEvent_Severity_SEV_CRITICAL = 60,
LogEvent_Severity_SEV_ERROR = 50,
LogEvent_Severity_SEV_WARNING = 40,
LogEvent_Severity_SEV_NOTICE = 30,
LogEvent_Severity_SEV_INFO = 20,
LogEvent_Severity_SEV_DEBUG = 10,
LogEvent_Severity_SEV_TRASH = 0
};
bool LogEvent_Severity_IsValid(int value);
const char* LogEvent_Severity_Name(LogEvent_Severity value);
enum TargetPlatform {
TARGET_WIN32 = 0,
TARGET_X64 = 1,
TARGET_XBOX360 = 2,
TARGET_PS3 = 3,
TARGET_DURANGO = 4,
TARGET_WIIU = 5,
TARGET_IPHONE = 6,
TARGET_OSX = 7,
TARGET_LINUX = 8
};
bool TargetPlatform_IsValid(int value);
const char* TargetPlatform_Name(TargetPlatform value);
enum HeapType {
UNKNOWN_HEAP = 0,
MAP_HEAP = 1,
SYSTEM_HEAP = 2
};
enum Xbox360ConsoleType {
XBOX360_DEVELOPMENT_KIT = 0,
XBOX360_TEST_KIT = 1,
XBOX360_UNKNOWN_KIT = 2
};
class BigUInt;
class Attachment;
class LogEvent;
class MemoryInfo;
class ExceptionInfo;
class CallstackLine;
class AssertReport;
class MapWarning;
class MapReport;
class ViewNoteReport;
class StringIDReferenceReport;
class CPUInfo;
class RegisterInfo;
class HeapCategoryUsage;
class HeapUsage;
class Xbox360VersionInfo;
class Xbox360KitInfo;
class XenonRegisterInfo;
class XenonMemoryInfo;
class PPURegisterInfo;
class SPURegisterInfo;
class PS3ExceptionInfo;
class PS3VMRegionStats;
class PS3MemoryInfo;
class CrashReport;
class Xbox360CrashReport;
class PS3CrashReport;
class CrashReportResult;
namespace internal {
enum FieldKind {
FIELD_UNKNOWN,
FIELD_STRING,
FIELD_UINT32,
FIELD_UINT64,
FIELD_INT32,
FIELD_INT64,
FIELD_ENUM,
FIELD_BOOL,
FIELD_FLOAT,
FIELD_MESSAGE,
FIELD_REPEATED_STRING,
FIELD_REPEATED_UINT32,
FIELD_REPEATED_UINT64,
FIELD_REPEATED_MESSAGE
};
struct FieldValue {
FieldValue();
FieldKind kind;
std::uint64_t integer;
float real;
std::string text;
std::shared_ptr<google::protobuf::MessageLite> message;
std::vector<std::string> strings;
std::vector<std::uint64_t> integers;
std::vector<std::shared_ptr<google::protobuf::MessageLite> > messages;
};
class ReportMessage : public google::protobuf::MessageLite {
public:
ReportMessage();
ReportMessage(const ReportMessage& other);
ReportMessage& operator=(const ReportMessage& other);
virtual ~ReportMessage();
void Clear() override;
bool IsInitialized() const override;
int ByteSize() const override;
bool SerializePartialToArray(void* data, int size) const override;
bool ParseFromArray(const void* data, int size) override;
void CopyFrom(const google::protobuf::MessageLite& other);
void MergeFrom(const google::protobuf::MessageLite& other);
void Swap(ReportMessage* other);
protected:
bool HasField(int number) const;
void ClearField(int number);
std::uint64_t GetInteger(int number) const;
void SetInteger(int number, FieldKind kind, std::uint64_t value);
float GetFloat(int number) const;
void SetFloat(int number, float value);
const std::string& GetString(int number) const;
std::string* MutableString(int number);
std::string* ReleaseString(int number);
void SetString(int number, const char* value, std::size_t size);
int RepeatedIntegerSize(int number) const;
std::uint64_t GetRepeatedInteger(int number, int index) const;
void SetRepeatedInteger(int number, int index, FieldKind kind,
std::uint64_t value);
void AddRepeatedInteger(int number, FieldKind kind, std::uint64_t value);
int RepeatedStringSize(int number) const;
const std::string& GetRepeatedString(int number, int index) const;
std::string* MutableRepeatedString(int number, int index);
std::string* AddRepeatedString(int number);
void SetRepeatedString(int number, int index, const char* value,
std::size_t size);
int RepeatedMessageSize(int number) const;
const google::protobuf::MessageLite* GetRepeatedMessage(int number,
int index) const;
google::protobuf::MessageLite* MutableRepeatedMessage(int number,
int index);
google::protobuf::MessageLite* AddRepeatedMessage(int number);
const google::protobuf::MessageLite* GetMessage(int number) const;
google::protobuf::MessageLite* MutableMessage(int number);
google::protobuf::MessageLite* ReleaseMessage(int number);
void SetAllocatedMessage(int number, google::protobuf::MessageLite* value);
virtual FieldKind FieldKindForNumber(int number) const = 0;
virtual google::protobuf::MessageLite* NewMessageForField(int number) const;
virtual bool HasRequiredFields() const = 0;
private:
bool Serialize(std::string& output) const;
std::map<int, FieldValue> fields_;
};
} // namespace internal
#define IDR_DECL_string(name, number, type) \
bool has_##name() const { return HasField(number); } \
void clear_##name() { ClearField(number); } \
const std::string& name() const { return GetString(number); } \
void set_##name(const std::string& value) { SetString(number, value.data(), value.size()); } \
void set_##name(const char* value) { SetString(number, value ? value : "", value ? std::char_traits<char>::length(value) : 0); } \
void set_##name(const char* value, std::size_t size) { SetString(number, value, size); } \
std::string* mutable_##name() { return MutableString(number); } \
std::string* release_##name() { return ReleaseString(number); } \
void set_allocated_##name(std::string* value) { if (value) { set_##name(*value); delete value; } else { clear_##name(); } }
#define IDR_DECL_uint32(name, number, type) \
bool has_##name() const { return HasField(number); } \
void clear_##name() { ClearField(number); } \
std::uint32_t name() const { return static_cast<std::uint32_t>(GetInteger(number)); } \
void set_##name(std::uint32_t value) { SetInteger(number, internal::FIELD_UINT32, value); }
#define IDR_DECL_uint64(name, number, type) \
bool has_##name() const { return HasField(number); } \
void clear_##name() { ClearField(number); } \
std::uint64_t name() const { return GetInteger(number); } \
void set_##name(std::uint64_t value) { SetInteger(number, internal::FIELD_UINT64, value); }
#define IDR_DECL_int32(name, number, type) \
bool has_##name() const { return HasField(number); } \
void clear_##name() { ClearField(number); } \
std::int32_t name() const { return static_cast<std::int32_t>(GetInteger(number)); } \
void set_##name(std::int32_t value) { SetInteger(number, internal::FIELD_INT32, static_cast<std::uint64_t>(static_cast<std::int64_t>(value))); }
#define IDR_DECL_int64(name, number, type) \
bool has_##name() const { return HasField(number); } \
void clear_##name() { ClearField(number); } \
std::int64_t name() const { return static_cast<std::int64_t>(GetInteger(number)); } \
void set_##name(std::int64_t value) { SetInteger(number, internal::FIELD_INT64, static_cast<std::uint64_t>(value)); }
#define IDR_DECL_enum(name, number, type) \
bool has_##name() const { return HasField(number); } \
void clear_##name() { ClearField(number); } \
type name() const { return static_cast<type>(GetInteger(number)); } \
void set_##name(type value) { SetInteger(number, internal::FIELD_ENUM, static_cast<std::uint64_t>(value)); }
#define IDR_DECL_bool(name, number, type) \
bool has_##name() const { return HasField(number); } \
void clear_##name() { ClearField(number); } \
bool name() const { return GetInteger(number) != 0; } \
void set_##name(bool value) { SetInteger(number, internal::FIELD_BOOL, value ? 1 : 0); }
#define IDR_DECL_float(name, number, type) \
bool has_##name() const { return HasField(number); } \
void clear_##name() { ClearField(number); } \
float name() const { return GetFloat(number); } \
void set_##name(float value) { SetFloat(number, value); }
#define IDR_DECL_message(name, number, type) \
bool has_##name() const { return HasField(number); } \
void clear_##name() { ClearField(number); } \
const type& name() const { const google::protobuf::MessageLite* value = GetMessage(number); return value ? *static_cast<const type*>(value) : type::default_instance(); } \
type* mutable_##name() { return static_cast<type*>(MutableMessage(number)); } \
type* release_##name() { return static_cast<type*>(ReleaseMessage(number)); } \
void set_allocated_##name(type* value) { SetAllocatedMessage(number, value); }
#define IDR_DECL_repeated_string(name, number, type) \
int name##_size() const { return RepeatedStringSize(number); } \
void clear_##name() { ClearField(number); } \
const std::string& name(int index) const { return GetRepeatedString(number, index); } \
std::string* mutable_##name(int index) { return MutableRepeatedString(number, index); } \
std::string* add_##name() { return AddRepeatedString(number); } \
void add_##name(const std::string& value) { std::string* item = AddRepeatedString(number); *item = value; } \
void add_##name(const char* value) { std::string* item = AddRepeatedString(number); *item = value ? value : ""; } \
void set_##name(int index, const std::string& value) { SetRepeatedString(number, index, value.data(), value.size()); } \
void set_##name(int index, const char* value) { SetRepeatedString(number, index, value ? value : "", value ? std::char_traits<char>::length(value) : 0); }
#define IDR_DECL_repeated_uint32(name, number, type) \
int name##_size() const { return RepeatedIntegerSize(number); } \
void clear_##name() { ClearField(number); } \
std::uint32_t name(int index) const { return static_cast<std::uint32_t>(GetRepeatedInteger(number, index)); } \
void set_##name(int index, std::uint32_t value) { SetRepeatedInteger(number, index, internal::FIELD_REPEATED_UINT32, value); } \
void add_##name(std::uint32_t value) { AddRepeatedInteger(number, internal::FIELD_REPEATED_UINT32, value); }
#define IDR_DECL_repeated_uint64(name, number, type) \
int name##_size() const { return RepeatedIntegerSize(number); } \
void clear_##name() { ClearField(number); } \
std::uint64_t name(int index) const { return GetRepeatedInteger(number, index); } \
void set_##name(int index, std::uint64_t value) { SetRepeatedInteger(number, index, internal::FIELD_REPEATED_UINT64, value); } \
void add_##name(std::uint64_t value) { AddRepeatedInteger(number, internal::FIELD_REPEATED_UINT64, value); }
#define IDR_DECL_repeated_message(name, number, type) \
int name##_size() const { return RepeatedMessageSize(number); } \
void clear_##name() { ClearField(number); } \
const type& name(int index) const { return *static_cast<const type*>(GetRepeatedMessage(number, index)); } \
type* mutable_##name(int index) { return static_cast<type*>(MutableRepeatedMessage(number, index)); } \
type* add_##name() { return static_cast<type*>(AddRepeatedMessage(number)); }
#define IDR_KIND_string(name, number, type) case number: return internal::FIELD_STRING;
#define IDR_KIND_uint32(name, number, type) case number: return internal::FIELD_UINT32;
#define IDR_KIND_uint64(name, number, type) case number: return internal::FIELD_UINT64;
#define IDR_KIND_int32(name, number, type) case number: return internal::FIELD_INT32;
#define IDR_KIND_int64(name, number, type) case number: return internal::FIELD_INT64;
#define IDR_KIND_enum(name, number, type) case number: return internal::FIELD_ENUM;
#define IDR_KIND_bool(name, number, type) case number: return internal::FIELD_BOOL;
#define IDR_KIND_float(name, number, type) case number: return internal::FIELD_FLOAT;
#define IDR_KIND_message(name, number, type) case number: return internal::FIELD_MESSAGE;
#define IDR_KIND_repeated_string(name, number, type) case number: return internal::FIELD_REPEATED_STRING;
#define IDR_KIND_repeated_uint32(name, number, type) case number: return internal::FIELD_REPEATED_UINT32;
#define IDR_KIND_repeated_uint64(name, number, type) case number: return internal::FIELD_REPEATED_UINT64;
#define IDR_KIND_repeated_message(name, number, type) case number: return internal::FIELD_REPEATED_MESSAGE;
#define IDR_FACTORY_string(name, number, type)
#define IDR_FACTORY_uint32(name, number, type)
#define IDR_FACTORY_uint64(name, number, type)
#define IDR_FACTORY_int32(name, number, type)
#define IDR_FACTORY_int64(name, number, type)
#define IDR_FACTORY_enum(name, number, type)
#define IDR_FACTORY_bool(name, number, type)
#define IDR_FACTORY_float(name, number, type)
#define IDR_FACTORY_repeated_string(name, number, type)
#define IDR_FACTORY_repeated_uint32(name, number, type)
#define IDR_FACTORY_repeated_uint64(name, number, type)
#define IDR_FACTORY_message(name, number, type) case number: return new type;
#define IDR_FACTORY_repeated_message(name, number, type) case number: return new type;
#define IDR_REQUIRE(kind, name, number, type) if (!HasField(number)) return false;
#define IDR_DECLARE(kind, name, number, type) IDR_DECL_##kind(name, number, type)
#define IDR_KIND(kind, name, number, type) IDR_KIND_##kind(name, number, type)
#define IDR_FACTORY(kind, name, number, type) IDR_FACTORY_##kind(name, number, type)
#define IDR_CLASS(name, fields, required, extras) \
class name : public internal::ReportMessage { \
public: \
name() {} \
name(const name& other) : internal::ReportMessage() { MergeFrom(other); } \
name& operator=(const name& other) { if (this != &other) CopyFrom(other); return *this; } \
std::string GetTypeName() const override { return "idreports." #name; } \
name* New() const override { return new name; } \
static const name& default_instance() { static const name value; return value; } \
void CopyFrom(const name& other) { internal::ReportMessage::CopyFrom(other); } \
void MergeFrom(const name& other) { internal::ReportMessage::MergeFrom(other); } \
void Swap(name* other) { internal::ReportMessage::Swap(other); } \
fields(IDR_DECLARE) \
extras \
protected: \
internal::FieldKind FieldKindForNumber(int number) const override { switch (number) { fields(IDR_KIND) default: return internal::FIELD_UNKNOWN; } } \
google::protobuf::MessageLite* NewMessageForField(int number) const override { switch (number) { case -1: return 0; fields(IDR_FACTORY) default: return 0; } } \
bool HasRequiredFields() const override { required(IDR_REQUIRE) return true; } \
};
#define IDR_NO_FIELDS(F)
#define IDR_NO_EXTRAS
#define IDR_BIGUINT_FIELDS(F) \
F(uint64, lowpart, 1, int) \
F(uint64, highpart, 2, int)
#define IDR_BIGUINT_REQUIRED(F) IDR_BIGUINT_FIELDS(F)
IDR_CLASS(BigUInt, IDR_BIGUINT_FIELDS, IDR_BIGUINT_REQUIRED, IDR_NO_EXTRAS)
#define IDR_ATTACHMENT_FIELDS(F) \
F(string, attachment, 1, int) \
F(string, filename, 2, int)
#define IDR_ATTACHMENT_REQUIRED(F) IDR_ATTACHMENT_FIELDS(F)
IDR_CLASS(Attachment, IDR_ATTACHMENT_FIELDS, IDR_ATTACHMENT_REQUIRED, IDR_NO_EXTRAS)
#define IDR_MEMORYINFO_FIELDS(F) \
F(uint32, inuse, 1, int) F(uint32, physicalmb, 2, int) \
F(uint32, physicalfree, 3, int) F(uint32, pagingfile, 4, int) \
F(uint32, pagingfree, 5, int) F(uint32, useraddress, 6, int) \
F(uint32, userfree, 7, int)
#define IDR_MEMORYINFO_REQUIRED(F) IDR_MEMORYINFO_FIELDS(F)
IDR_CLASS(MemoryInfo, IDR_MEMORYINFO_FIELDS, IDR_MEMORYINFO_REQUIRED, IDR_NO_EXTRAS)
#define IDR_EXCEPTIONINFO_FIELDS(F) \
F(string, expcode, 1, int) F(enum, expflags, 2, int) \
F(string, expaddress, 3, int)
#define IDR_EXCEPTIONINFO_REQUIRED(F) IDR_EXCEPTIONINFO_FIELDS(F)
IDR_CLASS(ExceptionInfo, IDR_EXCEPTIONINFO_FIELDS, IDR_EXCEPTIONINFO_REQUIRED, IDR_NO_EXTRAS)
#define IDR_CALLSTACKLINE_FIELDS(F) \
F(string, functioncall, 1, int) F(int32, line, 2, int) \
F(int32, bytepos, 3, int) F(string, filename, 4, int) \
F(string, address, 5, int)
#define IDR_CALLSTACKLINE_REQUIRED(F) IDR_CALLSTACKLINE_FIELDS(F)
IDR_CLASS(CallstackLine, IDR_CALLSTACKLINE_FIELDS, IDR_CALLSTACKLINE_REQUIRED, IDR_NO_EXTRAS)
#define IDR_MAPWARNING_FIELDS(F) \
F(string, message, 1, int) F(repeated_uint32, marks, 2, int) \
F(uint32, count, 3, int)
#define IDR_MAPWARNING_REQUIRED(F) F(string, message, 1, int)
IDR_CLASS(MapWarning, IDR_MAPWARNING_FIELDS, IDR_MAPWARNING_REQUIRED, IDR_NO_EXTRAS)
#define IDR_CPUINFO_FIELDS(F) \
F(string, cpuid, 1, int) F(int32, packages, 2, int) \
F(int32, cores, 3, int) F(int32, logical, 4, int) \
F(float, frequency, 5, int)
#define IDR_CPUINFO_REQUIRED(F) IDR_CPUINFO_FIELDS(F)
IDR_CLASS(CPUInfo, IDR_CPUINFO_FIELDS, IDR_CPUINFO_REQUIRED, IDR_NO_EXTRAS)
#define IDR_REGISTERINFO_FIELDS(F) \
F(string, edi, 1, int) F(string, esi, 2, int) F(string, eax, 3, int) \
F(string, ebx, 4, int) F(string, ecx, 5, int) F(string, edx, 6, int) \
F(string, eip, 7, int) F(string, ebp, 8, int) F(string, esp, 9, int) \
F(string, eflags, 10, int) F(string, segcs, 11, int) \
F(string, segss, 12, int) F(enum, platform, 13, TargetPlatform)
#define IDR_REGISTERINFO_REQUIRED(F) IDR_REGISTERINFO_FIELDS(F)
IDR_CLASS(RegisterInfo, IDR_REGISTERINFO_FIELDS, IDR_REGISTERINFO_REQUIRED, IDR_NO_EXTRAS)
#define IDR_HEAPCATEGORY_FIELDS(F) \
F(string, categoryname, 1, int) F(uint32, totalever, 2, int) \
F(uint32, totalcurrent, 3, int) F(uint32, totalsize, 4, int) \
F(uint32, totalwaste, 5, int)
#define IDR_HEAPCATEGORY_REQUIRED(F) IDR_HEAPCATEGORY_FIELDS(F)
IDR_CLASS(HeapCategoryUsage, IDR_HEAPCATEGORY_FIELDS, IDR_HEAPCATEGORY_REQUIRED, IDR_NO_EXTRAS)
#define IDR_XBOXVERSION_FIELDS(F) \
F(uint32, major, 1, int) F(uint32, minor, 2, int) \
F(uint32, build, 3, int) F(uint32, qfe, 4, int)
#define IDR_XBOXVERSION_REQUIRED(F) IDR_XBOXVERSION_FIELDS(F)
IDR_CLASS(Xbox360VersionInfo, IDR_XBOXVERSION_FIELDS, IDR_XBOXVERSION_REQUIRED, IDR_NO_EXTRAS)
#define IDR_XENONMEMORY_FIELDS(F) \
F(uint32, totalpages, 1, int) F(uint32, availablepages, 2, int) \
F(uint32, stackpages, 3, int) F(uint32, virtualpagetablepages, 4, int) \
F(uint32, systempagetablepages, 5, int) F(uint32, poolpages, 6, int) \
F(uint32, virtualmappedpages, 7, int) F(uint32, imagepages, 8, int) \
F(uint32, filecachepages, 9, int) F(uint32, contiguouspages, 10, int) \
F(uint32, debuggerpages, 11, int)
#define IDR_XENONMEMORY_REQUIRED(F) IDR_XENONMEMORY_FIELDS(F)
IDR_CLASS(XenonMemoryInfo, IDR_XENONMEMORY_FIELDS, IDR_XENONMEMORY_REQUIRED, IDR_NO_EXTRAS)
#define IDR_PS3EXCEPTION_FIELDS(F) \
F(string, exceptionname, 1, int) F(uint64, exceptioncode, 2, int) \
F(uint64, dar, 3, int)
#define IDR_PS3EXCEPTION_REQUIRED(F) \
F(string, exceptionname, 1, int) F(uint64, exceptioncode, 2, int)
IDR_CLASS(PS3ExceptionInfo, IDR_PS3EXCEPTION_FIELDS, IDR_PS3EXCEPTION_REQUIRED, IDR_NO_EXTRAS)
#define IDR_PS3VMREGION_FIELDS(F) \
F(string, regionname, 1, int) F(int32, virtualsizeinbytes, 3, int) \
F(int32, uncommittedpages, 4, int) F(int32, physicalpages, 5, int) \
F(int32, diskpages, 6, int) F(int32, maxpages, 7, int) \
F(int32, committedpages, 8, int)
#define IDR_PS3VMREGION_REQUIRED(F) IDR_PS3VMREGION_FIELDS(F)
IDR_CLASS(PS3VMRegionStats, IDR_PS3VMREGION_FIELDS, IDR_PS3VMREGION_REQUIRED, IDR_NO_EXTRAS)
#define IDR_LOGEVENT_FIELDS(F) \
F(string, timestamp, 1, int) F(enum, severity, 2, LogEvent_Severity) \
F(string, message, 3, int) F(repeated_string, tags, 4, int) \
F(string, data, 5, int) F(string, datatype, 6, int) \
F(repeated_message, externs, 7, Attachment) \
F(enum, platform, 8, TargetPlatform)
#define IDR_LOGEVENT_REQUIRED(F) \
F(string, timestamp, 1, int) F(string, message, 3, int)
#define IDR_LOGEVENT_EXTRAS \
typedef LogEvent_Severity Severity; \
static const Severity SEV_EMERGENCY = LogEvent_Severity_SEV_EMERGENCY; \
static const Severity SEV_ALERT = LogEvent_Severity_SEV_ALERT; \
static const Severity SEV_CRITICAL = LogEvent_Severity_SEV_CRITICAL; \
static const Severity SEV_ERROR = LogEvent_Severity_SEV_ERROR; \
static const Severity SEV_WARNING = LogEvent_Severity_SEV_WARNING; \
static const Severity SEV_NOTICE = LogEvent_Severity_SEV_NOTICE; \
static const Severity SEV_INFO = LogEvent_Severity_SEV_INFO; \
static const Severity SEV_DEBUG = LogEvent_Severity_SEV_DEBUG; \
static const Severity SEV_TRASH = LogEvent_Severity_SEV_TRASH;
IDR_CLASS(LogEvent, IDR_LOGEVENT_FIELDS, IDR_LOGEVENT_REQUIRED, IDR_LOGEVENT_EXTRAS)
#define IDR_ASSERTREPORT_FIELDS(F) \
F(string, mapname, 1, int) F(string, filename, 2, int) \
F(uint32, line, 3, int) F(string, expression, 4, int) \
F(string, username, 5, int)
#define IDR_ASSERTREPORT_REQUIRED(F) \
F(string, mapname, 1, int) F(string, filename, 2, int) \
F(uint32, line, 3, int) F(string, expression, 4, int)
IDR_CLASS(AssertReport, IDR_ASSERTREPORT_FIELDS, IDR_ASSERTREPORT_REQUIRED, IDR_NO_EXTRAS)
#define IDR_MAPREPORT_FIELDS(F) \
F(string, mapname, 1, int) F(uint32, loadtime, 2, int) \
F(string, username, 3, int) F(string, machinename, 4, int) \
F(string, platform, 5, int) F(string, game, 6, int) \
F(string, buildversion, 7, int) F(string, buildtimestamp, 8, int) \
F(repeated_message, warnings, 9, MapWarning)
#define IDR_MAPREPORT_REQUIRED(F) \
F(string, mapname, 1, int) F(uint32, loadtime, 2, int) \
F(string, platform, 5, int) F(string, game, 6, int) \
F(string, buildversion, 7, int) F(string, buildtimestamp, 8, int)
IDR_CLASS(MapReport, IDR_MAPREPORT_FIELDS, IDR_MAPREPORT_REQUIRED, IDR_NO_EXTRAS)
#define IDR_VIEWNOTE_FIELDS(F) \
F(string, game, 1, int) F(string, username, 2, int) \
F(string, reportedby, 3, int) F(bool, issingleplayer, 4, int) \
F(string, platform, 5, int) F(int32, buildnumbermajor, 6, int) \
F(int32, buildnumberminor, 7, int) F(string, timestamp, 9, int) \
F(string, launchcommand, 10, int) F(string, vtfilepath, 11, int) \
F(string, vtfilepathvmtroverride, 12, int) F(string, bugtitle, 13, int) \
F(string, tasktype, 14, int) F(string, reprosteps, 15, int) \
F(string, details, 16, int) F(string, severity, 17, int) \
F(string, mappath, 19, int) F(string, priority, 20, int) \
F(message, attachment, 22, Attachment)
#define IDR_VIEWNOTE_REQUIRED(F) \
F(string, game, 1, int) F(string, username, 2, int) \
F(string, reportedby, 3, int) F(bool, issingleplayer, 4, int) \
F(string, platform, 5, int) F(int32, buildnumbermajor, 6, int) \
F(int32, buildnumberminor, 7, int) F(string, timestamp, 9, int) \
F(string, launchcommand, 10, int) F(string, vtfilepath, 11, int) \
F(string, vtfilepathvmtroverride, 12, int) F(string, bugtitle, 13, int) \
F(string, tasktype, 14, int) F(string, reprosteps, 15, int) \
F(string, details, 16, int) F(string, severity, 17, int)
IDR_CLASS(ViewNoteReport, IDR_VIEWNOTE_FIELDS, IDR_VIEWNOTE_REQUIRED, IDR_NO_EXTRAS)
#define IDR_STRINGID_FIELDS(F) F(repeated_string, stringids, 1, int)
IDR_CLASS(StringIDReferenceReport, IDR_STRINGID_FIELDS, IDR_NO_FIELDS, IDR_NO_EXTRAS)
#define IDR_HEAPUSAGE_FIELDS(F) \
F(enum, heaptype, 1, HeapType) F(uint32, totalever, 2, int) \
F(uint32, totalcurrent, 3, int) F(uint32, totalsize, 4, int) \
F(uint32, totalwaste, 5, int) \
F(repeated_message, categories, 6, HeapCategoryUsage)
#define IDR_HEAPUSAGE_REQUIRED(F) \
F(enum, heaptype, 1, HeapType) F(uint32, totalever, 2, int) \
F(uint32, totalcurrent, 3, int) F(uint32, totalsize, 4, int) \
F(uint32, totalwaste, 5, int)
IDR_CLASS(HeapUsage, IDR_HEAPUSAGE_FIELDS, IDR_HEAPUSAGE_REQUIRED, IDR_NO_EXTRAS)
#define IDR_XBOXKIT_FIELDS(F) \
F(enum, kittype, 1, Xbox360ConsoleType) \
F(message, basekernelversion, 2, Xbox360VersionInfo) \
F(message, kernelversion, 3, Xbox360VersionInfo) \
F(message, xdkversion, 4, Xbox360VersionInfo) \
F(uint32, systeminfoflags, 5, int) F(string, consolename, 6, int)
#define IDR_XBOXKIT_REQUIRED(F) IDR_XBOXKIT_FIELDS(F)
IDR_CLASS(Xbox360KitInfo, IDR_XBOXKIT_FIELDS, IDR_XBOXKIT_REQUIRED, IDR_NO_EXTRAS)
#define IDR_XENONREGISTER_FIELDS(F) \
F(string, msr, 1, int) F(string, iar, 2, int) F(string, lr, 3, int) \
F(string, ctr, 4, int) F(string, fpscr, 5, int) \
F(repeated_string, fpr, 6, int) F(string, cr, 7, int) \
F(string, xer, 8, int) F(repeated_string, gpr, 9, int) \
F(string, vscr, 10, int) F(repeated_string, vr, 11, int)
#define IDR_XENONREGISTER_REQUIRED(F) \
F(string, msr, 1, int) F(string, iar, 2, int) F(string, lr, 3, int) \
F(string, ctr, 4, int) F(string, fpscr, 5, int) \
F(string, cr, 7, int) F(string, xer, 8, int) F(string, vscr, 10, int)
IDR_CLASS(XenonRegisterInfo, IDR_XENONREGISTER_FIELDS, IDR_XENONREGISTER_REQUIRED, IDR_NO_EXTRAS)
#define IDR_PPUREGISTER_FIELDS(F) \
F(repeated_uint64, gpr, 1, int) F(uint32, cr, 2, int) \
F(uint64, xer, 3, int) F(uint64, lr, 4, int) F(uint64, ctr, 5, int) \
F(uint64, pc, 6, int) F(repeated_uint64, fpr, 7, int) \
F(uint32, fpscr, 8, int) F(repeated_message, vr, 9, BigUInt) \
F(message, vscr, 10, BigUInt)
#define IDR_PPUREGISTER_REQUIRED(F) \
F(uint32, cr, 2, int) F(uint64, xer, 3, int) F(uint64, lr, 4, int) \
F(uint64, ctr, 5, int) F(uint64, pc, 6, int) \
F(uint32, fpscr, 8, int) F(message, vscr, 10, BigUInt)
IDR_CLASS(PPURegisterInfo, IDR_PPUREGISTER_FIELDS, IDR_PPUREGISTER_REQUIRED, IDR_NO_EXTRAS)
#define IDR_SPUREGISTER_FIELDS(F) \
F(repeated_message, gpr, 1, BigUInt) F(uint32, npc, 2, int) \
F(repeated_message, fpscr, 3, BigUInt) F(uint32, srr0, 4, int) \
F(uint32, spu_status, 5, int) F(uint64, spu_cfg, 6, int) \
F(uint32, mb_stat, 7, int) F(uint32, ppu_mb, 8, int) \
F(repeated_uint32, spu_mb, 9, int) F(uint32, decrementer, 10, int) \
F(repeated_uint64, mfc_cq_sr, 11, int)
#define IDR_SPUREGISTER_REQUIRED(F) \
F(uint32, npc, 2, int) F(uint32, srr0, 4, int) \
F(uint32, spu_status, 5, int) F(uint64, spu_cfg, 6, int) \
F(uint32, mb_stat, 7, int) F(uint32, ppu_mb, 8, int) \
F(uint32, decrementer, 10, int)
IDR_CLASS(SPURegisterInfo, IDR_SPUREGISTER_FIELDS, IDR_SPUREGISTER_REQUIRED, IDR_NO_EXTRAS)
#define IDR_PS3MEMORY_FIELDS(F) \
F(uint32, totalsystemmemory, 1, int) F(uint32, usedsystemmemory, 2, int) \
F(uint32, availablesystemmemory, 3, int) F(uint32, unaccountedfor, 4, int) \
F(uint32, pmem_total, 5, int) F(uint32, pmem_used, 6, int) \
F(uint64, page_in, 7, int) F(uint64, page_out, 8, int) \
F(uint64, page_fault_ppu, 9, int) F(uint64, page_fault_spu, 10, int) \
F(repeated_message, regions, 11, PS3VMRegionStats)
#define IDR_PS3MEMORY_REQUIRED(F) \
F(uint32, totalsystemmemory, 1, int) F(uint32, usedsystemmemory, 2, int) \
F(uint32, availablesystemmemory, 3, int) F(uint32, unaccountedfor, 4, int) \
F(uint32, pmem_total, 5, int) F(uint32, pmem_used, 6, int) \
F(uint64, page_in, 7, int) F(uint64, page_out, 8, int) \
F(uint64, page_fault_ppu, 9, int) F(uint64, page_fault_spu, 10, int)
IDR_CLASS(PS3MemoryInfo, IDR_PS3MEMORY_FIELDS, IDR_PS3MEMORY_REQUIRED, IDR_NO_EXTRAS)
#define IDR_COMMON_CRASH_FIELDS(F) \
F(string, game, 1, int) F(string, username, 2, int) \
F(string, platform, 3, int) F(int32, buildnumbermajor, 4, int) \
F(int32, buildnumberminor, 5, int) F(string, buildstring, 6, int) \
F(string, timestamp, 7, int) F(string, launchcommand, 8, int) \
F(string, vtfilepath, 9, int) F(string, vtfilepathvmtroverride, 10, int)
#define IDR_CRASHREPORT_FIELDS(F) \
IDR_COMMON_CRASH_FIELDS(F) F(string, bugtitle, 11, int) \
F(string, reprosteps, 12, int) F(string, details, 13, int) \
F(string, severity, 14, int) F(string, component, 15, int) \
F(string, mappath, 16, int) F(string, priority, 17, int) \
F(string, localfilename, 18, int) F(string, dmppath, 19, int) \
F(repeated_message, callstack, 20, CallstackLine) \
F(message, exception, 21, ExceptionInfo) F(message, registers, 22, RegisterInfo) \
F(message, attachment, 23, Attachment) F(message, cpuinfo, 24, CPUInfo) \
F(message, meminfo, 25, MemoryInfo) \
F(repeated_string, consolehistory, 26, int)
#define IDR_CRASHREPORT_REQUIRED(F) \
IDR_COMMON_CRASH_FIELDS(F) F(string, bugtitle, 11, int) \
F(string, reprosteps, 12, int) F(string, details, 13, int) \
F(string, severity, 14, int) F(string, component, 15, int) \
F(string, localfilename, 18, int)
IDR_CLASS(CrashReport, IDR_CRASHREPORT_FIELDS, IDR_CRASHREPORT_REQUIRED, IDR_NO_EXTRAS)
#define IDR_XBOXCRASH_FIELDS(F) \
IDR_COMMON_CRASH_FIELDS(F) F(string, mappath, 11, int) \
F(message, kitinfo, 12, Xbox360KitInfo) \
F(repeated_string, callstack, 13, int) \
F(message, exception, 14, ExceptionInfo) \
F(message, registers, 15, XenonRegisterInfo) \
F(message, consolememory, 16, XenonMemoryInfo) \
F(message, titlememory, 17, XenonMemoryInfo)
#define IDR_XBOXCRASH_REQUIRED(F) \
IDR_COMMON_CRASH_FIELDS(F) F(message, kitinfo, 12, Xbox360KitInfo)
IDR_CLASS(Xbox360CrashReport, IDR_XBOXCRASH_FIELDS, IDR_XBOXCRASH_REQUIRED, IDR_NO_EXTRAS)
#define IDR_PS3CRASH_FIELDS(F) \
IDR_COMMON_CRASH_FIELDS(F) F(string, mappath, 11, int) \
F(repeated_uint64, callstack, 12, int) \
F(message, exception, 13, PS3ExceptionInfo) \
F(message, ppuregisters, 14, PPURegisterInfo) \
F(repeated_message, spuregisters, 15, SPURegisterInfo) \
F(message, memoryinfo, 16, PS3MemoryInfo)
#define IDR_PS3CRASH_REQUIRED(F) IDR_COMMON_CRASH_FIELDS(F)
IDR_CLASS(PS3CrashReport, IDR_PS3CRASH_FIELDS, IDR_PS3CRASH_REQUIRED, IDR_NO_EXTRAS)
#define IDR_CRASHRESULT_FIELDS(F) F(int64, bugid, 1, int)
#define IDR_CRASHRESULT_REQUIRED(F) IDR_CRASHRESULT_FIELDS(F)
IDR_CLASS(CrashReportResult, IDR_CRASHRESULT_FIELDS, IDR_CRASHRESULT_REQUIRED, IDR_NO_EXTRAS)
#undef IDR_CLASS
#undef IDR_DECLARE
#undef IDR_KIND
#undef IDR_FACTORY
#undef IDR_REQUIRE
void protobuf_AddDesc_reports_2eproto();
void protobuf_ShutdownFile_reports_2eproto();
} // namespace idreports
@@ -0,0 +1,180 @@
#include "bitblockallocator.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
idBitBlockAllocator::idBitBlockAllocator(const int blocksWide,
const int blocksHigh)
: width(std::max(0, blocksWide)), height(std::max(0, blocksHigh)),
bits(nullptr) {
const std::size_t count = static_cast<std::size_t>(width)
* static_cast<std::size_t>(height);
if (count != 0) {
bits = static_cast<unsigned char*>(std::calloc((count + 7u) / 8u, 1));
}
}
idBitBlockAllocator::~idBitBlockAllocator() {
std::free(bits);
}
void idBitBlockAllocator::Clear() {
if (bits != nullptr) {
const std::size_t count = static_cast<std::size_t>(width)
* static_cast<std::size_t>(height);
std::memset(bits, 0, (count + 7u) / 8u);
}
}
bool idBitBlockAllocator::Get(const int x, const int y) const {
if (bits == nullptr || x < 0 || y < 0 || x >= width || y >= height) {
return false;
}
const int bit = y * width + x;
return (bits[bit >> 3] & (1u << (bit & 7))) != 0;
}
void idBitBlockAllocator::Set(const int x, const int y) {
if (bits == nullptr || x < 0 || y < 0 || x >= width || y >= height) {
return;
}
const int bit = y * width + x;
bits[bit >> 3] |= static_cast<unsigned char>(1u << (bit & 7));
}
void idBitBlockAllocator::FillBlock(const int x, const int y,
const int w, const int h) {
for (int row = 0; row < h; ++row) {
for (int column = 0; column < w; ++column) {
Set(x + column, y + row);
}
}
}
void idBitBlockAllocator::FillBitBlock(const int x, const int y,
const idBitBlockAllocator& block) {
for (int row = 0; row < block.height; ++row) {
for (int column = 0; column < block.width; ++column) {
if (block.Get(column, row)) {
Set(x + column, y + row);
}
}
}
}
bool idBitBlockAllocator::TestBlock(const int x, const int y, const int w,
const int h, int* const fail) const {
if (w < 0 || h < 0 || x < 0 || y < 0 || x + w > width || y + h > height) {
return false;
}
if (fail != nullptr && fail[0] >= x && fail[0] < x + w
&& fail[1] >= y && fail[1] < y + h) {
return false;
}
for (int row = 0; row < h; ++row) {
for (int column = w - 1; column >= 0; --column) {
if (Get(x + column, y + row)) {
if (fail != nullptr) {
fail[0] = x + column;
fail[1] = y + row;
}
return false;
}
}
}
return true;
}
bool idBitBlockAllocator::TestBitBlock(const int x, const int y,
const idBitBlockAllocator& block) const {
if (x < 0 || y < 0 || x + block.width > width || y + block.height > height) {
return false;
}
for (int row = 0; row < block.height; ++row) {
for (int column = 0; column < block.width; ++column) {
if (block.Get(column, row) && Get(x + column, y + row)) {
return false;
}
}
}
return true;
}
bool idBitBlockAllocator::FindBlockLinear(int& x, int& y, const int w,
const int h) {
int fail[2] = { -1, -1 };
for (y = 0; y <= height - h; ++y) {
for (x = 0; x <= width - w; ++x) {
if (TestBlock(x, y, w, h, fail)) {
FillBlock(x, y, w, h);
return true;
}
}
}
return false;
}
bool idBitBlockAllocator::FindBitBlockLinear(int& x, int& y,
const idBitBlockAllocator& block) {
for (y = 0; y <= height - block.height; ++y) {
for (x = 0; x <= width - block.width; ++x) {
if (TestBitBlock(x, y, block)) {
FillBitBlock(x, y, block);
return true;
}
}
}
return false;
}
void SeparateBits(const int value, int& x, int& y) {
x = 0;
y = 0;
const unsigned int bits = static_cast<unsigned int>(value);
for (unsigned int index = 0; index < 16; ++index) {
x |= static_cast<int>(((bits >> (index * 2)) & 1u) << index);
y |= static_cast<int>(((bits >> (index * 2 + 1)) & 1u) << index);
}
}
void SeparateBits2(const int bits, int& x, int& y) {
SeparateBits(bits, x, y);
}
bool idBitBlockAllocator::FindBlockInterleaved(int& x, int& y, const int w,
const int h, int& searchPoint, const bool naturallyAlign) {
if (w > width || h > height || w < 0 || h < 0) {
return false;
}
if (searchPoint < 0) {
searchPoint = 0;
}
const int count = width * height;
int fail[2] = { -1, -1 };
const auto nextPowerOfTwo = [](const int value) {
int power = 1;
while (power < std::max(1, value)) {
power <<= 1;
}
return power;
};
const int alignX = nextPowerOfTwo(w);
const int alignY = nextPowerOfTwo(h);
for (; searchPoint < count; ++searchPoint) {
const int interleaved = naturallyAlign
? searchPoint
: ((searchPoint >> 1) ^ searchPoint);
SeparateBits2(interleaved, x, y);
if (naturallyAlign
&& (x / alignX != (x + std::max(0, w - 1)) / alignX
|| y / alignY != (y + std::max(0, h - 1)) / alignY)) {
continue;
}
if (TestBlock(x, y, w, h, fail)) {
FillBlock(x, y, w, h);
return true;
}
}
return false;
}
@@ -0,0 +1,41 @@
#pragma once
#include <cstdint>
class idBitBlockAllocator {
public:
idBitBlockAllocator(int blocksWide, int blocksHigh);
~idBitBlockAllocator();
idBitBlockAllocator(const idBitBlockAllocator&) = delete;
idBitBlockAllocator& operator=(const idBitBlockAllocator&) = delete;
void Clear();
void FillBlock(int x, int y, int w, int h);
void FillBitBlock(int x, int y, const idBitBlockAllocator& block);
bool TestBlock(int x, int y, int w, int h, int* fail = nullptr) const;
bool TestBitBlock(int x, int y, const idBitBlockAllocator& block) const;
bool FindBlockLinear(int& x, int& y, int w, int h);
bool FindBitBlockLinear(int& x, int& y, const idBitBlockAllocator& block);
bool FindBlockInterleaved(int& x, int& y, int w, int h,
int& searchPoint, bool naturallyAlign);
int Width() const { return width; }
int Height() const { return height; }
bool Get(int x, int y) const;
private:
int width;
int height;
unsigned char* bits;
void Set(int x, int y);
};
void SeparateBits(int bits, int& x, int& y);
void SeparateBits2(int bits, int& x, int& y);
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idBitBlockAllocator) == 12,
"Recovered idBitBlockAllocator ABI changed");
#endif
@@ -0,0 +1,5 @@
#include "locklessqueue.h"
// The recovered translation unit contains the engine's producer/consumer
// stress command. The queue algorithms are template code in the header; their
// portable regression coverage lives in idlib_containers_test.
@@ -0,0 +1,139 @@
#pragma once
#include <atomic>
#include <cstddef>
#include <thread>
template<typename type, int queueSize>
class idLocklessQueueSingleProdCons {
public:
idLocklessQueueSingleProdCons()
: queue{}, queueStart(0), queueEnd(0) {
static_assert(queueSize > 1 && (queueSize & (queueSize - 1)) == 0,
"Lockless queue size must be a power of two");
}
void Add(type* element) {
int end = queueEnd.load(std::memory_order_relaxed);
while (((end + 1) & Mask())
== queueStart.load(std::memory_order_acquire)) {
std::this_thread::yield();
end = queueEnd.load(std::memory_order_relaxed);
}
queue[end] = element;
queueEnd.store((end + 1) & Mask(), std::memory_order_release);
}
type* Next() {
const int start = queueStart.load(std::memory_order_relaxed);
if (start == queueEnd.load(std::memory_order_acquire)) {
return nullptr;
}
type* const result = queue[start];
queueStart.store((start + 1) & Mask(), std::memory_order_release);
return result;
}
bool IsEmpty() const {
return queueStart.load(std::memory_order_acquire)
== queueEnd.load(std::memory_order_acquire);
}
private:
type* queue[queueSize];
std::atomic<int> queueStart;
alignas(128) std::atomic<int> queueEnd;
static constexpr int Mask() { return queueSize - 1; }
};
template<typename type, int queueSize>
class idLocklessQueueMultiProdCons {
public:
idLocklessQueueMultiProdCons()
: queue{}, queueStart(0), queueFetched(0), queueEnd(0), queueAlloced(0) {
static_assert(queueSize > 1 && (queueSize & (queueSize - 1)) == 0,
"Lockless queue size must be a power of two");
static_assert(sizeof(std::atomic<type*>) == sizeof(type*),
"PC atomics changed the recovered queue slot layout");
for (int index = 0; index < queueSize; ++index) {
queue[index].store(nullptr, std::memory_order_relaxed);
}
}
void Add(type* element) {
int allocated;
for (;;) {
allocated = queueAlloced.load(std::memory_order_relaxed);
while ((((allocated + 1)
^ queueFetched.load(std::memory_order_acquire)) & Mask()) == 0) {
std::this_thread::yield();
allocated = queueAlloced.load(std::memory_order_relaxed);
}
if (queueAlloced.compare_exchange_weak(allocated, allocated + 1,
std::memory_order_acq_rel, std::memory_order_relaxed)) {
break;
}
}
queue[allocated & Mask()].store(element, std::memory_order_release);
AdvancePublishedEnd();
}
type* Next() {
int start = queueStart.load(std::memory_order_relaxed);
for (;;) {
if (start == queueEnd.load(std::memory_order_acquire)) {
return nullptr;
}
if (queueStart.compare_exchange_weak(start, start + 1,
std::memory_order_acq_rel, std::memory_order_relaxed)) {
break;
}
}
type* const result = queue[start & Mask()].exchange(
nullptr, std::memory_order_acq_rel
);
AdvanceFetched();
return result;
}
bool IsEmpty() const {
return queueStart.load(std::memory_order_acquire)
== queueEnd.load(std::memory_order_acquire);
}
private:
std::atomic<type*> queue[queueSize];
std::atomic<int> queueStart;
alignas(128) std::atomic<int> queueFetched;
alignas(128) std::atomic<int> queueEnd;
alignas(128) std::atomic<int> queueAlloced;
static constexpr int Mask() { return queueSize - 1; }
void AdvancePublishedEnd() {
int end = queueEnd.load(std::memory_order_relaxed);
while (end != queueAlloced.load(std::memory_order_acquire)
&& queue[end & Mask()].load(std::memory_order_acquire) != nullptr) {
if (!queueEnd.compare_exchange_weak(end, end + 1,
std::memory_order_release, std::memory_order_relaxed)) {
continue;
}
end = queueEnd.load(std::memory_order_relaxed);
}
}
void AdvanceFetched() {
int fetched = queueFetched.load(std::memory_order_relaxed);
while (fetched != queueStart.load(std::memory_order_acquire)
&& queue[fetched & Mask()].load(std::memory_order_acquire) == nullptr) {
if (!queueFetched.compare_exchange_weak(fetched, fetched + 1,
std::memory_order_release, std::memory_order_relaxed)) {
continue;
}
fetched = queueFetched.load(std::memory_order_relaxed);
}
}
};
+95
View File
@@ -0,0 +1,95 @@
#include "runningaverage.h"
#include <algorithm>
#include <cstdlib>
idRunningAverage::idRunningAverage()
: maxNum(0)
, current(0)
, vals{nullptr, 0, 0, 16, 5, 0} {
}
idRunningAverage::~idRunningAverage() {
std::free(vals.list);
}
float idRunningAverage::GetAverage() {
if (vals.num == 0) {
return 0.0f;
}
float total = 0.0f;
for (int index = 0; index < vals.num; ++index) {
total += vals.list[index];
}
return total / static_cast<float>(vals.num);
}
float idRunningAverage::GetMin() {
if (vals.num == 0) {
return 0.0f;
}
float minimum = vals.list[0];
for (int index = 1; index < vals.num; ++index) {
minimum = std::min(minimum, vals.list[index]);
}
return minimum;
}
float idRunningAverage::GetMax() {
if (vals.num == 0) {
return 0.0f;
}
float maximum = vals.list[0];
for (int index = 1; index < vals.num; ++index) {
maximum = std::max(maximum, vals.list[index]);
}
return maximum;
}
void idRunningAverage::Init(const int num) {
current = 0;
if (num <= 0) {
vals.num = 0;
maxNum = 0;
return;
}
if (vals.size < num) {
float* const replacement = static_cast<float*>(
std::malloc(sizeof(float) * static_cast<std::size_t>(num))
);
if (replacement == nullptr) {
vals.num = 0;
maxNum = 0;
return;
}
std::free(vals.list);
vals.list = replacement;
vals.size = num;
}
vals.num = 0;
maxNum = num;
}
void idRunningAverage::Add(const float val) {
if (maxNum <= 0) {
return;
}
if (vals.num != maxNum) {
++vals.num;
current = vals.num - 1;
} else {
++current;
if (current >= maxNum) {
current = 0;
}
}
vals.list[current] = val;
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <cstdint>
// Recovered layout of idList<float, 5> from tungsten.exe.h type 12501. This
// private storage facade avoids importing the newer BFG container definition
// while retaining the original Win32 field widths and offsets.
struct idRunningAverageFloatList {
float* list;
int num;
int size;
std::int16_t granularity;
std::uint8_t memTag;
std::uint8_t listStatic;
};
static_assert(
sizeof(idRunningAverageFloatList) == 16,
"Recovered idList<float, 5> layout changed"
);
// tungsten.exe.h type 12505.
class idRunningAverage {
public:
idRunningAverage();
~idRunningAverage();
idRunningAverage(const idRunningAverage&) = delete;
idRunningAverage& operator=(const idRunningAverage&) = delete;
float GetAverage();
float GetMin();
float GetMax();
void Init(int num);
void Add(float val);
private:
int maxNum;
int current;
idRunningAverageFloatList vals;
};
static_assert(sizeof(idRunningAverage) == 24, "idRunningAverage ABI changed");
@@ -0,0 +1,5 @@
#include "runtimeexpression.h"
// The recovered RuntimeExpression.cpp is the console regression harness. The
// reusable parser/evaluator is template code in RuntimeExpression.h and is
// covered by idlib_runtimeexpression_test on the PC port.
+380
View File
@@ -0,0 +1,380 @@
#pragma once
#include "text/str.h"
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <new>
template<typename varType, typename varContextType>
class idRuntimeExpression {
public:
enum { MAX_NODES = 253, INVALID_INDEX = 255 };
class ExpNode {
public:
enum op_t {
OP_NONE = 0,
OP_VAL,
OP_VAR,
OP_MUL,
OP_DIV,
OP_MAX,
OP_MIN,
OP_LERP,
OP_CLAMP,
OP_SUB,
OP_ADD
};
ExpNode()
: coef(1.0f), op(OP_NONE), parent(INVALID_INDEX),
var0(INVALID_INDEX), var1(INVALID_INDEX), var2(INVALID_INDEX),
varId() {
}
float coef;
op_t op;
std::uint8_t parent;
std::uint8_t var0;
std::uint8_t var1;
std::uint8_t var2;
varType varId;
};
idRuntimeExpression()
: root(INVALID_INDEX), nodes(nullptr), numNodes(0), capacity(0),
granularity(5), memTag(5), listStatic(0) {
}
~idRuntimeExpression() {
Clear();
}
idRuntimeExpression(const idRuntimeExpression&) = delete;
idRuntimeExpression& operator=(const idRuntimeExpression&) = delete;
bool Parse(const char* expression, varContextType& context) {
Clear();
const char* cursor = expression == nullptr ? "" : expression;
const int parsedRoot = ParseAddSub(cursor, context);
SkipWhitespace(cursor);
if (parsedRoot == INVALID_INDEX || *cursor != '\0') {
Clear();
return false;
}
root = parsedRoot;
return true;
}
float Eval(varContextType& context) const {
return root == INVALID_INDEX ? 0.0f : EvalNode(root, context);
}
idStr PrintExp() const {
return root == INVALID_INDEX ? idStr() : PrintNode(root);
}
int NumNodes() const { return numNodes; }
void Clear() {
if (nodes != nullptr) {
for (int index = 0; index < capacity; ++index) {
nodes[index].~ExpNode();
}
std::free(nodes);
}
root = INVALID_INDEX;
nodes = nullptr;
numNodes = 0;
capacity = 0;
}
private:
int root;
ExpNode* nodes;
int numNodes;
int capacity;
std::int16_t granularity;
std::uint8_t memTag;
std::uint8_t listStatic;
static void SkipWhitespace(const char*& cursor) {
while (std::isspace(static_cast<unsigned char>(*cursor)) != 0) {
++cursor;
}
}
bool Grow() {
if (capacity >= MAX_NODES) {
return false;
}
const int newCapacity = std::min(static_cast<int>(MAX_NODES),
capacity + (granularity > 0 ? granularity : 5));
ExpNode* const replacement = static_cast<ExpNode*>(
std::malloc(sizeof(ExpNode) * static_cast<std::size_t>(newCapacity))
);
if (replacement == nullptr) {
return false;
}
for (int index = 0; index < newCapacity; ++index) {
new (&replacement[index]) ExpNode();
}
for (int index = 0; index < numNodes; ++index) {
replacement[index] = nodes[index];
}
if (nodes != nullptr) {
for (int index = 0; index < capacity; ++index) {
nodes[index].~ExpNode();
}
std::free(nodes);
}
nodes = replacement;
capacity = newCapacity;
return true;
}
int AddNode(const typename ExpNode::op_t op, const int child0 = INVALID_INDEX,
const int child1 = INVALID_INDEX, const int child2 = INVALID_INDEX,
const float coefficient = 1.0f, const varType* variable = nullptr) {
if (numNodes == capacity && !Grow()) {
return INVALID_INDEX;
}
const int index = numNodes++;
ExpNode& node = nodes[index];
node.op = op;
node.coef = coefficient;
node.var0 = static_cast<std::uint8_t>(child0);
node.var1 = static_cast<std::uint8_t>(child1);
node.var2 = static_cast<std::uint8_t>(child2);
if (variable != nullptr) {
node.varId = *variable;
}
if (child0 != INVALID_INDEX) nodes[child0].parent = static_cast<std::uint8_t>(index);
if (child1 != INVALID_INDEX) nodes[child1].parent = static_cast<std::uint8_t>(index);
if (child2 != INVALID_INDEX) nodes[child2].parent = static_cast<std::uint8_t>(index);
return index;
}
int ParseAddSub(const char*& cursor, varContextType& context) {
int left = ParseMulDiv(cursor, context);
while (left != INVALID_INDEX) {
SkipWhitespace(cursor);
const char operation = *cursor;
if (operation != '+' && operation != '-') break;
++cursor;
const int right = ParseMulDiv(cursor, context);
if (right == INVALID_INDEX) return INVALID_INDEX;
left = AddNode(operation == '+' ? ExpNode::OP_ADD : ExpNode::OP_SUB,
left, right);
}
return left;
}
int ParseMulDiv(const char*& cursor, varContextType& context) {
int left = ParseUnary(cursor, context);
while (left != INVALID_INDEX) {
SkipWhitespace(cursor);
const char operation = *cursor;
if (operation != '*' && operation != '/') break;
++cursor;
const int right = ParseUnary(cursor, context);
if (right == INVALID_INDEX) return INVALID_INDEX;
left = AddNode(operation == '*' ? ExpNode::OP_MUL : ExpNode::OP_DIV,
left, right);
}
return left;
}
int ParseUnary(const char*& cursor, varContextType& context) {
SkipWhitespace(cursor);
if (*cursor == '+') {
++cursor;
return ParseUnary(cursor, context);
}
if (*cursor == '-') {
++cursor;
const int child = ParseUnary(cursor, context);
return child == INVALID_INDEX ? INVALID_INDEX
: AddNode(ExpNode::OP_MUL, child, INVALID_INDEX,
INVALID_INDEX, -1.0f);
}
return ParsePrimary(cursor, context);
}
int ParsePrimary(const char*& cursor, varContextType& context) {
SkipWhitespace(cursor);
if (*cursor == '(') {
++cursor;
const int result = ParseAddSub(cursor, context);
SkipWhitespace(cursor);
if (*cursor != ')') return INVALID_INDEX;
++cursor;
return result;
}
if (std::isdigit(static_cast<unsigned char>(*cursor)) != 0
|| *cursor == '.') {
char* numberEnd = nullptr;
const float value = std::strtof(cursor, &numberEnd);
if (numberEnd == cursor) return INVALID_INDEX;
cursor = numberEnd;
return AddNode(ExpNode::OP_VAL, INVALID_INDEX, INVALID_INDEX,
INVALID_INDEX, value);
}
char name[128];
int nameLength = 0;
while ((std::isalnum(static_cast<unsigned char>(*cursor)) != 0
|| *cursor == '_' || *cursor == '.') && nameLength < 127) {
name[nameLength++] = *cursor++;
}
name[nameLength] = '\0';
if (nameLength == 0) return INVALID_INDEX;
SkipWhitespace(cursor);
if (*cursor != '(') {
varType variable;
if (!context.LookUpVar(name, variable)) return INVALID_INDEX;
return AddNode(ExpNode::OP_VAR, INVALID_INDEX, INVALID_INDEX,
INVALID_INDEX, 1.0f, &variable);
}
++cursor;
const int first = ParseAddSub(cursor, context);
SkipWhitespace(cursor);
if (first == INVALID_INDEX || *cursor != ',') return INVALID_INDEX;
++cursor;
const int second = ParseAddSub(cursor, context);
if (second == INVALID_INDEX) return INVALID_INDEX;
int third = INVALID_INDEX;
SkipWhitespace(cursor);
if (*cursor == ',') {
++cursor;
third = ParseAddSub(cursor, context);
if (third == INVALID_INDEX) return INVALID_INDEX;
}
SkipWhitespace(cursor);
if (*cursor != ')') return INVALID_INDEX;
++cursor;
typename ExpNode::op_t operation = ExpNode::OP_NONE;
if (std::strcmp(name, "max") == 0 && third == INVALID_INDEX) operation = ExpNode::OP_MAX;
else if (std::strcmp(name, "min") == 0 && third == INVALID_INDEX) operation = ExpNode::OP_MIN;
else if (std::strcmp(name, "lerp") == 0 && third != INVALID_INDEX) operation = ExpNode::OP_LERP;
else if (std::strcmp(name, "clamp") == 0 && third != INVALID_INDEX) operation = ExpNode::OP_CLAMP;
if (operation == ExpNode::OP_NONE) return INVALID_INDEX;
return AddNode(operation, first, second, third);
}
float EvalNode(const int index, varContextType& context) const {
const ExpNode& node = nodes[index];
const float first = node.var0 == INVALID_INDEX ? 0.0f
: EvalNode(node.var0, context);
const float second = node.var1 == INVALID_INDEX ? 0.0f
: EvalNode(node.var1, context);
const float third = node.var2 == INVALID_INDEX ? 0.0f
: EvalNode(node.var2, context);
float value = 0.0f;
switch (node.op) {
case ExpNode::OP_VAL: value = 1.0f; break;
case ExpNode::OP_VAR: value = context.GetVar(node.varId); break;
case ExpNode::OP_MUL:
value = node.var1 == INVALID_INDEX ? first : first * second;
break;
case ExpNode::OP_DIV: value = first / second; break;
case ExpNode::OP_MAX: value = std::max(first, second); break;
case ExpNode::OP_MIN: value = std::min(first, second); break;
case ExpNode::OP_LERP: value = first + (second - first) * third; break;
case ExpNode::OP_CLAMP: value = std::max(first, std::min(second, third)); break;
case ExpNode::OP_SUB: value = first - second; break;
case ExpNode::OP_ADD: value = first + second; break;
default: value = 0.0f; break;
}
return node.coef * value;
}
idStr PrintNode(const int index) const {
const ExpNode& node = nodes[index];
if (node.op == ExpNode::OP_VAL) {
char number[64];
std::snprintf(number, sizeof(number), "%g", node.coef);
return idStr(number);
}
if (node.op == ExpNode::OP_VAR) {
idStr result = node.varId.GetStr();
if (node.coef != 1.0f) {
char prefix[64];
std::snprintf(prefix, sizeof(prefix), "%g*", node.coef);
idStr scaled(prefix);
scaled.Append(result);
return scaled;
}
return result;
}
const char* opName = nullptr;
switch (node.op) {
case ExpNode::OP_MUL: opName = "*"; break;
case ExpNode::OP_DIV: opName = "/"; break;
case ExpNode::OP_SUB: opName = "-"; break;
case ExpNode::OP_ADD: opName = "+"; break;
case ExpNode::OP_MAX: opName = "max"; break;
case ExpNode::OP_MIN: opName = "min"; break;
case ExpNode::OP_LERP: opName = "lerp"; break;
case ExpNode::OP_CLAMP: opName = "clamp"; break;
default: return idStr();
}
idStr result;
if (node.op == ExpNode::OP_MAX || node.op == ExpNode::OP_MIN
|| node.op == ExpNode::OP_LERP || node.op == ExpNode::OP_CLAMP) {
result.Append(opName);
result.Append('(');
result.Append(PrintNode(node.var0));
result.Append(", ");
result.Append(PrintNode(node.var1));
if (node.var2 != INVALID_INDEX) {
result.Append(", ");
result.Append(PrintNode(node.var2));
}
result.Append(')');
} else if (node.var1 == INVALID_INDEX) {
char coefficient[64];
std::snprintf(coefficient, sizeof(coefficient), "%g*", node.coef);
result.Append(coefficient);
result.Append(PrintNode(node.var0));
return result;
} else {
result.Append('(');
result.Append(PrintNode(node.var0));
result.Append(' ');
result.Append(opName);
result.Append(' ');
result.Append(PrintNode(node.var1));
result.Append(')');
}
if (node.coef != 1.0f) {
char coefficient[64];
std::snprintf(coefficient, sizeof(coefficient), "%g*", node.coef);
idStr scaled(coefficient);
scaled.Append(result);
return scaled;
}
return result;
}
};
#if INTPTR_MAX == INT32_MAX
struct idRuntimeExpressionLayoutVar {
int index;
idStr GetStr() const { return idStr(); }
};
struct idRuntimeExpressionLayoutContext {
bool LookUpVar(const char*, idRuntimeExpressionLayoutVar&) { return false; }
float GetVar(const idRuntimeExpressionLayoutVar&) const { return 0.0f; }
};
static_assert(sizeof(idRuntimeExpression<idRuntimeExpressionLayoutVar,
idRuntimeExpressionLayoutContext>) == 20,
"Recovered idRuntimeExpression ABI changed");
#endif
+171
View File
@@ -0,0 +1,171 @@
#pragma once
#include "idlib/text/str.h"
#include <new>
enum scFileType_t {
SCT_TEXT = 0,
SCT_BINARY,
SCT_SYMLINK,
SCT_APPLE,
SCT_RESOURCE,
SCT_UNICODE,
SCT_UTF16,
SCT_NONE
};
enum scFileStatus_t {
SCF_NOTMANAGED = 0,
SCF_NOTIMPORTED,
SCF_CHECKEDOUT,
SCF_CHECKEDOUT_PLUS,
SCF_CHECKEDOUT_BYOTHER,
SCF_CHECKEDOUT_BYOTHER_EXCLUSIVE,
SCF_CHECKEDIN
};
template<class type, int tag = 5>
class idSourceControlList {
public:
explicit idSourceControlList(int listGranularity = 16)
: list(nullptr), num(0), size(0),
granularity(static_cast<short>(listGranularity > 0
? listGranularity : 16)), memTag(tag), listStatic(0) {}
idSourceControlList(const idSourceControlList& other)
: idSourceControlList(other.granularity) {
EnsureCapacity(other.num);
for (int index = 0; index < other.num; ++index) Append(other[index]);
}
~idSourceControlList() { delete[] list; }
idSourceControlList& operator=(const idSourceControlList& other) {
if (this == &other) return *this;
Clear();
granularity = other.granularity;
EnsureCapacity(other.num);
for (int index = 0; index < other.num; ++index) Append(other[index]);
return *this;
}
int Append(const type& value) {
if (!EnsureCapacity(num + 1)) return -1;
list[num] = value;
return num++;
}
void Clear() { num = 0; }
int Num() const { return num; }
type& operator[](int index) { return list[index]; }
const type& operator[](int index) const { return list[index]; }
private:
bool EnsureCapacity(int amount) {
if (amount <= size) return true;
int newSize = size == 0 ? granularity : size;
while (newSize < amount) newSize += granularity;
type* const replacement = new (std::nothrow) type[newSize];
if (replacement == nullptr) return false;
for (int index = 0; index < num; ++index) replacement[index] = list[index];
delete[] list;
list = replacement;
size = newSize;
return true;
}
type* list;
int num;
int size;
short granularity;
unsigned char memTag;
unsigned char listStatic;
};
using idSourceControlFileList = idSourceControlList<idStr, 5>;
class idSourceControl {
public:
struct idSourceControlWorkspace {
idStr name;
idStr lastAccessTime;
idStr owner;
idStr host;
};
using workspaceList_t = idSourceControlList<idSourceControlWorkspace, 5>;
virtual ~idSourceControl() = default;
virtual bool Init() { return false; }
virtual void Shutdown() {}
virtual void SetSilentCheckOut(bool) {}
virtual bool GetSilentCheckOut() const { return false; }
virtual void SetSilentCheckIn(bool) {}
virtual bool GetSilentCheckIn() const { return false; }
virtual bool IsConnected() const { return false; }
virtual bool IsInitialized() const { return false; }
virtual int CheckOut(const idSourceControlFileList&) { return 0; }
virtual int UndoCheckOut(const idSourceControlFileList&) { return 0; }
virtual int Import(const idSourceControlFileList&, bool, bool,
scFileType_t) { return 0; }
virtual int GetLatest(const idSourceControlFileList&, bool) { return 0; }
virtual int CheckIn(const idSourceControlFileList&, const idStr&,
bool) { return 0; }
virtual int Delete(const idSourceControlFileList&) { return 0; }
virtual int UpdateFolder(const char*) { return 0; }
virtual scFileStatus_t GetFileStatus(const idStr&) {
return SCF_NOTMANAGED;
}
virtual bool GetFileVersion(const idStr&, int&, int&) { return false; }
virtual int GetNumCheckOutUsers(const idStr&) { return 0; }
virtual void GetCheckOutUser(const idStr&, int, char* output,
int outputSize) {
if (output != nullptr && outputSize > 0) output[0] = '\0';
}
virtual void GetUsernameForFileVersion(const idStr&, int,
idStr& output) { output.Clear(); }
virtual void GetCurrentWorkspace(idSourceControlWorkspace& output) {
output = idSourceControlWorkspace();
}
virtual void GetAvailableWorkspaces(workspaceList_t& output) {
output.Clear();
}
virtual void SetWorkspace(const char*) {}
virtual void SetWorkspace(idSourceControlWorkspace&) {}
virtual bool IsWorkspaceValid() { return false; }
virtual idStr GetUsername() { return idStr(); }
int CheckOut(const idStr& file) {
idSourceControlFileList files(1);
files.Append(file);
return CheckOut(files);
}
int Import(const idStr& file, bool keepCheckedOut, bool submit,
scFileType_t fileType) {
idSourceControlFileList files(1);
files.Append(file);
return Import(files, keepCheckedOut, submit, fileType);
}
int GetLatest(const idStr& file, bool force) {
idSourceControlFileList files(1);
files.Append(file);
return GetLatest(files, force);
}
int Delete(const idStr& file) {
idSourceControlFileList files(1);
files.Append(file);
return Delete(files);
}
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idSourceControlFileList) == 16,
"Recovered source-control list ABI changed");
static_assert(sizeof(idSourceControl::idSourceControlWorkspace) == 128,
"Recovered source-control workspace ABI changed");
static_assert(sizeof(idSourceControl) == 4,
"Recovered idSourceControl ABI changed");
#endif
+208
View File
@@ -0,0 +1,208 @@
#include "sys_alloc.h"
#include <algorithm>
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <malloc.h>
#include <Windows.h>
namespace {
struct allocationRecord_t {
allocationRecord_t* next;
void* pointer;
unsigned int bytes;
memTag_t tag;
heapType_t heap;
const char* location;
};
SRWLOCK allocationLock = SRWLOCK_INIT;
allocationRecord_t* allocations = nullptr;
std::atomic<unsigned long long> allocatedBytes(0);
std::atomic<idOutOfMemoryCallback> outOfMemoryCallback(nullptr);
thread_local heapType_t currentHeap = HEAP_SYSTEMHEAP;
thread_local heapType_t pushedHeap = HEAP_SYSTEMHEAP;
thread_local int heapStackDepth = 0;
const char* const tagNames[TAG_NUM_TAGS] = {
"UNSET", "STATIC_EXE", "RESOURCE_GRAPH", "DEBUG", "NEW", "IDLIST",
"TEMP", "STRING", "ATOMIC_STRING", "BITARRAY", "MATH", "LEXER",
"COMPILER", "COLLISION", "COLLISION_QUERY", "CLIPMODEL", "MORPH",
"MD6_MISC", "MD6_NODES", "MD6", "MD6_ANIMS", "MD6_LIPSYNC",
"MD6_JOINTCACHE", "MD6_MESHES", "MD6_JOINTBUFFERS", "MD6_BLENDSTACK",
"MD6_JOINTMODS", "MD6_COLLISION", "MD6_ANIMEVENTS", "MD6_PHASE_TRACK",
"ANIMATION", "ANIMATION_DEBUG", "DECL_ANIMWEB", "ANIMWEB", "IMAGE",
"DXIMAGE", "VIRTUALTEXTURE", "AAS", "SOUND", "SOUND_BSP", "SOUND_DATA",
"SOUND_STREAM", "SOUND_MULTISTREAM", "SOUND_SAMPLETABLES", "IDLIB",
"TRIANGLES", "DECL", "DECLTEXT", "FILE", "CVAR", "PAGEFILECACHE",
"IDCLASS", "PRESENTABLE", "FOLIAGE", "WATER", "MEMORY_MAPPED_FILE",
"RENDERPARM", "NETWORKING", "SCRIPT", "FXPHYSICS", "LWO", "RENDERWORLD",
"RENDERER", "AI_GAMESTATE", "EVENTS", "VOICEOVER", "VOICETRACK_EVENTS",
"VOICETRACK_FRAMEREFS", "VOICETRACK_PHONEMES", "VISEMESET_VISEMES",
"VISEMESET_PHONEMES", "AF", "SWF", "GUI", "GUI_MODEL", "FUNC_CALLBACK",
"MENU", "GAME", "HASHINDEX", "PARTICLE", "EFFECT_PARTICLE", "CLOTH",
"ANIMTAGS", "IK", "STATICMODEL", "RENDERMODEL", "DXBUFFER", "TOOLS",
"CLOUD", "AMQP", "RENDERPROG", "HASHTABLE", "AI_FSM", "AI_VISCACHE",
"AI_SEARCH", "AI_OBSTACLE", "JOBLIST", "TRANSPARENCY", "DETAIL",
"RESOURCE", "FILE_RESOURCE", "RESOURCE_BGL", "RESOURCE_BGL_RING",
"RESOURCE_BGL_OVERSIZE", "RESOURCE_MGR", "PVS", "DEFERRED_VIS", "FIBER",
"SUPERSCRIPT", "FX", "SAVEGAMES", "AI_TRANSITIONS", "AI_STATEDATA",
"AI_COMBATANIM", "TYPEINFO", "DAMAGEDECAL", "TABLE", "VIDEO", "NAVPOWER",
"LANGDICT", "SPLINE", "BINK", "FONTS", "EVENT_LISTENER", "PHYSICAL_BLOCK",
"DXOBJECT"
};
void AddRecord(void* pointer, const unsigned int bytes, const memTag_t tag,
const heapType_t heap, const char* location) {
allocationRecord_t* const record = static_cast<allocationRecord_t*>(
std::malloc(sizeof(allocationRecord_t))
);
if (record == nullptr) {
return;
}
record->pointer = pointer;
record->bytes = bytes;
record->tag = tag;
record->heap = heap;
record->location = location;
AcquireSRWLockExclusive(&allocationLock);
record->next = allocations;
allocations = record;
ReleaseSRWLockExclusive(&allocationLock);
allocatedBytes.fetch_add(bytes, std::memory_order_relaxed);
}
unsigned int RemoveRecord(void* pointer) {
unsigned int bytes = 0;
AcquireSRWLockExclusive(&allocationLock);
allocationRecord_t** link = &allocations;
while (*link != nullptr) {
if ((*link)->pointer == pointer) {
allocationRecord_t* const record = *link;
*link = record->next;
bytes = record->bytes;
std::free(record);
break;
}
link = &(*link)->next;
}
ReleaseSRWLockExclusive(&allocationLock);
if (bytes != 0) {
allocatedBytes.fetch_sub(bytes, std::memory_order_relaxed);
}
return bytes;
}
}
idMem mem;
idMemLocal memLocal;
const char* GetMemTagName(const int tag) {
return tag >= 0 && tag < TAG_NUM_TAGS ? tagNames[tag] : "<BAD TAG NAME>";
}
void* idMem::AllocWithLocation(const char* const location,
const unsigned int size, const memTag_t tag, const bool zeroBuffer,
const align_t alignment, const heapType_t requestedHeap) {
const std::size_t allocationSize = size == 0 ? 1u : size;
const std::size_t requestedAlignment = static_cast<std::size_t>(alignment);
const std::size_t safeAlignment = requestedAlignment < sizeof(void*)
? sizeof(void*) : requestedAlignment;
void* pointer = _aligned_malloc(allocationSize, safeAlignment);
if (pointer == nullptr) {
idOutOfMemoryCallback callback = outOfMemoryCallback.load(
std::memory_order_acquire
);
if (callback != nullptr && callback()) {
pointer = _aligned_malloc(allocationSize, safeAlignment);
}
}
if (pointer == nullptr) {
return nullptr;
}
if (zeroBuffer) {
std::memset(pointer, 0, allocationSize);
}
const heapType_t usedHeap = requestedHeap == HEAP_DEFAULTHEAP
? currentHeap : requestedHeap;
AddRecord(pointer, size, tag, usedHeap, location);
return pointer;
}
void idMem::Free(void* const pointer, const align_t) {
if (pointer == nullptr) {
return;
}
RemoveRecord(pointer);
_aligned_free(pointer);
}
int idMem::BytesCurrentlyAllocated() const {
const unsigned long long bytes = allocatedBytes.load(std::memory_order_relaxed);
return bytes > static_cast<unsigned long long>((std::numeric_limits<int>::max)())
? (std::numeric_limits<int>::max)() : static_cast<int>(bytes);
}
void idMem::InitMapHeap() {
}
void idMem::ResetMapHeap() {
// The PC port uses the process CRT heap for both logical heaps. Allocations
// remain individually tracked so stale map allocations can still be found.
}
void idMem::PushHeap(const heapType_t heapType) {
if (heapStackDepth++ == 0) {
pushedHeap = currentHeap;
currentHeap = heapType == HEAP_DEFAULTHEAP ? HEAP_MAPHEAP : heapType;
}
}
void idMem::PopHeap() {
if (heapStackDepth <= 0) {
heapStackDepth = 0;
return;
}
if (--heapStackDepth == 0) {
currentHeap = pushedHeap;
}
}
bool idMem::IsGlobalHeap() const {
return currentHeap == HEAP_SYSTEMHEAP;
}
void idMem::SetOutOfMemoryCallback(const idOutOfMemoryCallback callback) {
outOfMemoryCallback.store(callback, std::memory_order_release);
}
idOutOfMemoryCallback idMem::GetOutOfMemoryCallback() const {
return outOfMemoryCallback.load(std::memory_order_acquire);
}
void idMem::WriteMemoryReport(const char* const directory,
const char* const fileName) const {
char path[MAX_PATH];
const char* const safeDirectory = directory == nullptr ? "." : directory;
const char* const safeFile = fileName == nullptr ? "memory_report.txt" : fileName;
std::snprintf(path, sizeof(path), "%s\\%s", safeDirectory, safeFile);
FILE* report = nullptr;
if (fopen_s(&report, path, "w") != 0 || report == nullptr) {
return;
}
std::fprintf(report, "bytes currently allocated: %d\n", BytesCurrentlyAllocated());
AcquireSRWLockShared(&allocationLock);
for (allocationRecord_t* record = allocations; record != nullptr;
record = record->next) {
std::fprintf(report, "%p %10u %-24s heap=%d %s\n", record->pointer,
record->bytes, GetMemTagName(record->tag), record->heap,
record->location == nullptr ? "<unknown>" : record->location);
}
ReleaseSRWLockShared(&allocationLock);
std::fclose(report);
}
+133
View File
@@ -0,0 +1,133 @@
#pragma once
#include <cstdint>
enum memTag_t : int {
TAG_UNSET = 0x00, TAG_STATIC_EXE, TAG_RESOURCE_GRAPH, TAG_DEBUG,
TAG_NEW, TAG_IDLIST, TAG_TEMP, TAG_STRING, TAG_ATOMIC_STRING,
TAG_BITARRAY, TAG_MATH, TAG_LEXER, TAG_COMPILER, TAG_COLLISION,
TAG_COLLISION_QUERY, TAG_CLIPMODEL, TAG_MORPH, TAG_MD6_MISC,
TAG_MD6_NODES, TAG_MD6, TAG_MD6_ANIMS, TAG_MD6_LIPSYNC,
TAG_MD6_JOINTCACHE, TAG_MD6_MESHES, TAG_MD6_JOINTBUFFERS,
TAG_MD6_BLENDSTACK, TAG_MD6_JOINTMODS, TAG_MD6_COLLISION,
TAG_MD6_ANIMEVENTS, TAG_MD6_PHASE_TRACK, TAG_ANIMATION,
TAG_ANIMATION_DEBUG, TAG_DECL_ANIMWEB, TAG_ANIMWEB, TAG_IMAGE,
TAG_DXIMAGE, TAG_VIRTUALTEXTURE, TAG_AAS, TAG_SOUND, TAG_SOUND_BSP,
TAG_SOUND_DATA, TAG_SOUND_STREAM, TAG_SOUND_MULTISTREAM,
TAG_SOUND_SAMPLETABLES, TAG_IDLIB, TAG_TRIANGLES, TAG_DECL,
TAG_DECLTEXT, TAG_FILE, TAG_CVAR, TAG_PAGEFILECACHE, TAG_IDCLASS,
TAG_PRESENTABLE, TAG_FOLIAGE, TAG_WATER, TAG_MEMORY_MAPPED_FILE,
TAG_RENDERPARM, TAG_NETWORKING, TAG_SCRIPT, TAG_FXPHYSICS, TAG_LWO,
TAG_RENDERWORLD, TAG_RENDERER, TAG_AI_GAMESTATE, TAG_EVENTS,
TAG_VOICEOVER, TAG_VOICETRACK_EVENTS, TAG_VOICETRACK_FRAMEREFS,
TAG_VOICETRACK_PHONEMES, TAG_VISEMESET_VISEMES,
TAG_VISEMESET_PHONEMES, TAG_AF, TAG_SWF, TAG_GUI, TAG_GUI_MODEL,
TAG_FUNC_CALLBACK, TAG_MENU, TAG_GAME, TAG_HASHINDEX, TAG_PARTICLE,
TAG_EFFECT_PARTICLE, TAG_CLOTH, TAG_ANIMTAGS, TAG_IK, TAG_STATICMODEL,
TAG_RENDERMODEL, TAG_DXBUFFER, TAG_TOOLS, TAG_CLOUD, TAG_AMQP,
TAG_RENDERPROG, TAG_HASHTABLE, TAG_AI_FSM, TAG_AI_VISCACHE,
TAG_AI_SEARCH, TAG_AI_OBSTACLE, TAG_JOBLIST, TAG_TRANSPARENCY,
TAG_DETAIL, TAG_RESOURCE, TAG_FILE_RESOURCE, TAG_RESOURCE_BGL,
TAG_RESOURCE_BGL_RING, TAG_RESOURCE_BGL_OVERSIZE, TAG_RESOURCE_MGR,
TAG_PVS, TAG_DEFERRED_VIS, TAG_FIBER, TAG_SUPERSCRIPT, TAG_FX,
TAG_SAVEGAMES, TAG_AI_TRANSITIONS, TAG_AI_STATEDATA, TAG_AI_COMBATANIM,
TAG_TYPEINFO, TAG_DAMAGEDECAL, TAG_TABLE, TAG_VIDEO, TAG_NAVPOWER,
TAG_LANGDICT, TAG_SPLINE, TAG_BINK, TAG_FONTS, TAG_EVENT_LISTENER,
TAG_PHYSICAL_BLOCK, TAG_DXOBJECT, TAG_NUM_TAGS
};
enum align_t : int {
ALIGN_16 = 0x10,
ALIGN_128 = 0x80,
ALIGN_1M = 0x100000
};
enum heapType_t : int {
HEAP_DEFAULTHEAP = -1,
HEAP_SYSTEMHEAP = 0,
HEAP_MAPHEAP = 1
};
using idOutOfMemoryCallback = bool (*)();
class idMem {
public:
void* AllocWithLocation(const char* location, unsigned int size,
memTag_t tag, bool zeroBuffer = false, align_t alignment = ALIGN_16,
heapType_t heap = HEAP_DEFAULTHEAP);
void Free(void* pointer, align_t alignment = ALIGN_16);
int BytesCurrentlyAllocated() const;
void InitMapHeap();
void ResetMapHeap();
void PushHeap(heapType_t heapType);
void PopHeap();
bool IsGlobalHeap() const;
void SetOutOfMemoryCallback(idOutOfMemoryCallback callback);
idOutOfMemoryCallback GetOutOfMemoryCallback() const;
void WriteMemoryReport(const char* directory, const char* fileName) const;
};
class idMemLocal : public idMem {
};
extern idMem mem;
extern idMemLocal memLocal;
const char* GetMemTagName(int tag);
bool Sys_AllocWillUseMapHeap();
void Sys_ReportHeaps();
void ReportGlobalMemoryStatus();
void* Sys_Alloc(unsigned int size, memTag_t tag,
align_t alignment = ALIGN_16,
heapType_t heap = HEAP_DEFAULTHEAP);
void Sys_Free(void* pointer);
unsigned int Sys_GetStreamFileCacheUsage();
unsigned int Sys_GetMemoryUsage();
unsigned int Sys_GetFreeMemory();
void Sys_WriteMemoryReport(const char* mapName, const char* version);
void Sys_DumpMemory();
class idPhysicalMemoryBlock {
public:
idPhysicalMemoryBlock();
void Init(int bytesToAllocate);
void RevertToDiscreteAllocations();
void BeginResourceLoads();
void EndResourceLoads(bool neverFreeAllocatedData);
void* PhysicalAlloc(unsigned int bytes, int alignment, memTag_t tag);
void* OverlayAlloc(unsigned int bytes, const char* name);
void OverlayFree(void* pointer);
bool AddressIsInReservedPhysicalMemoryBlock(const void* pointer) const;
bool AddressIsInOverlayPhysicalMemoryBlock(const void* pointer) const;
void ReportPhysicalMemoryBlock() const;
void ReportUntouchedPhysicalMemory() const;
private:
unsigned char* reservedPhysicalMemoryBlock;
int totalBlockSize;
int commonBytes;
int overlayBytes;
int cacheBytes;
bool insideResourceBlockLoad;
int physicalBytesAllocated;
int imageBytesAllocated;
int bufferBytesAllocated;
int otherBytesAllocated;
int alignmentWaste;
int bytesForcedOutsideBlock;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idPhysicalMemoryBlock) == 48,
"Recovered idPhysicalMemoryBlock ABI changed");
#endif
class idScopedGlobalHeap {
public:
idScopedGlobalHeap() { mem.PushHeap(HEAP_SYSTEMHEAP); }
~idScopedGlobalHeap() { mem.PopHeap(); }
};
+15
View File
@@ -0,0 +1,15 @@
#include "sys_mgrd.h"
// MGRD was an optional external memory-profiler transport selected with the
// Xbox-era -mgrd switch. The PC recovery keeps the instrumentation API intact
// while making it a no-op until a desktop profiler backend is selected.
void RD_Init() {}
void RD_CreateGPUHeaps(void*, unsigned int, void*, unsigned int) {}
void RD_DestroyGPUHeaps() {}
void RD_CreateMapHeap() {}
void RD_DestroyMapHeap() {}
void RD_MemAlloc(void*, unsigned int, unsigned int, int) {}
void RD_MemFree(void*, int) {}
void RD_EventBegin(const char*) {}
void RD_EventEnd() {}
void RD_Syncpoint(const char*) {}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
void RD_Init();
void RD_CreateGPUHeaps(void* gpuMemory, unsigned int gpuBytes,
void* systemMemory, unsigned int systemBytes);
void RD_DestroyGPUHeaps();
void RD_CreateMapHeap();
void RD_DestroyMapHeap();
void RD_MemAlloc(void* pointer, unsigned int size, unsigned int waste, int heap);
void RD_MemFree(void* pointer, int heap);
void RD_EventBegin(const char* name);
void RD_EventEnd();
void RD_Syncpoint(const char* name);
class idRDScopedEvent {
public:
explicit idRDScopedEvent(const char* name) { RD_EventBegin(name); }
~idRDScopedEvent() { RD_EventEnd(); }
};
+123
View File
@@ -0,0 +1,123 @@
#pragma once
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#include <cstdint>
#ifndef __SYS_PUBLIC__
enum netadrtype_t {
NA_BAD = 0,
NA_LOOPBACK,
NA_BROADCAST,
NA_IP
};
struct netadr_t {
netadrtype_t type;
unsigned char ip[4];
unsigned short port;
};
#endif
class idSimpleSerializer {
public:
idSimpleSerializer(unsigned char* buffer, int bufferSize, bool write)
: data(buffer), size(bufferSize), pos(0), writing(write) {
}
bool Serialize(unsigned char& value);
bool Serialize(unsigned int& value);
bool SerializeBytes(char* bytes, unsigned int& numBytes);
bool SerializeString(char* text, int maxSize);
int GetPos() const { return pos; }
int GetSize() const { return size; }
int GetSerializedSize() const { return writing ? pos : size; }
bool IsWriting() const { return writing; }
private:
unsigned char* data;
int size;
int pos;
bool writing;
};
#ifndef __SYS_PUBLIC__
class idUDP {
public:
idUDP();
virtual ~idUDP();
bool InitForPort(int portNumber, bool useBackend = false);
void Close();
bool GetPacket(netadr_t& from, void* data, int& size, int maxSize);
bool GetPacketBlocking(netadr_t& from, void* data, int& size, int maxSize,
int timeoutMS);
void SendPacket(netadr_t to, const void* data, int size);
int GetPort() const { return bound_to.port; }
netadr_t GetAdr() const { return bound_to; }
bool IsOpen() const { return netSocket != 0; }
void SetSilent(bool value) { silent = value; }
bool GetSilent() const { return silent; }
int packetsRead;
int bytesRead;
int packetsWritten;
int bytesWritten;
private:
netadr_t bound_to;
int netSocket;
bool silent;
};
#endif
class idTCP {
public:
idTCP();
virtual ~idTCP();
bool Connect(const char* host, unsigned short port, bool nonBlocking = false,
bool silent = false, bool nagle = true);
bool Select(int timeoutMS);
bool IsOpen() const;
void Close();
int Read(void* data, int size);
int ReadBlocking(void* data, int size, int timeoutMS);
int Write(const void* data, int size);
int WriteBlocking(const void* data, int size, int timeoutMS);
bool WriteDataBlock(const char* buffer, int size, int timeoutMS);
int ReadDataBlock(char* buffer, int bufferSize, int timeoutMS);
netadr_t GetAddress() const { return address; }
private:
netadr_t address;
int fd;
};
#ifndef __SYS_PUBLIC__
void Sys_InitNetworking();
void Sys_ShutdownNetworking();
bool Sys_StringToNetAdr(const char* text, netadr_t* address, bool doDNSResolve);
const char* Sys_NetAdrToString(const netadr_t& address);
bool Sys_IsLANAddress(const netadr_t& address);
bool Sys_CompareNetAdrBase(const netadr_t& left, const netadr_t& right);
int Sys_GetLocalIPCount();
const char* Sys_GetLocalIP(int index);
#endif
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(netadr_t) == 12, "Recovered netadr_t ABI changed");
static_assert(sizeof(idSimpleSerializer) == 16,
"Recovered idSimpleSerializer ABI changed");
#ifndef __SYS_PUBLIC__
static_assert(sizeof(idUDP) == 40, "Recovered idUDP ABI changed");
#endif
static_assert(sizeof(idTCP) == 20, "Recovered idTCP ABI changed");
#endif
+115
View File
@@ -0,0 +1,115 @@
#include "sys_time.h"
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <ctime>
#if defined(_WIN32)
#include <Windows.h>
#endif
namespace {
thread_local char timeString[128];
bool LocalTime(const std::time_t value, std::tm& output) {
#if defined(_WIN32)
return localtime_s(&output, &value) == 0;
#else
return localtime_r(&value, &output) != nullptr;
#endif
}
}
std::uint64_t Sys_CurrentSystemTime() {
#if defined(_WIN32)
FILETIME fileTime;
GetSystemTimeAsFileTime(&fileTime);
return (static_cast<std::uint64_t>(fileTime.dwHighDateTime) << 32)
| static_cast<std::uint64_t>(fileTime.dwLowDateTime);
#else
constexpr std::uint64_t windowsEpochOffset = 116444736000000000ULL;
const auto ticks = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() / 100;
return windowsEpochOffset + static_cast<std::uint64_t>(ticks);
#endif
}
char* Sys_TimeStampToLogFormat(const int timeStamp) {
timeString[0] = '\0';
if (timeStamp == -1) {
return timeString;
}
const std::time_t source = static_cast<std::time_t>(timeStamp);
std::tm local = {};
if (LocalTime(source, local)) {
std::snprintf(timeString, sizeof(timeString),
"%d-%02d-%02dT%02d:%02d:%02dZ",
local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
local.tm_hour, local.tm_min, local.tm_sec);
}
return timeString;
}
char* Sys_TimeStampToStr(const int timeStamp, const bool padded) {
timeString[0] = '\0';
if (timeStamp == -1) {
return timeString;
}
const std::time_t source = static_cast<std::time_t>(timeStamp);
std::tm local = {};
if (!LocalTime(source, local)) {
return timeString;
}
int hour = local.tm_hour % 12;
if (hour == 0) {
hour = 12;
}
if (padded) {
std::snprintf(timeString, sizeof(timeString),
"%02d/%02d/%d %02d:%02d:%02d%s",
local.tm_mon + 1, local.tm_mday, local.tm_year + 1900,
hour, local.tm_min, local.tm_sec,
local.tm_hour < 12 ? "am" : "pm");
} else {
std::snprintf(timeString, sizeof(timeString),
"%d/%d/%d %d:%02d:%02d%s",
local.tm_mon + 1, local.tm_mday, local.tm_year + 1900,
hour, local.tm_min, local.tm_sec,
local.tm_hour < 12 ? "am" : "pm");
}
return timeString;
}
char* Sys_DateStr(const bool padded) {
return Sys_TimeStampToStr(static_cast<int>(std::time(nullptr)), padded);
}
idStr Sys_SecToStr(int seconds) {
idStr result;
if (seconds < 0) {
seconds = 0;
}
char buffer[32];
const int weeks = seconds / 604800;
if (weeks > 0) {
std::snprintf(buffer, sizeof(buffer), "%dw, ", weeks);
result.Append(buffer);
seconds %= 604800;
}
const int days = seconds / 86400;
if (weeks > 0 || days > 0) {
std::snprintf(buffer, sizeof(buffer), "%dd, ", days);
result.Append(buffer);
seconds %= 86400;
}
const int hours = seconds / 3600;
const int minutes = (seconds % 3600) / 60;
const int remainingSeconds = seconds % 60;
std::snprintf(buffer, sizeof(buffer), "%d:%02d:%02d",
hours, minutes, remainingSeconds);
result.Append(buffer);
return result;
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <cstdint>
#include "../text/str.h"
std::uint64_t Sys_CurrentSystemTime();
char* Sys_TimeStampToLogFormat(int timeStamp);
char* Sys_TimeStampToStr(int timeStamp, bool padded);
char* Sys_DateStr(bool padded);
idStr Sys_SecToStr(int seconds);
+5
View File
@@ -0,0 +1,5 @@
#pragma once
const char* Sys_GetOSUserName();
const char* Sys_GetMachineName();
@@ -0,0 +1,63 @@
#include "win_fibers.h"
#include <cstdlib>
#include <cstring>
namespace {
char* CopyFiberName(const char* source) {
const char* const text = source == nullptr ? "" : source;
const std::size_t length = std::strlen(text);
char* const copy = static_cast<char*>(std::malloc(length + 1));
if (copy != nullptr) {
std::memcpy(copy, text, length + 1);
}
return copy;
}
} // namespace
idSysFiber::idSysFiber(const char* fiberName)
: name(CopyFiberName(fiberName))
, alive(true)
, fiber(nullptr)
, parent(nullptr) {
fiber = CreateFiber(0x20000u, &idSysFiber::FiberRoutine, this);
if (fiber == nullptr) {
alive = false;
}
}
idSysFiber::~idSysFiber() {
std::free(name);
if (fiber != nullptr) {
DeleteFiber(fiber);
}
}
bool idSysFiber::Execute() {
if (alive && fiber != nullptr) {
parent = GetCurrentFiber();
SwitchToFiber(fiber);
}
return alive;
}
void idSysFiber::YieldFiber() {
if (parent != nullptr) {
SwitchToFiber(parent);
}
}
void WINAPI idSysFiber::FiberRoutine(void* data) {
idSysFiber* const self = static_cast<idSysFiber*>(data);
self->Run();
self->alive = false;
self->YieldFiber();
// A finished Windows fiber must never return from its entry routine when
// its parent is expected to retain control. Match the recovered guard.
for (;;) {
self->YieldFiber();
}
}
@@ -0,0 +1,28 @@
#pragma once
#include <Windows.h>
class idSysFiber {
public:
explicit idSysFiber(const char* name);
virtual ~idSysFiber();
idSysFiber(const idSysFiber&) = delete;
idSysFiber& operator=(const idSysFiber&) = delete;
bool Execute();
virtual void Run() = 0;
protected:
void YieldFiber();
private:
static void WINAPI FiberRoutine(void* data);
char* name;
bool alive;
void* fiber;
void* parent;
};
static_assert(sizeof(idSysFiber) == 20, "Recovered idSysFiber ABI changed");
@@ -0,0 +1,23 @@
#include "../sys_alloc.h"
#include <cstdio>
#include <Windows.h>
bool Sys_AllocWillUseMapHeap() {
return !mem.IsGlobalHeap();
}
void Sys_ReportHeaps() {
std::printf("PC logical heap: %s, tracked bytes: %d\n",
mem.IsGlobalHeap() ? "global" : "map", mem.BytesCurrentlyAllocated());
}
void ReportGlobalMemoryStatus() {
MEMORYSTATUSEX status = {};
status.dwLength = sizeof(status);
if (GlobalMemoryStatusEx(&status)) {
std::printf("physical memory: %llu / %llu bytes available\n",
static_cast<unsigned long long>(status.ullAvailPhys),
static_cast<unsigned long long>(status.ullTotalPhys));
}
}
+203
View File
@@ -0,0 +1,203 @@
#include "idlib/sys/sys_alloc.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <psapi.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <limits>
namespace {
unsigned int ClampToUInt64(const unsigned long long value) {
return value > (std::numeric_limits<unsigned int>::max)()
? (std::numeric_limits<unsigned int>::max)()
: static_cast<unsigned int>(value);
}
int AlignUp(const int value, const int alignment) {
const int safeAlignment = alignment <= 0 ? 16 : alignment;
return (value + safeAlignment - 1) & ~(safeAlignment - 1);
}
} // namespace
void MapVirtualAddressSpace() {
// Win32 already supplies a flat virtual address space. Logical map/system
// heap selection remains handled by idMem.
}
unsigned int Sys_GetStreamFileCacheUsage() {
// The PC filesystem owns its cache allocations through idMem rather than
// a carved physical-memory range.
return 0;
}
unsigned int Sys_GetMemoryUsage() {
PROCESS_MEMORY_COUNTERS_EX counters = {};
counters.cb = sizeof(counters);
if (!GetProcessMemoryInfo(GetCurrentProcess(),
reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&counters),
sizeof(counters))) {
return static_cast<unsigned int>(mem.BytesCurrentlyAllocated());
}
return ClampToUInt64(counters.PrivateUsage);
}
unsigned int Sys_GetFreeMemory() {
MEMORYSTATUSEX status = {};
status.dwLength = sizeof(status);
return GlobalMemoryStatusEx(&status) ? ClampToUInt64(status.ullAvailPhys) : 0;
}
void Sys_WriteMemoryReport(const char* mapName, const char* version) {
char fileName[192] = {};
std::snprintf(fileName, sizeof(fileName), "memory_%s_%s.txt",
mapName == nullptr || mapName[0] == '\0' ? "nomap" : mapName,
version == nullptr || version[0] == '\0' ? "unknown" : version);
for (char* cursor = fileName; *cursor != '\0'; ++cursor) {
if (*cursor == '\\' || *cursor == '/' || *cursor == ':'
|| *cursor == '*' || *cursor == '?' || *cursor == '"'
|| *cursor == '<' || *cursor == '>' || *cursor == '|') {
*cursor = '_';
}
}
mem.WriteMemoryReport(".", fileName);
}
void Sys_DumpMemory() {
mem.WriteMemoryReport(".", "memory_dump.txt");
}
void AddTagStats(int, int, int, int) {
// idMem records tags per allocation, so a second accounting table would
// double count on PC.
}
void SubtractTagStats(int, int, int, int) {
}
void* XMemAlloc(const unsigned int size, const int) {
return mem.AllocWithLocation("XMemAlloc PC replacement", size, TAG_IDLIB,
false, ALIGN_16, HEAP_DEFAULTHEAP);
}
void XMemFree(unsigned char* pointer, const unsigned int) {
mem.Free(pointer);
}
void* Sys_Alloc(const unsigned int size, const memTag_t tag,
const align_t alignment, const heapType_t heap) {
return mem.AllocWithLocation("Sys_Alloc PC replacement", size, tag,
false, alignment, heap);
}
void Sys_Free(void* pointer) {
mem.Free(pointer);
}
idPhysicalMemoryBlock::idPhysicalMemoryBlock()
: reservedPhysicalMemoryBlock(nullptr), totalBlockSize(0), commonBytes(0),
overlayBytes(0), cacheBytes(0), insideResourceBlockLoad(false),
physicalBytesAllocated(0), imageBytesAllocated(0), bufferBytesAllocated(0),
otherBytesAllocated(0), alignmentWaste(0), bytesForcedOutsideBlock(0) {
}
void idPhysicalMemoryBlock::Init(const int bytesToAllocate) {
if (reservedPhysicalMemoryBlock != nullptr || bytesToAllocate <= 0) return;
totalBlockSize = AlignUp(bytesToAllocate, 65536);
reservedPhysicalMemoryBlock = static_cast<unsigned char*>(
mem.AllocWithLocation("idPhysicalMemoryBlock PC reserve",
totalBlockSize, TAG_PHYSICAL_BLOCK, true, ALIGN_16,
HEAP_SYSTEMHEAP));
if (reservedPhysicalMemoryBlock == nullptr) totalBlockSize = 0;
}
void idPhysicalMemoryBlock::RevertToDiscreteAllocations() {
if (physicalBytesAllocated != 0) return;
mem.Free(reservedPhysicalMemoryBlock);
reservedPhysicalMemoryBlock = nullptr;
totalBlockSize = 0;
commonBytes = overlayBytes = cacheBytes = 0;
}
void idPhysicalMemoryBlock::BeginResourceLoads() {
insideResourceBlockLoad = true;
}
void idPhysicalMemoryBlock::EndResourceLoads(const bool neverFreeAllocatedData) {
insideResourceBlockLoad = false;
physicalBytesAllocated = std::min(AlignUp(physicalBytesAllocated, 65536),
totalBlockSize);
if (neverFreeAllocatedData) commonBytes = physicalBytesAllocated;
cacheBytes = std::max(0, totalBlockSize - physicalBytesAllocated);
overlayBytes = 0;
}
void* idPhysicalMemoryBlock::PhysicalAlloc(const unsigned int bytes,
const int alignment, const memTag_t tag) {
const int alignedOffset = AlignUp(physicalBytesAllocated, alignment);
alignmentWaste += alignedOffset - physicalBytesAllocated;
if (reservedPhysicalMemoryBlock != nullptr
&& alignedOffset >= 0
&& bytes <= static_cast<unsigned int>(totalBlockSize - alignedOffset)) {
physicalBytesAllocated = alignedOffset + static_cast<int>(bytes);
if (tag == TAG_DXIMAGE) imageBytesAllocated += bytes;
else if (tag == TAG_DXBUFFER) bufferBytesAllocated += bytes;
else otherBytesAllocated += bytes;
return reservedPhysicalMemoryBlock + alignedOffset;
}
bytesForcedOutsideBlock += bytes;
return mem.AllocWithLocation("physical allocation fallback", bytes, tag,
false, alignment >= ALIGN_128 ? ALIGN_128 : ALIGN_16,
HEAP_SYSTEMHEAP);
}
void* idPhysicalMemoryBlock::OverlayAlloc(const unsigned int bytes,
const char*) {
// PC resources are individually reclaimable; keeping overlays discrete
// avoids the Xenon-only 64 KiB overlay fragmentation rules.
return mem.AllocWithLocation("overlay allocation PC replacement", bytes,
TAG_PHYSICAL_BLOCK, false, ALIGN_16, HEAP_SYSTEMHEAP);
}
void idPhysicalMemoryBlock::OverlayFree(void* pointer) {
if (!AddressIsInReservedPhysicalMemoryBlock(pointer)) mem.Free(pointer);
}
bool idPhysicalMemoryBlock::AddressIsInReservedPhysicalMemoryBlock(
const void* pointer) const {
const unsigned char* const address = static_cast<const unsigned char*>(pointer);
return reservedPhysicalMemoryBlock != nullptr
&& address >= reservedPhysicalMemoryBlock
&& address < reservedPhysicalMemoryBlock + totalBlockSize;
}
bool idPhysicalMemoryBlock::AddressIsInOverlayPhysicalMemoryBlock(
const void* pointer) const {
if (!AddressIsInReservedPhysicalMemoryBlock(pointer) || overlayBytes <= 0) {
return false;
}
const unsigned char* const overlayStart =
reservedPhysicalMemoryBlock + totalBlockSize - overlayBytes;
return static_cast<const unsigned char*>(pointer) >= overlayStart;
}
void idPhysicalMemoryBlock::ReportPhysicalMemoryBlock() const {
std::printf("physical block: %d/%d bytes, images=%d buffers=%d other=%d "
"alignment=%d fallback=%d\n", physicalBytesAllocated, totalBlockSize,
imageBytesAllocated, bufferBytesAllocated, otherBytesAllocated,
alignmentWaste, bytesForcedOutsideBlock);
}
void idPhysicalMemoryBlock::ReportUntouchedPhysicalMemory() const {
std::printf("physical block untouched/available: %d bytes\n",
std::max(0, totalBlockSize - physicalBytesAllocated));
}
+431
View File
@@ -0,0 +1,431 @@
#include "idlib/sys/sys_networking.h"
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <string>
#include <vector>
namespace {
std::mutex networkMutex;
bool networkInitialized = false;
std::vector<std::string> localAddresses;
SOCKET ToSocket(const int value) {
return static_cast<SOCKET>(static_cast<unsigned int>(value));
}
int FromSocket(const SOCKET value) {
return static_cast<int>(value);
}
void ClearAddress(netadr_t& address) {
std::memset(&address, 0, sizeof(address));
address.type = NA_BAD;
}
void NetAdrToSockAdr(const netadr_t& source, sockaddr_in& destination) {
std::memset(&destination, 0, sizeof(destination));
destination.sin_family = AF_INET;
if (source.type == NA_BROADCAST) {
destination.sin_addr.s_addr = INADDR_BROADCAST;
} else {
std::memcpy(&destination.sin_addr.s_addr, source.ip, sizeof(source.ip));
}
destination.sin_port = htons(source.port);
}
void SockAdrToNetAdr(const sockaddr_in& source, netadr_t& destination) {
std::memset(&destination, 0, sizeof(destination));
std::memcpy(destination.ip, &source.sin_addr.s_addr, sizeof(destination.ip));
destination.port = ntohs(source.sin_port);
destination.type = ntohl(source.sin_addr.s_addr) == INADDR_LOOPBACK
? NA_LOOPBACK : NA_IP;
}
bool WaitForSocket(const SOCKET socketValue, const int timeoutMS,
const bool write) {
if (socketValue == INVALID_SOCKET) return false;
fd_set set;
FD_ZERO(&set);
FD_SET(socketValue, &set);
timeval timeout = {};
timeout.tv_sec = timeoutMS < 0 ? 0 : timeoutMS / 1000;
timeout.tv_usec = timeoutMS < 0 ? 0 : (timeoutMS % 1000) * 1000;
const int result = select(0, write ? nullptr : &set,
write ? &set : nullptr, nullptr, timeoutMS < 0 ? nullptr : &timeout);
return result > 0 && FD_ISSET(socketValue, &set) != 0;
}
void EnsureNetworking() {
std::lock_guard<std::mutex> guard(networkMutex);
if (networkInitialized) return;
WSADATA data = {};
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) {
return;
}
networkInitialized = true;
localAddresses.clear();
char hostName[256] = {};
if (gethostname(hostName, sizeof(hostName)) == 0) {
addrinfo hints = {};
hints.ai_family = AF_INET;
addrinfo* result = nullptr;
if (getaddrinfo(hostName, nullptr, &hints, &result) == 0) {
for (addrinfo* item = result; item != nullptr; item = item->ai_next) {
const sockaddr_in* address =
reinterpret_cast<const sockaddr_in*>(item->ai_addr);
char text[INET_ADDRSTRLEN] = {};
if (inet_ntop(AF_INET, &address->sin_addr, text, sizeof(text))
!= nullptr
&& std::find(localAddresses.begin(), localAddresses.end(), text)
== localAddresses.end()) {
localAddresses.emplace_back(text);
}
}
freeaddrinfo(result);
}
}
if (localAddresses.empty()) localAddresses.emplace_back("127.0.0.1");
}
} // namespace
bool idSimpleSerializer::Serialize(unsigned char& value) {
if (data == nullptr || pos < 0 || pos + 1 > size) return false;
if (writing) data[pos] = value;
else value = data[pos];
++pos;
return true;
}
bool idSimpleSerializer::Serialize(unsigned int& value) {
if (data == nullptr || pos < 0 || pos + 4 > size) return false;
if (writing) {
data[pos + 0] = static_cast<unsigned char>(value >> 0);
data[pos + 1] = static_cast<unsigned char>(value >> 8);
data[pos + 2] = static_cast<unsigned char>(value >> 16);
data[pos + 3] = static_cast<unsigned char>(value >> 24);
} else {
value = static_cast<unsigned int>(data[pos + 0])
| (static_cast<unsigned int>(data[pos + 1]) << 8)
| (static_cast<unsigned int>(data[pos + 2]) << 16)
| (static_cast<unsigned int>(data[pos + 3]) << 24);
}
pos += 4;
return true;
}
bool idSimpleSerializer::SerializeBytes(char* bytes, unsigned int& numBytes) {
const unsigned int capacity = numBytes;
if (!Serialize(numBytes)) return false;
if (!writing && numBytes > capacity) {
numBytes = 0;
return false;
}
if (numBytes > static_cast<unsigned int>(size - pos)
|| (numBytes > 0 && bytes == nullptr)) {
return false;
}
if (writing) std::memcpy(data + pos, bytes, numBytes);
else std::memcpy(bytes, data + pos, numBytes);
pos += static_cast<int>(numBytes);
return true;
}
bool idSimpleSerializer::SerializeString(char* text, const int maxSize) {
if (text == nullptr || maxSize <= 0) return false;
unsigned int bytes = writing
? static_cast<unsigned int>(std::strlen(text))
: static_cast<unsigned int>(maxSize - 1);
if (!SerializeBytes(text, bytes)) return false;
if (!writing) text[bytes] = '\0';
return true;
}
void Sys_InitNetworking() {
EnsureNetworking();
}
void Sys_ShutdownNetworking() {
std::lock_guard<std::mutex> guard(networkMutex);
if (!networkInitialized) return;
localAddresses.clear();
WSACleanup();
networkInitialized = false;
}
bool Sys_StringToNetAdr(const char* text, netadr_t* address,
const bool doDNSResolve) {
if (text == nullptr || address == nullptr) return false;
EnsureNetworking();
std::string host(text);
unsigned short port = 0;
const std::size_t separator = host.rfind(':');
if (separator != std::string::npos) {
const long parsed = std::strtol(host.c_str() + separator + 1, nullptr, 10);
if (parsed < 0 || parsed > 65535) return false;
port = static_cast<unsigned short>(parsed);
host.resize(separator);
}
if (host == "localhost") host = "127.0.0.1";
sockaddr_in socketAddress = {};
socketAddress.sin_family = AF_INET;
socketAddress.sin_port = htons(port);
if (inet_pton(AF_INET, host.c_str(), &socketAddress.sin_addr) != 1) {
if (!doDNSResolve) return false;
addrinfo hints = {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
addrinfo* result = nullptr;
if (getaddrinfo(host.c_str(), nullptr, &hints, &result) != 0
|| result == nullptr) {
if (result != nullptr) freeaddrinfo(result);
return false;
}
socketAddress.sin_addr =
reinterpret_cast<sockaddr_in*>(result->ai_addr)->sin_addr;
freeaddrinfo(result);
}
SockAdrToNetAdr(socketAddress, *address);
return true;
}
const char* Sys_NetAdrToString(const netadr_t& address) {
thread_local char result[64];
const char* prefix = address.type == NA_LOOPBACK ? "127.0.0.1" : nullptr;
char ipText[INET_ADDRSTRLEN] = {};
in_addr ipAddress = {};
std::memcpy(&ipAddress.s_addr, address.ip, sizeof(address.ip));
if (prefix == nullptr) {
prefix = inet_ntop(AF_INET, &ipAddress, ipText, sizeof(ipText));
}
if (prefix == nullptr) prefix = "0.0.0.0";
if (address.port != 0) {
std::snprintf(result, sizeof(result), "%s:%u", prefix, address.port);
} else {
std::snprintf(result, sizeof(result), "%s", prefix);
}
return result;
}
bool Sys_IsLANAddress(const netadr_t& address) {
if (address.type == NA_LOOPBACK) return true;
if (address.type != NA_IP) return false;
return address.ip[0] == 10
|| (address.ip[0] == 172 && address.ip[1] >= 16 && address.ip[1] <= 31)
|| (address.ip[0] == 192 && address.ip[1] == 168)
|| address.ip[0] == 127;
}
bool Sys_CompareNetAdrBase(const netadr_t& left, const netadr_t& right) {
if (left.type != right.type) return false;
if (left.type == NA_LOOPBACK) return true;
if (left.type != NA_IP && left.type != NA_BROADCAST) return false;
return std::memcmp(left.ip, right.ip, sizeof(left.ip)) == 0;
}
int Sys_GetLocalIPCount() {
EnsureNetworking();
return static_cast<int>(localAddresses.size());
}
const char* Sys_GetLocalIP(const int index) {
EnsureNetworking();
return index >= 0 && index < static_cast<int>(localAddresses.size())
? localAddresses[index].c_str() : nullptr;
}
idUDP::idUDP()
: packetsRead(0), bytesRead(0), packetsWritten(0), bytesWritten(0),
netSocket(0), silent(false) {
ClearAddress(bound_to);
}
idUDP::~idUDP() { Close(); }
bool idUDP::InitForPort(const int portNumber, const bool) {
Close();
EnsureNetworking();
const SOCKET socketValue = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (socketValue == INVALID_SOCKET) return false;
u_long nonBlocking = 1;
ioctlsocket(socketValue, FIONBIO, &nonBlocking);
BOOL broadcast = TRUE;
setsockopt(socketValue, SOL_SOCKET, SO_BROADCAST,
reinterpret_cast<const char*>(&broadcast), sizeof(broadcast));
sockaddr_in bindAddress = {};
bindAddress.sin_family = AF_INET;
bindAddress.sin_addr.s_addr = htonl(INADDR_ANY);
bindAddress.sin_port = htons(static_cast<unsigned short>(portNumber));
if (bind(socketValue, reinterpret_cast<sockaddr*>(&bindAddress),
sizeof(bindAddress)) == SOCKET_ERROR) {
closesocket(socketValue);
return false;
}
int addressLength = sizeof(bindAddress);
getsockname(socketValue, reinterpret_cast<sockaddr*>(&bindAddress), &addressLength);
SockAdrToNetAdr(bindAddress, bound_to);
netSocket = FromSocket(socketValue);
return true;
}
void idUDP::Close() {
if (netSocket != 0) closesocket(ToSocket(netSocket));
netSocket = 0;
ClearAddress(bound_to);
}
bool idUDP::GetPacket(netadr_t& from, void* data, int& dataSize,
const int maxSize) {
if (!IsOpen() || data == nullptr || maxSize <= 0) return false;
sockaddr_in source = {};
int sourceLength = sizeof(source);
const int received = recvfrom(ToSocket(netSocket), static_cast<char*>(data),
maxSize, 0, reinterpret_cast<sockaddr*>(&source), &sourceLength);
if (received == SOCKET_ERROR) return false;
SockAdrToNetAdr(source, from);
dataSize = received;
++packetsRead;
bytesRead += received;
return true;
}
bool idUDP::GetPacketBlocking(netadr_t& from, void* data, int& dataSize,
const int maxSize, const int timeoutMS) {
return WaitForSocket(ToSocket(netSocket), timeoutMS, false)
&& GetPacket(from, data, dataSize, maxSize);
}
void idUDP::SendPacket(const netadr_t to, const void* data, const int dataSize) {
if (!IsOpen() || data == nullptr || dataSize < 0 || to.type == NA_BAD) return;
sockaddr_in destination = {};
NetAdrToSockAdr(to, destination);
const int sent = sendto(ToSocket(netSocket), static_cast<const char*>(data),
dataSize, 0, reinterpret_cast<const sockaddr*>(&destination),
sizeof(destination));
if (sent >= 0) {
++packetsWritten;
bytesWritten += sent;
}
}
idTCP::idTCP() : fd(0) { ClearAddress(address); }
idTCP::~idTCP() { Close(); }
bool idTCP::Connect(const char* host, const unsigned short port,
const bool nonBlocking, const bool, const bool nagle) {
Close();
EnsureNetworking();
if (!Sys_StringToNetAdr(host, &address, true)) return false;
if (address.port == 0) address.port = port;
address.type = address.type == NA_LOOPBACK ? NA_LOOPBACK : NA_IP;
sockaddr_in destination = {};
NetAdrToSockAdr(address, destination);
const SOCKET socketValue = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketValue == INVALID_SOCKET) return false;
BOOL noDelay = nagle ? FALSE : TRUE;
setsockopt(socketValue, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<const char*>(&noDelay), sizeof(noDelay));
if (nonBlocking) {
u_long mode = 1;
ioctlsocket(socketValue, FIONBIO, &mode);
}
const int result = connect(socketValue,
reinterpret_cast<const sockaddr*>(&destination), sizeof(destination));
if (result == SOCKET_ERROR) {
const int error = WSAGetLastError();
if (!nonBlocking || (error != WSAEWOULDBLOCK
&& error != WSAEINPROGRESS && error != WSAEALREADY)) {
closesocket(socketValue);
return false;
}
}
fd = FromSocket(socketValue);
return true;
}
bool idTCP::Select(const int timeoutMS) {
return WaitForSocket(ToSocket(fd), timeoutMS, false);
}
bool idTCP::IsOpen() const { return fd != 0; }
void idTCP::Close() {
if (fd != 0) closesocket(ToSocket(fd));
fd = 0;
}
int idTCP::Read(void* data, const int dataSize) {
if (!IsOpen() || data == nullptr || dataSize < 0) return -1;
const int result = recv(ToSocket(fd), static_cast<char*>(data), dataSize, 0);
if (result > 0) return result;
if (result == 0) {
Close();
return -2;
}
if (WSAGetLastError() == WSAEWOULDBLOCK) return 0;
Close();
return -1;
}
int idTCP::ReadBlocking(void* data, const int dataSize, const int timeoutMS) {
int total = 0;
while (total < dataSize && WaitForSocket(ToSocket(fd), timeoutMS, false)) {
const int received = Read(static_cast<char*>(data) + total, dataSize - total);
if (received < 0) return received;
total += received;
}
return total;
}
int idTCP::Write(const void* data, const int dataSize) {
if (!IsOpen() || data == nullptr || dataSize < 0) return -1;
const int result = send(ToSocket(fd), static_cast<const char*>(data), dataSize, 0);
if (result >= 0) return result;
if (WSAGetLastError() == WSAEWOULDBLOCK) return 0;
Close();
return -1;
}
int idTCP::WriteBlocking(const void* data, const int dataSize, const int timeoutMS) {
int total = 0;
while (total < dataSize && WaitForSocket(ToSocket(fd), timeoutMS, true)) {
const int sent = Write(static_cast<const char*>(data) + total, dataSize - total);
if (sent < 0) return sent;
total += sent;
}
return total;
}
bool idTCP::WriteDataBlock(const char* buffer, const int dataSize,
const int timeoutMS) {
unsigned char lengthData[4] = {};
unsigned int length = static_cast<unsigned int>(dataSize);
idSimpleSerializer serializer(lengthData, sizeof(lengthData), true);
return serializer.Serialize(length)
&& WriteBlocking(lengthData, sizeof(lengthData), timeoutMS)
== sizeof(lengthData)
&& WriteBlocking(buffer, dataSize, timeoutMS) == dataSize;
}
int idTCP::ReadDataBlock(char* buffer, const int bufferSize,
const int timeoutMS) {
unsigned char lengthData[4] = {};
if (ReadBlocking(lengthData, sizeof(lengthData), timeoutMS)
!= sizeof(lengthData)) return -1;
unsigned int length = 0;
idSimpleSerializer serializer(lengthData, sizeof(lengthData), false);
if (!serializer.Serialize(length) || length > static_cast<unsigned int>(bufferSize)) {
return -1;
}
return ReadBlocking(buffer, static_cast<int>(length), timeoutMS)
== static_cast<int>(length) ? static_cast<int>(length) : -1;
}
@@ -0,0 +1,25 @@
#include "idlib/sys/sys_utils.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
const char* Sys_GetOSUserName() {
static char userName[64] = {};
DWORD length = static_cast<DWORD>(sizeof(userName));
if (!GetUserNameA(userName, &length)) {
userName[0] = '\0';
}
return userName;
}
const char* Sys_GetMachineName() {
static char machineName[64] = {};
DWORD length = static_cast<DWORD>(sizeof(machineName));
if (!GetComputerNameA(machineName, &length)) {
machineName[0] = '\0';
}
return machineName;
}

Some files were not shown because too many files have changed in this diff Show More