feat: UGC server that makes and serves player models' meshes and icons

A new server (dUgcServer, started by master with enable_ugc_server=1) that
takes unprocessed ugc and ugc_modular_build rows from the database and makes
what the client downloads with UGCUSE3DSERVICES: an optimized NIF (hidden
faces removed, ambient occlusion baked into vertex colors) and a 128px DDS
icon for player models, rendered by a software rasterizer from the client's
LDD brick primitives, and icons for cars and rockets assembled from their
modules per ModularBuildComponent/ModuleComponent. It serves them, with the
models' LXFML, over HTTP in the client's UGCC<dc>/3DOPTIMIZED and
IMAGE128DDS layout with .gz and .checksum files, and keeps its folder under
a size cap.

Processing state lives in ugc.is_optimized plus new processed_at,
process_attempts and process_error columns (and the same on
ugc_modular_build). ServiceType::UGC is appended. NifFile moves to dCommon
and records named node transforms for the modules' attach points.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 03:52:22 -05:00
parent 90ded73596
commit abf1cc1d80
52 changed files with 3869 additions and 14 deletions

View File

@@ -82,6 +82,7 @@ std::map<uint32_t, std::string> activeSessions;
SystemAddress authServerMasterPeerSysAddr;
SystemAddress chatServerMasterPeerSysAddr;
SystemAddress dashboardServerMasterPeerSysAddr;
SystemAddress ugcServerMasterPeerSysAddr;
namespace {
// Dashboard player actions waiting for world servers to answer
@@ -444,6 +445,11 @@ int main(int argc, char** argv) {
StartDashboardServer();
}
// The UGC server makes and serves player models' meshes and icons (docs/UgcServer.md)
if (Game::config->GetValue("enable_ugc_server") == "1") {
StartUgcServer();
}
auto t = std::chrono::high_resolution_clock::now();
Packet* packet = nullptr;
constexpr uint32_t logFlushTime = 15 * masterFramerate;
@@ -624,6 +630,9 @@ namespace {
case ServiceType::DASHBOARD:
dashboardServerMasterPeerSysAddr = sysAddr;
break;
case ServiceType::UGC:
ugcServerMasterPeerSysAddr = sysAddr;
break;
default:
break;
}
@@ -1025,6 +1034,19 @@ void HandlePacket(Packet* packet) {
dashboardServerMasterPeerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
StartDashboardServer();
}
if (packet->systemAddress == ugcServerMasterPeerSysAddr) {
ugcServerMasterPeerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
if (dashboardServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) {
MasterPackets::ServerInfo offline;
offline.serverType = ServiceType::UGC;
offline.ip = LUString("offline");
MasterPackets::SendTo(dashboardServerMasterPeerSysAddr, offline);
}
StartUgcServer();
}
}
if (packet->length < 4) return;

View File

@@ -143,6 +143,42 @@ uint32_t StartDashboardServer() {
return web_pid;
}
uint32_t StartUgcServer() {
if (Game::ShouldShutdown()) {
LOG("Currently shutting down. UgcServer will not be restarted.");
return 0;
}
auto ugc_path = BinaryPathFinder::GetBinaryDir() / "UgcServer";
#ifdef _WIN32
ugc_path.replace_extension(".exe");
auto ugc_startup = startup;
auto ugc_info = PROCESS_INFORMATION{};
if (!CreateProcessW(ugc_path.wstring().data(), ugc_path.wstring().data(),
nullptr, nullptr, false, 0, nullptr, nullptr,
&ugc_startup, &ugc_info))
{
LOG("Failed to launch UgcServer");
return 0;
}
// get pid and close unused handles
auto ugc_pid = ugc_info.dwProcessId;
CloseHandle(ugc_info.hProcess);
CloseHandle(ugc_info.hThread);
#else // *nix systems
const auto ugc_pid = fork();
if (ugc_pid < 0) {
LOG("Failed to launch UgcServer");
return 0;
} else if (ugc_pid == 0) {
// We are the child process
execl(ugc_path.string().c_str(), ugc_path.string().c_str(), nullptr);
}
#endif
LOG("UgcServer PID is %d", ugc_pid);
return ugc_pid;
}
uint32_t StartWorldServer(LWOMAPID mapID, uint16_t port, LWOINSTANCEID lastInstanceID, int maxPlayers, LWOCLONEID cloneID) {
auto world_path = BinaryPathFinder::GetBinaryDir() / "WorldServer";
#ifdef _WIN32

View File

@@ -4,4 +4,5 @@
uint32_t StartAuthServer();
uint32_t StartChatServer();
uint32_t StartDashboardServer();
uint32_t StartUgcServer();
uint32_t StartWorldServer(LWOMAPID mapID, uint16_t port, LWOINSTANCEID lastInstanceID, int maxPlayers, LWOCLONEID cloneID);