Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
/*! \file engine_com.cpp
|
2026-03-09 12:34:51 -06:00
|
|
|
|
* \brief Engine-side COM class implementations and COM front-door.
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
*
|
|
|
|
|
|
* These classes implement the server-provided COM interfaces that
|
2026-03-09 12:34:51 -06:00
|
|
|
|
* external modules use to interact with the game engine.
|
|
|
|
|
|
*
|
|
|
|
|
|
* engine.so is a proper COM server. Only 4 extern "C" functions are
|
|
|
|
|
|
* exported: mux_Register, mux_Unregister, mux_GetClassObject, and
|
2026-03-09 21:14:04 -06:00
|
|
|
|
* mux_CanUnloadNow. All other symbols have hidden visibility.
|
|
|
|
|
|
* g_debug_cmd lives in libmux.so (shared crash breadcrumb).
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "copyright.h"
|
|
|
|
|
|
#include "autoconf.h"
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
#include "externs.h"
|
|
|
|
|
|
#include "sqlite_backend.h"
|
2026-03-09 19:40:40 -06:00
|
|
|
|
#include "mguests.h"
|
2026-03-26 16:35:21 -06:00
|
|
|
|
#include "engine_api.h"
|
Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
|
|
|
|
#include "routing.h"
|
2026-03-29 08:54:51 -06:00
|
|
|
|
#include "walk.h"
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
2026-03-09 19:40:40 -06:00
|
|
|
|
// g_debug_cmd moved to libmux.cpp — shared by all layers.
|
2026-03-09 15:12:06 -06:00
|
|
|
|
|
2026-03-09 12:34:51 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Factory class declarations for engine-side COM components.
|
|
|
|
|
|
// These are internal to engine.so (no DCL_EXPORT).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
#define DEFINE_ENGINE_FACTORY(x) \
|
|
|
|
|
|
class x : public mux_IClassFactory \
|
|
|
|
|
|
{ \
|
|
|
|
|
|
public: \
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv); \
|
|
|
|
|
|
virtual uint32_t AddRef(void); \
|
|
|
|
|
|
virtual uint32_t Release(void); \
|
|
|
|
|
|
virtual MUX_RESULT CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv); \
|
|
|
|
|
|
virtual MUX_RESULT LockServer(bool bLock); \
|
|
|
|
|
|
x(void); \
|
|
|
|
|
|
virtual ~x(); \
|
|
|
|
|
|
private: \
|
|
|
|
|
|
uint32_t m_cRef; \
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Factory classes defined in log.cpp (also in engine.so).
|
|
|
|
|
|
// Full declarations needed here for the COM front-door dispatch.
|
|
|
|
|
|
//
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CLogFactory)
|
|
|
|
|
|
|
|
|
|
|
|
class CLogPSFactory : public mux_IPSFactoryBuffer, public mux_IClassFactory
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
virtual MUX_RESULT CreateProxy(mux_IUnknown *pUnknownOuter, MUX_IID riid, mux_IRpcProxyBuffer **ppProxy, void **ppv);
|
|
|
|
|
|
virtual MUX_RESULT CreateStub(MUX_IID riid, mux_IUnknown *pUnknownOuter, mux_IRpcStubBuffer **ppStub);
|
|
|
|
|
|
virtual MUX_RESULT CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual MUX_RESULT LockServer(bool bLock);
|
|
|
|
|
|
CLogPSFactory(void);
|
|
|
|
|
|
virtual ~CLogPSFactory();
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CServerEventsSourceFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CQueryClientFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CFunctionsFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CNotifyFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CObjectInfoFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CAttributeAccessFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CEvaluatorFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CPermissionsFactory)
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
DEFINE_ENGINE_FACTORY(CGameConfigFactory)
|
2026-03-09 12:34:51 -06:00
|
|
|
|
DEFINE_ENGINE_FACTORY(CMailDeliveryFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CHelpSystemFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CGameEngineFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CPlayerSessionFactory)
|
2026-03-10 10:39:15 -06:00
|
|
|
|
DEFINE_ENGINE_FACTORY(CComsysStorageFactory)
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CMailStorageFactory)
|
2026-03-09 12:34:51 -06:00
|
|
|
|
|
2026-03-18 13:13:40 -06:00
|
|
|
|
#if defined(TINYMUX_JIT)
|
2026-03-18 09:59:36 -06:00
|
|
|
|
// JIT Compile factory — implementation delegates to jit_lua.cpp.
|
|
|
|
|
|
extern MUX_RESULT jit_compile_create_instance(MUX_IID iid, void **ppv);
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CJITCompileFactory)
|
2026-03-18 13:13:40 -06:00
|
|
|
|
#endif
|
2026-03-18 09:59:36 -06:00
|
|
|
|
|
2026-03-18 15:11:05 -06:00
|
|
|
|
// Lua module factory — implementation delegates to lua_mod.cpp.
|
|
|
|
|
|
extern MUX_RESULT lua_mod_create_instance(MUX_IID iid, void **ppv);
|
|
|
|
|
|
DEFINE_ENGINE_FACTORY(CLuaModFactory)
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
// CServerEventsSource component which is not directly accessible.
|
|
|
|
|
|
//
|
|
|
|
|
|
class CServerEventsSource : public mux_IServerEventsControl
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
// mux_IUnknown
|
|
|
|
|
|
//
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
// mux_IServerEventsControl
|
|
|
|
|
|
//
|
|
|
|
|
|
virtual MUX_RESULT Advise(mux_IServerEventsSink *pIServerEvents);
|
|
|
|
|
|
|
|
|
|
|
|
CServerEventsSource(void);
|
|
|
|
|
|
virtual ~CServerEventsSource();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
mux_IServerEventsSink *m_pSink;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CServerEventsSource::CServerEventsSource(void) : m_cRef(1), m_pSink(nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 21:14:04 -06:00
|
|
|
|
ServerEventsSinkNode *g_pServerEventsSinkListHead = nullptr;
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
|
|
|
|
|
CServerEventsSource::~CServerEventsSource()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pSink)
|
|
|
|
|
|
{
|
|
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
|
|
|
|
ServerEventsSinkNode *q = nullptr;
|
|
|
|
|
|
while (nullptr != p)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (p->pSink == m_pSink)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Unlink node p from list.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr == q)
|
|
|
|
|
|
{
|
|
|
|
|
|
g_pServerEventsSinkListHead = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
q->pNext = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
p->pNext = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
// Free sink and node.
|
|
|
|
|
|
//
|
|
|
|
|
|
p->pSink->Release();
|
|
|
|
|
|
p->pSink = nullptr;
|
|
|
|
|
|
delete p;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
q = p;
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_pSink->Release();
|
|
|
|
|
|
m_pSink = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CServerEventsSource::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IServerEventsControl *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IServerEventsControl == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IServerEventsControl *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CServerEventsSource::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CServerEventsSource::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CServerEventsSource::Advise(mux_IServerEventsSink *pIServerEventsSink)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pIServerEventsSink)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If this pointer is already in the list, we will prevent it from being
|
|
|
|
|
|
// added again.
|
|
|
|
|
|
//
|
|
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
|
|
|
|
while (nullptr != p)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (p->pSink == pIServerEventsSink)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Allocate a list node.
|
|
|
|
|
|
//
|
|
|
|
|
|
p = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
p = new ServerEventsSinkNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == p)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Add the pointer to the list.
|
|
|
|
|
|
//
|
|
|
|
|
|
p->pNext = g_pServerEventsSinkListHead;
|
|
|
|
|
|
pIServerEventsSink->AddRef();
|
|
|
|
|
|
p->pSink = pIServerEventsSink;
|
|
|
|
|
|
pIServerEventsSink->AddRef();
|
|
|
|
|
|
m_pSink = pIServerEventsSink;
|
|
|
|
|
|
g_pServerEventsSinkListHead = p;
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Factory for CServerEventsSource component which is not directly accessible.
|
|
|
|
|
|
//
|
|
|
|
|
|
CServerEventsSourceFactory::CServerEventsSourceFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CServerEventsSourceFactory::~CServerEventsSourceFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CServerEventsSourceFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CServerEventsSourceFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CServerEventsSourceFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CServerEventsSourceFactory::CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Disallow attempts to aggregate this component.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != pUnknownOuter)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOAGGREGATION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CServerEventsSource *pServerEventsSource = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pServerEventsSource = new CServerEventsSource;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pServerEventsSource)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pServerEventsSource->QueryInterface(iid, ppv);
|
|
|
|
|
|
pServerEventsSource->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CServerEventsSourceFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CQueryClient component which is not directly accessible.
|
|
|
|
|
|
//
|
|
|
|
|
|
class CQueryClient : public mux_IQuerySink
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
// mux_IUnknown
|
|
|
|
|
|
//
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
// mux_IQuerySink
|
|
|
|
|
|
//
|
|
|
|
|
|
virtual MUX_RESULT Result(uint32_t iQueryHandle, uint32_t iError, QUEUE_INFO *pqiResultsSet);
|
|
|
|
|
|
|
|
|
|
|
|
CQueryClient(void);
|
|
|
|
|
|
virtual ~CQueryClient();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CQueryClient::CQueryClient(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CQueryClient::~CQueryClient()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CQueryClient::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IQuerySink *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IQuerySink == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IQuerySink *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CQueryClient::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CQueryClient::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CQueryClient::Result(uint32_t hQuery, uint32_t iError, QUEUE_INFO *pqiResultsSet)
|
|
|
|
|
|
{
|
|
|
|
|
|
#if defined(STUB_SLAVE)
|
|
|
|
|
|
CResultsSet *prs = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
prs = new CResultsSet(pqiResultsSet);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
query_complete(hQuery, iError, prs);
|
|
|
|
|
|
prs->Release();
|
|
|
|
|
|
#else
|
|
|
|
|
|
UNUSED_PARAMETER(hQuery);
|
|
|
|
|
|
UNUSED_PARAMETER(iError);
|
|
|
|
|
|
UNUSED_PARAMETER(pqiResultsSet);
|
|
|
|
|
|
#endif // STUB_SLAVE
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Factory for CQueryClient component which is not directly accessible.
|
|
|
|
|
|
//
|
|
|
|
|
|
CQueryClientFactory::CQueryClientFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CQueryClientFactory::~CQueryClientFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CQueryClientFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CQueryClientFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CQueryClientFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CQueryClientFactory::CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Disallow attempts to aggregate this component.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != pUnknownOuter)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOAGGREGATION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CQueryClient *pLog = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pLog = new CQueryClient;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pLog)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pLog->QueryInterface(iid, ppv);
|
|
|
|
|
|
pLog->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CQueryClientFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CResultsSet::CResultsSet(QUEUE_INFO *pqi) : m_cRef(1), m_nFields(0),
|
|
|
|
|
|
m_nBlob(0), m_bLoaded(false), m_iError(QS_SUCCESS), m_nRows(0)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pBlob = nullptr;
|
|
|
|
|
|
m_pRows = nullptr;
|
|
|
|
|
|
size_t nWanted = sizeof(m_nFields);
|
|
|
|
|
|
if ( Pipe_GetBytes(pqi, &nWanted, &m_nFields)
|
|
|
|
|
|
&& nWanted == sizeof(m_nFields))
|
|
|
|
|
|
{
|
|
|
|
|
|
size_t nRows;
|
|
|
|
|
|
m_nBlob = Pipe_QueueLength(pqi);
|
|
|
|
|
|
if (sizeof(nRows) < m_nBlob)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bError = false;
|
|
|
|
|
|
m_nBlob -= sizeof(nRows);
|
|
|
|
|
|
if (0 < m_nBlob)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pBlob = new UTF8[m_nBlob];
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nWanted = m_nBlob;
|
|
|
|
|
|
if ( nullptr == m_pBlob
|
|
|
|
|
|
|| !Pipe_GetBytes(pqi, &nWanted, m_pBlob)
|
|
|
|
|
|
|| nWanted != m_nBlob)
|
|
|
|
|
|
{
|
|
|
|
|
|
bError = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!bError)
|
|
|
|
|
|
{
|
|
|
|
|
|
nWanted = sizeof(nRows);
|
|
|
|
|
|
if ( Pipe_GetBytes(pqi, &nWanted, &nRows)
|
|
|
|
|
|
&& nWanted == sizeof(nRows))
|
|
|
|
|
|
{
|
|
|
|
|
|
m_nRows = static_cast<int>(nRows);
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pRows = new PUTF8[m_nRows];
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pRows)
|
|
|
|
|
|
{
|
2026-07-31 09:12:46 -06:00
|
|
|
|
// #1878: never trust field lengths without an end
|
|
|
|
|
|
// bound. A truncated or hostile blob used to
|
|
|
|
|
|
// memcpy/advance past m_pBlob + m_nBlob.
|
|
|
|
|
|
//
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
int i, j;
|
|
|
|
|
|
UTF8 *p = m_pBlob;
|
2026-07-31 09:12:46 -06:00
|
|
|
|
UTF8 *const pEnd = m_pBlob + m_nBlob;
|
|
|
|
|
|
bool bRowError = false;
|
|
|
|
|
|
for (i = 0; i < m_nRows && !bRowError; i++)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
|
|
|
|
|
m_pRows[i] = p;
|
2026-07-31 09:12:46 -06:00
|
|
|
|
for (j = 0; j < m_nFields; j++)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-07-31 09:12:46 -06:00
|
|
|
|
if (static_cast<size_t>(pEnd - p) < sizeof(size_t))
|
|
|
|
|
|
{
|
|
|
|
|
|
bRowError = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
size_t n;
|
|
|
|
|
|
memcpy(&n, p, sizeof(size_t));
|
2026-07-31 09:12:46 -06:00
|
|
|
|
p += sizeof(size_t);
|
|
|
|
|
|
if (static_cast<size_t>(pEnd - p) < n)
|
|
|
|
|
|
{
|
|
|
|
|
|
bRowError = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
p += n;
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-31 09:12:46 -06:00
|
|
|
|
if (!bRowError && p == pEnd && i == m_nRows)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
|
|
|
|
|
m_bLoaded = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CResultsSet::isLoaded(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_bLoaded;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CResultsSet::SetError(uint32_t iError)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_iError = iError;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CResultsSet::GetError(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_iError;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int CResultsSet::GetRowCount(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_nRows;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UTF8 *CResultsSet::FirstField(int iRow)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( 0 <= iRow
|
|
|
|
|
|
&& iRow < m_nRows
|
|
|
|
|
|
&& nullptr != m_pRows
|
|
|
|
|
|
&& 0 < m_nFields)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pCurrentField = m_pRows[iRow];
|
|
|
|
|
|
m_iCurrentField = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pCurrentField = nullptr;
|
|
|
|
|
|
m_iCurrentField = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_pCurrentField;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UTF8 *CResultsSet::NextField(void)
|
|
|
|
|
|
{
|
2026-07-31 09:12:46 -06:00
|
|
|
|
// #1878: validate the current field header and payload fit in the blob
|
|
|
|
|
|
// before advancing; return null on a truncated or oversize length.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( nullptr == m_pCurrentField
|
|
|
|
|
|
|| nullptr == m_pBlob
|
|
|
|
|
|
|| 0 >= m_nFields
|
|
|
|
|
|
|| m_iCurrentField >= m_nFields)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-07-31 09:12:46 -06:00
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
2026-07-31 09:12:46 -06:00
|
|
|
|
UTF8 *const pEnd = m_pBlob + m_nBlob;
|
|
|
|
|
|
if (m_pCurrentField < m_pBlob
|
|
|
|
|
|
|| m_pCurrentField >= pEnd
|
|
|
|
|
|
|| static_cast<size_t>(pEnd - m_pCurrentField) < sizeof(size_t))
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pCurrentField = nullptr;
|
|
|
|
|
|
return nullptr;
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
2026-07-31 09:12:46 -06:00
|
|
|
|
|
|
|
|
|
|
size_t n;
|
|
|
|
|
|
memcpy(&n, m_pCurrentField, sizeof(size_t));
|
|
|
|
|
|
const UTF8 *pNext = m_pCurrentField + sizeof(size_t);
|
|
|
|
|
|
if (static_cast<size_t>(pEnd - pNext) < n)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pCurrentField = nullptr;
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
pNext += n;
|
|
|
|
|
|
if (pNext > pEnd)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pCurrentField = nullptr;
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_iCurrentField++;
|
|
|
|
|
|
m_pCurrentField = pNext;
|
|
|
|
|
|
return m_pCurrentField;
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CResultsSet::~CResultsSet(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pBlob)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete [] m_pBlob;
|
|
|
|
|
|
m_pBlob = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pRows)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete [] m_pRows;
|
|
|
|
|
|
m_pRows = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CResultsSet::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CResultsSet::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CNotify — mux_INotify implementation (in-process only, no marshaling).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CNotify : public mux_INotify
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT Notify(dbref target, const UTF8 *msg);
|
|
|
|
|
|
virtual MUX_RESULT RawNotify(dbref target, const UTF8 *msg);
|
|
|
|
|
|
virtual MUX_RESULT NotifyCheck(dbref target, dbref sender,
|
|
|
|
|
|
const UTF8 *msg, int key);
|
|
|
|
|
|
|
|
|
|
|
|
CNotify(void);
|
|
|
|
|
|
virtual ~CNotify();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CNotify::CNotify(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CNotify::~CNotify()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CNotify::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_INotify *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_INotify == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_INotify *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CNotify::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CNotify::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CNotify::Notify(dbref target, const UTF8 *msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(target))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
notify(target, msg);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CNotify::RawNotify(dbref target, const UTF8 *msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(target))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
raw_notify(target, msg);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CNotify::NotifyCheck(dbref target, dbref sender,
|
|
|
|
|
|
const UTF8 *msg, int key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(target))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
notify_check(target, sender, msg, key);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CNotifyFactory::CNotifyFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CNotifyFactory::~CNotifyFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CNotifyFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CNotifyFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CNotifyFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CNotifyFactory::CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != pUnknownOuter)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOAGGREGATION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CNotify *pNotify = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pNotify = new CNotify;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pNotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pNotify->QueryInterface(iid, ppv);
|
|
|
|
|
|
pNotify->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CNotifyFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CObjectInfo — mux_IObjectInfo implementation (in-process only).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CObjectInfo : public mux_IObjectInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT IsValid(dbref obj, bool *pValid);
|
|
|
|
|
|
virtual MUX_RESULT GetName(dbref obj, const UTF8 **ppName);
|
|
|
|
|
|
virtual MUX_RESULT GetOwner(dbref obj, dbref *pOwner);
|
|
|
|
|
|
virtual MUX_RESULT GetLocation(dbref obj, dbref *pLocation);
|
|
|
|
|
|
virtual MUX_RESULT GetType(dbref obj, int *pType);
|
|
|
|
|
|
virtual MUX_RESULT IsConnected(dbref obj, bool *pConnected);
|
|
|
|
|
|
virtual MUX_RESULT IsPlayer(dbref obj, bool *pPlayer);
|
|
|
|
|
|
virtual MUX_RESULT IsGoing(dbref obj, bool *pGoing);
|
|
|
|
|
|
virtual MUX_RESULT GetMoniker(dbref obj, const UTF8 **ppMoniker);
|
|
|
|
|
|
virtual MUX_RESULT MatchThing(dbref executor, const UTF8 *pName,
|
|
|
|
|
|
dbref *pResult);
|
|
|
|
|
|
|
Expand mux_IObjectInfo COM interface; eliminate all db[] from driver
Add 11 new methods to mux_IObjectInfo (GetFlags, SetFlags, GetPowers,
GetPennies, GetPureName, DecodeFlags, IsWizard, IsWizRoy, CanIdle,
WizardWho, SeeHidden) and implement in CObjectInfo.
Create driver_bridge.h/.cpp with 13 drv_* bridge functions that
delegate through COM into the engine. Driver acquires g_pIObjectInfo
after LoadGame.
Replace all db[] macro usage (Flags, Flags2, Flags3, Powers, Owner,
Location, Pennies, PureName, Name, Moniker, Hidden, Hideout, Suspect,
SiteMon, KeepAlive, Ansi, Html, NoBleed, Color256, decode_flags,
s_Flags, Good_obj, isPlayer, Wizard, WizRoy, Can_Idle, Wizard_Who,
See_Hidden) in net.cpp, bsd.cpp, ganl_adapter.cpp, sitemon.cpp with
drv_* bridge calls.
Linker errors: 149 → 97 (db references: 34 → 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:39:14 -06:00
|
|
|
|
virtual MUX_RESULT GetFlags(dbref obj, int word, unsigned int *pFlags);
|
|
|
|
|
|
virtual MUX_RESULT SetFlags(dbref obj, int word, unsigned int flags);
|
|
|
|
|
|
virtual MUX_RESULT GetPowers(dbref obj, unsigned int *pPowers);
|
|
|
|
|
|
virtual MUX_RESULT GetPennies(dbref obj, int *pPennies);
|
2026-07-25 13:43:00 -06:00
|
|
|
|
virtual MUX_RESULT PayFor(dbref who, int cost, bool *pPaid);
|
|
|
|
|
|
virtual MUX_RESULT GiveTo(dbref who, int amount);
|
Expand mux_IObjectInfo COM interface; eliminate all db[] from driver
Add 11 new methods to mux_IObjectInfo (GetFlags, SetFlags, GetPowers,
GetPennies, GetPureName, DecodeFlags, IsWizard, IsWizRoy, CanIdle,
WizardWho, SeeHidden) and implement in CObjectInfo.
Create driver_bridge.h/.cpp with 13 drv_* bridge functions that
delegate through COM into the engine. Driver acquires g_pIObjectInfo
after LoadGame.
Replace all db[] macro usage (Flags, Flags2, Flags3, Powers, Owner,
Location, Pennies, PureName, Name, Moniker, Hidden, Hideout, Suspect,
SiteMon, KeepAlive, Ansi, Html, NoBleed, Color256, decode_flags,
s_Flags, Good_obj, isPlayer, Wizard, WizRoy, Can_Idle, Wizard_Who,
See_Hidden) in net.cpp, bsd.cpp, ganl_adapter.cpp, sitemon.cpp with
drv_* bridge calls.
Linker errors: 149 → 97 (db references: 34 → 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:39:14 -06:00
|
|
|
|
virtual MUX_RESULT GetPureName(dbref obj, const UTF8 **ppName);
|
|
|
|
|
|
virtual MUX_RESULT DecodeFlags(dbref player, dbref obj, UTF8 **ppStr);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
virtual MUX_RESULT UnparseObject(dbref player, dbref target,
|
|
|
|
|
|
bool bObeyMyopic, UTF8 *pBuf, size_t nBufMax);
|
Expand mux_IObjectInfo COM interface; eliminate all db[] from driver
Add 11 new methods to mux_IObjectInfo (GetFlags, SetFlags, GetPowers,
GetPennies, GetPureName, DecodeFlags, IsWizard, IsWizRoy, CanIdle,
WizardWho, SeeHidden) and implement in CObjectInfo.
Create driver_bridge.h/.cpp with 13 drv_* bridge functions that
delegate through COM into the engine. Driver acquires g_pIObjectInfo
after LoadGame.
Replace all db[] macro usage (Flags, Flags2, Flags3, Powers, Owner,
Location, Pennies, PureName, Name, Moniker, Hidden, Hideout, Suspect,
SiteMon, KeepAlive, Ansi, Html, NoBleed, Color256, decode_flags,
s_Flags, Good_obj, isPlayer, Wizard, WizRoy, Can_Idle, Wizard_Who,
See_Hidden) in net.cpp, bsd.cpp, ganl_adapter.cpp, sitemon.cpp with
drv_* bridge calls.
Linker errors: 149 → 97 (db references: 34 → 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:39:14 -06:00
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT IsWizard(dbref obj, bool *pResult);
|
|
|
|
|
|
virtual MUX_RESULT IsWizRoy(dbref obj, bool *pResult);
|
|
|
|
|
|
virtual MUX_RESULT CanIdle(dbref obj, bool *pResult);
|
|
|
|
|
|
virtual MUX_RESULT WizardWho(dbref obj, bool *pResult);
|
|
|
|
|
|
virtual MUX_RESULT SeeHidden(dbref obj, bool *pResult);
|
|
|
|
|
|
|
2026-03-09 17:50:20 -06:00
|
|
|
|
virtual MUX_RESULT AtrAddRaw(dbref obj, int attrnum, const UTF8 *value);
|
|
|
|
|
|
virtual MUX_RESULT AtrClr(dbref obj, int attrnum);
|
|
|
|
|
|
virtual MUX_RESULT AtrGet(dbref obj, int attrnum, UTF8 *pValue,
|
|
|
|
|
|
size_t nValueMax, dbref *pOwner, int *pFlags);
|
|
|
|
|
|
virtual MUX_RESULT AtrPGet(dbref obj, int attrnum, UTF8 *pValue,
|
|
|
|
|
|
size_t nValueMax, dbref *pOwner, int *pFlags);
|
|
|
|
|
|
virtual MUX_RESULT LookupPlayer(dbref executor, const UTF8 *pName,
|
|
|
|
|
|
bool bConnected, dbref *pResult);
|
|
|
|
|
|
virtual MUX_RESULT FetchConnectionInfoFields(dbref player,
|
2026-07-28 19:35:57 -06:00
|
|
|
|
int64_t anFields[4]);
|
2026-03-09 17:50:20 -06:00
|
|
|
|
virtual MUX_RESULT PutConnectionInfoFields(dbref player,
|
2026-07-28 19:35:57 -06:00
|
|
|
|
int64_t anFields[4], CLinearTimeAbsolute <aNow);
|
2026-03-09 17:50:20 -06:00
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
CObjectInfo(void);
|
|
|
|
|
|
virtual ~CObjectInfo();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CObjectInfo::CObjectInfo(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CObjectInfo::~CObjectInfo()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IObjectInfo *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IObjectInfo == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IObjectInfo *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CObjectInfo::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CObjectInfo::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::IsValid(dbref obj, bool *pValid)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
if (nullptr == pValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
*pValid = Good_obj(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::GetName(dbref obj, const UTF8 **ppName)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
if (nullptr == ppName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppName = nullptr;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*ppName = Name(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::GetOwner(dbref obj, dbref *pOwner)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
if (nullptr == pOwner)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pOwner = NOTHING;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pOwner = Owner(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::GetLocation(dbref obj, dbref *pLocation)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
if (nullptr == pLocation)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pLocation = NOTHING;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pLocation = Location(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::GetType(dbref obj, int *pType)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
if (nullptr == pType)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pType = TYPE_GARBAGE;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pType = Typeof(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::IsConnected(dbref obj, bool *pConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pConnected = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pConnected = Connected(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::IsPlayer(dbref obj, bool *pPlayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pPlayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pPlayer = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pPlayer = isPlayer(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::IsGoing(dbref obj, bool *pGoing)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pGoing)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pGoing = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pGoing = Going(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::GetMoniker(dbref obj, const UTF8 **ppMoniker)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == ppMoniker)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppMoniker = nullptr;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*ppMoniker = Moniker(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::MatchThing(dbref executor, const UTF8 *pName,
|
|
|
|
|
|
dbref *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = NOTHING;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = match_thing(executor, pName);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Expand mux_IObjectInfo COM interface; eliminate all db[] from driver
Add 11 new methods to mux_IObjectInfo (GetFlags, SetFlags, GetPowers,
GetPennies, GetPureName, DecodeFlags, IsWizard, IsWizRoy, CanIdle,
WizardWho, SeeHidden) and implement in CObjectInfo.
Create driver_bridge.h/.cpp with 13 drv_* bridge functions that
delegate through COM into the engine. Driver acquires g_pIObjectInfo
after LoadGame.
Replace all db[] macro usage (Flags, Flags2, Flags3, Powers, Owner,
Location, Pennies, PureName, Name, Moniker, Hidden, Hideout, Suspect,
SiteMon, KeepAlive, Ansi, Html, NoBleed, Color256, decode_flags,
s_Flags, Good_obj, isPlayer, Wizard, WizRoy, Can_Idle, Wizard_Who,
See_Hidden) in net.cpp, bsd.cpp, ganl_adapter.cpp, sitemon.cpp with
drv_* bridge calls.
Linker errors: 149 → 97 (db references: 34 → 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:39:14 -06:00
|
|
|
|
MUX_RESULT CObjectInfo::GetFlags(dbref obj, int word, unsigned int *pFlags)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pFlags)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj) || word < FLAG_WORD1 || word > FLAG_WORD3)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pFlags = 0;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pFlags = db[obj].fs.word[word];
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::SetFlags(dbref obj, int word, unsigned int flags)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(obj) || word < FLAG_WORD1 || word > FLAG_WORD3)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
s_Flags(obj, word, flags);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::GetPowers(dbref obj, unsigned int *pPowers)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pPowers)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pPowers = 0;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pPowers = Powers(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::GetPennies(dbref obj, int *pPennies)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pPennies)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pPennies = 0;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pPennies = Pennies(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
MUX_RESULT CObjectInfo::PayFor(dbref who, int cost, bool *pPaid)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pPaid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pPaid = false;
|
|
|
|
|
|
if (!Good_obj(who))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
// payfor() is wizard/free-money aware and debits Owner(who).
|
|
|
|
|
|
//
|
|
|
|
|
|
*pPaid = payfor(who, cost);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::GiveTo(dbref who, int amount)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(who))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
// giveto() no-ops for wizards/free-money holders.
|
|
|
|
|
|
//
|
|
|
|
|
|
giveto(who, amount);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Expand mux_IObjectInfo COM interface; eliminate all db[] from driver
Add 11 new methods to mux_IObjectInfo (GetFlags, SetFlags, GetPowers,
GetPennies, GetPureName, DecodeFlags, IsWizard, IsWizRoy, CanIdle,
WizardWho, SeeHidden) and implement in CObjectInfo.
Create driver_bridge.h/.cpp with 13 drv_* bridge functions that
delegate through COM into the engine. Driver acquires g_pIObjectInfo
after LoadGame.
Replace all db[] macro usage (Flags, Flags2, Flags3, Powers, Owner,
Location, Pennies, PureName, Name, Moniker, Hidden, Hideout, Suspect,
SiteMon, KeepAlive, Ansi, Html, NoBleed, Color256, decode_flags,
s_Flags, Good_obj, isPlayer, Wizard, WizRoy, Can_Idle, Wizard_Who,
See_Hidden) in net.cpp, bsd.cpp, ganl_adapter.cpp, sitemon.cpp with
drv_* bridge calls.
Linker errors: 149 → 97 (db references: 34 → 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:39:14 -06:00
|
|
|
|
MUX_RESULT CObjectInfo::GetPureName(dbref obj, const UTF8 **ppName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == ppName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppName = nullptr;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*ppName = PureName(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::DecodeFlags(dbref player, dbref obj, UTF8 **ppStr)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == ppStr)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppStr = nullptr;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*ppStr = decode_flags(player, &(db[obj].fs));
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
MUX_RESULT CObjectInfo::UnparseObject(dbref player, dbref target,
|
|
|
|
|
|
bool bObeyMyopic, UTF8 *pBuf, size_t nBufMax)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( nullptr == pBuf
|
|
|
|
|
|
|| 0 == nBufMax)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
pBuf[0] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 *pName = unparse_object(player, target, bObeyMyopic);
|
|
|
|
|
|
if (nullptr == pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_strncpy(pBuf, pName, nBufMax - 1);
|
|
|
|
|
|
free_lbuf(pName);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Expand mux_IObjectInfo COM interface; eliminate all db[] from driver
Add 11 new methods to mux_IObjectInfo (GetFlags, SetFlags, GetPowers,
GetPennies, GetPureName, DecodeFlags, IsWizard, IsWizRoy, CanIdle,
WizardWho, SeeHidden) and implement in CObjectInfo.
Create driver_bridge.h/.cpp with 13 drv_* bridge functions that
delegate through COM into the engine. Driver acquires g_pIObjectInfo
after LoadGame.
Replace all db[] macro usage (Flags, Flags2, Flags3, Powers, Owner,
Location, Pennies, PureName, Name, Moniker, Hidden, Hideout, Suspect,
SiteMon, KeepAlive, Ansi, Html, NoBleed, Color256, decode_flags,
s_Flags, Good_obj, isPlayer, Wizard, WizRoy, Can_Idle, Wizard_Who,
See_Hidden) in net.cpp, bsd.cpp, ganl_adapter.cpp, sitemon.cpp with
drv_* bridge calls.
Linker errors: 149 → 97 (db references: 34 → 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:39:14 -06:00
|
|
|
|
MUX_RESULT CObjectInfo::IsWizard(dbref obj, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = Wizard(obj) ? true : false;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::IsWizRoy(dbref obj, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = WizRoy(obj) ? true : false;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::CanIdle(dbref obj, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = Can_Idle(obj) ? true : false;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::WizardWho(dbref obj, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = Wizard_Who(obj) ? true : false;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::SeeHidden(dbref obj, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = See_Hidden(obj) ? true : false;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 17:50:20 -06:00
|
|
|
|
MUX_RESULT CObjectInfo::AtrAddRaw(dbref obj, int attrnum, const UTF8 *value)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
// Privileged raw write (no bCanSetAttr). Trusted callers only: driver
|
|
|
|
|
|
// lifecycle and modules that must touch AF_INTERNAL attrs mail_mod cannot
|
|
|
|
|
|
// reach through IAttributeAccess (#1229). Untrusted softcode must use
|
|
|
|
|
|
// IAttributeAccess instead.
|
|
|
|
|
|
//
|
2026-03-09 17:50:20 -06:00
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
atr_add_raw(obj, attrnum, value);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::AtrClr(dbref obj, int attrnum)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
atr_clr(obj, attrnum);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::AtrGet(dbref obj, int attrnum, UTF8 *pValue,
|
|
|
|
|
|
size_t nValueMax, dbref *pOwner, int *pFlags)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
if ( nullptr == pValue
|
|
|
|
|
|
|| nullptr == pOwner
|
|
|
|
|
|
|| nullptr == pFlags
|
|
|
|
|
|
|| 0 == nValueMax)
|
2026-03-09 17:50:20 -06:00
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pValue = '\0';
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
2026-04-05 18:34:51 -06:00
|
|
|
|
LBuf p = LBuf_Adopt(atr_get("com_bridge", obj, attrnum, pOwner, pFlags));
|
2026-03-09 17:50:20 -06:00
|
|
|
|
mux_strncpy(pValue, p, nValueMax - 1);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::AtrPGet(dbref obj, int attrnum, UTF8 *pValue,
|
|
|
|
|
|
size_t nValueMax, dbref *pOwner, int *pFlags)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
if ( nullptr == pValue
|
|
|
|
|
|
|| nullptr == pOwner
|
|
|
|
|
|
|| nullptr == pFlags
|
|
|
|
|
|
|| 0 == nValueMax)
|
2026-03-09 17:50:20 -06:00
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pValue = '\0';
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
2026-04-05 18:34:51 -06:00
|
|
|
|
LBuf p = LBuf_Adopt(atr_pget(obj, attrnum, pOwner, pFlags));
|
2026-03-09 17:50:20 -06:00
|
|
|
|
mux_strncpy(pValue, p, nValueMax - 1);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::LookupPlayer(dbref executor, const UTF8 *pName,
|
|
|
|
|
|
bool bConnected, dbref *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = NOTHING;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
2026-08-06 15:24:55 -06:00
|
|
|
|
*pResult = lookup_player(executor, pName, bConnected);
|
2026-03-09 17:50:20 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::FetchConnectionInfoFields(dbref player,
|
2026-07-28 19:35:57 -06:00
|
|
|
|
int64_t anFields[4])
|
2026-03-09 17:50:20 -06:00
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
fetch_ConnectionInfoFields(player, anFields);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfo::PutConnectionInfoFields(dbref player,
|
2026-07-28 19:35:57 -06:00
|
|
|
|
int64_t anFields[4], CLinearTimeAbsolute <aNow)
|
2026-03-09 17:50:20 -06:00
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
put_ConnectionInfoFields(player, anFields, ltaNow);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
CObjectInfoFactory::CObjectInfoFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CObjectInfoFactory::~CObjectInfoFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfoFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CObjectInfoFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CObjectInfoFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfoFactory::CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != pUnknownOuter)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOAGGREGATION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CObjectInfo *pObjectInfo = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pObjectInfo = new CObjectInfo;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pObjectInfo->QueryInterface(iid, ppv);
|
|
|
|
|
|
pObjectInfo->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CObjectInfoFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CAttributeAccess — mux_IAttributeAccess implementation (in-process only).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CAttributeAccess : public mux_IAttributeAccess
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT GetAttribute(dbref executor, dbref obj,
|
|
|
|
|
|
const UTF8 *pAttrName, UTF8 *pValue, size_t nValueMax,
|
|
|
|
|
|
size_t *pnValueLen);
|
|
|
|
|
|
virtual MUX_RESULT SetAttribute(dbref executor, dbref obj,
|
|
|
|
|
|
const UTF8 *pAttrName, const UTF8 *pValue);
|
|
|
|
|
|
|
|
|
|
|
|
CAttributeAccess(void);
|
|
|
|
|
|
virtual ~CAttributeAccess();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CAttributeAccess::CAttributeAccess(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CAttributeAccess::~CAttributeAccess()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CAttributeAccess::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IAttributeAccess *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IAttributeAccess == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IAttributeAccess *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CAttributeAccess::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CAttributeAccess::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CAttributeAccess::GetAttribute(dbref executor, dbref obj,
|
|
|
|
|
|
const UTF8 *pAttrName, UTF8 *pValue, size_t nValueMax,
|
|
|
|
|
|
size_t *pnValueLen)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
if (nullptr == pValue || 0 == nValueMax || nullptr == pAttrName)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
pValue[0] = '\0';
|
|
|
|
|
|
if (nullptr != pnValueLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pnValueLen = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ATTR *pattr = atr_str(pAttrName);
|
|
|
|
|
|
if (nullptr == pattr)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!bCanReadAttr(executor, obj, pattr, false))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_PERMISSION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dbref aowner;
|
|
|
|
|
|
int aflags;
|
|
|
|
|
|
size_t nLen;
|
2026-03-23 21:52:18 -06:00
|
|
|
|
LBuf buf = LBuf_Src("GetFormatCmd");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
atr_get_str_LEN(buf, obj, pattr->number, &aowner, &aflags, &nLen);
|
|
|
|
|
|
|
|
|
|
|
|
if (0 == nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nLen >= nValueMax)
|
|
|
|
|
|
{
|
|
|
|
|
|
nLen = nValueMax - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
memcpy(pValue, buf, nLen);
|
|
|
|
|
|
pValue[nLen] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != pnValueLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pnValueLen = nLen;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CAttributeAccess::SetAttribute(dbref executor, dbref obj,
|
|
|
|
|
|
const UTF8 *pAttrName, const UTF8 *pValue)
|
|
|
|
|
|
{
|
2026-07-25 20:21:38 -06:00
|
|
|
|
if (!Good_obj(obj) || nullptr == pAttrName)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// mkattr() looks up or creates the vattr as needed.
|
|
|
|
|
|
//
|
|
|
|
|
|
int anum = mkattr(executor, pAttrName);
|
|
|
|
|
|
if (anum <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ATTR *pattr = atr_num(anum);
|
|
|
|
|
|
if (nullptr == pattr)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!bCanSetAttr(executor, obj, pattr))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_PERMISSION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dbref aowner;
|
|
|
|
|
|
int aflags;
|
|
|
|
|
|
atr_pget_info(obj, pattr->number, &aowner, &aflags);
|
|
|
|
|
|
atr_add(obj, pattr->number, pValue, Owner(executor), aflags);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CAttributeAccessFactory
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CAttributeAccessFactory::CAttributeAccessFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CAttributeAccessFactory::~CAttributeAccessFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CAttributeAccessFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CAttributeAccessFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CAttributeAccessFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CAttributeAccessFactory::CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(pUnknownOuter);
|
|
|
|
|
|
|
|
|
|
|
|
CAttributeAccess *pAttributeAccess = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pAttributeAccess = new CAttributeAccess;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pAttributeAccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pAttributeAccess->QueryInterface(iid, ppv);
|
|
|
|
|
|
pAttributeAccess->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CAttributeAccessFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CEvaluator — mux_IEvaluator implementation (in-process only).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CEvaluator : public mux_IEvaluator
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT Eval(dbref executor, dbref caller, dbref enactor,
|
|
|
|
|
|
const UTF8 *pExpr, UTF8 *pResult, size_t nResultMax,
|
|
|
|
|
|
size_t *pnResultLen);
|
2026-07-25 13:43:00 -06:00
|
|
|
|
virtual MUX_RESULT EvalWithArgs(dbref executor, dbref caller, dbref enactor,
|
|
|
|
|
|
const UTF8 *pExpr, const UTF8 *args[], int nargs,
|
|
|
|
|
|
UTF8 *pResult, size_t nResultMax, size_t *pnResultLen);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
|
|
|
|
|
CEvaluator(void);
|
|
|
|
|
|
virtual ~CEvaluator();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CEvaluator::CEvaluator(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CEvaluator::~CEvaluator()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CEvaluator::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IEvaluator *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IEvaluator == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IEvaluator *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CEvaluator::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CEvaluator::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CEvaluator::Eval(dbref executor, dbref caller, dbref enactor,
|
|
|
|
|
|
const UTF8 *pExpr, UTF8 *pResult, size_t nResultMax,
|
|
|
|
|
|
size_t *pnResultLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pExpr || nullptr == pResult || 0 == nResultMax)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!Good_obj(executor))
|
|
|
|
|
|
{
|
|
|
|
|
|
pResult[0] = '\0';
|
|
|
|
|
|
if (nullptr != pnResultLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pnResultLen = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Use the caller/enactor if valid, otherwise default to executor.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!Good_obj(caller))
|
|
|
|
|
|
{
|
|
|
|
|
|
caller = executor;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(enactor))
|
|
|
|
|
|
{
|
|
|
|
|
|
enactor = executor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Evaluate into an LBUF, then copy to the caller's buffer.
|
|
|
|
|
|
//
|
2026-03-23 21:52:18 -06:00
|
|
|
|
LBuf buf = LBuf_Src("EvalExpr");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
UTF8 *bufc = buf;
|
|
|
|
|
|
size_t nExpr = strlen((const char *)pExpr);
|
|
|
|
|
|
mux_exec(pExpr, nExpr, buf, &bufc, executor, caller, enactor,
|
|
|
|
|
|
EV_FCHECK | EV_STRIP_CURLY | EV_EVAL, nullptr, 0);
|
|
|
|
|
|
*bufc = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
size_t nLen = bufc - buf;
|
|
|
|
|
|
if (nLen >= nResultMax)
|
|
|
|
|
|
{
|
|
|
|
|
|
nLen = nResultMax - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
memcpy(pResult, buf, nLen);
|
|
|
|
|
|
pResult[nLen] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != pnResultLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pnResultLen = nLen;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
MUX_RESULT CEvaluator::EvalWithArgs(dbref executor, dbref caller, dbref enactor,
|
|
|
|
|
|
const UTF8 *pExpr, const UTF8 *args[], int nargs,
|
|
|
|
|
|
UTF8 *pResult, size_t nResultMax, size_t *pnResultLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pExpr || nullptr == pResult || 0 == nResultMax)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nargs < 0 || (nargs > 0 && nullptr == args))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!Good_obj(executor))
|
|
|
|
|
|
{
|
|
|
|
|
|
pResult[0] = '\0';
|
|
|
|
|
|
if (nullptr != pnResultLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pnResultLen = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(caller))
|
|
|
|
|
|
{
|
|
|
|
|
|
caller = executor;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(enactor))
|
|
|
|
|
|
{
|
|
|
|
|
|
enactor = executor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Match call_mogrifier: EV_FCHECK|EV_EVAL|EV_TOP with %0..%n-1.
|
|
|
|
|
|
//
|
|
|
|
|
|
LBuf buf = LBuf_Src("EvalWithArgs");
|
|
|
|
|
|
UTF8 *bufc = buf;
|
|
|
|
|
|
size_t nExpr = strlen(reinterpret_cast<const char *>(pExpr));
|
|
|
|
|
|
mux_exec(pExpr, nExpr, buf, &bufc, executor, caller, enactor,
|
|
|
|
|
|
EV_FCHECK | EV_EVAL | EV_TOP, args, nargs);
|
|
|
|
|
|
*bufc = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
size_t nLen = bufc - buf;
|
|
|
|
|
|
if (nLen >= nResultMax)
|
|
|
|
|
|
{
|
|
|
|
|
|
nLen = nResultMax - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
memcpy(pResult, buf, nLen);
|
|
|
|
|
|
pResult[nLen] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != pnResultLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pnResultLen = nLen;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CEvaluatorFactory
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CEvaluatorFactory::CEvaluatorFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CEvaluatorFactory::~CEvaluatorFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CEvaluatorFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CEvaluatorFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CEvaluatorFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CEvaluatorFactory::CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(pUnknownOuter);
|
|
|
|
|
|
|
|
|
|
|
|
CEvaluator *pEvaluator = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pEvaluator = new CEvaluator;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pEvaluator)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pEvaluator->QueryInterface(iid, ppv);
|
|
|
|
|
|
pEvaluator->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CEvaluatorFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CPermissions — mux_IPermissions implementation (in-process only).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CPermissions : public mux_IPermissions
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT IsWizard(dbref obj, bool *pWizard);
|
|
|
|
|
|
virtual MUX_RESULT IsGod(dbref obj, bool *pGod);
|
|
|
|
|
|
virtual MUX_RESULT HasControl(dbref who, dbref what, bool *pControls);
|
|
|
|
|
|
virtual MUX_RESULT HasCommAll(dbref obj, bool *pCommAll);
|
2026-07-24 13:18:24 -06:00
|
|
|
|
virtual MUX_RESULT CouldDoit(dbref who, dbref what, int atr, bool *pResult);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
|
|
|
|
|
CPermissions(void);
|
|
|
|
|
|
virtual ~CPermissions();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CPermissions::CPermissions(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CPermissions::~CPermissions()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPermissions::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IPermissions *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IPermissions == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IPermissions *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CPermissions::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CPermissions::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPermissions::IsWizard(dbref obj, bool *pWizard)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pWizard)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pWizard = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pWizard = Wizard(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPermissions::IsGod(dbref obj, bool *pGod)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pGod)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pGod = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pGod = God(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPermissions::HasControl(dbref who, dbref what, bool *pControls)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pControls)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(who) || !Good_obj(what))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pControls = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pControls = Controls(who, what);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPermissions::HasCommAll(dbref obj, bool *pCommAll)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pCommAll)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pCommAll = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pCommAll = Comm_All(obj);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 13:18:24 -06:00
|
|
|
|
MUX_RESULT CPermissions::CouldDoit(dbref who, dbref what, int atr, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(who) || !Good_obj(what))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = could_doit(who, what, atr);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
// CGameConfig -- mux_IGameConfig (#1654).
|
|
|
|
|
|
//
|
|
|
|
|
|
// Game-policy values a module may need, snapshotted from mudconf at call
|
|
|
|
|
|
// time. See the GAME_CONFIG comment in modules.h for the cbSize contract
|
|
|
|
|
|
// and why callers must not cache the result (#1613).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CGameConfig : public mux_IGameConfig
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT GetGameConfig(GAME_CONFIG *pConfig);
|
|
|
|
|
|
|
|
|
|
|
|
CGameConfig(void);
|
|
|
|
|
|
virtual ~CGameConfig();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CGameConfig::CGameConfig(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CGameConfig::~CGameConfig()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameConfig::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IGameConfig *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IGameConfig == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IGameConfig *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CGameConfig::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CGameConfig::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameConfig::GetGameConfig(GAME_CONFIG *pConfig)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( nullptr == pConfig
|
|
|
|
|
|
|| pConfig->cbSize < sizeof(GAME_CONFIG))
|
|
|
|
|
|
{
|
|
|
|
|
|
// A larger cbSize is fine -- a newer module against this engine gets
|
|
|
|
|
|
// the fields we know and keeps its zeros for the rest. A smaller
|
|
|
|
|
|
// one means a struct from before v1, which cannot exist.
|
|
|
|
|
|
//
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pConfig->searchcost = mudconf.searchcost;
|
|
|
|
|
|
pConfig->eval_comtitle = mudconf.eval_comtitle;
|
|
|
|
|
|
mux_strncpy(pConfig->one_coin, mudconf.one_coin,
|
|
|
|
|
|
sizeof(pConfig->one_coin) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->many_coins, mudconf.many_coins,
|
|
|
|
|
|
sizeof(pConfig->many_coins) - 1);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CGameConfigFactory
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CGameConfigFactory::CGameConfigFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CGameConfigFactory::~CGameConfigFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameConfigFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CGameConfigFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CGameConfigFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameConfigFactory::CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(pUnknownOuter);
|
|
|
|
|
|
|
|
|
|
|
|
CGameConfig *pGameConfig = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pGameConfig = new CGameConfig;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pGameConfig)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pGameConfig->QueryInterface(iid, ppv);
|
|
|
|
|
|
pGameConfig->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameConfigFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
// CPermissionsFactory
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CPermissionsFactory::CPermissionsFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CPermissionsFactory::~CPermissionsFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPermissionsFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CPermissionsFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CPermissionsFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPermissionsFactory::CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(pUnknownOuter);
|
|
|
|
|
|
|
|
|
|
|
|
CPermissions *pPermissions = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pPermissions = new CPermissions;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pPermissions->QueryInterface(iid, ppv);
|
|
|
|
|
|
pPermissions->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPermissionsFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CMailDelivery — server-provided implementation of mux_IMailDelivery.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Wraps server-internal lock evaluation, attribute triggers, flag management,
|
|
|
|
|
|
// and throttling so that the mail module can deliver mail without linking
|
|
|
|
|
|
// against server internals.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CMailDelivery : public mux_IMailDelivery
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT MailCheck(dbref player, dbref target, bool *pResult);
|
|
|
|
|
|
virtual MUX_RESULT NotifyDelivery(dbref sender, dbref target,
|
|
|
|
|
|
const UTF8 *subject, bool silent);
|
|
|
|
|
|
virtual MUX_RESULT IsComposing(dbref player, bool *pResult);
|
|
|
|
|
|
virtual MUX_RESULT SetComposing(dbref player, bool bComposing);
|
|
|
|
|
|
virtual MUX_RESULT ThrottleCheck(dbref player, bool *pResult);
|
|
|
|
|
|
|
|
|
|
|
|
CMailDelivery(void);
|
|
|
|
|
|
virtual ~CMailDelivery();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CMailDelivery::CMailDelivery(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CMailDelivery::~CMailDelivery()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailDelivery::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IMailDelivery *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IMailDelivery == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IMailDelivery *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CMailDelivery::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CMailDelivery::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MailCheck — Evaluate A_LMAIL lock in both directions.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Mirrors the server's mail_check() function:
|
|
|
|
|
|
// 1. If player can't pass target's A_LMAIL, call mail_return (sends MFAIL).
|
|
|
|
|
|
// 2. If target can't pass player's A_LMAIL, reject unless player is Wizard.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CMailDelivery::MailCheck(dbref player, dbref target, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(player) || !Good_obj(target))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!could_doit(player, target, A_LMAIL))
|
|
|
|
|
|
{
|
|
|
|
|
|
// Target rejects player's mail — send MFAIL message.
|
|
|
|
|
|
//
|
|
|
|
|
|
dbref aowner;
|
|
|
|
|
|
int aflags;
|
2026-04-05 18:34:51 -06:00
|
|
|
|
LBuf str = LBuf_Adopt(atr_pget(target, A_MFAIL, &aowner, &aflags));
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
if (*str)
|
|
|
|
|
|
{
|
Migrate 47 more alloc_lbuf/free_lbuf sites to LBuf RAII
Second wave: convert manual alloc/free pairs in cque (1), db_rw (4),
conf (5), db (2), engine_com (6), help (7), levels (2), predicates (5),
player (7), funmath (8). Eliminates ~80 explicit free_lbuf calls
including multi-exit error paths in getboolexp1 (5 frees → 0),
get_list, AnnounceConnect/Disconnect, and the eight NOEVAL function
variants (cand/cor/firstof/allof and bool variants).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:55:40 -06:00
|
|
|
|
LBuf str2 = LBuf_Src("mail_delivery.check");
|
|
|
|
|
|
UTF8 *bp = str2.get();
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
mux_exec(str, LBUF_SIZE-1, str2, &bp, target, player, player,
|
|
|
|
|
|
AttrTrace(aflags, EV_FCHECK|EV_EVAL|EV_TOP|EV_NO_LOCATION),
|
|
|
|
|
|
nullptr, 0);
|
|
|
|
|
|
*bp = '\0';
|
|
|
|
|
|
if (*str2)
|
|
|
|
|
|
{
|
|
|
|
|
|
CLinearTimeAbsolute ltaNow;
|
|
|
|
|
|
ltaNow.GetLocal();
|
|
|
|
|
|
FIELDEDTIME ft;
|
|
|
|
|
|
ltaNow.ReturnFields(&ft);
|
|
|
|
|
|
|
2026-07-28 15:47:20 +00:00
|
|
|
|
raw_notify(player, tprintf(M_("MAIL: Reject message from %s: %s"),
|
Migrate 47 more alloc_lbuf/free_lbuf sites to LBuf RAII
Second wave: convert manual alloc/free pairs in cque (1), db_rw (4),
conf (5), db (2), engine_com (6), help (7), levels (2), predicates (5),
player (7), funmath (8). Eliminates ~80 explicit free_lbuf calls
including multi-exit error paths in getboolexp1 (5 frees → 0),
get_list, AnnounceConnect/Disconnect, and the eight NOEVAL function
variants (cand/cor/firstof/allof and bool variants).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:55:40 -06:00
|
|
|
|
Moniker(target), str2.get()));
|
2026-07-28 15:47:20 +00:00
|
|
|
|
raw_notify(target, tprintf(M_("[%d:%02d] MAIL: Reject message sent to %s."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
ft.iHour, ft.iMinute, Moniker(player)));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-07-28 15:47:20 +00:00
|
|
|
|
raw_notify(player, tprintf(M_("Sorry, %s is not accepting mail."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(target)));
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!could_doit(target, player, A_LMAIL))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Wizard(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
raw_notify(player, tprintf(
|
2026-07-28 15:47:20 +00:00
|
|
|
|
M_("Warning: %s can’t return your mail."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(target)));
|
|
|
|
|
|
*pResult = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
raw_notify(player, tprintf(
|
2026-07-28 15:47:20 +00:00
|
|
|
|
M_("Sorry, %s can’t return your mail."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(target)));
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
*pResult = true;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NotifyDelivery — Send post-delivery notifications and trigger attributes.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Called by the module after a message has been stored. This handles:
|
|
|
|
|
|
// - "You sent your message to <target>" to sender (unless silent)
|
|
|
|
|
|
// - "You have new mail from <sender>" to target
|
|
|
|
|
|
// - did_it(sender, target, A_MAIL, ..., A_AMAIL, ...) for attribute triggers
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CMailDelivery::NotifyDelivery(dbref sender, dbref target,
|
|
|
|
|
|
const UTF8 *subject, bool silent)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(sender) || !Good_obj(target))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!silent)
|
|
|
|
|
|
{
|
2026-07-28 15:47:20 +00:00
|
|
|
|
raw_notify(sender, tprintf(M_("MAIL: You sent your message to %s."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(target)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
raw_notify(target, tprintf(
|
2026-07-28 15:47:20 +00:00
|
|
|
|
M_("MAIL: You have a new message from %s. Subject: %s"),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(sender), subject));
|
|
|
|
|
|
|
|
|
|
|
|
did_it(sender, target, A_MAIL, nullptr, 0, nullptr, A_AMAIL, 0,
|
|
|
|
|
|
nullptr, NOTHING);
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IsComposing — Check PLAYER_MAILS flag in Flags2.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CMailDelivery::IsComposing(dbref player, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = false;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = (Flags2(player) & PLAYER_MAILS) != 0;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetComposing — Set or clear the PLAYER_MAILS flag in Flags2.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CMailDelivery::SetComposing(dbref player, bool bComposing)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Good_obj(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (bComposing)
|
|
|
|
|
|
{
|
|
|
|
|
|
s_Flags(player, FLAG_WORD2, Flags2(player) | PLAYER_MAILS);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
s_Flags(player, FLAG_WORD2, Flags2(player) & ~PLAYER_MAILS);
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ThrottleCheck — Has player sent too much mail recently?
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CMailDelivery::ThrottleCheck(dbref player, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Good_obj(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pResult = true;
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = ThrottleMail(player);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CMailDeliveryFactory
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CMailDeliveryFactory::CMailDeliveryFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CMailDeliveryFactory::~CMailDeliveryFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailDeliveryFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CMailDeliveryFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CMailDeliveryFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailDeliveryFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
|
|
|
|
|
|
MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(pUnknownOuter);
|
|
|
|
|
|
|
|
|
|
|
|
CMailDelivery *pMailDelivery = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pMailDelivery = new CMailDelivery;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pMailDelivery)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pMailDelivery->QueryInterface(iid, ppv);
|
|
|
|
|
|
pMailDelivery->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailDeliveryFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CHelpSystem — server-provided implementation of mux_IHelpSystem.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Wraps the existing help.cpp lookup and indexing functions so that
|
|
|
|
|
|
// modules can access the in-game help system without linking against
|
|
|
|
|
|
// server internals.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CHelpSystem : public mux_IHelpSystem
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT LookupTopic(dbref executor, int iHelpfile,
|
|
|
|
|
|
const UTF8 *pTopic, UTF8 *pResult, size_t nResultMax,
|
|
|
|
|
|
size_t *pnResultLen);
|
|
|
|
|
|
virtual MUX_RESULT FindHelpFile(const UTF8 *pCommandName,
|
|
|
|
|
|
int *pIndex);
|
|
|
|
|
|
virtual MUX_RESULT GetHelpFileCount(int *pCount);
|
|
|
|
|
|
virtual MUX_RESULT ReloadIndexes(dbref player);
|
|
|
|
|
|
|
|
|
|
|
|
CHelpSystem(void);
|
|
|
|
|
|
virtual ~CHelpSystem();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CHelpSystem::CHelpSystem(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CHelpSystem::~CHelpSystem()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CHelpSystem::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IHelpSystem *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IHelpSystem == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IHelpSystem *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CHelpSystem::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CHelpSystem::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// LookupTopic — look up a help topic and return the rendered text.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Delegates to help_helper() which does the full lookup including
|
|
|
|
|
|
// prefix matching, file reading, and optional softcode evaluation.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CHelpSystem::LookupTopic(dbref executor, int iHelpfile,
|
|
|
|
|
|
const UTF8 *pTopic, UTF8 *pResult, size_t nResultMax,
|
|
|
|
|
|
size_t *pnResultLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult || nullptr == pnResultLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( iHelpfile < 0
|
|
|
|
|
|
|| mudstate.mHelpDesc <= iHelpfile
|
|
|
|
|
|
|| mudstate.nHelpDesc <= iHelpfile)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pnResultLen = 0;
|
|
|
|
|
|
pResult[0] = '\0';
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// help_helper writes into an alloc_lbuf using safe_str pattern.
|
|
|
|
|
|
//
|
Migrate 47 more alloc_lbuf/free_lbuf sites to LBuf RAII
Second wave: convert manual alloc/free pairs in cque (1), db_rw (4),
conf (5), db (2), engine_com (6), help (7), levels (2), predicates (5),
player (7), funmath (8). Eliminates ~80 explicit free_lbuf calls
including multi-exit error paths in getboolexp1 (5 frees → 0),
get_list, AnnounceConnect/Disconnect, and the eight NOEVAL function
variants (cand/cor/firstof/allof and bool variants).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:55:40 -06:00
|
|
|
|
LBuf buff = LBuf_Src("CHelpSystem.LookupTopic");
|
|
|
|
|
|
UTF8 *bufc = buff.get();
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
feat(#2136): flip fargs to const UTF8 * const — and convert every site the compiler surfaced
The flip: FUNCTION/XFUNCTION/FUN::fun/delim_check and the module
interfaces take `const UTF8 * const fargs[]`. Double-const is
load-bearing: C++ qualification conversion needs const at both pointer
levels, so builder-side `UTF8 *[]` arrays convert implicitly — the
evaluator, the JIT marshaller, and every owner site need zero casts,
and slot reassignment inside bodies becomes a compile error for free.
The conversions: the flip landed first so the compiler enumerated every
violation; this commit is that inventory worked to zero — ~250 sites
across funceval, funceval2, functions, funmath, help, mail, session,
powers, levels, predicates, conf, walkdb, stringutil, timeutil/
date_scan (regenerated, one-line diff), exp3, and mux_main, each
classified per docs/campaign-2136-const-fargs.md's four recipes.
New idioms (functions.h): trim_space_sep_n() — non-destructive trim for
(pointer, length) consumers, so trim-then-scan sites need no copy at
all; FargVec — the argv counterpart of FargCopy for CS_ARGV handlers.
countwords() and DecodeListOfIntegers() rewritten non-destructive.
The flip deleted more than it added: #2157's fun_munge list1 copy, the
engine_com help-topic copy, fun_index's in-place NUL write, and five
const_casts (process_sex x4, sha1_helper). const_cast budget: zero
added.
Trap recorded in the brief: an old-signature definition doesn't fail
the build — it becomes a C++ overload, and the new-signature symbol
stays undefined until dlopen(RTLD_NOW). delim_check, the conn_bridge
bridges, the dbt_spike stub, and exp3::Call were all silently shadowed;
muxscript was the only host that noticed, because netmux's own net.cpp
resolved the flat-namespace lookup. After any signature flip, grep the
old spelling.
Verified: make test EXPECT_CONFIG="jit=yes" (35 passed / 0 failed) and
make test-scenario, including the new tests/scenario/sidefx_fargs.py
that live-probes the class-3 wrappers smoke never touches (pemit/
trigger/link/tel/wipe/destroy).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:34:23 -06:00
|
|
|
|
help_helper(executor, iHelpfile, pTopic, buff, &bufc);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
*bufc = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
size_t nLen = bufc - buff;
|
|
|
|
|
|
if (nLen > nResultMax)
|
|
|
|
|
|
{
|
|
|
|
|
|
nLen = nResultMax;
|
|
|
|
|
|
}
|
|
|
|
|
|
memcpy(pResult, buff, nLen);
|
|
|
|
|
|
pResult[nLen] = '\0';
|
|
|
|
|
|
*pnResultLen = nLen;
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// FindHelpFile — find the help file index for a given command name.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CHelpSystem::FindHelpFile(const UTF8 *pCommandName,
|
|
|
|
|
|
int *pIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pCommandName || nullptr == pIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < mudstate.nHelpDesc; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( nullptr != mudstate.aHelpDesc[i].CommandName
|
|
|
|
|
|
&& 0 == mux_stricmp(pCommandName,
|
|
|
|
|
|
mudstate.aHelpDesc[i].CommandName))
|
|
|
|
|
|
{
|
|
|
|
|
|
*pIndex = i;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
*pIndex = -1;
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetHelpFileCount — return the number of registered help files.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CHelpSystem::GetHelpFileCount(int *pCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pCount = mudstate.nHelpDesc;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ReloadIndexes — reload all help file indexes.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CHelpSystem::ReloadIndexes(dbref player)
|
|
|
|
|
|
{
|
|
|
|
|
|
helpindex_load(player);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CHelpSystemFactory
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CHelpSystemFactory::CHelpSystemFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CHelpSystemFactory::~CHelpSystemFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CHelpSystemFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CHelpSystemFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CHelpSystemFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CHelpSystemFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
|
|
|
|
|
|
MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(pUnknownOuter);
|
|
|
|
|
|
|
|
|
|
|
|
CHelpSystem *pHelpSystem = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pHelpSystem = new CHelpSystem;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pHelpSystem)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pHelpSystem->QueryInterface(iid, ppv);
|
|
|
|
|
|
pHelpSystem->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CHelpSystemFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CGameEngine — in-process COM wrapper for the game engine.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The driver creates this via mux_CreateInstance(CID_GameEngine) and calls
|
|
|
|
|
|
// through the mux_IGameEngine interface. In the current single-binary
|
|
|
|
|
|
// build this is a thin delegation layer; when the engine moves to
|
|
|
|
|
|
// engine.so, this class moves with it and becomes the COM front door.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CGameEngine : public mux_IGameEngine
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT LoadGame(const UTF8 *configFile, const UTF8 *inputDb,
|
|
|
|
|
|
bool bMinDB);
|
|
|
|
|
|
virtual MUX_RESULT Startup(void);
|
|
|
|
|
|
virtual MUX_RESULT RunTasks(CLinearTimeAbsolute <aNow);
|
|
|
|
|
|
virtual MUX_RESULT UpdateQuotas(CLinearTimeAbsolute <aLast,
|
|
|
|
|
|
const CLinearTimeAbsolute <aCurrent);
|
|
|
|
|
|
virtual MUX_RESULT WhenNext(CLinearTimeAbsolute *pltaWhen);
|
2026-06-04 12:11:13 -05:00
|
|
|
|
virtual MUX_RESULT HasPendingUserTasks(bool *pbResult);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
virtual MUX_RESULT DumpDatabase(void);
|
|
|
|
|
|
virtual MUX_RESULT Shutdown(void);
|
2026-03-09 13:44:58 -06:00
|
|
|
|
virtual MUX_RESULT DbConvert(const UTF8 *infile, const UTF8 *outfile,
|
|
|
|
|
|
const UTF8 *basename, bool bCheck, bool bLoad, bool bUnload,
|
2026-06-07 13:23:12 -05:00
|
|
|
|
const UTF8 *comsys_file, const UTF8 *mail_file, bool bForce);
|
2026-03-09 14:34:03 -06:00
|
|
|
|
virtual MUX_RESULT GetConfig(DRIVER_CONFIG *pConfig);
|
2026-03-20 14:26:32 -06:00
|
|
|
|
virtual MUX_RESULT MarkConnected(dbref player);
|
2026-03-09 16:03:40 -06:00
|
|
|
|
virtual MUX_RESULT DumpChildExited(int child_pid);
|
2026-03-09 15:30:06 -06:00
|
|
|
|
virtual MUX_RESULT SetStartTime(const CLinearTimeAbsolute &time);
|
|
|
|
|
|
virtual MUX_RESULT GetStartTime(CLinearTimeAbsolute *pTime);
|
|
|
|
|
|
virtual MUX_RESULT SetRestartTime(const CLinearTimeAbsolute &time);
|
|
|
|
|
|
virtual MUX_RESULT SetRestartCount(unsigned int count);
|
|
|
|
|
|
virtual MUX_RESULT GetRestartCount(unsigned int *pCount);
|
|
|
|
|
|
virtual MUX_RESULT SetCpuCountFrom(const CLinearTimeAbsolute &time);
|
|
|
|
|
|
virtual MUX_RESULT SetRecordPlayers(int count);
|
|
|
|
|
|
virtual MUX_RESULT GetDoingHdr(UTF8 *buf, size_t bufSize);
|
|
|
|
|
|
virtual MUX_RESULT SetDoingHdr(const UTF8 *hdr, size_t len);
|
|
|
|
|
|
virtual MUX_RESULT GetRecordPlayers(int *pCount);
|
|
|
|
|
|
virtual MUX_RESULT GetBCanRestart(bool *pbCanRestart);
|
2026-03-09 19:40:40 -06:00
|
|
|
|
virtual MUX_RESULT CancelTask(void (*fpTask)(void *, int),
|
|
|
|
|
|
void *arg_voidptr, int arg_Integer);
|
|
|
|
|
|
virtual MUX_RESULT DeferImmediateTask(int iPriority,
|
|
|
|
|
|
void (*fpTask)(void *, int), void *arg_voidptr,
|
|
|
|
|
|
int arg_Integer);
|
|
|
|
|
|
virtual MUX_RESULT DeferTask(const CLinearTimeAbsolute <When,
|
|
|
|
|
|
int iPriority, void (*fpTask)(void *, int),
|
|
|
|
|
|
void *arg_voidptr, int arg_Integer);
|
|
|
|
|
|
virtual MUX_RESULT PrepareForCommand(dbref player);
|
|
|
|
|
|
virtual MUX_RESULT ProcessCommand(dbref executor, dbref caller,
|
|
|
|
|
|
dbref enactor, int eval, bool bHasCmdArg, UTF8 *command,
|
|
|
|
|
|
const UTF8 *cargs[], int ncargs, UTF8 **ppLogBuf);
|
|
|
|
|
|
virtual MUX_RESULT FinishCommand(void);
|
|
|
|
|
|
virtual MUX_RESULT HaltQueue(dbref executor, dbref target);
|
|
|
|
|
|
virtual MUX_RESULT WaitQueue(dbref executor, dbref caller,
|
|
|
|
|
|
dbref enactor, int eval, bool bTimed,
|
|
|
|
|
|
const CLinearTimeAbsolute <aWhen, dbref sem, int attr,
|
|
|
|
|
|
UTF8 *command, int ncargs, const UTF8 *cargs[],
|
|
|
|
|
|
reg_ref *regs[], NamedRegsMap *named);
|
|
|
|
|
|
virtual MUX_RESULT MoveObject(dbref thing, dbref dest);
|
|
|
|
|
|
virtual MUX_RESULT WhereRoom(dbref what, dbref *pRoom);
|
|
|
|
|
|
virtual MUX_RESULT TimeFormat1(int seconds, size_t maxWidth,
|
|
|
|
|
|
const UTF8 **ppResult);
|
|
|
|
|
|
virtual MUX_RESULT TimeFormat2(int seconds,
|
|
|
|
|
|
const UTF8 **ppResult);
|
|
|
|
|
|
virtual MUX_RESULT GetDbTop(int *pDbTop);
|
|
|
|
|
|
virtual MUX_RESULT GetInfoTable(const UTF8 ***pppTable);
|
|
|
|
|
|
virtual MUX_RESULT Report(void);
|
|
|
|
|
|
virtual MUX_RESULT PresyncDatabaseSigsegv(void);
|
|
|
|
|
|
virtual MUX_RESULT DoRestart(dbref executor, dbref caller,
|
|
|
|
|
|
dbref enactor, int eval, int key);
|
|
|
|
|
|
virtual MUX_RESULT CacheClose(void);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
|
|
|
|
|
CGameEngine(void);
|
|
|
|
|
|
virtual ~CGameEngine();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CGameEngine::CGameEngine(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CGameEngine::~CGameEngine()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IGameEngine *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IGameEngine == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IGameEngine *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CGameEngine::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CGameEngine::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 19:40:40 -06:00
|
|
|
|
// alloc.cpp callback for @list buffers output.
|
|
|
|
|
|
//
|
|
|
|
|
|
static void engine_alloc_notify(dbref player, const UTF8 *text)
|
|
|
|
|
|
{
|
|
|
|
|
|
notify(player, text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 08:00:15 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// discover_comsys_mail_modules — post-SQLite module init (#1190)
|
|
|
|
|
|
//
|
|
|
|
|
|
// Must run only after init_dbfile()/cache_init() has set g_pSQLiteBackend.
|
|
|
|
|
|
// On any failure, Release the control pointer and leave it nullptr so the
|
|
|
|
|
|
// in-tree engine comsys/mail paths stay active.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
static void discover_comsys_mail_modules(void)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
mudstate.pIComsysControl = nullptr;
|
|
|
|
|
|
mudstate.pIMailControl = nullptr;
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
2026-07-25 08:00:15 -06:00
|
|
|
|
if (nullptr == g_pSQLiteBackend)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "MOD");
|
|
|
|
|
|
log_text(T("Skipping comsys/mail module init: SQLite backend not open."));
|
|
|
|
|
|
ENDLOG;
|
|
|
|
|
|
return;
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 08:00:15 -06:00
|
|
|
|
MUX_RESULT mr;
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
2026-07-25 08:00:15 -06:00
|
|
|
|
// Comsys module.
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
//
|
|
|
|
|
|
mr = mux_CreateInstance(CID_Comsys, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IComsysControl,
|
|
|
|
|
|
reinterpret_cast<void **>(&mudstate.pIComsysControl));
|
2026-07-25 08:00:15 -06:00
|
|
|
|
if (MUX_SUCCEEDED(mr) && nullptr != mudstate.pIComsysControl)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
mux_IComsysStorage *pComsysStorage = nullptr;
|
|
|
|
|
|
mr = mux_CreateInstance(CID_ComsysStorage, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IComsysStorage,
|
|
|
|
|
|
reinterpret_cast<void **>(&pComsysStorage));
|
2026-07-25 08:00:15 -06:00
|
|
|
|
if (MUX_SUCCEEDED(mr) && nullptr != pComsysStorage)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
mr = mudstate.pIComsysControl->Initialize(pComsysStorage);
|
|
|
|
|
|
pComsysStorage->Release();
|
2026-07-25 08:00:15 -06:00
|
|
|
|
pComsysStorage = nullptr;
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "MOD");
|
2026-03-10 10:50:00 -06:00
|
|
|
|
log_text(T("Comsys module initialized (via engine storage)."));
|
2026-03-10 10:39:15 -06:00
|
|
|
|
ENDLOG;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "MOD");
|
2026-07-25 08:00:15 -06:00
|
|
|
|
log_printf(T("Comsys module Initialize failed (mr=%d); using built-in."), mr);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
ENDLOG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
mudstate.pIComsysControl->Release();
|
|
|
|
|
|
mudstate.pIComsysControl = nullptr;
|
2026-03-10 10:39:15 -06:00
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
2026-03-27 16:23:30 -06:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "MOD");
|
2026-07-25 08:00:15 -06:00
|
|
|
|
log_printf(T("Comsys storage interface creation failed (mr=%d); using built-in."), mr);
|
2026-03-27 16:23:30 -06:00
|
|
|
|
ENDLOG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
mudstate.pIComsysControl->Release();
|
|
|
|
|
|
mudstate.pIComsysControl = nullptr;
|
2026-03-27 16:23:30 -06:00
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
2026-07-25 08:00:15 -06:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.pIComsysControl = nullptr;
|
|
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
fix(modules): say which comsys/mail implementation is live (#1581)
#1581's item 1: the silent fallback in discover_comsys_mail_modules. Both
subsystems have two implementations that demonstrably disagree -- #1564 (the
module never wrote HISTORY_n), #1585 (LOG_TIMESTAMPS), #1572 (four MOGRIFY
hooks), #1587 (mail message size) -- and nothing said which one a server got.
Now, at startup:
Comsys: using module implementation.
Mail: using built-in engine implementation.
Both outcomes, not only the failure, because "which one am I running" is the
question and a silence that means success is still a silence.
## The log call is fprintf(stderr), and that is not laziness
I tried STARTLOG first, matching the failure paths already in that function.
No output. Then Log.tinyprintf, matching the surrounding LoadGame code. Also
no output -- and the control settles it: the neighbouring
Log.tinyprintf(T("LOADING: %s" ENDLINE), mudconf.mail_db);
produces nothing either, in muxscript or through the smoke harness.
So there is no working log channel at this point in LoadGame, and the
consequence is worth recording: **the STARTLOG failure paths already in
discover_comsys_mail_modules have never been visible to anyone.** Three
existing branches that report "using built-in" on module-init failure emit
nothing at all. That is a second, quieter instance of the same defect #1581
is about.
fprintf(stderr) is what CSQLiteDB::MigrateSchema already uses from this same
phase, and it is what the harness captures.
## Answering the question #1581 could not
The issue establishes Windows behaviour and says: "What I could not determine
is what Linux does -- and that is the question this issue needs answered
first." I cannot answer for Linux either, but macOS arm64 now reports, and
the answer is configuration-driven rather than platform-driven:
bare config, no module directives -> built-in engine, both subsystems
module comsys_mod / module mail_mod -> module, both subsystems
So the modules do NOT load by default from game/bin; they load when asked for.
tools/Smoke:145-147 generates those directives, so the suite exercises the
module path here -- and tools/Makesmoke does not, so the upload phase
exercises the built-in. The harness has been running both implementations all
along, in different phases.
Note the Windows finding in #1581 predates #1569, which fixed the missing
DCL_EXPORT on the module entry points -- the reason the DLLs exported nothing
and could not load. Whether Windows now matches this wants re-measuring
there; this change makes that a one-line check.
Suite: 1560 passed / 0 failed / 320 of 320.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 10:21:54 -06:00
|
|
|
|
// Say which implementation is live, both ways (#1581).
|
|
|
|
|
|
//
|
|
|
|
|
|
// comsys has two implementations -- this module and the engine's built-in
|
|
|
|
|
|
// (mux/modules/engine/comsys.cpp) -- and they demonstrably disagree: the
|
|
|
|
|
|
// module never wrote HISTORY_n until #1569, and still differs on
|
|
|
|
|
|
// LOG_TIMESTAMPS (#1585) and four MOGRIFY hooks (#1572). Until now
|
|
|
|
|
|
// nothing said which one a given server got: the two inner failure paths
|
|
|
|
|
|
// logged, but "the module is not there at all" fell through in silence and
|
|
|
|
|
|
// success said nothing either.
|
|
|
|
|
|
//
|
|
|
|
|
|
// That silence is why #1564 took a multi-session investigation and why the
|
|
|
|
|
|
// same test file passed on one platform and failed on another at the
|
|
|
|
|
|
// commit that added it. One line at startup makes it a glance.
|
|
|
|
|
|
//
|
|
|
|
|
|
// fprintf(stderr), not STARTLOG or Log.tinyprintf. Neither reaches
|
|
|
|
|
|
// anything at this point in LoadGame -- I checked both, including the
|
|
|
|
|
|
// neighbouring Log.tinyprintf("LOADING: ...") calls, and none of them
|
|
|
|
|
|
// produce output the harness or an operator can see. Which also means
|
|
|
|
|
|
// the STARTLOG failure paths above this have never been visible to
|
|
|
|
|
|
// anyone. stderr is what the SQLite migration notices already use from
|
|
|
|
|
|
// this same phase, and the smoke harness captures it.
|
|
|
|
|
|
//
|
|
|
|
|
|
fprintf(stderr, "Comsys: using %s implementation.\n",
|
|
|
|
|
|
(nullptr != mudstate.pIComsysControl) ? "module" : "built-in engine");
|
|
|
|
|
|
|
2026-07-25 08:00:15 -06:00
|
|
|
|
// Mail module.
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
//
|
|
|
|
|
|
mr = mux_CreateInstance(CID_Mail, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IMailControl,
|
|
|
|
|
|
reinterpret_cast<void **>(&mudstate.pIMailControl));
|
2026-07-25 08:00:15 -06:00
|
|
|
|
if (MUX_SUCCEEDED(mr) && nullptr != mudstate.pIMailControl)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
mux_IMailStorage *pMailStorage = nullptr;
|
|
|
|
|
|
mr = mux_CreateInstance(CID_MailStorage, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IMailStorage,
|
|
|
|
|
|
reinterpret_cast<void **>(&pMailStorage));
|
2026-07-25 08:00:15 -06:00
|
|
|
|
if (MUX_SUCCEEDED(mr) && nullptr != pMailStorage)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
mr = mudstate.pIMailControl->Initialize(pMailStorage,
|
|
|
|
|
|
mudconf.mail_expiration, mudconf.mail_max_per_player);
|
|
|
|
|
|
pMailStorage->Release();
|
2026-07-25 08:00:15 -06:00
|
|
|
|
pMailStorage = nullptr;
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "MOD");
|
2026-03-10 10:50:00 -06:00
|
|
|
|
log_text(T("Mail module initialized (via engine storage)."));
|
2026-03-10 10:39:15 -06:00
|
|
|
|
ENDLOG;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "MOD");
|
2026-07-25 08:00:15 -06:00
|
|
|
|
log_printf(T("Mail module Initialize failed (mr=%d); using built-in."), mr);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
ENDLOG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
mudstate.pIMailControl->Release();
|
|
|
|
|
|
mudstate.pIMailControl = nullptr;
|
2026-03-10 10:39:15 -06:00
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
2026-03-27 16:23:30 -06:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "MOD");
|
2026-07-25 08:00:15 -06:00
|
|
|
|
log_printf(T("Mail storage interface creation failed (mr=%d); using built-in."), mr);
|
2026-03-27 16:23:30 -06:00
|
|
|
|
ENDLOG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
mudstate.pIMailControl->Release();
|
|
|
|
|
|
mudstate.pIMailControl = nullptr;
|
2026-03-27 16:23:30 -06:00
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
2026-07-25 08:00:15 -06:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.pIMailControl = nullptr;
|
|
|
|
|
|
}
|
fix(modules): say which comsys/mail implementation is live (#1581)
#1581's item 1: the silent fallback in discover_comsys_mail_modules. Both
subsystems have two implementations that demonstrably disagree -- #1564 (the
module never wrote HISTORY_n), #1585 (LOG_TIMESTAMPS), #1572 (four MOGRIFY
hooks), #1587 (mail message size) -- and nothing said which one a server got.
Now, at startup:
Comsys: using module implementation.
Mail: using built-in engine implementation.
Both outcomes, not only the failure, because "which one am I running" is the
question and a silence that means success is still a silence.
## The log call is fprintf(stderr), and that is not laziness
I tried STARTLOG first, matching the failure paths already in that function.
No output. Then Log.tinyprintf, matching the surrounding LoadGame code. Also
no output -- and the control settles it: the neighbouring
Log.tinyprintf(T("LOADING: %s" ENDLINE), mudconf.mail_db);
produces nothing either, in muxscript or through the smoke harness.
So there is no working log channel at this point in LoadGame, and the
consequence is worth recording: **the STARTLOG failure paths already in
discover_comsys_mail_modules have never been visible to anyone.** Three
existing branches that report "using built-in" on module-init failure emit
nothing at all. That is a second, quieter instance of the same defect #1581
is about.
fprintf(stderr) is what CSQLiteDB::MigrateSchema already uses from this same
phase, and it is what the harness captures.
## Answering the question #1581 could not
The issue establishes Windows behaviour and says: "What I could not determine
is what Linux does -- and that is the question this issue needs answered
first." I cannot answer for Linux either, but macOS arm64 now reports, and
the answer is configuration-driven rather than platform-driven:
bare config, no module directives -> built-in engine, both subsystems
module comsys_mod / module mail_mod -> module, both subsystems
So the modules do NOT load by default from game/bin; they load when asked for.
tools/Smoke:145-147 generates those directives, so the suite exercises the
module path here -- and tools/Makesmoke does not, so the upload phase
exercises the built-in. The harness has been running both implementations all
along, in different phases.
Note the Windows finding in #1581 predates #1569, which fixed the missing
DCL_EXPORT on the module entry points -- the reason the DLLs exported nothing
and could not load. Whether Windows now matches this wants re-measuring
there; this change makes that a one-line check.
Suite: 1560 passed / 0 failed / 320 of 320.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 10:21:54 -06:00
|
|
|
|
|
|
|
|
|
|
// Same for mail -- two implementations, and #1587 records them disagreeing
|
|
|
|
|
|
// on message size (#1581).
|
|
|
|
|
|
//
|
|
|
|
|
|
// fprintf(stderr), not STARTLOG or Log.tinyprintf. Neither reaches
|
|
|
|
|
|
// anything at this point in LoadGame -- I checked both, including the
|
|
|
|
|
|
// neighbouring Log.tinyprintf("LOADING: ...") calls, and none of them
|
|
|
|
|
|
// produce output the harness or an operator can see. Which also means
|
|
|
|
|
|
// the STARTLOG failure paths above this have never been visible to
|
|
|
|
|
|
// anyone. stderr is what the SQLite migration notices already use from
|
|
|
|
|
|
// this same phase, and the smoke harness captures it.
|
|
|
|
|
|
//
|
|
|
|
|
|
fprintf(stderr, "Mail: using %s implementation.\n",
|
|
|
|
|
|
(nullptr != mudstate.pIMailControl) ? "module" : "built-in engine");
|
2026-07-25 08:00:15 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::LoadGame(const UTF8 *configFile,
|
|
|
|
|
|
const UTF8 *inputDb, bool bMinDB)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(inputDb);
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr;
|
|
|
|
|
|
|
|
|
|
|
|
// Wire up alloc.cpp's output callback so @list buffers works.
|
|
|
|
|
|
//
|
|
|
|
|
|
g_alloc_notify_fn = engine_alloc_notify;
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize engine-owned pools. These depend on engine types
|
|
|
|
|
|
// (BOOLEXP, BQUE) that the caller (driver or script binary)
|
|
|
|
|
|
// cannot see without including engine headers.
|
|
|
|
|
|
//
|
|
|
|
|
|
pool_init(POOL_BOOL, sizeof(BOOLEXP));
|
|
|
|
|
|
pool_init(POOL_QENTRY, sizeof(BQUE));
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize engine subsystems.
|
|
|
|
|
|
//
|
|
|
|
|
|
tcache_init();
|
|
|
|
|
|
pcache_init();
|
|
|
|
|
|
cf_init();
|
|
|
|
|
|
init_cmdtab();
|
|
|
|
|
|
init_flagtab();
|
|
|
|
|
|
init_powertab();
|
|
|
|
|
|
init_functab();
|
|
|
|
|
|
init_attrtab();
|
|
|
|
|
|
|
|
|
|
|
|
// Read configuration file.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != configFile)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudconf.config_file = StringClone(configFile);
|
|
|
|
|
|
}
|
fix(conf): make an unreadable configuration file fatal (#1601)
cf_read() returns -1 when the top-level config cannot be read, and its only
call site -- CGameEngine::LoadGame -- discarded that. Both callers therefore
took the success branch and the game came up on compiled-in defaults.
On netmux a mistyped -c path produced a live server: it bound the default
port 2860 and served a two-object database while the real one sat untouched,
having logged one CNF/NFND line between two INI/LOAD lines that read like
success. On muxscript it exited 0 and printed "loaded game from ...", so a
harness could not distinguish a green run against the intended database from
one against an empty default.
LoadGame now returns MUX_E_NOTFOUND, and both callers name the config file
rather than reporting only "LoadGame failed (-9)" -- this is the startup
failure most likely to be a simple typo.
Two cases stay deliberately non-fatal. An unrecognized directive still only
logs: games carry config files forward across releases, and cf_include
discards cf_set()'s per-line return, which is load-bearing rather than a
matching oversight. An empty file still succeeds, and is the supported way
to ask for the compiled-in defaults on purpose.
Also in cf_include, where fgets returns nothing:
- fopen() succeeds on a directory on Linux and macOS and only the read
fails, so `-c somedir` was indistinguishable from an empty file and was
silently accepted. Checking ferror() separates the two.
- the early return skipped the fclose the normal path does, leaking the
handle on every empty include.
tests/config/run.sh covers all six corners, wired in as `make test-config`.
The good-config case asserts mud_name actually took effect rather than just
that the process exited 0 -- exit status alone cannot tell "read the config"
from "silently used defaults", which is the bug itself. Verified to fail
against the unfixed engine (2 of 6, the two non-fatal cases still passing).
make test green: 1560/1560 on both smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:20:21 -06:00
|
|
|
|
|
fix(conf): a config file that cannot be opened is fatal (#1601)
cf_read() has always returned cf_include()'s -1 when the config file cannot
be opened, and its only call site -- in CGameEngine::LoadGame -- discarded
it. LoadGame returned MUX_S_OK, and both callers took the success branch.
netmux is the serious victim: it booted and *listened*, on whatever
database the compiled-in defaults name rather than the one the operator
asked for. Measured before this change, `netmux -c nosuch.conf` ran until
a 20-second timeout killed it. After, it exits 2 in 30ms with no listener
opened. muxscript printed "loaded game from" and exited 0, so a harness
could not distinguish a green run against the intended database from a
green run against an empty default one; it now exits 2 and prints no such
line.
-1 means exactly "could not open the file". cf_include returns it only
for that and for being called outside configuration reading; an
unparseable *directive* inside a file that does exist returns 0 and stays
non-fatal. Keeping that distinction is deliberate -- games carry old
config files naming directives this build no longer knows, and refusing to
boot over one would be a compatibility break, while failing to find the
file at all is never what anyone wanted.
Reported on stderr as well as through the log. cf_log_notfound's STARTLOG
is not visible at this point in LoadGame for muxscript, which is half of
why this stayed hidden -- the diagnostic existed and nobody saw it.
Both callers already tested MUX_FAILED and refused, so propagating the
failure was the only missing link; neither caller needed changing.
netmux -c nosuch.conf before: rc=124 (still listening at 20s)
after: rc=2 in 30ms, no listener
muxscript -c nosuch.conf before: rc=0, "loaded game from"
after: rc=2, no such line
muxscript -c p.conf unchanged: rc=0, "loaded game from"
Negative control: with the return value discarded again, netmux boots on
the nonexistent config and keeps running.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:17:01 -06:00
|
|
|
|
// A config file that cannot be opened is fatal (#1601).
|
fix(conf): make an unreadable configuration file fatal (#1601)
cf_read() returns -1 when the top-level config cannot be read, and its only
call site -- CGameEngine::LoadGame -- discarded that. Both callers therefore
took the success branch and the game came up on compiled-in defaults.
On netmux a mistyped -c path produced a live server: it bound the default
port 2860 and served a two-object database while the real one sat untouched,
having logged one CNF/NFND line between two INI/LOAD lines that read like
success. On muxscript it exited 0 and printed "loaded game from ...", so a
harness could not distinguish a green run against the intended database from
one against an empty default.
LoadGame now returns MUX_E_NOTFOUND, and both callers name the config file
rather than reporting only "LoadGame failed (-9)" -- this is the startup
failure most likely to be a simple typo.
Two cases stay deliberately non-fatal. An unrecognized directive still only
logs: games carry config files forward across releases, and cf_include
discards cf_set()'s per-line return, which is load-bearing rather than a
matching oversight. An empty file still succeeds, and is the supported way
to ask for the compiled-in defaults on purpose.
Also in cf_include, where fgets returns nothing:
- fopen() succeeds on a directory on Linux and macOS and only the read
fails, so `-c somedir` was indistinguishable from an empty file and was
silently accepted. Checking ferror() separates the two.
- the early return skipped the fclose the normal path does, leaking the
handle on every empty include.
tests/config/run.sh covers all six corners, wired in as `make test-config`.
The good-config case asserts mud_name actually took effect rather than just
that the process exited 0 -- exit status alone cannot tell "read the config"
from "silently used defaults", which is the bug itself. Verified to fail
against the unfixed engine (2 of 6, the two non-fatal cases still passing).
make test green: 1560/1560 on both smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:20:21 -06:00
|
|
|
|
//
|
2026-07-27 18:01:51 +00:00
|
|
|
|
// cf_read() returns -1 when the top-level configuration file could not
|
|
|
|
|
|
// be opened (or, after the ferror check in cf_include, when a path that
|
|
|
|
|
|
// fopen accepts cannot be read -- e.g. a directory on Linux/macOS).
|
|
|
|
|
|
// Errors in individual directives take a different route: cf_include
|
|
|
|
|
|
// calls cf_set() per line and discards its return, so a config carrying
|
|
|
|
|
|
// a directive this build no longer recognizes still reads as success.
|
|
|
|
|
|
// That asymmetry is deliberate -- games carry config files forward across
|
|
|
|
|
|
// releases, and refusing to boot over one stale line would be worse than
|
|
|
|
|
|
// logging it. An empty regular file also succeeds and is the supported
|
|
|
|
|
|
// way to ask for compiled-in defaults on purpose.
|
fix(conf): make an unreadable configuration file fatal (#1601)
cf_read() returns -1 when the top-level config cannot be read, and its only
call site -- CGameEngine::LoadGame -- discarded that. Both callers therefore
took the success branch and the game came up on compiled-in defaults.
On netmux a mistyped -c path produced a live server: it bound the default
port 2860 and served a two-object database while the real one sat untouched,
having logged one CNF/NFND line between two INI/LOAD lines that read like
success. On muxscript it exited 0 and printed "loaded game from ...", so a
harness could not distinguish a green run against the intended database from
one against an empty default.
LoadGame now returns MUX_E_NOTFOUND, and both callers name the config file
rather than reporting only "LoadGame failed (-9)" -- this is the startup
failure most likely to be a simple typo.
Two cases stay deliberately non-fatal. An unrecognized directive still only
logs: games carry config files forward across releases, and cf_include
discards cf_set()'s per-line return, which is load-bearing rather than a
matching oversight. An empty file still succeeds, and is the supported way
to ask for the compiled-in defaults on purpose.
Also in cf_include, where fgets returns nothing:
- fopen() succeeds on a directory on Linux and macOS and only the read
fails, so `-c somedir` was indistinguishable from an empty file and was
silently accepted. Checking ferror() separates the two.
- the early return skipped the fclose the normal path does, leaking the
handle on every empty include.
tests/config/run.sh covers all six corners, wired in as `make test-config`.
The good-config case asserts mud_name actually took effect rather than just
that the process exited 0 -- exit status alone cannot tell "read the config"
from "silently used defaults", which is the bug itself. Verified to fail
against the unfixed engine (2 of 6, the two non-fatal cases still passing).
make test green: 1560/1560 on both smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:20:21 -06:00
|
|
|
|
//
|
2026-07-27 18:01:51 +00:00
|
|
|
|
// Discarding this return made an unreadable config silently non-fatal:
|
|
|
|
|
|
// netmux booted and *listened* on compiled-in defaults; muxscript printed
|
|
|
|
|
|
// "loaded game from" and exited 0. Callers already refuse on MUX_FAILED.
|
fix(conf): a config file that cannot be opened is fatal (#1601)
cf_read() has always returned cf_include()'s -1 when the config file cannot
be opened, and its only call site -- in CGameEngine::LoadGame -- discarded
it. LoadGame returned MUX_S_OK, and both callers took the success branch.
netmux is the serious victim: it booted and *listened*, on whatever
database the compiled-in defaults name rather than the one the operator
asked for. Measured before this change, `netmux -c nosuch.conf` ran until
a 20-second timeout killed it. After, it exits 2 in 30ms with no listener
opened. muxscript printed "loaded game from" and exited 0, so a harness
could not distinguish a green run against the intended database from a
green run against an empty default one; it now exits 2 and prints no such
line.
-1 means exactly "could not open the file". cf_include returns it only
for that and for being called outside configuration reading; an
unparseable *directive* inside a file that does exist returns 0 and stays
non-fatal. Keeping that distinction is deliberate -- games carry old
config files naming directives this build no longer knows, and refusing to
boot over one would be a compatibility break, while failing to find the
file at all is never what anyone wanted.
Reported on stderr as well as through the log. cf_log_notfound's STARTLOG
is not visible at this point in LoadGame for muxscript, which is half of
why this stayed hidden -- the diagnostic existed and nobody saw it.
Both callers already tested MUX_FAILED and refused, so propagating the
failure was the only missing link; neither caller needed changing.
netmux -c nosuch.conf before: rc=124 (still listening at 20s)
after: rc=2 in 30ms, no listener
muxscript -c nosuch.conf before: rc=0, "loaded game from"
after: rc=2, no such line
muxscript -c p.conf unchanged: rc=0, "loaded game from"
Negative control: with the return value discarded again, netmux boots on
the nonexistent config and keeps running.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:17:01 -06:00
|
|
|
|
//
|
2026-07-27 18:01:51 +00:00
|
|
|
|
// stderr: STARTLOG is not visible at this phase under muxscript, which is
|
|
|
|
|
|
// half of why the defect stayed hidden. Callers also name the file.
|
fix(conf): make an unreadable configuration file fatal (#1601)
cf_read() returns -1 when the top-level config cannot be read, and its only
call site -- CGameEngine::LoadGame -- discarded that. Both callers therefore
took the success branch and the game came up on compiled-in defaults.
On netmux a mistyped -c path produced a live server: it bound the default
port 2860 and served a two-object database while the real one sat untouched,
having logged one CNF/NFND line between two INI/LOAD lines that read like
success. On muxscript it exited 0 and printed "loaded game from ...", so a
harness could not distinguish a green run against the intended database from
one against an empty default.
LoadGame now returns MUX_E_NOTFOUND, and both callers name the config file
rather than reporting only "LoadGame failed (-9)" -- this is the startup
failure most likely to be a simple typo.
Two cases stay deliberately non-fatal. An unrecognized directive still only
logs: games carry config files forward across releases, and cf_include
discards cf_set()'s per-line return, which is load-bearing rather than a
matching oversight. An empty file still succeeds, and is the supported way
to ask for the compiled-in defaults on purpose.
Also in cf_include, where fgets returns nothing:
- fopen() succeeds on a directory on Linux and macOS and only the read
fails, so `-c somedir` was indistinguishable from an empty file and was
silently accepted. Checking ferror() separates the two.
- the early return skipped the fclose the normal path does, leaking the
handle on every empty include.
tests/config/run.sh covers all six corners, wired in as `make test-config`.
The good-config case asserts mud_name actually took effect rather than just
that the process exited 0 -- exit status alone cannot tell "read the config"
from "silently used defaults", which is the bug itself. Verified to fail
against the unfixed engine (2 of 6, the two non-fatal cases still passing).
make test green: 1560/1560 on both smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:20:21 -06:00
|
|
|
|
//
|
|
|
|
|
|
if (0 != cf_read())
|
|
|
|
|
|
{
|
fix(conf): a config file that cannot be opened is fatal (#1601)
cf_read() has always returned cf_include()'s -1 when the config file cannot
be opened, and its only call site -- in CGameEngine::LoadGame -- discarded
it. LoadGame returned MUX_S_OK, and both callers took the success branch.
netmux is the serious victim: it booted and *listened*, on whatever
database the compiled-in defaults name rather than the one the operator
asked for. Measured before this change, `netmux -c nosuch.conf` ran until
a 20-second timeout killed it. After, it exits 2 in 30ms with no listener
opened. muxscript printed "loaded game from" and exited 0, so a harness
could not distinguish a green run against the intended database from a
green run against an empty default one; it now exits 2 and prints no such
line.
-1 means exactly "could not open the file". cf_include returns it only
for that and for being called outside configuration reading; an
unparseable *directive* inside a file that does exist returns 0 and stays
non-fatal. Keeping that distinction is deliberate -- games carry old
config files naming directives this build no longer knows, and refusing to
boot over one would be a compatibility break, while failing to find the
file at all is never what anyone wanted.
Reported on stderr as well as through the log. cf_log_notfound's STARTLOG
is not visible at this point in LoadGame for muxscript, which is half of
why this stayed hidden -- the diagnostic existed and nobody saw it.
Both callers already tested MUX_FAILED and refused, so propagating the
failure was the only missing link; neither caller needed changing.
netmux -c nosuch.conf before: rc=124 (still listening at 20s)
after: rc=2 in 30ms, no listener
muxscript -c nosuch.conf before: rc=0, "loaded game from"
after: rc=2, no such line
muxscript -c p.conf unchanged: rc=0, "loaded game from"
Negative control: with the return value discarded again, netmux boots on
the nonexistent config and keeps running.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:17:01 -06:00
|
|
|
|
fprintf(stderr,
|
2026-07-27 18:01:51 +00:00
|
|
|
|
"FATAL: cannot read configuration file '%s' -- refusing to start "
|
|
|
|
|
|
"on compiled-in defaults.\n",
|
fix(conf): a config file that cannot be opened is fatal (#1601)
cf_read() has always returned cf_include()'s -1 when the config file cannot
be opened, and its only call site -- in CGameEngine::LoadGame -- discarded
it. LoadGame returned MUX_S_OK, and both callers took the success branch.
netmux is the serious victim: it booted and *listened*, on whatever
database the compiled-in defaults name rather than the one the operator
asked for. Measured before this change, `netmux -c nosuch.conf` ran until
a 20-second timeout killed it. After, it exits 2 in 30ms with no listener
opened. muxscript printed "loaded game from" and exited 0, so a harness
could not distinguish a green run against the intended database from a
green run against an empty default one; it now exits 2 and prints no such
line.
-1 means exactly "could not open the file". cf_include returns it only
for that and for being called outside configuration reading; an
unparseable *directive* inside a file that does exist returns 0 and stays
non-fatal. Keeping that distinction is deliberate -- games carry old
config files naming directives this build no longer knows, and refusing to
boot over one would be a compatibility break, while failing to find the
file at all is never what anyone wanted.
Reported on stderr as well as through the log. cf_log_notfound's STARTLOG
is not visible at this point in LoadGame for muxscript, which is half of
why this stayed hidden -- the diagnostic existed and nobody saw it.
Both callers already tested MUX_FAILED and refused, so propagating the
failure was the only missing link; neither caller needed changing.
netmux -c nosuch.conf before: rc=124 (still listening at 20s)
after: rc=2 in 30ms, no listener
muxscript -c nosuch.conf before: rc=0, "loaded game from"
after: rc=2, no such line
muxscript -c p.conf unchanged: rc=0, "loaded game from"
Negative control: with the return value discarded again, netmux boots on
the nonexistent config and keeps running.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:17:01 -06:00
|
|
|
|
reinterpret_cast<const char *>(mudconf.config_file));
|
fix(conf): make an unreadable configuration file fatal (#1601)
cf_read() returns -1 when the top-level config cannot be read, and its only
call site -- CGameEngine::LoadGame -- discarded that. Both callers therefore
took the success branch and the game came up on compiled-in defaults.
On netmux a mistyped -c path produced a live server: it bound the default
port 2860 and served a two-object database while the real one sat untouched,
having logged one CNF/NFND line between two INI/LOAD lines that read like
success. On muxscript it exited 0 and printed "loaded game from ...", so a
harness could not distinguish a green run against the intended database from
one against an empty default.
LoadGame now returns MUX_E_NOTFOUND, and both callers name the config file
rather than reporting only "LoadGame failed (-9)" -- this is the startup
failure most likely to be a simple typo.
Two cases stay deliberately non-fatal. An unrecognized directive still only
logs: games carry config files forward across releases, and cf_include
discards cf_set()'s per-line return, which is load-bearing rather than a
matching oversight. An empty file still succeeds, and is the supported way
to ask for the compiled-in defaults on purpose.
Also in cf_include, where fgets returns nothing:
- fopen() succeeds on a directory on Linux and macOS and only the read
fails, so `-c somedir` was indistinguishable from an empty file and was
silently accepted. Checking ferror() separates the two.
- the early return skipped the fclose the normal path does, leaking the
handle on every empty include.
tests/config/run.sh covers all six corners, wired in as `make test-config`.
The good-config case asserts mud_name actually took effect rather than just
that the process exited 0 -- exit status alone cannot tell "read the config"
from "silently used defaults", which is the bug itself. Verified to fail
against the unfixed engine (2 of 6, the two non-fatal cases still passing).
make test green: 1560/1560 on both smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:20:21 -06:00
|
|
|
|
STARTLOG(LOG_ALWAYS, "CNF", "LOAD");
|
|
|
|
|
|
log_printf(T("Fatal: configuration file '%s' could not be read."),
|
|
|
|
|
|
mudconf.config_file);
|
|
|
|
|
|
ENDLOG;
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
2026-07-25 08:00:15 -06:00
|
|
|
|
|
2026-07-26 21:18:43 +00:00
|
|
|
|
// Optional gettext domain for server notifies (#1419). Softcode #-1
|
|
|
|
|
|
// diagnostics use S_() and stay English. Locale packs live under
|
|
|
|
|
|
// game/locale/<lang>/LC_MESSAGES/tinymux.mo (cwd is typically game/).
|
|
|
|
|
|
// Path is not a player message — do not run it through T()/gettext.
|
|
|
|
|
|
//
|
feat(nls): `language` in netmux.conf, and one catalogue reader everywhere (#1702)
Selecting a language was the one server-wide setting that did not live in
netmux.conf, and the documented way to do it did not work on every platform.
LANGUAGE=ko ./bin/netmux # mux/po/README.md
libintl honours LANGUAGE. The built-in reader -- the MSVC path, since the
Windows SDK ships no <libintl.h> -- reads only LC_ALL, LC_MESSAGES and LANG,
so on Windows that selected nothing and the server ran English with no
diagnostic. Two readers, two answers, one documented recipe.
## `language <catalogue>`
language ko
names game/locale/ko/LC_MESSAGES/tinymux.mo. Empty (every existing config)
keeps the environment behaviour exactly. It is passed to mux_nls_init() as a
parameter rather than exported into the environment, so selection cannot mean
different things on different platforms.
## One reader
libintl is no longer used for lookup: mux_nls.cpp does not include
<libintl.h> and calls neither gettext nor ngettext. The built-in MO reader
serves every platform.
That removes gettext's rule that the C/POSIX locale suppresses translation
outright. Via libintl, `language ko` in a bare service environment could
only print "cannot take effect" and continue in English; opening a catalogue
by path has no such rule, so it now simply works:
LANG=C LC_ALL=C, language ko -> kowidget을(를) 사물 #12(으)로 만들었습니다
The format is unchanged -- .po/.mo, msgfmt, xgettext, msgmerge, Plural-Forms.
Only the runtime is ours now, so a translator's workflow is untouched.
## Plural-Forms, which is what libintl was really providing
The built-in reader returned English plural forms regardless of catalogue, so
Windows already got "1 vs many" for all 18 msgid_plural entries -- wrong for
ko, which declares nplurals=1. Making it the only reader meant implementing
the rule properly: a recursive-descent evaluator over the grammar gettext
uses (?: || && == != < > <= >= + - * / % ! and parens, over n).
Total and bounded by construction: no allocation, division and modulo by zero
yield 0, an unparseable rule or an out-of-range form falls back to form 0.
tests/nls/test_plural.cpp covers it directly -- 43 cases over en, ko, fr, ru,
pl and ar rules plus div-by-zero, unbalanced parens, truncated ternaries,
garbage and out-of-range forms. Reaching plural_eval() means including
mux_nls.cpp, the same way tests/dbt/test_interp.cpp reaches its file-static
mem_check. Wired as `make test-nls-plural`.
Mutation-checked rather than assumed: flipping % to / in the evaluator fails
13 cases across ru/pl/ar while en and ko still pass, so the suite measures
the thing it is named after.
## A regression the matrix caught
Dropping libintl dropped its LANGUAGE support, and mo_language() had never
read that variable -- so LANGUAGE=ko, the recipe in mux/po/README.md, would
have silently stopped working on Unix. mo_language() now reads gettext's
documented order (LANGUAGE, LC_ALL, LC_MESSAGES, LANG) and takes the first
entry of a colon list, which also gives Windows LANGUAGE support it never
had.
Verified, each row distinguishing a fix from a no-op:
language ko, no LANGUAGE Korean <- the case that failed
no directive, nothing set English <- no regression
no directive, LANGUAGE=ko Korean <- env still works
no directive, LANGUAGE=ko:fr Korean <- priority list
language ko vs LANGUAGE=xx Korean <- directive outranks env
language ko under LANG=C Korean <- impossible via libintl
tests/nls/run.sh gains the directive cases (7 total, from 5); confirmed both
fail when the directive is disabled while the pre-existing cases pass.
make test: Smoke 1561 x3, tests/nls 7 cases, ko 4 cases, plural 43 cases.
TESTEXIT=0.
Documented in wizhelp (& LANGUAGE) and mux/po/README.md.
Per-player locale is deliberately not addressed; the parameter form leaves
room for it without committing to a design.
Refs #1419, #1444, #1473, #1523, #1580, #1622, #1702.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 13:04:47 -06:00
|
|
|
|
// mudconf.language is populated: this runs after the configuration file
|
|
|
|
|
|
// has been read (#1702). Empty means follow the environment.
|
|
|
|
|
|
//
|
|
|
|
|
|
mux_nls_init(reinterpret_cast<const UTF8 *>("locale"), mudconf.language);
|
2026-07-26 21:18:43 +00:00
|
|
|
|
mux_nls_refresh_messages();
|
|
|
|
|
|
|
2026-07-25 08:00:15 -06:00
|
|
|
|
// Sync function aliases into JIT lookup table now that config
|
|
|
|
|
|
// (including alias.conf) has been processed.
|
|
|
|
|
|
engine_api_sync_aliases();
|
|
|
|
|
|
|
|
|
|
|
|
// Sync core-layer globals from mudconf after config is loaded.
|
|
|
|
|
|
//
|
|
|
|
|
|
g_float_precision = mudconf.float_precision;
|
|
|
|
|
|
g_space_compress = mudstate.bStandAlone || mudconf.space_compress;
|
|
|
|
|
|
|
|
|
|
|
|
// Comsys/mail modules are discovered after SQLite is open (#1190).
|
|
|
|
|
|
// Leaving the control pointers null keeps the built-in paths active
|
|
|
|
|
|
// until discover_comsys_mail_modules() runs at the end of LoadGame.
|
|
|
|
|
|
//
|
|
|
|
|
|
mudstate.pIComsysControl = nullptr;
|
|
|
|
|
|
mudstate.pIMailControl = nullptr;
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
2026-03-18 07:26:58 -06:00
|
|
|
|
// Try to discover the Lua scripting module.
|
|
|
|
|
|
//
|
|
|
|
|
|
mudstate.pILuaControl = nullptr;
|
|
|
|
|
|
mr = mux_CreateInstance(CID_LuaMod, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_ILuaControl,
|
|
|
|
|
|
reinterpret_cast<void **>(&mudstate.pILuaControl));
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
2026-03-18 08:59:21 -06:00
|
|
|
|
mudstate.pILuaControl->SetLimits(
|
|
|
|
|
|
mudconf.lua_instruction_limit,
|
|
|
|
|
|
mudconf.lua_memory_limit);
|
|
|
|
|
|
|
2026-03-18 07:26:58 -06:00
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "MOD");
|
|
|
|
|
|
log_text(T("Lua scripting module discovered."));
|
|
|
|
|
|
ENDLOG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
mr = mux_CreateInstance(CID_QueryServer, nullptr, UseSlaveProcess,
|
|
|
|
|
|
IID_IQueryControl,
|
|
|
|
|
|
(void **)&mudstate.pIQueryControl);
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
mr = mudstate.pIQueryControl->Connect(mudconf.sql_server,
|
|
|
|
|
|
mudconf.sql_database, mudconf.sql_user, mudconf.sql_password);
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_IQuerySink *pIQuerySink = nullptr;
|
|
|
|
|
|
mr = mux_CreateInstance(CID_QueryClient, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IQuerySink,
|
|
|
|
|
|
(void **)&pIQuerySink);
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
mr = mudstate.pIQueryControl->Advise(pIQuerySink);
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
pIQuerySink->Release();
|
|
|
|
|
|
pIQuerySink = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.pIQueryControl->Release();
|
|
|
|
|
|
mudstate.pIQueryControl = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
|
2026-07-26 21:52:48 -06:00
|
|
|
|
log_printf(T("Couldn’t connect sink to server (%d)."), mr);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
ENDLOG;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.pIQueryControl->Release();
|
|
|
|
|
|
mudstate.pIQueryControl = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
|
2026-07-26 21:52:48 -06:00
|
|
|
|
log_printf(T("Couldn’t create Query Sink (%d)."), mr);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
ENDLOG;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.pIQueryControl->Release();
|
|
|
|
|
|
mudstate.pIQueryControl = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
|
2026-07-26 21:52:48 -06:00
|
|
|
|
log_printf(T("Couldn’t connect to Query Server (%d)."), mr);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
ENDLOG;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
|
2026-07-26 21:52:48 -06:00
|
|
|
|
log_text(T("Couldn’t create interface to Query Server."));
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
ENDLOG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(INLINESQL)
|
|
|
|
|
|
init_sql();
|
|
|
|
|
|
#endif // INLINESQL
|
|
|
|
|
|
|
|
|
|
|
|
fcache_init();
|
|
|
|
|
|
helpindex_init();
|
|
|
|
|
|
|
Move version, COM pointers, and logout_cmd_htab to driver-local
version/short_ver strings, pIGameEngine, pIPlayerSession, and
logout_cmd_htab are driver-owned state that doesn't need to live
in mudstate. Move them to driverstate.h / bsd.cpp globals.
pIPlayerSession creation moves from engine_com.cpp (LoadGame) to
driver.cpp (after GetConfig), matching pIGameEngine's pattern.
Remaining mudstate refs (43) are genuinely shared state: access_list,
shutdown_flag, debug_cmd, bStandAlone, signal/dump coordination,
and command execution context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 14:40:14 -06:00
|
|
|
|
// PlayerSession interface is now created by the driver after LoadGame.
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
|
|
|
|
|
// Open or create the SQLite database.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (bMinDB)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Remove the SQLite database to start fresh.
|
|
|
|
|
|
//
|
fix(engine): bound the .sqlite path derivation (#1411)
Two sites derived the ".sqlite" sibling of mudconf.indb by open-coding
the same replace-or-append with strcpy/strcat, neither checking the
remaining capacity:
mux/modules/engine/engine_com.cpp (bMinDB RemoveFile) SIZEOF_PATHNAME
mux/modules/engine/attrcache.cpp (cache_init) LBUF_SIZE
input_database is cf_string_dyn with a maximum of SIZEOF_PATHNAME, so its
content can be SIZEOF_PATHNAME-1 characters. ".sqlite" is seven more plus
a terminator, so the engine_com buffer overflows in both branches: the
append writes eight bytes past a full buffer, and even the ".db" branch
that replaces three characters with seven needs four bytes it may not
have. attrcache was safe only because indb is capped well below
LBUF_SIZE -- an implicit cross-module invariant, not a check.
Replaced with one bounds-checked derive_sqlite_path() in sqlite_backend,
which both TUs already include. It returns false and leaves the buffer
untouched when the result will not fit; callers log and take a defined
path -- cache_init returns HF_OPEN_STATUS_ERROR after releasing the
backend it had just allocated, and the bMinDB case skips the removal,
since a path that cannot be formed names no file to remove. No new
abort: the failure is reported, not asserted.
Behaviour is unchanged for every input that fits, including the edge
cases the old `n > 3` test produced -- ".db" appends rather than
replaces, "a.dbx" and "db" append -- so this is not a silent change of
the derived name.
Verified with ASan on a standalone harness carrying both versions, an
input_database at exactly the configured maximum:
old, no .db suffix heap-buffer-overflow, WRITE of size 8
old, .db suffix heap-buffer-overflow, WRITE of size 8
new, both returns false, no diagnostic
and the normal cases: netmux.db -> netmux.sqlite, netmux ->
netmux.sqlite, data/netmux.db -> data/netmux.sqlite, plus the exact-fit
boundary accepted and one byte over rejected.
smoke 1489/1489 on both routes, 0 crashes, 314/314 dispatched.
Closes #1411.
2026-07-26 13:59:30 -06:00
|
|
|
|
// Bounds-checked: input_database may be SIZEOF_PATHNAME-1 long,
|
|
|
|
|
|
// and ".sqlite" needs seven more bytes plus a terminator, so the
|
|
|
|
|
|
// old strcpy/strcat pair wrote past this buffer (#1411). If it
|
|
|
|
|
|
// does not fit there is nothing to remove under that name, so
|
|
|
|
|
|
// say so and carry on rather than starting from a bad path.
|
|
|
|
|
|
//
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
char sqlitefile[SIZEOF_PATHNAME];
|
fix(engine): bound the .sqlite path derivation (#1411)
Two sites derived the ".sqlite" sibling of mudconf.indb by open-coding
the same replace-or-append with strcpy/strcat, neither checking the
remaining capacity:
mux/modules/engine/engine_com.cpp (bMinDB RemoveFile) SIZEOF_PATHNAME
mux/modules/engine/attrcache.cpp (cache_init) LBUF_SIZE
input_database is cf_string_dyn with a maximum of SIZEOF_PATHNAME, so its
content can be SIZEOF_PATHNAME-1 characters. ".sqlite" is seven more plus
a terminator, so the engine_com buffer overflows in both branches: the
append writes eight bytes past a full buffer, and even the ".db" branch
that replaces three characters with seven needs four bytes it may not
have. attrcache was safe only because indb is capped well below
LBUF_SIZE -- an implicit cross-module invariant, not a check.
Replaced with one bounds-checked derive_sqlite_path() in sqlite_backend,
which both TUs already include. It returns false and leaves the buffer
untouched when the result will not fit; callers log and take a defined
path -- cache_init returns HF_OPEN_STATUS_ERROR after releasing the
backend it had just allocated, and the bMinDB case skips the removal,
since a path that cannot be formed names no file to remove. No new
abort: the failure is reported, not asserted.
Behaviour is unchanged for every input that fits, including the edge
cases the old `n > 3` test produced -- ".db" appends rather than
replaces, "a.dbx" and "db" append -- so this is not a silent change of
the derived name.
Verified with ASan on a standalone harness carrying both versions, an
input_database at exactly the configured maximum:
old, no .db suffix heap-buffer-overflow, WRITE of size 8
old, .db suffix heap-buffer-overflow, WRITE of size 8
new, both returns false, no diagnostic
and the normal cases: netmux.db -> netmux.sqlite, netmux ->
netmux.sqlite, data/netmux.db -> data/netmux.sqlite, plus the exact-fit
boundary accepted and one byte over rejected.
smoke 1489/1489 on both routes, 0 crashes, 314/314 dispatched.
Closes #1411.
2026-07-26 13:59:30 -06:00
|
|
|
|
if (derive_sqlite_path(sqlitefile, sizeof(sqlitefile), mudconf.indb))
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
fix(engine): bound the .sqlite path derivation (#1411)
Two sites derived the ".sqlite" sibling of mudconf.indb by open-coding
the same replace-or-append with strcpy/strcat, neither checking the
remaining capacity:
mux/modules/engine/engine_com.cpp (bMinDB RemoveFile) SIZEOF_PATHNAME
mux/modules/engine/attrcache.cpp (cache_init) LBUF_SIZE
input_database is cf_string_dyn with a maximum of SIZEOF_PATHNAME, so its
content can be SIZEOF_PATHNAME-1 characters. ".sqlite" is seven more plus
a terminator, so the engine_com buffer overflows in both branches: the
append writes eight bytes past a full buffer, and even the ".db" branch
that replaces three characters with seven needs four bytes it may not
have. attrcache was safe only because indb is capped well below
LBUF_SIZE -- an implicit cross-module invariant, not a check.
Replaced with one bounds-checked derive_sqlite_path() in sqlite_backend,
which both TUs already include. It returns false and leaves the buffer
untouched when the result will not fit; callers log and take a defined
path -- cache_init returns HF_OPEN_STATUS_ERROR after releasing the
backend it had just allocated, and the bMinDB case skips the removal,
since a path that cannot be formed names no file to remove. No new
abort: the failure is reported, not asserted.
Behaviour is unchanged for every input that fits, including the edge
cases the old `n > 3` test produced -- ".db" appends rather than
replaces, "a.dbx" and "db" append -- so this is not a silent change of
the derived name.
Verified with ASan on a standalone harness carrying both versions, an
input_database at exactly the configured maximum:
old, no .db suffix heap-buffer-overflow, WRITE of size 8
old, .db suffix heap-buffer-overflow, WRITE of size 8
new, both returns false, no diagnostic
and the normal cases: netmux.db -> netmux.sqlite, netmux ->
netmux.sqlite, data/netmux.db -> data/netmux.sqlite, plus the exact-fit
boundary accepted and one byte over rejected.
smoke 1489/1489 on both routes, 0 crashes, 314/314 dispatched.
Closes #1411.
2026-07-26 13:59:30 -06:00
|
|
|
|
RemoveFile((UTF8 *)sqlitefile);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
fix(engine): bound the .sqlite path derivation (#1411)
Two sites derived the ".sqlite" sibling of mudconf.indb by open-coding
the same replace-or-append with strcpy/strcat, neither checking the
remaining capacity:
mux/modules/engine/engine_com.cpp (bMinDB RemoveFile) SIZEOF_PATHNAME
mux/modules/engine/attrcache.cpp (cache_init) LBUF_SIZE
input_database is cf_string_dyn with a maximum of SIZEOF_PATHNAME, so its
content can be SIZEOF_PATHNAME-1 characters. ".sqlite" is seven more plus
a terminator, so the engine_com buffer overflows in both branches: the
append writes eight bytes past a full buffer, and even the ".db" branch
that replaces three characters with seven needs four bytes it may not
have. attrcache was safe only because indb is capped well below
LBUF_SIZE -- an implicit cross-module invariant, not a check.
Replaced with one bounds-checked derive_sqlite_path() in sqlite_backend,
which both TUs already include. It returns false and leaves the buffer
untouched when the result will not fit; callers log and take a defined
path -- cache_init returns HF_OPEN_STATUS_ERROR after releasing the
backend it had just allocated, and the bMinDB case skips the removal,
since a path that cannot be formed names no file to remove. No new
abort: the failure is reported, not asserted.
Behaviour is unchanged for every input that fits, including the edge
cases the old `n > 3` test produced -- ".db" appends rather than
replaces, "a.dbx" and "db" append -- so this is not a silent change of
the derived name.
Verified with ASan on a standalone harness carrying both versions, an
input_database at exactly the configured maximum:
old, no .db suffix heap-buffer-overflow, WRITE of size 8
old, .db suffix heap-buffer-overflow, WRITE of size 8
new, both returns false, no diagnostic
and the normal cases: netmux.db -> netmux.sqlite, netmux ->
netmux.sqlite, data/netmux.db -> data/netmux.sqlite, plus the exact-fit
boundary accepted and one byte over rejected.
smoke 1489/1489 on both routes, 0 crashes, 314/314 dispatched.
Closes #1411.
2026-07-26 13:59:30 -06:00
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
|
|
|
|
|
|
log_text(T("input_database too long to derive a .sqlite path; not removed."));
|
|
|
|
|
|
ENDLOG;
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int ccPageFile = init_dbfile(mudconf.indb);
|
|
|
|
|
|
if (HF_OPEN_STATUS_ERROR == ccPageFile)
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
|
2026-07-26 21:52:48 -06:00
|
|
|
|
log_text(T("Couldn’t open storage backend."));
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
ENDLOG;
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mudstate.record_players = 0;
|
|
|
|
|
|
bool bLoadedGameFromSQLite = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (bMinDB)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!db_make_minimal())
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD")
|
|
|
|
|
|
log_text(T("Failed to build minimal database."));
|
|
|
|
|
|
ENDLOG
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bDoFlatfileLoad = true;
|
|
|
|
|
|
if (HF_OPEN_STATUS_OLD == ccPageFile)
|
|
|
|
|
|
{
|
|
|
|
|
|
int sqlite_load_rc = sqlite_load_game();
|
|
|
|
|
|
if (sqlite_load_rc > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Warm start: loaded everything from SQLite.
|
|
|
|
|
|
// No flatfile needed.
|
|
|
|
|
|
//
|
|
|
|
|
|
bDoFlatfileLoad = false;
|
|
|
|
|
|
bLoadedGameFromSQLite = true;
|
2026-06-07 13:23:12 -05:00
|
|
|
|
|
|
|
|
|
|
// Make the precedence visible: a populated SQLite database
|
|
|
|
|
|
// shadows the configured flatfile, so a freshly dropped-in
|
|
|
|
|
|
// flatfile would otherwise appear to be silently ignored.
|
|
|
|
|
|
//
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD")
|
|
|
|
|
|
log_text(T("Warm-started from SQLite database; the flatfile "));
|
|
|
|
|
|
log_text(mudconf.indb);
|
2026-06-10 15:00:33 -06:00
|
|
|
|
log_text(T(" was not consulted. The SQLite database is authoritative; to rebase"
|
|
|
|
|
|
" on a flatfile, export current state with db_unload first, then db_load -f."));
|
2026-06-07 13:23:12 -05:00
|
|
|
|
ENDLOG
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
else if (sqlite_load_rc < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD")
|
|
|
|
|
|
log_text(T("SQLite warm-load failed."));
|
|
|
|
|
|
ENDLOG
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int ccInFile = LOAD_GAME_SUCCESS;
|
|
|
|
|
|
if (bDoFlatfileLoad)
|
|
|
|
|
|
{
|
|
|
|
|
|
ccInFile = load_game(ccPageFile);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (LOAD_GAME_NO_INPUT_DB == ccInFile)
|
|
|
|
|
|
{
|
|
|
|
|
|
// The input file didn't exist.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (HF_OPEN_STATUS_NEW == ccPageFile)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Since the .db file didn't exist, and the .pag/.dir files
|
|
|
|
|
|
// were newly created, just create a minimal DB.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!db_make_minimal())
|
|
|
|
|
|
{
|
|
|
|
|
|
ccInFile = LOAD_GAME_LOADING_PROBLEM;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
ccInFile = LOAD_GAME_SUCCESS;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (ccInFile != LOAD_GAME_SUCCESS)
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD")
|
2026-07-26 21:52:48 -06:00
|
|
|
|
log_text(T("Couldn’t load: "));
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
log_text(mudconf.indb);
|
|
|
|
|
|
ENDLOG
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 00:33:05 -06:00
|
|
|
|
// Repair any out-of-range object field dbrefs from the loaded database
|
2026-06-12 02:06:50 -05:00
|
|
|
|
// (#810). No-op for a valid DB; prevents OOB at use (e.g. DOLIST chain
|
2026-06-12 00:33:05 -06:00
|
|
|
|
// walks) for a corrupt/malicious one. Covers both the warm (SQLite) and
|
|
|
|
|
|
// cold (flatfile) load paths, which both leave db[] populated here.
|
|
|
|
|
|
//
|
|
|
|
|
|
db_validate_refs();
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
// Warm-start path skips load_game(); explicitly load aux SQLite/flatfile
|
|
|
|
|
|
// subsystems here.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (bLoadedGameFromSQLite)
|
|
|
|
|
|
{
|
|
|
|
|
|
int load_comsys_rc = sqlite_load_comsys();
|
|
|
|
|
|
if (load_comsys_rc < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD")
|
|
|
|
|
|
log_text(T("SQLite comsys load failed."));
|
|
|
|
|
|
ENDLOG
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (0 == load_comsys_rc)
|
|
|
|
|
|
{
|
|
|
|
|
|
load_comsys(mudconf.comsys_db);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int load_mail_rc = sqlite_load_mail();
|
|
|
|
|
|
if (load_mail_rc < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "LOAD")
|
|
|
|
|
|
log_text(T("SQLite mail load failed."));
|
|
|
|
|
|
ENDLOG
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (0 == load_mail_rc)
|
|
|
|
|
|
{
|
|
|
|
|
|
FILE *f;
|
|
|
|
|
|
if (mux_fopen(&f, mudconf.mail_db, T("rb")))
|
|
|
|
|
|
{
|
|
|
|
|
|
setvbuf(f, nullptr, _IOFBF, 16384);
|
|
|
|
|
|
Log.tinyprintf(T("LOADING: %s" ENDLINE), mudconf.mail_db);
|
|
|
|
|
|
load_mail(f);
|
|
|
|
|
|
Log.tinyprintf(T("LOADING: %s (done)" ENDLINE),
|
|
|
|
|
|
mudconf.mail_db);
|
2026-03-20 06:07:24 -06:00
|
|
|
|
mux_fclose(f);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 08:00:15 -06:00
|
|
|
|
// #1190: Discover comsys/mail modules only after SQLite is open and
|
|
|
|
|
|
// game + aux data are loaded. Failure clears the control pointers so
|
|
|
|
|
|
// the built-in engine paths remain authoritative.
|
|
|
|
|
|
//
|
|
|
|
|
|
discover_comsys_mail_modules();
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::Startup(void)
|
|
|
|
|
|
{
|
2026-03-09 13:44:58 -06:00
|
|
|
|
// Set up the COM bridge so engine-side code can reach the driver's
|
|
|
|
|
|
// connection manager through free-function stubs.
|
|
|
|
|
|
//
|
|
|
|
|
|
conn_bridge_init();
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Guest.StartUp();
|
|
|
|
|
|
|
|
|
|
|
|
// Do a consistency check and set up the freelist.
|
|
|
|
|
|
//
|
|
|
|
|
|
do_dbck(NOTHING, NOTHING, NOTHING, 0, 0);
|
|
|
|
|
|
|
Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
|
|
|
|
route_init();
|
2026-03-29 08:54:51 -06:00
|
|
|
|
walk_init();
|
Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
ValidateConfigurationDbrefs();
|
|
|
|
|
|
process_preload();
|
|
|
|
|
|
|
|
|
|
|
|
local_startup();
|
|
|
|
|
|
|
|
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
|
|
|
|
while (nullptr != p)
|
|
|
|
|
|
{
|
|
|
|
|
|
p->pSink->startup();
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
init_timer();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::RunTasks(CLinearTimeAbsolute <aNow)
|
|
|
|
|
|
{
|
|
|
|
|
|
scheduler.RunTasks(ltaNow);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::UpdateQuotas(CLinearTimeAbsolute <aLast,
|
|
|
|
|
|
const CLinearTimeAbsolute <aCurrent)
|
|
|
|
|
|
{
|
|
|
|
|
|
update_quotas(ltaLast, ltaCurrent);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::WhenNext(CLinearTimeAbsolute *pltaWhen)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pltaWhen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
2026-06-04 12:11:13 -05:00
|
|
|
|
|
|
|
|
|
|
// Propagate whether a next task exists. When the scheduler is empty,
|
|
|
|
|
|
// scheduler.WhenNext() returns false and leaves *pltaWhen untouched, so
|
|
|
|
|
|
// we must report failure rather than success-with-garbage. Callers
|
|
|
|
|
|
// (muxscript's loop, the netmux idle loop) branch on MUX_FAILED to mean
|
|
|
|
|
|
// "no task scheduled."
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!scheduler.WhenNext(pltaWhen))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::HasPendingUserTasks(bool *pbResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pbResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pbResult = scheduler.HasPendingUserTasks();
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::DumpDatabase(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
dump_database();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-16 17:42:02 -06:00
|
|
|
|
#if defined(TINYMUX_JIT)
|
2026-03-15 22:33:58 -06:00
|
|
|
|
extern void dbt_compile_cleanup(void);
|
2026-03-16 17:42:02 -06:00
|
|
|
|
#endif
|
2026-03-15 22:33:58 -06:00
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
MUX_RESULT CGameEngine::Shutdown(void)
|
|
|
|
|
|
{
|
2026-03-16 17:42:02 -06:00
|
|
|
|
#if defined(TINYMUX_JIT)
|
2026-03-15 22:33:58 -06:00
|
|
|
|
dbt_compile_cleanup();
|
2026-03-16 17:42:02 -06:00
|
|
|
|
#endif
|
2026-03-29 08:54:51 -06:00
|
|
|
|
walk_shutdown();
|
Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
|
|
|
|
route_shutdown();
|
2026-03-09 13:44:58 -06:00
|
|
|
|
conn_bridge_final();
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
local_shutdown();
|
|
|
|
|
|
|
|
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
|
|
|
|
while (nullptr != p)
|
|
|
|
|
|
{
|
|
|
|
|
|
p->pSink->shutdown();
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 14:34:03 -06:00
|
|
|
|
static void safe_copy_str(UTF8 *dst, size_t dstSize, const UTF8 *src)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != src)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_strncpy(dst, src, dstSize - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
dst[0] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::GetConfig(DRIVER_CONFIG *pConfig)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pConfig)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
memset(pConfig, 0, sizeof(DRIVER_CONFIG));
|
|
|
|
|
|
|
|
|
|
|
|
// Ports and networking.
|
|
|
|
|
|
//
|
|
|
|
|
|
pConfig->nPorts = static_cast<int>(mudconf.ports.size());
|
|
|
|
|
|
if (pConfig->nPorts > DRIVER_CONFIG_MAX_PORTS)
|
|
|
|
|
|
{
|
|
|
|
|
|
pConfig->nPorts = DRIVER_CONFIG_MAX_PORTS;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int i = 0; i < pConfig->nPorts; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
pConfig->ports[i] = mudconf.ports[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(UNIX_SSL) || defined(_WIN32)
|
|
|
|
|
|
pConfig->nSslPorts = static_cast<int>(mudconf.sslPorts.size());
|
|
|
|
|
|
if (pConfig->nSslPorts > DRIVER_CONFIG_MAX_PORTS)
|
|
|
|
|
|
{
|
|
|
|
|
|
pConfig->nSslPorts = DRIVER_CONFIG_MAX_PORTS;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int i = 0; i < pConfig->nSslPorts; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
pConfig->sslPorts[i] = mudconf.sslPorts[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_strncpy(pConfig->ssl_certificate_file, mudconf.ssl_certificate_file, sizeof(pConfig->ssl_certificate_file) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->ssl_certificate_key, mudconf.ssl_certificate_key, sizeof(pConfig->ssl_certificate_key) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->ssl_certificate_password, mudconf.ssl_certificate_password, sizeof(pConfig->ssl_certificate_password) - 1);
|
|
|
|
|
|
#else
|
|
|
|
|
|
pConfig->nSslPorts = 0;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
safe_copy_str(pConfig->ip_address, sizeof(pConfig->ip_address), mudconf.ip_address);
|
|
|
|
|
|
pConfig->use_hostname = mudconf.use_hostname;
|
|
|
|
|
|
pConfig->retry_limit = mudconf.retry_limit;
|
feat(net): per-source failed-login throttle
retry_limit (3) is per-SOCKET: after three bad passwords the connection
closes and the attacker reconnects for three more. Nothing remembered
anything across connections, so brute-force-by-reconnect was unbounded.
Add that memory as a token bucket per source address, consumed only by
failed connect attempts:
login_fail_limit default 10, 0 = off -- burst of failed logins per source
login_fail_period default 60 seconds -- interval over which it refills
Sustained rate is limit/period, so the default is 10/minute against a
previously unlimited rate. Checked in check_connect BEFORE ConnectPlayer, so
a throttled source also stops costing a password hash per guess. Guests are
exempt (fixed password, separately bounded by the guest pool). On refusal the
socket is left open and retries_left untouched -- the attempt never reached a
password check, so it is not a failed login -- and conn_timeout still reaps an
idle one.
Two shapes of this defense are actively harmful in a MUSH and are not used:
* Per-ACCOUNT lockout. Player names are public (WHO, in-game, the
directory), so anyone could lock any player -- including a wizard -- out
of their own game by spamming failures at their name. That trades a
brute-force risk for a guaranteed griefing tool.
* A delay before answering a failed login. This server is single-threaded;
sleeping to slow one attacker stops the world for every other player. The
throttle must be non-blocking, so it refuses rather than stalls.
Keying: IPv6 by /64, not by address. One IPv6 customer normally holds a whole
/64, so a single host can source 2**64 addresses -- keying on the full address
would let one attacker both evade the throttle and flood the table with
single-use entries. mux_sockaddr::source_key() returns the 4-byte v4 address
or the 8-byte v6 /64 prefix; differing lengths keep the families from
colliding.
The table must not become the resource it protects: a fixed 512-slot array
scanned linearly, no allocation and no growth, consulted only on login
attempts (already bounded by max_preauth_per_site). When full, eviction takes
the LEAST suspicious entry (fullest bucket, oldest as tie-break) so table
pressure never costs us the record of an active attacker.
The dorm/NAT cost is real and deliberate: an exhausted bucket briefly refuses
legitimate players from a shared address, including ones typing the correct
password. It is bounded (continuous refill; seconds, not a lockout), the
default is generous relative to how often real players mistype, and admins can
widen or disable it. There is deliberately no "this source already has an
authenticated session" exemption -- it would read as dorm-friendly while
handing a full bypass to an existing player going after someone else's account.
Verified on a live netmux at limit 3 / period 600s: guesses 1-3 rejected
normally, 4-6 refused with the wait message, each from a FRESH connection --
reconnecting no longer buys a fresh batch. At limit 5 / period 20s the budget
demonstrably refills. Both the new CON/THR line and the earlier NET/SITE
pre-auth line confirmed to emit. Regression: smoke 1319/1319, stress 8/8,
netaddr 46/46 (+7 source_key tests), ganl 14/14.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 08:46:41 -06:00
|
|
|
|
pConfig->login_fail_limit = mudconf.login_fail_limit;
|
|
|
|
|
|
pConfig->login_fail_period = mudconf.login_fail_period;
|
feat(net): per-source connection-rate limit (connect/disconnect churn)
The Rhost item our own follow-up list skipped, and the vector was genuinely
uncovered. max_preauth_sitecons bounds how many pre-auth connections are held
AT ONCE; login_fail_limit bounds FAILED LOGINS. An attacker who connects and
immediately disconnects -- never logging in, never failing a login -- is
touched by neither, while every cycle still costs an accept, a DESC, the
welcome screen's file dump, the site checks and a log line.
max_lastsite_cnt default 40, 0 = off -- connections per source per window
min_con_attempt default 60 seconds -- window over which it refills
Both names are Rhost's. Checked in the GANL accept path before the pre-auth
cap (it is the cheaper test). Only ACCEPTED connections are charged: a refused
attempt costs the attacker nothing extra, but charging it would hold a shared
address at zero for as long as one attacker kept trying, starving the
legitimate users behind it of the refill.
Two deliberate differences from Rhost:
* The response is a transient refusal, not their lastsite_paranoia
auto-register/auto-forbid. A permanent sitelock earned by a burst is
precisely the wrong answer on a shared address: one abuser in a dorm would
lock out the whole building until an admin undid it by hand. A refusal
that heals as the bucket refills costs a legitimate player seconds. Admins
wanting the permanent form already have forbid_site, now with graduated
thresholds. We therefore ship no lastsite_paranoia knob at all rather than
one whose values mean something different from theirs.
* The counting is a true per-source bucket. Rhost keeps a single "last site"
slot, so only CONSECUTIVE connections from one address count and one
interleaved connection from anywhere else resets the counter -- trivially
walked around by alternating two addresses. Keyed per source (v6 by /64),
ours cannot be reset that way.
Default is 40/60 rather than Rhost's 20/60 because ours counts strictly harder
AND is on by default where theirs is gated behind lastsite_paranoia 0. 40/60
still cuts churn from unlimited to 40/minute -- three orders of magnitude --
while clearing the burst that matters: a whole dorm reconnecting after a
reboot. Verified 30 connect-and-login cycles from one address at the shipping
default: 30 accepted, 0 refused.
The failed-login bucket was generalized rather than copied: one source_bucket
mechanism (fixed 512-slot table, lazy refill, least-suspicious eviction, /64
v6 keying) now backs both g_login_fail and g_connect_rate.
Verified at max_lastsite_cnt 10 / min_con_attempt 60 with the pre-auth cap and
login-fail throttle both DISABLED -- proving this defense alone catches it --
pure connect/disconnect churn ran 11 cycles then was refused, NET/RATE damped
by nospam_connect to one line. At 5/10s, 5 of 12 rapid cycles refused and a
connection 8s later succeeded. Regression at the shipping default: smoke
1319/1319, stress 8/8, netaddr 57/57, ganl 14/14.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 13:43:52 -06:00
|
|
|
|
pConfig->max_lastsite_cnt = mudconf.max_lastsite_cnt;
|
|
|
|
|
|
pConfig->min_con_attempt = mudconf.min_con_attempt;
|
feat(net): damp connection-refusal logging (nospam_connect); adopt Rhost naming
Surveyed RhostMUSH at ce5226ff for prior art on connection/login DoS defenses.
Two results change this work.
1. Refusal logging is itself a DoS vector, and we had missed it.
Refusing a connection still costs a log write, so a flood we successfully
refuse fills a disk instead. Rhost's nospam_connect exists solely for this and
their help text names it outright ("Real twinkish players may try multiple
connects to overload a log file"). Both of our new defenses -- the pre-auth
cap and the failed-login throttle -- logged every single refusal, converting a
connection flood into a disk flood.
Adopt their parameter by name and semantics:
nospam_connect 0 = log every refusal
1 = log the first of a consecutive run from one address,
then one summary line when the run ends (default)
2 = do not log refusals
Two deliberate differences from Rhost:
* Default 1, not their 0. The collapse loses no signal -- first line plus an
exact count -- so there is no reason to ship the hole open.
* Flushed on the periodic idle sweep, not only when a run ends. Rhost's
refusals happen at accept, so an accepted connection ends the run. Ours
also refuse at LOGIN, and every login attempt arrives on a freshly
accepted socket -- flushing on accept would end the run before every
single refusal and defeat the damping entirely. Flushing on the sweep also
bounds refusal logging by TIME rather than by the attacker's rate, which
is the property actually wanted: a sustained attacker produces neither a
different address nor a successful login, so the count would otherwise sit
unreported for the whole attack.
State is one address slot plus a counter, as Rhost does it -- the anti-flood
measure cannot itself be flooded.
2. Match Rhost's configuration vocabulary where the knob is the same thing.
nospam_connect is adopted verbatim. max_preauth_per_site is renamed to
max_preauth_sitecons to sit in Rhost's max_sitecons family -- but deliberately
NOT named max_sitecons, because theirs caps ALL connections from a site and
ours caps only unauthenticated ones. An identical name would be a false friend
that reads as configured while behaving differently.
The survey also validates two earlier decisions: Rhost has no per-account
lockout anywhere and no per-IP failed-login throttle either, and every Rhost
auto-generated ACL entry keys IPv6 on the full /128 address against a list with
no cap or expiry -- exactly the evade-and-flood hole our /64 source_key avoids.
Verified: 9 consecutive refusals collapse to 1 full line plus a periodic
summary ("[127.0.0.1] Connection refused [total 4 more times]."), and the
renamed knob still refuses correctly. Regression: smoke 1319/1319, stress 8/8,
netaddr 46/46, ganl 14/14.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 12:20:11 -06:00
|
|
|
|
pConfig->nospam_connect = mudconf.nospam_connect;
|
2026-03-09 14:34:03 -06:00
|
|
|
|
pConfig->idle_timeout = mudconf.idle_timeout;
|
|
|
|
|
|
pConfig->conn_timeout = mudconf.conn_timeout;
|
|
|
|
|
|
pConfig->cmd_quota_max = mudconf.cmd_quota_max;
|
|
|
|
|
|
pConfig->output_limit = mudconf.output_limit;
|
2026-07-21 19:57:22 -06:00
|
|
|
|
pConfig->input_limit = mudconf.input_limit;
|
feat(net): damp connection-refusal logging (nospam_connect); adopt Rhost naming
Surveyed RhostMUSH at ce5226ff for prior art on connection/login DoS defenses.
Two results change this work.
1. Refusal logging is itself a DoS vector, and we had missed it.
Refusing a connection still costs a log write, so a flood we successfully
refuse fills a disk instead. Rhost's nospam_connect exists solely for this and
their help text names it outright ("Real twinkish players may try multiple
connects to overload a log file"). Both of our new defenses -- the pre-auth
cap and the failed-login throttle -- logged every single refusal, converting a
connection flood into a disk flood.
Adopt their parameter by name and semantics:
nospam_connect 0 = log every refusal
1 = log the first of a consecutive run from one address,
then one summary line when the run ends (default)
2 = do not log refusals
Two deliberate differences from Rhost:
* Default 1, not their 0. The collapse loses no signal -- first line plus an
exact count -- so there is no reason to ship the hole open.
* Flushed on the periodic idle sweep, not only when a run ends. Rhost's
refusals happen at accept, so an accepted connection ends the run. Ours
also refuse at LOGIN, and every login attempt arrives on a freshly
accepted socket -- flushing on accept would end the run before every
single refusal and defeat the damping entirely. Flushing on the sweep also
bounds refusal logging by TIME rather than by the attacker's rate, which
is the property actually wanted: a sustained attacker produces neither a
different address nor a successful login, so the count would otherwise sit
unreported for the whole attack.
State is one address slot plus a counter, as Rhost does it -- the anti-flood
measure cannot itself be flooded.
2. Match Rhost's configuration vocabulary where the knob is the same thing.
nospam_connect is adopted verbatim. max_preauth_per_site is renamed to
max_preauth_sitecons to sit in Rhost's max_sitecons family -- but deliberately
NOT named max_sitecons, because theirs caps ALL connections from a site and
ours caps only unauthenticated ones. An identical name would be a false friend
that reads as configured while behaving differently.
The survey also validates two earlier decisions: Rhost has no per-account
lockout anywhere and no per-IP failed-login throttle either, and every Rhost
auto-generated ACL entry keys IPv6 on the full /128 address against a list with
no cap or expiry -- exactly the evade-and-flood hole our /64 source_key avoids.
Verified: 9 consecutive refusals collapse to 1 full line plus a periodic
summary ("[127.0.0.1] Connection refused [total 4 more times]."), and the
renamed knob still refuses correctly. Regression: smoke 1319/1319, stress 8/8,
netaddr 46/46, ganl 14/14.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 12:20:11 -06:00
|
|
|
|
pConfig->max_preauth_sitecons = mudconf.max_preauth_sitecons;
|
feat(net): proto_detect_window — make the 500ms connect wait configurable (#2193)
Every classic telnet connect waited a hardcoded 500ms in total silence
before the server sent its first byte. Measured over 30 connects to an
idle server: min 500.6ms, p50 501.0ms, max 504.5ms -- the distribution is
the grace window itself. Base command latency on the same setup is ~51us.
The window exists so telnet negotiation cannot corrupt a WebSocket
handshake (#1074), and the incentives are inverted: WebSocket and TLS
clients speak first and are served immediately, while the classic MUD
client -- which waits for the server to speak, and is the primary audience
-- is the only kind that always pays in full.
Now `proto_detect_window`, in milliseconds, default 500. Nothing changes
for a site that does not touch it. 0 disables detection: the banner goes
out at accept, as in 2.13. That is the correct setting for a port that
never serves WebSocket -- it has nothing to detect and no reason to wait.
On keeping 500 as the default rather than shaving it: the window is NOT
covering a round trip, which is what the original issue text assumed. A
WebSocket client's GET rides directly behind the handshake's final ACK, so
the healthy case needs ~0ms however distant the client -- the RTT is
already spent by the time the window opens. What the window must survive
is that first packet being LOST, where the retransmit arrives on an RTO
that Linux floors at 200ms. 500 covers one retransmit with headroom. 100
would sit in the dead zone -- past every healthy client, short of every
retransmit -- and would break real WebSocket handshakes intermittently on
a lossy link. A slow banner is a far better failure mode than that.
The value was a literal in two places (the age-out sweep and the main-loop
timeout clamp) with nothing tying them together, so they could drift and
the clamp would silently stop bounding the sweep. One accessor now.
0 is expressed as arithmetic rather than a second finalize call site: with
the window at 0, `age >= 0` is true on the first sweep and the clamp drives
processEvents to a 0ms timeout, so the connection finalizes in the same
main-loop iteration it was accepted in. A shortcut around the sweep would
have had to duplicate both the #2018 exception barrier and the #1800
partial-preface replay.
IID_IGameEngine bumped C9D2 -> C9D3. The vtable is unchanged, but
GetConfig() memsets and fills sizeof(DRIVER_CONFIG) as the ENGINE sees it
into storage the DRIVER sized, so a size disagreement is an out-of-bounds
write rather than a wrong answer.
tests/scenario/proto_detect.py asserts it against a live server:
ok 1 - silent client waits the configured proto-detect window # 500 ms
ok 2 - client that speaks first is served without waiting # 0 ms
ok 3 - engine accepted the runtime @admin to 0 # config='0'
ok 4 - #2193 window 0 serves a silent client at accept # 0 ms
ok 5 - restoring the window restores the wait (live push, both ways)
Catch-verified by reverting only the driver's USE of the knob, leaving the
config plumbing intact -- the #1222 shape, where the engine reports the
change and the driver ignores it until restart:
ok 3 - engine accepted the runtime @admin to 0 # config='0'
not ok 4 - #2193 window 0 serves a silent client at accept (500 ms)
Clean rebuild, since DRIVER_CONFIG's layout changed (#2107).
make test: 36 targets, 34 passed, 2 skipped (NLS), 0 failed
config: jit=yes stubslave=yes nls=no realitylvls=yes wodrealms=yes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 10:01:47 -06:00
|
|
|
|
pConfig->proto_detect_window = mudconf.proto_detect_window;
|
2026-03-09 14:34:03 -06:00
|
|
|
|
pConfig->default_charset = mudconf.default_charset;
|
|
|
|
|
|
pConfig->max_players = mudconf.max_players;
|
|
|
|
|
|
pConfig->control_flags = mudconf.control_flags;
|
|
|
|
|
|
|
|
|
|
|
|
// Guest configuration.
|
|
|
|
|
|
//
|
|
|
|
|
|
mux_strncpy(pConfig->guest_prefix, mudconf.guest_prefix, sizeof(pConfig->guest_prefix) - 1);
|
|
|
|
|
|
pConfig->number_guests = mudconf.number_guests;
|
|
|
|
|
|
pConfig->guest_char = mudconf.guest_char;
|
|
|
|
|
|
|
|
|
|
|
|
// Game identity.
|
|
|
|
|
|
//
|
|
|
|
|
|
mux_strncpy(pConfig->mud_name, mudconf.mud_name, sizeof(pConfig->mud_name) - 1);
|
|
|
|
|
|
|
|
|
|
|
|
// File paths (pointer fields copied into fixed buffers).
|
|
|
|
|
|
//
|
|
|
|
|
|
safe_copy_str(pConfig->pid_file, sizeof(pConfig->pid_file), mudconf.pid_file);
|
|
|
|
|
|
safe_copy_str(pConfig->log_dir, sizeof(pConfig->log_dir), mudconf.log_dir);
|
|
|
|
|
|
safe_copy_str(pConfig->config_file, sizeof(pConfig->config_file), mudconf.config_file);
|
|
|
|
|
|
|
|
|
|
|
|
// Messages.
|
|
|
|
|
|
//
|
|
|
|
|
|
mux_strncpy(pConfig->crash_msg, mudconf.crash_msg, sizeof(pConfig->crash_msg) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->downmotd_msg, mudconf.downmotd_msg, sizeof(pConfig->downmotd_msg) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->fullmotd_msg, mudconf.fullmotd_msg, sizeof(pConfig->fullmotd_msg) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->pueblo_msg, mudconf.pueblo_msg, sizeof(pConfig->pueblo_msg) - 1);
|
|
|
|
|
|
|
|
|
|
|
|
// Timing (raw 100ns ticks).
|
|
|
|
|
|
//
|
|
|
|
|
|
pConfig->max_cmdsecs = mudconf.max_cmdsecs.Return100ns();
|
|
|
|
|
|
pConfig->rpt_cmdsecs = mudconf.rpt_cmdsecs.Return100ns();
|
|
|
|
|
|
pConfig->timeslice = mudconf.timeslice.Return100ns();
|
Feature: Add MSSP, GMCP, @protect, and benchmark()
Four new features identified from the TinyMUSH/PennMUSH/RhostMUSH survey:
MSSP (MUD Server Status Protocol, telnet option 70):
- Server sends structured key-value data (NAME, PLAYERS, UPTIME, PORT,
CODEBASE, FAMILY) to MU* directory crawlers on IAC DO MSSP
- Stateless response via send_mssp() in telnet.cpp using g_dc config basket
- start_time_utc added to DRIVER_CONFIG for uptime calculation
GMCP (Generic MUD Communication Protocol, telnet option 201):
- Protocol negotiation: server offers WILL GMCP, tracks gmcp_enabled per DESC
- Inbound: GMCP subneg queued as synthetic "\x01GMCP" command, dispatched
to handle_gmcp() which fires A_GMCP attribute with %0=package %1=json
- Outbound: gmcp(<player>, <package>, <json>) softcode function sends
GMCP frames to all GMCP-enabled descriptors via SendGmcp COM method
- Full COM architecture: mux_IConnectionManager::SendGmcp in driver,
send_gmcp() bridge in engine, CConnectionManager impl in modules.cpp
@protect (player name reservation):
- @protect[/add] <name>, @protect/del <name>, @protect/list [<player>]
- A_PROTECTNAME attribute (234) stores space-separated protected names
- protectname_check() hooked into create_player() and do_name()
- max_name_protect config param (default 5)
benchmark(<expression>, <iterations>):
- FN_NOEVAL, CA_PUBLIC, 10000 iteration cap
- Uses clock_gettime(CLOCK_MONOTONIC) / QueryPerformanceCounter
- Returns elapsed seconds as floating point
Also fixes: add unicode_tables.c to libmux.so LIBMUX_C_SRC (resolves
pre-existing tr_tolower_sbt etc. link errors).
505/505 smoke tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 22:08:55 -06:00
|
|
|
|
pConfig->start_time_utc = mudstate.start_time.ReturnSeconds();
|
2026-03-09 14:34:03 -06:00
|
|
|
|
|
|
|
|
|
|
// Behavior flags and limits.
|
|
|
|
|
|
//
|
|
|
|
|
|
pConfig->sig_action = mudconf.sig_action;
|
|
|
|
|
|
pConfig->fork_dump = mudconf.fork_dump;
|
|
|
|
|
|
pConfig->name_spaces = mudconf.name_spaces;
|
|
|
|
|
|
pConfig->idle_wiz_dark = mudconf.idle_wiz_dark;
|
|
|
|
|
|
pConfig->reset_players = mudconf.reset_players;
|
|
|
|
|
|
pConfig->site_chars = mudconf.site_chars;
|
|
|
|
|
|
pConfig->start_room = mudconf.start_room;
|
|
|
|
|
|
|
|
|
|
|
|
// SQL.
|
|
|
|
|
|
//
|
|
|
|
|
|
mux_strncpy(pConfig->sql_server, mudconf.sql_server, sizeof(pConfig->sql_server) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->sql_user, mudconf.sql_user, sizeof(pConfig->sql_user) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->sql_password, mudconf.sql_password, sizeof(pConfig->sql_password) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->sql_database, mudconf.sql_database, sizeof(pConfig->sql_database) - 1);
|
|
|
|
|
|
|
|
|
|
|
|
// Mail relay.
|
|
|
|
|
|
//
|
|
|
|
|
|
mux_strncpy(pConfig->mail_server, mudconf.mail_server, sizeof(pConfig->mail_server) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->mail_sendaddr, mudconf.mail_sendaddr, sizeof(pConfig->mail_sendaddr) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->mail_sendname, mudconf.mail_sendname, sizeof(pConfig->mail_sendname) - 1);
|
|
|
|
|
|
mux_strncpy(pConfig->mail_ehlo, mudconf.mail_ehlo, sizeof(pConfig->mail_ehlo) - 1);
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 14:26:32 -06:00
|
|
|
|
MUX_RESULT CGameEngine::MarkConnected(dbref player)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( Good_obj(player)
|
|
|
|
|
|
&& isPlayer(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
s_Connected(player);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 16:03:40 -06:00
|
|
|
|
MUX_RESULT CGameEngine::DumpChildExited(int child_pid)
|
|
|
|
|
|
{
|
Rework Windows build for component-based directory layout
Adapt all vcxproj files and solution to the new directory structure
(src/ driver, lib/ shared library, modules/engine/ game logic,
modules/{comsys,mail,exp3,sqlproxy,sqlslave}/ loadable modules).
Key changes:
- libmux.dll exports utility symbols via LIBMUX_API macro
(__declspec(dllexport) when BUILDING_LIBMUX, dllimport otherwise)
- LIBMUX_API added to all shared headers: stringutil.h, timeutil.h,
mathutil.h, utf8tables.h, svdhash.h, svdrand.h, sha1.h, alloc.h,
dbutil.h, core.h
- Per-file PreprocessorDefinitions in libmux.vcxproj inherit from
project-level via %(PreprocessorDefinitions)
- Driver factory declarations (CDriverControlFactory,
CConnectionManagerFactory) guarded with BUILDING_DRIVER
- PCG-XSH-RR-64/32 (pcg32) for Windows (no __int128 needed);
Unix PCG-XSL-RR-128/64 unchanged
- MSVC portability fixes: _strnicmp, _BitScanForward64, (std::min)(),
HAVE_WORKING_FORK guards, WINDOWS_FILES/UNIX_FILES ModuleAdd paths
- Remove stubslave.cpp and slave.cpp from netmux.vcxproj (separate
processes)
- Fix sqlproxy/sqlslave vcxproj relative paths for new layout
- Add ws2_32.lib to engine.vcxproj for socket functions
- Add strcasecmp/strtok_r/strndup compat shims for comsys/mail
Builds successfully: libmux.dll, engine.dll, netmux.exe, exp3.dll,
sqlproxy.dll, sqlslave.dll. Comsys/mail blocked on sqlite3 linking
architecture (need COM-mediated or independent sqlite3 linkage).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:38:57 -06:00
|
|
|
|
#if defined(HAVE_WORKING_FORK)
|
2026-03-09 16:03:40 -06:00
|
|
|
|
if (!mudstate.dumping)
|
|
|
|
|
|
{
|
2026-07-24 21:19:45 -06:00
|
|
|
|
return MUX_S_FALSE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// #1136: ignore reaps that are not the dump child once dumper is known.
|
|
|
|
|
|
// Under fork_dump the driver may report any child exit; a DNS slave or
|
|
|
|
|
|
// other helper must not clear dump state early. dumper == 0 means the
|
|
|
|
|
|
// classic race (SIGCHLD before fork() returned) — accept that child.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( 0 != mudstate.dumper
|
|
|
|
|
|
&& mudstate.dumper != static_cast<pid_t>(child_pid))
|
|
|
|
|
|
{
|
2026-03-09 16:03:40 -06:00
|
|
|
|
return MUX_S_FALSE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mudstate.dumped = child_pid;
|
|
|
|
|
|
if (mudstate.dumper == mudstate.dumped)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Normal completion — fork() returned before SIGCHLD.
|
|
|
|
|
|
//
|
|
|
|
|
|
mudstate.dumper = 0;
|
|
|
|
|
|
mudstate.dumped = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// SIGCHLD arrived before fork() returned the PID.
|
|
|
|
|
|
// dumped is set; fork_and_dump will notice on return.
|
|
|
|
|
|
//
|
|
|
|
|
|
}
|
|
|
|
|
|
mudstate.dumping = false;
|
|
|
|
|
|
|
|
|
|
|
|
local_dump_complete_signal();
|
|
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
|
|
|
|
while (nullptr != p)
|
|
|
|
|
|
{
|
|
|
|
|
|
p->pSink->dump_complete_signal();
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
Rework Windows build for component-based directory layout
Adapt all vcxproj files and solution to the new directory structure
(src/ driver, lib/ shared library, modules/engine/ game logic,
modules/{comsys,mail,exp3,sqlproxy,sqlslave}/ loadable modules).
Key changes:
- libmux.dll exports utility symbols via LIBMUX_API macro
(__declspec(dllexport) when BUILDING_LIBMUX, dllimport otherwise)
- LIBMUX_API added to all shared headers: stringutil.h, timeutil.h,
mathutil.h, utf8tables.h, svdhash.h, svdrand.h, sha1.h, alloc.h,
dbutil.h, core.h
- Per-file PreprocessorDefinitions in libmux.vcxproj inherit from
project-level via %(PreprocessorDefinitions)
- Driver factory declarations (CDriverControlFactory,
CConnectionManagerFactory) guarded with BUILDING_DRIVER
- PCG-XSH-RR-64/32 (pcg32) for Windows (no __int128 needed);
Unix PCG-XSL-RR-128/64 unchanged
- MSVC portability fixes: _strnicmp, _BitScanForward64, (std::min)(),
HAVE_WORKING_FORK guards, WINDOWS_FILES/UNIX_FILES ModuleAdd paths
- Remove stubslave.cpp and slave.cpp from netmux.vcxproj (separate
processes)
- Fix sqlproxy/sqlslave vcxproj relative paths for new layout
- Add ws2_32.lib to engine.vcxproj for socket functions
- Add strcasecmp/strtok_r/strndup compat shims for comsys/mail
Builds successfully: libmux.dll, engine.dll, netmux.exe, exp3.dll,
sqlproxy.dll, sqlslave.dll. Comsys/mail blocked on sqlite3 linking
architecture (need COM-mediated or independent sqlite3 linkage).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:38:57 -06:00
|
|
|
|
#else
|
|
|
|
|
|
UNUSED_PARAMETER(child_pid);
|
|
|
|
|
|
return MUX_S_FALSE;
|
|
|
|
|
|
#endif
|
2026-03-09 16:03:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 15:30:06 -06:00
|
|
|
|
MUX_RESULT CGameEngine::SetStartTime(const CLinearTimeAbsolute &time)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.start_time = time;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::GetStartTime(CLinearTimeAbsolute *pTime)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pTime)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pTime = mudstate.start_time;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::SetRestartTime(const CLinearTimeAbsolute &time)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.restart_time = time;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::SetRestartCount(unsigned int count)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.restart_count = count;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::GetRestartCount(unsigned int *pCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pCount = mudstate.restart_count;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::SetCpuCountFrom(const CLinearTimeAbsolute &time)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.cpu_count_from = time;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::SetRecordPlayers(int count)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.record_players = count;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::GetDoingHdr(UTF8 *buf, size_t bufSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == buf || 0 == bufSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_strncpy(buf, mudstate.doing_hdr, bufSize - 1);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::SetDoingHdr(const UTF8 *hdr, size_t len)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == hdr)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
size_t nCopy = len;
|
|
|
|
|
|
if (nCopy >= sizeof(mudstate.doing_hdr))
|
|
|
|
|
|
{
|
|
|
|
|
|
nCopy = sizeof(mudstate.doing_hdr) - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
memcpy(mudstate.doing_hdr, hdr, nCopy);
|
|
|
|
|
|
mudstate.doing_hdr[nCopy] = '\0';
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::GetRecordPlayers(int *pCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pCount = mudstate.record_players;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::GetBCanRestart(bool *pbCanRestart)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pbCanRestart)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pbCanRestart = mudstate.bCanRestart;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 19:40:40 -06:00
|
|
|
|
MUX_RESULT CGameEngine::CancelTask(void (*fpTask)(void *, int),
|
|
|
|
|
|
void *arg_voidptr, int arg_Integer)
|
|
|
|
|
|
{
|
|
|
|
|
|
scheduler.CancelTask(fpTask, arg_voidptr, arg_Integer);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::DeferImmediateTask(int iPriority,
|
|
|
|
|
|
void (*fpTask)(void *, int), void *arg_voidptr, int arg_Integer)
|
|
|
|
|
|
{
|
2026-07-31 09:09:18 -06:00
|
|
|
|
// #1871: surface scheduler OOM/insert failure to the driver bridge.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!scheduler.DeferImmediateTask(iPriority, fpTask, arg_voidptr, arg_Integer))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
2026-03-09 19:40:40 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::DeferTask(const CLinearTimeAbsolute <When,
|
|
|
|
|
|
int iPriority, void (*fpTask)(void *, int), void *arg_voidptr,
|
|
|
|
|
|
int arg_Integer)
|
|
|
|
|
|
{
|
2026-07-31 09:09:18 -06:00
|
|
|
|
if (!scheduler.DeferTask(ltWhen, iPriority, fpTask, arg_voidptr, arg_Integer))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
2026-03-09 19:40:40 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::PrepareForCommand(dbref player)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.curr_executor = player;
|
|
|
|
|
|
mudstate.curr_enactor = player;
|
|
|
|
|
|
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mudstate.global_regs[i])
|
|
|
|
|
|
{
|
|
|
|
|
|
RegRelease(mudstate.global_regs[i]);
|
|
|
|
|
|
mudstate.global_regs[i] = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
NamedRegsClear(mudstate.named_regs);
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(STUB_SLAVE)
|
|
|
|
|
|
mudstate.iRow = RS_TOP;
|
|
|
|
|
|
if (nullptr != mudstate.pResultsSet)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.pResultsSet->Release();
|
|
|
|
|
|
mudstate.pResultsSet = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif // STUB_SLAVE
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::ProcessCommand(dbref executor, dbref caller,
|
|
|
|
|
|
dbref enactor, int eval, bool bHasCmdArg, UTF8 *command,
|
|
|
|
|
|
const UTF8 *cargs[], int ncargs, UTF8 **ppLogBuf)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == ppLogBuf)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*ppLogBuf = process_command(executor, caller, enactor, eval, bHasCmdArg,
|
|
|
|
|
|
command, cargs, ncargs);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::FinishCommand(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.curr_cmd = T("");
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::HaltQueue(dbref executor, dbref target)
|
|
|
|
|
|
{
|
|
|
|
|
|
halt_que(executor, target);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::WaitQueue(dbref executor, dbref caller,
|
|
|
|
|
|
dbref enactor, int eval, bool bTimed,
|
|
|
|
|
|
const CLinearTimeAbsolute <aWhen, dbref sem, int attr,
|
|
|
|
|
|
UTF8 *command, int ncargs, const UTF8 *cargs[],
|
|
|
|
|
|
reg_ref *regs[], NamedRegsMap *named)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(executor, caller, enactor, eval, bTimed, ltaWhen, sem, attr,
|
|
|
|
|
|
command, ncargs, cargs, regs, named);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::MoveObject(dbref thing, dbref dest)
|
|
|
|
|
|
{
|
|
|
|
|
|
move_object(thing, dest);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::WhereRoom(dbref what, dbref *pRoom)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pRoom)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pRoom = where_room(what);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::TimeFormat1(int seconds, size_t maxWidth,
|
|
|
|
|
|
const UTF8 **ppResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == ppResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*ppResult = time_format_1(seconds, maxWidth);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::TimeFormat2(int seconds,
|
|
|
|
|
|
const UTF8 **ppResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == ppResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*ppResult = time_format_2(seconds);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::GetDbTop(int *pDbTop)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pDbTop)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pDbTop = mudstate.db_top;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::GetInfoTable(const UTF8 ***pppTable)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pppTable)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pppTable = local_get_info_table();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::Report(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
report();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::PresyncDatabaseSigsegv(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
local_presync_database_sigsegv();
|
|
|
|
|
|
|
|
|
|
|
|
// Notify all registered module sinks.
|
|
|
|
|
|
//
|
|
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
|
|
|
|
while (nullptr != p)
|
|
|
|
|
|
{
|
|
|
|
|
|
p->pSink->presync_database_sigsegv();
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::DoRestart(dbref executor, dbref caller,
|
|
|
|
|
|
dbref enactor, int eval, int key)
|
|
|
|
|
|
{
|
|
|
|
|
|
do_restart(executor, caller, enactor, eval, key);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::CacheClose(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
cache_close();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 13:44:58 -06:00
|
|
|
|
static void dbconvert_info(int fmt, int flags, int ver)
|
|
|
|
|
|
{
|
|
|
|
|
|
const UTF8 *cp;
|
|
|
|
|
|
|
|
|
|
|
|
if (fmt == F_MUX)
|
|
|
|
|
|
{
|
|
|
|
|
|
cp = T("MUX");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
cp = T("*unknown*");
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_fprintf(stderr, T("%s version %d:"), cp, ver);
|
|
|
|
|
|
if ( ver < MIN_SUPPORTED_VERSION
|
|
|
|
|
|
|| MAX_SUPPORTED_VERSION < ver)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T(" Unsupported version"));
|
|
|
|
|
|
exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if ( ( ( 1 == ver
|
|
|
|
|
|
|| 2 == ver)
|
|
|
|
|
|
&& (flags & MANDFLAGS_V2) != MANDFLAGS_V2)
|
|
|
|
|
|
|| ( 3 == ver
|
|
|
|
|
|
&& (flags & MANDFLAGS_V3) != MANDFLAGS_V3)
|
|
|
|
|
|
|| ( 4 == ver
|
2026-03-16 14:56:12 -06:00
|
|
|
|
&& (flags & MANDFLAGS_V4) != MANDFLAGS_V4)
|
|
|
|
|
|
|| ( 5 == ver
|
|
|
|
|
|
&& (flags & MANDFLAGS_V5) != MANDFLAGS_V5))
|
2026-03-09 13:44:58 -06:00
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T(" Unsupported flags"));
|
|
|
|
|
|
exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (flags & V_DATABASE)
|
|
|
|
|
|
mux_fprintf(stderr, T(" Database"));
|
|
|
|
|
|
if (flags & V_ATRNAME)
|
|
|
|
|
|
mux_fprintf(stderr, T(" AtrName"));
|
|
|
|
|
|
if (flags & V_ATRKEY)
|
|
|
|
|
|
mux_fprintf(stderr, T(" AtrKey"));
|
|
|
|
|
|
if (flags & V_ATRMONEY)
|
|
|
|
|
|
mux_fprintf(stderr, T(" AtrMoney"));
|
|
|
|
|
|
mux_fprintf(stderr, T(ENDLINE));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngine::DbConvert(const UTF8 *infile, const UTF8 *outfile,
|
|
|
|
|
|
const UTF8 *basename, bool bCheck, bool bLoad, bool bUnload,
|
2026-06-07 13:23:12 -05:00
|
|
|
|
const UTF8 *comsys_file, const UTF8 *mail_file, bool bForce)
|
2026-03-09 13:44:58 -06:00
|
|
|
|
{
|
|
|
|
|
|
int setflags, clrflags, ver;
|
|
|
|
|
|
int db_ver, db_format, db_flags;
|
|
|
|
|
|
|
|
|
|
|
|
SeedRandomNumberGenerator();
|
|
|
|
|
|
|
|
|
|
|
|
pool_init(POOL_LBUF, LBUF_SIZE);
|
|
|
|
|
|
pool_init(POOL_MBUF, MBUF_SIZE);
|
|
|
|
|
|
pool_init(POOL_SBUF, SBUF_SIZE);
|
|
|
|
|
|
pool_init(POOL_BOOL, sizeof(struct boolexp));
|
|
|
|
|
|
|
2026-03-09 20:38:37 -06:00
|
|
|
|
pcache_init();
|
2026-03-09 13:44:58 -06:00
|
|
|
|
cf_init();
|
|
|
|
|
|
|
2026-07-24 08:26:59 -06:00
|
|
|
|
// dbconvert path: write-through attribute cache immediately (no
|
|
|
|
|
|
// scheduler). Cleared before return if the process continues (#1046).
|
|
|
|
|
|
//
|
|
|
|
|
|
mudstate.bStandAlone = true;
|
|
|
|
|
|
|
2026-03-09 13:44:58 -06:00
|
|
|
|
// Decide what conversions to do and how to format the output file.
|
|
|
|
|
|
//
|
|
|
|
|
|
setflags = clrflags = ver = 0;
|
|
|
|
|
|
bool do_redirect = false;
|
|
|
|
|
|
|
|
|
|
|
|
bool do_write = true;
|
|
|
|
|
|
if (bCheck || bLoad)
|
|
|
|
|
|
{
|
|
|
|
|
|
do_write = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (bLoad)
|
|
|
|
|
|
{
|
|
|
|
|
|
clrflags = 0xffffffff;
|
|
|
|
|
|
setflags = OUTPUT_FLAGS;
|
|
|
|
|
|
ver = OUTPUT_VERSION;
|
|
|
|
|
|
do_redirect = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (bUnload)
|
|
|
|
|
|
{
|
|
|
|
|
|
clrflags = 0xffffffff;
|
|
|
|
|
|
setflags = UNLOAD_FLAGS;
|
|
|
|
|
|
ver = UNLOAD_VERSION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Open the database
|
|
|
|
|
|
//
|
|
|
|
|
|
init_attrtab();
|
|
|
|
|
|
|
|
|
|
|
|
int cc = init_dbfile(basename);
|
|
|
|
|
|
if (cc == HF_OPEN_STATUS_ERROR)
|
|
|
|
|
|
{
|
2026-07-26 21:52:48 -06:00
|
|
|
|
mux_fprintf(stderr, T("Can’t open SQLite database.\n"));
|
2026-03-09 13:44:58 -06:00
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
2026-06-07 13:23:12 -05:00
|
|
|
|
|
|
|
|
|
|
// Report exactly which file we opened so it is never a mystery where the
|
|
|
|
|
|
// database lives relative to the server's configured input_database.
|
|
|
|
|
|
//
|
|
|
|
|
|
const char *pDbPath = g_pSQLiteBackend->GetDB().GetPath();
|
|
|
|
|
|
mux_fprintf(stderr, T("Database file: %s\n"),
|
|
|
|
|
|
(nullptr != pDbPath && '\0' != pDbPath[0]) ? pDbPath : "(unknown)");
|
|
|
|
|
|
|
|
|
|
|
|
if (cc == HF_OPEN_STATUS_OLD)
|
2026-03-09 13:44:58 -06:00
|
|
|
|
{
|
2026-06-07 13:23:12 -05:00
|
|
|
|
if (setflags == OUTPUT_FLAGS && !bForce)
|
2026-03-09 13:44:58 -06:00
|
|
|
|
{
|
2026-06-10 15:00:33 -06:00
|
|
|
|
// HF_OPEN_STATUS_OLD only means the FILE existed. An empty
|
|
|
|
|
|
// shell (e.g. created as a side effect of running db_unload
|
|
|
|
|
|
// against a never-imported game) holds no game, so loading
|
|
|
|
|
|
// into it is not destructive -- don't refuse with a false
|
|
|
|
|
|
// "already contains a game" claim (#783).
|
2026-06-07 13:23:12 -05:00
|
|
|
|
//
|
2026-06-10 15:00:33 -06:00
|
|
|
|
int db_top_val = 0;
|
|
|
|
|
|
bool bHasGame =
|
|
|
|
|
|
g_pSQLiteBackend->GetDB().GetMeta("db_top", &db_top_val)
|
|
|
|
|
|
&& 0 < db_top_val;
|
|
|
|
|
|
if (bHasGame)
|
|
|
|
|
|
{
|
|
|
|
|
|
// The target SQLite database already holds a game. Loading
|
|
|
|
|
|
// would replace it, so refuse unless the caller explicitly
|
|
|
|
|
|
// forces it. Name the file and spell out both ways forward
|
|
|
|
|
|
// rather than leaving a dead-end that tempts running from
|
|
|
|
|
|
// the wrong directory.
|
|
|
|
|
|
//
|
|
|
|
|
|
mux_fprintf(stderr,
|
|
|
|
|
|
T("Refusing to overwrite the existing SQLite database:\n"
|
|
|
|
|
|
" %s\n"
|
|
|
|
|
|
"That file already contains a game. To replace it, either:\n"
|
|
|
|
|
|
" * remove the file shown above and re-run, or\n"
|
|
|
|
|
|
" * re-run this load with the -f (force) option.\n"),
|
|
|
|
|
|
(nullptr != pDbPath && '\0' != pDbPath[0]) ? pDbPath : "(unknown)");
|
|
|
|
|
|
CLOSE;
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
2026-03-09 13:44:58 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (cc == HF_OPEN_STATUS_NEW)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (setflags == UNLOAD_FLAGS)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T("SQLite database is empty.\n"));
|
|
|
|
|
|
CLOSE;
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool bLoadedFromSQLite = false;
|
|
|
|
|
|
if (nullptr == infile && HF_OPEN_STATUS_OLD == cc)
|
|
|
|
|
|
{
|
|
|
|
|
|
int sqlite_load_rc = sqlite_load_game();
|
|
|
|
|
|
if (sqlite_load_rc < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T("Input: SQLite database load failed.\n"));
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
bLoadedFromSQLite = (sqlite_load_rc > 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (bLoadedFromSQLite)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T("Input: SQLite database\n"));
|
|
|
|
|
|
db_format = F_MUX;
|
|
|
|
|
|
db_ver = OUTPUT_VERSION;
|
|
|
|
|
|
db_flags = OUTPUT_FLAGS;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == infile)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T("No input flatfile provided and SQLite has no loadable game data.\n"));
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
FILE *fpIn;
|
|
|
|
|
|
if (!mux_fopen(&fpIn, infile, T("rb")))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setvbuf(fpIn, nullptr, _IOFBF, 16384);
|
|
|
|
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
|
|
|
|
if (!sqldb.Begin() || !sqldb.ClearAttributes())
|
|
|
|
|
|
{
|
|
|
|
|
|
sqldb.Rollback();
|
|
|
|
|
|
mux_fprintf(stderr, T("SQLite attribute clear failed before flatfile import.\n"));
|
2026-03-20 06:07:24 -06:00
|
|
|
|
mux_fclose(fpIn);
|
2026-03-09 13:44:58 -06:00
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
mudstate.bSQLiteLoading = true;
|
|
|
|
|
|
if (db_read(fpIn, &db_format, &db_ver, &db_flags) < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.bSQLiteLoading = false;
|
|
|
|
|
|
sqldb.Rollback();
|
2026-07-24 08:26:59 -06:00
|
|
|
|
// Queued attrs targeted the aborted transaction — drop them.
|
|
|
|
|
|
//
|
|
|
|
|
|
cache_discard_writes();
|
|
|
|
|
|
mux_fclose(fpIn);
|
|
|
|
|
|
mudstate.bStandAlone = false;
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Flush remaining write-queue puts into the open import transaction
|
|
|
|
|
|
// before Commit (#1047). Must run while bSQLiteLoading is still true
|
|
|
|
|
|
// so flush does not open a nested Begin/Commit. With bStandAlone
|
|
|
|
|
|
// this is typically a no-op (write-through).
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!cache_flush_writes())
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.bSQLiteLoading = false;
|
|
|
|
|
|
sqldb.Rollback();
|
|
|
|
|
|
cache_discard_writes();
|
|
|
|
|
|
mux_fprintf(stderr, T("SQLite attribute import flush failed.\n"));
|
2026-03-20 06:07:24 -06:00
|
|
|
|
mux_fclose(fpIn);
|
2026-07-24 08:26:59 -06:00
|
|
|
|
mudstate.bStandAlone = false;
|
2026-03-09 13:44:58 -06:00
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
mudstate.bSQLiteLoading = false;
|
|
|
|
|
|
if (!sqldb.Commit())
|
|
|
|
|
|
{
|
|
|
|
|
|
sqldb.Rollback();
|
2026-07-24 08:26:59 -06:00
|
|
|
|
cache_discard_writes();
|
2026-03-09 13:44:58 -06:00
|
|
|
|
mux_fprintf(stderr, T("SQLite attribute import commit failed.\n"));
|
2026-03-20 06:07:24 -06:00
|
|
|
|
mux_fclose(fpIn);
|
2026-07-24 08:26:59 -06:00
|
|
|
|
mudstate.bStandAlone = false;
|
2026-03-09 13:44:58 -06:00
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!sqlite_sync_runtime())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!clear_sqlite_after_sync_failure(sqldb))
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T("SQLite cleanup failed after sync failure.\n"));
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_fprintf(stderr, T("SQLite metadata sync failed.\n"));
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_fprintf(stderr, T("Input: "));
|
|
|
|
|
|
dbconvert_info(db_format, db_flags, db_ver);
|
|
|
|
|
|
|
|
|
|
|
|
if (bCheck)
|
|
|
|
|
|
{
|
|
|
|
|
|
do_dbck(NOTHING, NOTHING, NOTHING, 0, DBCK_FULL);
|
|
|
|
|
|
}
|
2026-03-20 06:07:24 -06:00
|
|
|
|
mux_fclose(fpIn);
|
2026-03-09 13:44:58 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-10 15:00:33 -06:00
|
|
|
|
// A flatfile game load replaces the database's game, so channel and
|
|
|
|
|
|
// mail rows referencing the OLD game's dbrefs must not survive into
|
|
|
|
|
|
// the new one (#783). Clear both table sets; the -C/-m imports
|
|
|
|
|
|
// below repopulate them when flatfiles are supplied. (The pre-force
|
|
|
|
|
|
// workflow -- deleting the whole .sqlite -- removed them too.)
|
|
|
|
|
|
//
|
|
|
|
|
|
if (bLoad && !bLoadedFromSQLite)
|
|
|
|
|
|
{
|
|
|
|
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
|
|
|
|
if ( !sqldb.Begin()
|
|
|
|
|
|
|| !sqldb.ClearComsysTables()
|
|
|
|
|
|
|| !sqldb.ClearMailTables()
|
|
|
|
|
|
|| !sqldb.Commit())
|
|
|
|
|
|
{
|
|
|
|
|
|
sqldb.Rollback();
|
|
|
|
|
|
mux_fprintf(stderr,
|
|
|
|
|
|
T("SQLite comsys/mail clear failed after game load.\n"));
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_fprintf(stderr,
|
|
|
|
|
|
T("Cleared comsys and mail tables (game replaced).\n"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 13:44:58 -06:00
|
|
|
|
// Import comsys from flatfile into SQLite.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (bLoad && comsys_file)
|
|
|
|
|
|
{
|
|
|
|
|
|
load_comsys(const_cast<UTF8 *>(comsys_file));
|
|
|
|
|
|
if (!sqlite_sync_comsys())
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T("Import comsys into SQLite failed.\n"));
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_fprintf(stderr, T("Imported comsys into SQLite.\n"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Import mail from flatfile into SQLite.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (bLoad && mail_file)
|
|
|
|
|
|
{
|
|
|
|
|
|
FILE *fpMail;
|
|
|
|
|
|
if (mux_fopen(&fpMail, mail_file, T("rb")))
|
|
|
|
|
|
{
|
|
|
|
|
|
setvbuf(fpMail, nullptr, _IOFBF, 16384);
|
|
|
|
|
|
load_mail(fpMail);
|
2026-03-20 06:07:24 -06:00
|
|
|
|
mux_fclose(fpMail);
|
2026-03-09 13:44:58 -06:00
|
|
|
|
if (!sqlite_sync_mail())
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T("Import mail into SQLite failed.\n"));
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_fprintf(stderr, T("Imported mail into SQLite.\n"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Export comsys from SQLite to flatfile.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (bUnload && comsys_file)
|
|
|
|
|
|
{
|
|
|
|
|
|
int sqlite_comsys_rc = sqlite_load_comsys();
|
|
|
|
|
|
if (sqlite_comsys_rc > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
save_comsys(const_cast<UTF8 *>(comsys_file));
|
|
|
|
|
|
mux_fprintf(stderr, T("Exported comsys from SQLite.\n"));
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (sqlite_comsys_rc < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T("Export comsys from SQLite failed.\n"));
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Export mail from SQLite to flatfile.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (bUnload && mail_file)
|
|
|
|
|
|
{
|
|
|
|
|
|
int sqlite_mail_rc = sqlite_load_mail();
|
|
|
|
|
|
if (sqlite_mail_rc > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
FILE *fpMail;
|
|
|
|
|
|
if (mux_fopen(&fpMail, mail_file, T("wb")))
|
|
|
|
|
|
{
|
|
|
|
|
|
dump_mail(fpMail);
|
2026-03-20 06:07:24 -06:00
|
|
|
|
mux_fclose(fpMail);
|
2026-03-09 13:44:58 -06:00
|
|
|
|
mux_fprintf(stderr, T("Exported mail from SQLite.\n"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (sqlite_mail_rc < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_fprintf(stderr, T("Export mail from SQLite failed.\n"));
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (do_write)
|
|
|
|
|
|
{
|
|
|
|
|
|
FILE *fpOut;
|
|
|
|
|
|
if (!mux_fopen(&fpOut, outfile, T("wb")))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
db_flags = (db_flags & ~clrflags) | setflags;
|
|
|
|
|
|
if (db_format != F_MUX)
|
|
|
|
|
|
{
|
|
|
|
|
|
db_ver = 3;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (ver != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
db_ver = ver;
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_fprintf(stderr, T("Output: "));
|
|
|
|
|
|
dbconvert_info(F_MUX, db_flags, db_ver);
|
|
|
|
|
|
setvbuf(fpOut, nullptr, _IOFBF, 16384);
|
2026-07-31 09:09:18 -06:00
|
|
|
|
// #1869: propagate flatfile I/O failure so dbconvert does not
|
|
|
|
|
|
// claim success on a truncated dump.
|
|
|
|
|
|
//
|
|
|
|
|
|
const dbref nWritten = db_write(fpOut, F_MUX, db_ver | db_flags);
|
2026-03-20 06:07:24 -06:00
|
|
|
|
mux_fclose(fpOut);
|
2026-07-31 09:09:18 -06:00
|
|
|
|
if (nWritten < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
CLOSE;
|
|
|
|
|
|
#ifdef SELFCHECK
|
|
|
|
|
|
db_free();
|
|
|
|
|
|
#endif
|
|
|
|
|
|
mudstate.bStandAlone = false;
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
2026-03-09 13:44:58 -06:00
|
|
|
|
}
|
|
|
|
|
|
CLOSE;
|
|
|
|
|
|
#ifdef SELFCHECK
|
|
|
|
|
|
db_free();
|
|
|
|
|
|
#endif
|
2026-07-24 08:26:59 -06:00
|
|
|
|
mudstate.bStandAlone = false;
|
2026-03-09 13:44:58 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CGameEngineFactory
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CGameEngineFactory::CGameEngineFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CGameEngineFactory::~CGameEngineFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngineFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CGameEngineFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CGameEngineFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngineFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
|
|
|
|
|
|
MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != pUnknownOuter)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOAGGREGATION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CGameEngine *pGameEngine = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pGameEngine = new CGameEngine;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pGameEngine)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pGameEngine->QueryInterface(iid, ppv);
|
|
|
|
|
|
pGameEngine->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CGameEngineFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CPlayerSession — engine-side implementation of mux_IPlayerSession.
|
|
|
|
|
|
// Handles player authentication, creation, and connect/disconnect lifecycle.
|
|
|
|
|
|
// The driver calls these methods; all player/game-state operations are here.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class CPlayerSession : public mux_IPlayerSession
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT ConnectPlayer(const UTF8 *name, const UTF8 *password,
|
|
|
|
|
|
const UTF8 *host, const UTF8 *username, const UTF8 *ipaddr,
|
|
|
|
|
|
dbref *pPlayer);
|
|
|
|
|
|
virtual MUX_RESULT CreatePlayer(const UTF8 *name, const UTF8 *password,
|
|
|
|
|
|
dbref creator, bool isRobot, dbref *pPlayer,
|
|
|
|
|
|
const UTF8 **ppMsg);
|
|
|
|
|
|
virtual MUX_RESULT AddToPublicChannel(dbref player);
|
|
|
|
|
|
virtual MUX_RESULT AddToPlayerChannels(dbref player);
|
|
|
|
|
|
virtual MUX_RESULT AnnounceConnect(dbref player, int numConnections,
|
|
|
|
|
|
bool isPueblo, bool isSuspect, const UTF8 *host,
|
2026-03-10 00:00:30 -06:00
|
|
|
|
const UTF8 *username, const UTF8 *ipaddr, int *pTimeout,
|
|
|
|
|
|
int64_t *pConnlogId);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
virtual MUX_RESULT AnnounceDisconnect(dbref player, int numConnections,
|
2026-03-10 00:00:30 -06:00
|
|
|
|
bool isSuspect, bool wasAutoDark, const UTF8 *reason,
|
|
|
|
|
|
int64_t connlogId);
|
2026-03-09 17:50:20 -06:00
|
|
|
|
virtual MUX_RESULT FcacheSend(DESC *d, int num);
|
|
|
|
|
|
virtual MUX_RESULT FcacheRawSend(SOCKET fd, int num);
|
2026-03-09 19:40:40 -06:00
|
|
|
|
virtual MUX_RESULT CreateGuest(DESC *d, const UTF8 **ppName);
|
|
|
|
|
|
virtual MUX_RESULT CheckGuest(dbref player, bool *pResult);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
|
|
|
|
|
CPlayerSession(void);
|
|
|
|
|
|
virtual ~CPlayerSession();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CPlayerSession::CPlayerSession(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CPlayerSession::~CPlayerSession()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSession::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IPlayerSession *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IPlayerSession == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IPlayerSession *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CPlayerSession::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CPlayerSession::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSession::ConnectPlayer(const UTF8 *name,
|
|
|
|
|
|
const UTF8 *password, const UTF8 *host, const UTF8 *username,
|
|
|
|
|
|
const UTF8 *ipaddr, dbref *pPlayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pPlayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pPlayer = connect_player(const_cast<UTF8 *>(name),
|
|
|
|
|
|
const_cast<UTF8 *>(password), const_cast<UTF8 *>(host),
|
|
|
|
|
|
const_cast<UTF8 *>(username), const_cast<UTF8 *>(ipaddr));
|
|
|
|
|
|
return (*pPlayer == NOTHING) ? MUX_E_NOTFOUND : MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSession::CreatePlayer(const UTF8 *name,
|
|
|
|
|
|
const UTF8 *password, dbref creator, bool isRobot,
|
|
|
|
|
|
dbref *pPlayer, const UTF8 **ppMsg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pPlayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pPlayer = create_player(name, password, creator, isRobot, ppMsg);
|
|
|
|
|
|
return (*pPlayer == NOTHING) ? MUX_E_FAIL : MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSession::AddToPublicChannel(dbref player)
|
|
|
|
|
|
{
|
|
|
|
|
|
::AddToPublicChannel(player);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSession::AddToPlayerChannels(dbref player)
|
|
|
|
|
|
{
|
|
|
|
|
|
::AddToPlayerChannels(player);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSession::AnnounceConnect(dbref player, int numConnections,
|
|
|
|
|
|
bool isPueblo, bool isSuspect, const UTF8 *host,
|
2026-03-10 00:00:30 -06:00
|
|
|
|
const UTF8 *username, const UTF8 *ipaddr, int *pTimeout,
|
|
|
|
|
|
int64_t *pConnlogId)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-04-03 19:51:01 -06:00
|
|
|
|
// Preload attributes for the player and nearby rooms.
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
//
|
2026-04-03 19:51:01 -06:00
|
|
|
|
cache_preload_nearby(player, mudconf.cache_preload_depth);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
|
|
|
|
|
// Track record player count.
|
|
|
|
|
|
//
|
|
|
|
|
|
int count = numConnections;
|
|
|
|
|
|
if (mudstate.record_players < count)
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.record_players = count;
|
|
|
|
|
|
g_pSQLiteBackend->GetDB().PutMeta("record_players",
|
|
|
|
|
|
mudstate.record_players);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read A_TIMEOUT and return it to the driver.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != pTimeout)
|
|
|
|
|
|
{
|
Migrate 47 more alloc_lbuf/free_lbuf sites to LBuf RAII
Second wave: convert manual alloc/free pairs in cque (1), db_rw (4),
conf (5), db (2), engine_com (6), help (7), levels (2), predicates (5),
player (7), funmath (8). Eliminates ~80 explicit free_lbuf calls
including multi-exit error paths in getboolexp1 (5 frees → 0),
get_list, AnnounceConnect/Disconnect, and the eight NOEVAL function
variants (cand/cor/firstof/allof and bool variants).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:55:40 -06:00
|
|
|
|
LBuf buf = LBuf_Src("AnnounceConnect.timeout");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
dbref aowner;
|
|
|
|
|
|
int aflags;
|
|
|
|
|
|
size_t nLen;
|
|
|
|
|
|
atr_pget_str_LEN(buf, player, A_TIMEOUT, &aowner, &aflags, &nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
fix(win32): migrate the remaining mux_atol callers to mux_atoi64 (#1373)
Completes the sweep the issue called for. mux_atol returns long, which
is 32-bit on LLP64, so every caller silently truncated on Windows. Two
of those were real defects (the truthiness family and cf_size, fixed in
the preceding commits); the rest were latent, waiting for a value large
enough to matter.
Rather than audit 290 sites for whether each can reach 2^31 today, use
the 64-bit parser everywhere and remove the class. A dbref cannot
overflow now, but nothing stops a later caller passing that same site a
timestamp or a byte count.
Pure 1:1 substitution: 285 lines changed, and every removed line
contained mux_atol while every added line contains mux_atoi64. No
control flow, no types, no behaviour beyond the wider parse.
This is a NO-OP on LP64 -- long is already 64-bit on Linux and macOS, so
the generated code there is unchanged. It only widens the parse on
Windows. Narrowing destinations are unaffected either way: `int x =
mux_atoi64(s)` truncates exactly as `int x = mux_atol(s)` did, on both
models.
Left alone: mux_atol itself in mathutil, its declaration, and three
comments that name it. Callers that genuinely want 32-bit semantics can
still ask for them; none appear to.
Verified on Windows: full solution builds clean with no new warnings,
smoke is 1418 passed / 16 failed / 0 crashes / 306 of 306 dispatched --
identical to before the sweep, with the same 16 build-configuration
failures (exp3 module not loaded, hmac/digest behind UNIX_DIGEST).
Spot checks after the change: the boolean family returns 1 for multiples
of 2^32, cf_size round-trips 3000000000 and still reads -1 as unlimited,
and arithmetic, string and list functions are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:03:45 -06:00
|
|
|
|
*pTimeout = mux_atoi64(buf);
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
if (*pTimeout <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pTimeout = mudconf.idle_timeout;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*pTimeout = mudconf.idle_timeout;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set Connected flag. Set Html if Pueblo client.
|
|
|
|
|
|
//
|
|
|
|
|
|
const dbref loc = Location(player);
|
|
|
|
|
|
s_Connected(player);
|
|
|
|
|
|
|
|
|
|
|
|
if (isPueblo)
|
|
|
|
|
|
{
|
|
|
|
|
|
s_Html(player);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MOTD messages.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ('\0' != mudconf.motd_msg[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
raw_notify(player, tprintf(T("\n%sMOTD:%s %s\n"), COLOR_INTENSE,
|
|
|
|
|
|
COLOR_RESET, mudconf.motd_msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Wizard(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
if ('\0' != mudconf.wizmotd_msg[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
raw_notify(player, tprintf(T("%sWIZMOTD:%s %s\n"), COLOR_INTENSE,
|
|
|
|
|
|
COLOR_RESET, mudconf.wizmotd_msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!(mudconf.control_flags & CF_LOGIN))
|
|
|
|
|
|
{
|
2026-07-27 12:40:51 +00:00
|
|
|
|
raw_notify(player, M_("*** Logins are disabled."));
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Page lock warning.
|
|
|
|
|
|
//
|
|
|
|
|
|
{
|
Migrate 47 more alloc_lbuf/free_lbuf sites to LBuf RAII
Second wave: convert manual alloc/free pairs in cque (1), db_rw (4),
conf (5), db (2), engine_com (6), help (7), levels (2), predicates (5),
player (7), funmath (8). Eliminates ~80 explicit free_lbuf calls
including multi-exit error paths in getboolexp1 (5 frees → 0),
get_list, AnnounceConnect/Disconnect, and the eight NOEVAL function
variants (cand/cor/firstof/allof and bool variants).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:55:40 -06:00
|
|
|
|
LBuf buf = LBuf_Src("AnnounceConnect.lpage");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
dbref aowner;
|
|
|
|
|
|
int aflags;
|
|
|
|
|
|
size_t nLen;
|
|
|
|
|
|
atr_get_str_LEN(buf, player, A_LPAGE, &aowner, &aflags, &nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
2026-07-27 12:40:51 +00:00
|
|
|
|
raw_notify(player, M_("Your PAGE LOCK is set. You may be unable to receive some pages."));
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for forced encoding.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (Unicode(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
set_player_encoding(player, CHARSET_UTF8);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (Ascii(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
set_player_encoding(player, CHARSET_ASCII);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Reset vacation flag. Clear DARK on guests.
|
|
|
|
|
|
//
|
|
|
|
|
|
s_Flags(player, FLAG_WORD2, Flags2(player) & ~VACATION);
|
|
|
|
|
|
if (Guest(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
s_Flags(player, FLAG_WORD1, db[player].fs.word[FLAG_WORD1] & ~DARK);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Room and monitor announcements.
|
|
|
|
|
|
//
|
|
|
|
|
|
const UTF8 *pRoomAnnounceFmt;
|
|
|
|
|
|
const UTF8 *pMonitorAnnounceFmt;
|
|
|
|
|
|
if (numConnections < 2)
|
|
|
|
|
|
{
|
2026-07-28 12:07:45 -06:00
|
|
|
|
pRoomAnnounceFmt = M_("%s has connected.");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
do_comconnect(player);
|
|
|
|
|
|
if ( Hidden(player)
|
|
|
|
|
|
&& Can_Hide(player))
|
|
|
|
|
|
{
|
2026-07-28 12:07:45 -06:00
|
|
|
|
pMonitorAnnounceFmt = M_("GAME: %s has DARK-connected.");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-07-28 12:07:45 -06:00
|
|
|
|
pMonitorAnnounceFmt = M_("GAME: %s has connected.");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
}
|
|
|
|
|
|
if ( Suspect(player)
|
|
|
|
|
|
|| isSuspect)
|
|
|
|
|
|
{
|
2026-07-28 12:07:45 -06:00
|
|
|
|
raw_broadcast(WIZARD, M_("[Suspect] %s has connected."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(player));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-07-28 12:07:45 -06:00
|
|
|
|
pRoomAnnounceFmt = M_("%s has reconnected.");
|
|
|
|
|
|
pMonitorAnnounceFmt = M_("GAME: %s has reconnected.");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
if ( Suspect(player)
|
|
|
|
|
|
|| isSuspect)
|
|
|
|
|
|
{
|
2026-07-28 12:07:45 -06:00
|
|
|
|
raw_broadcast(WIZARD, M_("[Suspect] %s has reconnected."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(player));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Migrate 47 more alloc_lbuf/free_lbuf sites to LBuf RAII
Second wave: convert manual alloc/free pairs in cque (1), db_rw (4),
conf (5), db (2), engine_com (6), help (7), levels (2), predicates (5),
player (7), funmath (8). Eliminates ~80 explicit free_lbuf calls
including multi-exit error paths in getboolexp1 (5 frees → 0),
get_list, AnnounceConnect/Disconnect, and the eight NOEVAL function
variants (cand/cor/firstof/allof and bool variants).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:55:40 -06:00
|
|
|
|
LBuf buf = LBuf_Src("AnnounceConnect.room");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
mux_sprintf(buf, LBUF_SIZE, pRoomAnnounceFmt, Moniker(player));
|
|
|
|
|
|
raw_broadcast(MONITOR, pMonitorAnnounceFmt, Moniker(player));
|
|
|
|
|
|
|
|
|
|
|
|
int key = MSG_INV;
|
|
|
|
|
|
if ( loc != NOTHING
|
|
|
|
|
|
&& !( Hidden(player)
|
|
|
|
|
|
&& Can_Hide(player)))
|
|
|
|
|
|
{
|
|
|
|
|
|
key |= (MSG_NBR | MSG_NBR_EXITS | MSG_LOC | MSG_FWDLIST);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dbref temp = mudstate.curr_enactor;
|
|
|
|
|
|
mudstate.curr_enactor = player;
|
|
|
|
|
|
#ifdef REALITY_LVLS
|
|
|
|
|
|
if (NOTHING == loc)
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_check(player, player, buf, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_except_rlevel(loc, player, player, buf, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
notify_check(player, player, buf, key);
|
|
|
|
|
|
#endif // REALITY_LVLS
|
|
|
|
|
|
|
|
|
|
|
|
// ACONNECT triggers: player, master room, zone.
|
|
|
|
|
|
//
|
|
|
|
|
|
dbref aowner, zone, obj;
|
|
|
|
|
|
int aflags;
|
|
|
|
|
|
size_t nLen;
|
|
|
|
|
|
CLinearTimeAbsolute lta;
|
|
|
|
|
|
atr_pget_str_LEN(buf, player, A_ACONNECT, &aowner, &aflags, &nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(player, player, player, AttrTrace(aflags, 0), false, lta,
|
|
|
|
|
|
NOTHING, 0, buf, 0, nullptr, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (mudconf.master_room != NOTHING)
|
|
|
|
|
|
{
|
|
|
|
|
|
atr_pget_str_LEN(buf, mudconf.master_room, A_ACONNECT, &aowner,
|
|
|
|
|
|
&aflags, &nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(mudconf.master_room, player, player,
|
|
|
|
|
|
AttrTrace(aflags, 0), false, lta, NOTHING, 0, buf,
|
|
|
|
|
|
0, nullptr, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
DOLIST(obj, Contents(mudconf.master_room))
|
|
|
|
|
|
{
|
|
|
|
|
|
atr_pget_str_LEN(buf, obj, A_ACONNECT, &aowner, &aflags, &nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(obj, player, player, AttrTrace(aflags, 0), false, lta,
|
|
|
|
|
|
NOTHING, 0, buf, 0, nullptr, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Zone ACONNECT.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( mudconf.have_zones
|
|
|
|
|
|
&& Good_obj(zone = Zone(loc)))
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (Typeof(zone))
|
|
|
|
|
|
{
|
|
|
|
|
|
case TYPE_THING:
|
|
|
|
|
|
atr_pget_str_LEN(buf, zone, A_ACONNECT, &aowner, &aflags, &nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(zone, player, player, AttrTrace(aflags, 0), false,
|
|
|
|
|
|
lta, NOTHING, 0, buf, 0, nullptr, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case TYPE_ROOM:
|
|
|
|
|
|
DOLIST(obj, Contents(zone))
|
|
|
|
|
|
{
|
|
|
|
|
|
atr_pget_str_LEN(buf, obj, A_ACONNECT, &aowner, &aflags,
|
|
|
|
|
|
&nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(obj, player, player, AttrTrace(aflags, 0), false,
|
|
|
|
|
|
lta, NOTHING, 0, buf, 0, nullptr, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
log_printf(T("Invalid zone #%d for %s(#%d) has bad type %d"),
|
|
|
|
|
|
zone, PureName(player), player, Typeof(zone));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Record login, check mail, show room.
|
|
|
|
|
|
//
|
|
|
|
|
|
CLinearTimeAbsolute ltaNow;
|
|
|
|
|
|
ltaNow.GetLocal();
|
|
|
|
|
|
const UTF8 *time_str = ltaNow.ReturnDateString(7);
|
|
|
|
|
|
record_login(player, true, time_str, host, username, ipaddr);
|
2026-03-10 00:00:30 -06:00
|
|
|
|
|
|
|
|
|
|
// Log connection to connlog table.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != pConnlogId)
|
|
|
|
|
|
{
|
|
|
|
|
|
CLinearTimeAbsolute ltaUtc;
|
|
|
|
|
|
ltaUtc.GetUTC();
|
|
|
|
|
|
int64_t utcSeconds = ltaUtc.ReturnSeconds();
|
|
|
|
|
|
*pConnlogId = g_pSQLiteBackend->GetDB().ConnlogInsert(
|
|
|
|
|
|
player, utcSeconds, host, ipaddr);
|
|
|
|
|
|
}
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
check_mail(player, 0, false);
|
|
|
|
|
|
look_in(player, Location(player), (LK_SHOWEXIT|LK_OBEYTERSE|LK_SHOWVRML));
|
|
|
|
|
|
mudstate.curr_enactor = temp;
|
|
|
|
|
|
|
|
|
|
|
|
local_connect(player, 0, numConnections);
|
|
|
|
|
|
|
|
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
|
|
|
|
while (nullptr != p)
|
|
|
|
|
|
{
|
|
|
|
|
|
p->pSink->connect(player, 0, numConnections);
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSession::AnnounceDisconnect(dbref player,
|
|
|
|
|
|
int numConnections, bool isSuspect, bool wasAutoDark,
|
2026-03-10 00:00:30 -06:00
|
|
|
|
const UTF8 *reason, int64_t connlogId)
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
{
|
2026-03-10 00:00:30 -06:00
|
|
|
|
// Update connlog row with disconnect time and reason.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (0 != connlogId)
|
|
|
|
|
|
{
|
|
|
|
|
|
CLinearTimeAbsolute ltaUtc;
|
|
|
|
|
|
ltaUtc.GetUTC();
|
|
|
|
|
|
int64_t utcSeconds = ltaUtc.ReturnSeconds();
|
|
|
|
|
|
g_pSQLiteBackend->GetDB().ConnlogUpdate(connlogId, utcSeconds, reason);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
int key;
|
|
|
|
|
|
const dbref temp = mudstate.curr_enactor;
|
|
|
|
|
|
mudstate.curr_enactor = player;
|
|
|
|
|
|
const dbref loc = Location(player);
|
|
|
|
|
|
|
|
|
|
|
|
if (numConnections < 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Last connection — full disconnect.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( Suspect(player)
|
|
|
|
|
|
|| isSuspect)
|
|
|
|
|
|
{
|
2026-07-28 12:07:45 -06:00
|
|
|
|
raw_broadcast(WIZARD, M_("[Suspect] %s has disconnected."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(player));
|
|
|
|
|
|
}
|
Migrate 47 more alloc_lbuf/free_lbuf sites to LBuf RAII
Second wave: convert manual alloc/free pairs in cque (1), db_rw (4),
conf (5), db (2), engine_com (6), help (7), levels (2), predicates (5),
player (7), funmath (8). Eliminates ~80 explicit free_lbuf calls
including multi-exit error paths in getboolexp1 (5 frees → 0),
get_list, AnnounceConnect/Disconnect, and the eight NOEVAL function
variants (cand/cor/firstof/allof and bool variants).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:55:40 -06:00
|
|
|
|
LBuf buf = LBuf_Src("AnnounceDisconnect.only");
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
|
2026-07-28 12:07:45 -06:00
|
|
|
|
mux_sprintf(buf, LBUF_SIZE, M_("%s has disconnected."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(player));
|
|
|
|
|
|
key = MSG_INV;
|
|
|
|
|
|
if ( loc != NOTHING
|
|
|
|
|
|
&& !( Hidden(player)
|
|
|
|
|
|
&& Can_Hide(player)))
|
|
|
|
|
|
{
|
|
|
|
|
|
key |= (MSG_NBR | MSG_NBR_EXITS | MSG_LOC | MSG_FWDLIST);
|
|
|
|
|
|
}
|
|
|
|
|
|
#ifdef REALITY_LVLS
|
|
|
|
|
|
if (NOTHING == loc)
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_check(player, player, buf, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_except_rlevel(loc, player, player, buf, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
notify_check(player, player, buf, key);
|
|
|
|
|
|
#endif // REALITY_LVLS
|
|
|
|
|
|
|
|
|
|
|
|
do_mail_purge(player);
|
|
|
|
|
|
|
2026-07-28 12:07:45 -06:00
|
|
|
|
raw_broadcast(MONITOR, M_("GAME: %s has disconnected. <%s>"),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(player), reason);
|
|
|
|
|
|
|
|
|
|
|
|
c_Connected(player);
|
|
|
|
|
|
do_comdisconnect(player);
|
|
|
|
|
|
|
|
|
|
|
|
// ADISCONNECT triggers: player, master room, zone.
|
|
|
|
|
|
//
|
|
|
|
|
|
dbref aowner, zone, obj;
|
|
|
|
|
|
int aflags;
|
|
|
|
|
|
size_t nLen;
|
|
|
|
|
|
CLinearTimeAbsolute lta;
|
|
|
|
|
|
atr_pget_str_LEN(buf, player, A_ADISCONNECT, &aowner, &aflags, &nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(player, player, player, AttrTrace(aflags, 0), false,
|
|
|
|
|
|
lta, NOTHING, 0, buf, 1, &reason, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (mudconf.master_room != NOTHING)
|
|
|
|
|
|
{
|
|
|
|
|
|
atr_pget_str_LEN(buf, mudconf.master_room, A_ADISCONNECT, &aowner,
|
|
|
|
|
|
&aflags, &nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(mudconf.master_room, player, player,
|
|
|
|
|
|
AttrTrace(aflags, 0), false, lta, NOTHING, 0, buf,
|
|
|
|
|
|
0, nullptr, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
DOLIST(obj, Contents(mudconf.master_room))
|
|
|
|
|
|
{
|
|
|
|
|
|
atr_pget_str_LEN(buf, obj, A_ADISCONNECT, &aowner, &aflags,
|
|
|
|
|
|
&nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(obj, player, player, AttrTrace(aflags, 0), false,
|
|
|
|
|
|
lta, NOTHING, 0, buf, 0, nullptr, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Zone ADISCONNECT.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (mudconf.have_zones && Good_obj(zone = Zone(loc)))
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (Typeof(zone))
|
|
|
|
|
|
{
|
|
|
|
|
|
case TYPE_THING:
|
|
|
|
|
|
atr_pget_str_LEN(buf, zone, A_ADISCONNECT, &aowner, &aflags,
|
|
|
|
|
|
&nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(zone, player, player, AttrTrace(aflags, 0),
|
|
|
|
|
|
false, lta, NOTHING, 0, buf, 0, nullptr, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case TYPE_ROOM:
|
|
|
|
|
|
DOLIST(obj, Contents(zone))
|
|
|
|
|
|
{
|
|
|
|
|
|
atr_pget_str_LEN(buf, obj, A_ADISCONNECT, &aowner, &aflags,
|
|
|
|
|
|
&nLen);
|
|
|
|
|
|
if (nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
wait_que(obj, player, player, AttrTrace(aflags, 0),
|
|
|
|
|
|
false, lta, NOTHING, 0, buf, 0, nullptr, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
log_printf(T("Invalid zone #%d for %s(#%d) has bad type %d"),
|
|
|
|
|
|
zone, PureName(player), player, Typeof(zone));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Clear AUTODARK, darken guests, halt guest queues.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (wasAutoDark)
|
|
|
|
|
|
{
|
|
|
|
|
|
s_Flags(player, FLAG_WORD1,
|
|
|
|
|
|
db[player].fs.word[FLAG_WORD1] & ~DARK);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Guest(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
s_Flags(player, FLAG_WORD1,
|
|
|
|
|
|
db[player].fs.word[FLAG_WORD1] | DARK);
|
|
|
|
|
|
halt_que(NOTHING, player);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Partial disconnect — other connections remain.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( Suspect(player)
|
|
|
|
|
|
|| isSuspect)
|
|
|
|
|
|
{
|
|
|
|
|
|
raw_broadcast(WIZARD,
|
2026-07-28 12:07:45 -06:00
|
|
|
|
M_("[Suspect] %s has partially disconnected."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(player));
|
|
|
|
|
|
}
|
|
|
|
|
|
UTF8 *mbuf = alloc_mbuf("AnnounceDisconnect.partial");
|
2026-07-28 12:07:45 -06:00
|
|
|
|
mux_sprintf(mbuf, MBUF_SIZE, M_("%s has partially disconnected."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(player));
|
|
|
|
|
|
key = MSG_INV;
|
|
|
|
|
|
if ( loc != NOTHING
|
|
|
|
|
|
&& !( Hidden(player)
|
|
|
|
|
|
&& Can_Hide(player)))
|
|
|
|
|
|
{
|
|
|
|
|
|
key |= (MSG_NBR | MSG_NBR_EXITS | MSG_LOC | MSG_FWDLIST);
|
|
|
|
|
|
}
|
|
|
|
|
|
#ifdef REALITY_LVLS
|
|
|
|
|
|
if (NOTHING == loc)
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_check(player, player, mbuf, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
notify_except_rlevel(loc, player, player, mbuf, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
notify_check(player, player, mbuf, key);
|
|
|
|
|
|
#endif // REALITY_LVLS
|
2026-07-28 12:07:45 -06:00
|
|
|
|
raw_broadcast(MONITOR, M_("GAME: %s has partially disconnected."),
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
Moniker(player));
|
|
|
|
|
|
free_mbuf(mbuf);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mudstate.curr_enactor = temp;
|
|
|
|
|
|
|
|
|
|
|
|
local_disconnect(player, numConnections);
|
|
|
|
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
|
|
|
|
while (nullptr != p)
|
|
|
|
|
|
{
|
|
|
|
|
|
p->pSink->disconnect(player, numConnections);
|
|
|
|
|
|
p = p->pNext;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 17:50:20 -06:00
|
|
|
|
MUX_RESULT CPlayerSession::FcacheSend(DESC *d, int num)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == d)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
fcache_dump(d, num);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSession::FcacheRawSend(SOCKET fd, int num)
|
|
|
|
|
|
{
|
|
|
|
|
|
fcache_rawdump(fd, num);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 19:40:40 -06:00
|
|
|
|
MUX_RESULT CPlayerSession::CreateGuest(DESC *d, const UTF8 **ppName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == ppName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*ppName = Guest.Create(d);
|
|
|
|
|
|
if (nullptr == *ppName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSession::CheckGuest(dbref player, bool *pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pResult = Guest.CheckGuest(player);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Split modules.cpp into driver registration + engine_com.cpp
Move all COM class implementations (CServerEventsSource, CQueryClient,
CNotify, CObjectInfo, CAttributeAccess, CEvaluator, CPermissions,
CMailDelivery, CHelpSystem, CGameEngine, CPlayerSession + factories)
to engine_com.cpp (compiled into engine.so).
CConnectionManager stays in modules.cpp (driver) since it wraps
net.cpp driver functions — placing it in engine.so caused infinite
recursion through conn_bridge.cpp.
Fix cf_init() clobbering pIGameEngine/pIPlayerSession: these COM
interface pointers are set by the driver before LoadGame() calls
cf_init(), so they must not be cleared there.
Export g_pServerEventsSinkListHead from engine.so for signals.cpp.
modules.cpp: 4358 → 909 lines (driver registration + CConnectionManager)
engine_com.cpp: 3465 lines (all engine-side COM implementations)
411/411 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:57:26 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CPlayerSessionFactory
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CPlayerSessionFactory::CPlayerSessionFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CPlayerSessionFactory::~CPlayerSessionFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSessionFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CPlayerSessionFactory::AddRef(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef++;
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CPlayerSessionFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSessionFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
|
|
|
|
|
|
MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != pUnknownOuter)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOAGGREGATION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CPlayerSession *pPlayerSession = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pPlayerSession = new CPlayerSession;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pPlayerSession)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pPlayerSession->QueryInterface(iid, ppv);
|
|
|
|
|
|
pPlayerSession->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CPlayerSessionFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
// CComsysStorage — engine-provided SQLite access for comsys_mod.so.
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
class CComsysStorage : public mux_IComsysStorage
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT LoadAllChannels(PFN_CHANNEL_CB pfn, void *context);
|
|
|
|
|
|
virtual MUX_RESULT LoadAllChannelUsers(PFN_CHANNEL_USER_CB pfn, void *context);
|
|
|
|
|
|
virtual MUX_RESULT LoadAllPlayerChannels(PFN_PLAYER_CHANNEL_CB pfn, void *context);
|
|
|
|
|
|
virtual MUX_RESULT SyncChannel(const UTF8 *name, const UTF8 *header,
|
|
|
|
|
|
int type, int temp1, int temp2, int charge, int charge_who,
|
|
|
|
|
|
int amount_col, int num_messages, int chan_obj);
|
|
|
|
|
|
virtual MUX_RESULT SyncChannelUser(const UTF8 *channel_name, int who,
|
|
|
|
|
|
bool is_on, bool comtitle_status, bool gag_join_leave,
|
|
|
|
|
|
const UTF8 *title);
|
|
|
|
|
|
virtual MUX_RESULT SyncPlayerChannel(int who, const UTF8 *alias,
|
|
|
|
|
|
const UTF8 *channel_name);
|
|
|
|
|
|
virtual MUX_RESULT DeleteChannel(const UTF8 *name);
|
|
|
|
|
|
virtual MUX_RESULT DeleteChannelUser(const UTF8 *channel_name, int who);
|
|
|
|
|
|
virtual MUX_RESULT DeletePlayerChannel(int who, const UTF8 *alias);
|
|
|
|
|
|
virtual MUX_RESULT DeleteAllPlayerChannels(int who);
|
|
|
|
|
|
virtual MUX_RESULT ClearComsysTables(void);
|
fix(comsys): let the module write engine-owned channel attributes (#1585, #1620)
Both bugs are one refusal. The engine writes HISTORY_%d, MAX_LOG and
LOG_TIMESTAMPS on the channel object as GOD with AF_CONST, and
bCanSetAttr denies AF_CONST in every branch -- God included, which is
unlike every other flag it checks. So the module's permission-checked
SetAttribute is refused, and whichever implementation did not write a
value first can never change it.
#1585 a channel whose timestamps the engine enabled could not be
turned off from the module, and before #1624 the module
reported success anyway.
#1620 the same refusal reaching HISTORY_%d once the ring wraps onto
an engine-written slot. num_messages is a column and keeps
counting, so the counter and the history diverge -- #1564's
symptom arriving by a second route.
mux_IComsysStorage gains SetChannelAttr: the module asks the ENGINE to
perform the write. The engine owns the attribute layer and the
permission check, and here it is writing its own data rather than a
player's, so an attribute add is its operation to make.
Two alternatives were weighed and rejected:
- Dropping AF_CONST from the five comsys writes is the smallest
change, but then anyone controlling the channel object can forge
recall history with a plain @set. Channel logs stop being
tamper-evident, which is what AF_CONST was buying.
- Giving God an escape from AF_CONST in bCanSetAttr is one line and
restores an invariant that holds for every other flag there, but it
is a global permission change to fix one subsystem's problem.
Scope is deliberately narrow. The channel is passed by NAME, so the
engine resolves the object itself and this cannot become "write any
attribute anywhere as GOD". Player-owned attributes are untouched --
mail's Mailcurf/Mailfolders keep going through the permission-checked
mux_IAttributeAccess, which is the right path for anything a player
owns.
tests/comsys_handoff now passes with no TODO markers:
ok 4 - module can clear a flag the engine set (#1585)
ok 6 - module overwrites an engine-written history slot (#1620)
=== comsys handoff: 8 passed, 0 failed, 0 known-failing ===
The harness demanded this edit rather than allowing it: with the fix in
and the markers still present it reported "2 unexpectedly passing" and
failed the run, which is what those markers are for.
One assertion changed direction rather than being deleted. #1624 added
a check that a REFUSED write was reported honestly instead of as
success; there is no refusal left to report, so it now checks that the
clear reports success -- because it now is one. Asserting on the old
failure text would have failed for the right reason in the wrong
direction.
make test green: 1561/1561 on all three smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:26:08 -06:00
|
|
|
|
virtual MUX_RESULT SetChannelAttr(const UTF8 *channel_name,
|
|
|
|
|
|
const UTF8 *pAttrName, const UTF8 *pValue);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
|
|
|
|
|
|
CComsysStorage(void);
|
|
|
|
|
|
virtual ~CComsysStorage();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CComsysStorage::CComsysStorage(void) : m_cRef(1) {}
|
|
|
|
|
|
CComsysStorage::~CComsysStorage() {}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid || IID_IComsysStorage == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IComsysStorage *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CComsysStorage::AddRef(void) { m_cRef++; return m_cRef; }
|
|
|
|
|
|
uint32_t CComsysStorage::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 08:00:15 -06:00
|
|
|
|
// #1190: Comsys/mail storage COM must not dereference a closed backend.
|
|
|
|
|
|
//
|
|
|
|
|
|
static CSQLiteDB *sqlite_storage_db(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == g_pSQLiteBackend)
|
|
|
|
|
|
{
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
return &g_pSQLiteBackend->GetDB();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
MUX_RESULT CComsysStorage::LoadAllChannels(PFN_CHANNEL_CB pfn, void *context)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == pfn) return MUX_E_INVALIDARG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
bool ok = pDb->LoadAllChannels(
|
2026-03-10 10:39:15 -06:00
|
|
|
|
[pfn, context](const UTF8 *name, const UTF8 *header,
|
|
|
|
|
|
int type, int temp1, int temp2, int charge, int charge_who,
|
|
|
|
|
|
int amount_col, int num_messages, int chan_obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
pfn(context, name, header, type, temp1, temp2, charge,
|
|
|
|
|
|
charge_who, amount_col, num_messages, chan_obj);
|
|
|
|
|
|
});
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::LoadAllChannelUsers(PFN_CHANNEL_USER_CB pfn, void *context)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == pfn) return MUX_E_INVALIDARG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
bool ok = pDb->LoadAllChannelUsers(
|
2026-03-10 10:39:15 -06:00
|
|
|
|
[pfn, context](const UTF8 *channel_name, int who,
|
|
|
|
|
|
bool is_on, bool comtitle_status, bool gag_join_leave,
|
|
|
|
|
|
const UTF8 *title)
|
|
|
|
|
|
{
|
|
|
|
|
|
pfn(context, channel_name, who, is_on, comtitle_status,
|
|
|
|
|
|
gag_join_leave, title);
|
|
|
|
|
|
});
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::LoadAllPlayerChannels(PFN_PLAYER_CHANNEL_CB pfn, void *context)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == pfn) return MUX_E_INVALIDARG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
bool ok = pDb->LoadAllPlayerChannels(
|
2026-03-10 10:39:15 -06:00
|
|
|
|
[pfn, context](int who, const UTF8 *alias,
|
|
|
|
|
|
const UTF8 *channel_name)
|
|
|
|
|
|
{
|
|
|
|
|
|
pfn(context, who, alias, channel_name);
|
|
|
|
|
|
});
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::SyncChannel(const UTF8 *name, const UTF8 *header,
|
|
|
|
|
|
int type, int temp1, int temp2, int charge, int charge_who,
|
|
|
|
|
|
int amount_col, int num_messages, int chan_obj)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->SyncChannel(name, header, type,
|
2026-03-10 10:39:15 -06:00
|
|
|
|
temp1, temp2, charge, charge_who, amount_col, num_messages, chan_obj);
|
2026-07-25 08:33:42 -06:00
|
|
|
|
if (ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
// #1191 / #783: softcode reloads via sqlite_load_comsys() only when
|
|
|
|
|
|
// has_comsys meta is set. Module creates must open that gate.
|
|
|
|
|
|
//
|
|
|
|
|
|
ok = pDb->PutMeta("has_comsys", 1);
|
|
|
|
|
|
}
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): let the module write engine-owned channel attributes (#1585, #1620)
Both bugs are one refusal. The engine writes HISTORY_%d, MAX_LOG and
LOG_TIMESTAMPS on the channel object as GOD with AF_CONST, and
bCanSetAttr denies AF_CONST in every branch -- God included, which is
unlike every other flag it checks. So the module's permission-checked
SetAttribute is refused, and whichever implementation did not write a
value first can never change it.
#1585 a channel whose timestamps the engine enabled could not be
turned off from the module, and before #1624 the module
reported success anyway.
#1620 the same refusal reaching HISTORY_%d once the ring wraps onto
an engine-written slot. num_messages is a column and keeps
counting, so the counter and the history diverge -- #1564's
symptom arriving by a second route.
mux_IComsysStorage gains SetChannelAttr: the module asks the ENGINE to
perform the write. The engine owns the attribute layer and the
permission check, and here it is writing its own data rather than a
player's, so an attribute add is its operation to make.
Two alternatives were weighed and rejected:
- Dropping AF_CONST from the five comsys writes is the smallest
change, but then anyone controlling the channel object can forge
recall history with a plain @set. Channel logs stop being
tamper-evident, which is what AF_CONST was buying.
- Giving God an escape from AF_CONST in bCanSetAttr is one line and
restores an invariant that holds for every other flag there, but it
is a global permission change to fix one subsystem's problem.
Scope is deliberately narrow. The channel is passed by NAME, so the
engine resolves the object itself and this cannot become "write any
attribute anywhere as GOD". Player-owned attributes are untouched --
mail's Mailcurf/Mailfolders keep going through the permission-checked
mux_IAttributeAccess, which is the right path for anything a player
owns.
tests/comsys_handoff now passes with no TODO markers:
ok 4 - module can clear a flag the engine set (#1585)
ok 6 - module overwrites an engine-written history slot (#1620)
=== comsys handoff: 8 passed, 0 failed, 0 known-failing ===
The harness demanded this edit rather than allowing it: with the fix in
and the markers still present it reported "2 unexpectedly passing" and
failed the run, which is what those markers are for.
One assertion changed direction rather than being deleted. #1624 added
a check that a REFUSED write was reported honestly instead of as
success; there is no refusal left to report, so it now checks that the
clear reports success -- because it now is one. Asserting on the old
failure text would have failed for the right reason in the wrong
direction.
make test green: 1561/1561 on all three smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:26:08 -06:00
|
|
|
|
// Write an engine-owned attribute on a channel object, as GOD (#1585/#1620).
|
|
|
|
|
|
//
|
|
|
|
|
|
// This bypasses bCanSetAttr deliberately, and that is the whole point: the
|
|
|
|
|
|
// engine is the owner of these attributes and of the permission check, and it
|
|
|
|
|
|
// is writing its own data rather than a player's. The module cannot reach
|
|
|
|
|
|
// atr_add itself, and its permission-checked route is refused by the AF_CONST
|
|
|
|
|
|
// the engine sets.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The channel is named rather than passed as a dbref so the engine resolves
|
|
|
|
|
|
// the object, which keeps this from being a general "write any attribute
|
|
|
|
|
|
// anywhere as GOD" hole.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT CComsysStorage::SetChannelAttr(const UTF8 *channel_name,
|
|
|
|
|
|
const UTF8 *pAttrName, const UTF8 *pValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == channel_name || nullptr == pAttrName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-06 15:24:55 -06:00
|
|
|
|
struct channel *ch = select_channel(channel_name);
|
fix(comsys): let the module write engine-owned channel attributes (#1585, #1620)
Both bugs are one refusal. The engine writes HISTORY_%d, MAX_LOG and
LOG_TIMESTAMPS on the channel object as GOD with AF_CONST, and
bCanSetAttr denies AF_CONST in every branch -- God included, which is
unlike every other flag it checks. So the module's permission-checked
SetAttribute is refused, and whichever implementation did not write a
value first can never change it.
#1585 a channel whose timestamps the engine enabled could not be
turned off from the module, and before #1624 the module
reported success anyway.
#1620 the same refusal reaching HISTORY_%d once the ring wraps onto
an engine-written slot. num_messages is a column and keeps
counting, so the counter and the history diverge -- #1564's
symptom arriving by a second route.
mux_IComsysStorage gains SetChannelAttr: the module asks the ENGINE to
perform the write. The engine owns the attribute layer and the
permission check, and here it is writing its own data rather than a
player's, so an attribute add is its operation to make.
Two alternatives were weighed and rejected:
- Dropping AF_CONST from the five comsys writes is the smallest
change, but then anyone controlling the channel object can forge
recall history with a plain @set. Channel logs stop being
tamper-evident, which is what AF_CONST was buying.
- Giving God an escape from AF_CONST in bCanSetAttr is one line and
restores an invariant that holds for every other flag there, but it
is a global permission change to fix one subsystem's problem.
Scope is deliberately narrow. The channel is passed by NAME, so the
engine resolves the object itself and this cannot become "write any
attribute anywhere as GOD". Player-owned attributes are untouched --
mail's Mailcurf/Mailfolders keep going through the permission-checked
mux_IAttributeAccess, which is the right path for anything a player
owns.
tests/comsys_handoff now passes with no TODO markers:
ok 4 - module can clear a flag the engine set (#1585)
ok 6 - module overwrites an engine-written history slot (#1620)
=== comsys handoff: 8 passed, 0 failed, 0 known-failing ===
The harness demanded this edit rather than allowing it: with the fix in
and the markers still present it reported "2 unexpectedly passing" and
failed the run, which is what those markers are for.
One assertion changed direction rather than being deleted. #1624 added
a check that a REFUSED write was reported honestly instead of as
success; there is no refusal left to report, so it now checks that the
clear reports success -- because it now is one. Asserting on the old
failure text would have failed for the right reason in the wrong
direction.
make test green: 1561/1561 on all three smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:26:08 -06:00
|
|
|
|
if (nullptr == ch || !Good_obj(ch->chan_obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const int atr = mkattr(GOD, pAttrName);
|
|
|
|
|
|
if (atr <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// atr_add treats an empty value as a delete, which is how the engine has
|
|
|
|
|
|
// always cleared these -- so the module gets the same two operations the
|
|
|
|
|
|
// engine has, through one call.
|
|
|
|
|
|
//
|
|
|
|
|
|
atr_add(ch->chan_obj, atr, (nullptr != pValue) ? pValue : T(""), GOD,
|
|
|
|
|
|
AF_CONST | AF_NOPROG | AF_NOPARSE);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
MUX_RESULT CComsysStorage::SyncChannelUser(const UTF8 *channel_name, int who,
|
|
|
|
|
|
bool is_on, bool comtitle_status, bool gag_join_leave, const UTF8 *title)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->SyncChannelUser(channel_name, who,
|
2026-03-10 10:39:15 -06:00
|
|
|
|
is_on, comtitle_status, gag_join_leave, title);
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::SyncPlayerChannel(int who, const UTF8 *alias,
|
|
|
|
|
|
const UTF8 *channel_name)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->SyncPlayerChannel(who, alias,
|
2026-03-10 10:39:15 -06:00
|
|
|
|
channel_name);
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::DeleteChannel(const UTF8 *name)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->DeleteChannel(name);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::DeleteChannelUser(const UTF8 *channel_name, int who)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->DeleteChannelUser(channel_name, who);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::DeletePlayerChannel(int who, const UTF8 *alias)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->DeletePlayerChannel(who, alias);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::DeleteAllPlayerChannels(int who)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->DeleteAllPlayerChannels(who);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorage::ClearComsysTables(void)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->ClearComsysTables();
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CComsysStorageFactory
|
|
|
|
|
|
//
|
|
|
|
|
|
CComsysStorageFactory::CComsysStorageFactory(void) : m_cRef(1) {}
|
|
|
|
|
|
CComsysStorageFactory::~CComsysStorageFactory() {}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorageFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid || mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CComsysStorageFactory::AddRef(void) { m_cRef++; return m_cRef; }
|
|
|
|
|
|
uint32_t CComsysStorageFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef) { delete this; return 0; }
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorageFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
|
|
|
|
|
|
MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(pUnknownOuter);
|
|
|
|
|
|
CComsysStorage *p = nullptr;
|
|
|
|
|
|
try { p = new CComsysStorage; } catch (...) { ; }
|
|
|
|
|
|
if (nullptr == p) return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
MUX_RESULT mr = p->QueryInterface(iid, ppv);
|
|
|
|
|
|
p->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysStorageFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
// CMailStorage — engine-provided SQLite access for mail_mod.so.
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
class CMailStorage : public mux_IMailStorage
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual MUX_RESULT QueryInterface(MUX_IID iid, void **ppv);
|
|
|
|
|
|
virtual uint32_t AddRef(void);
|
|
|
|
|
|
virtual uint32_t Release(void);
|
|
|
|
|
|
|
|
|
|
|
|
virtual MUX_RESULT LoadAllMailHeaders(PFN_MAIL_HEADER_CB pfn, void *context);
|
|
|
|
|
|
virtual MUX_RESULT LoadAllMailBodies(PFN_MAIL_BODY_CB pfn, void *context);
|
|
|
|
|
|
virtual MUX_RESULT LoadAllMailAliases(PFN_MAIL_ALIAS_CB pfn, void *context);
|
|
|
|
|
|
virtual MUX_RESULT GetMeta(const UTF8 *key, int *pValue);
|
2026-07-25 08:33:42 -06:00
|
|
|
|
virtual MUX_RESULT PutMeta(const UTF8 *key, int value);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
virtual MUX_RESULT InsertMailHeader(int to_player, int from_player,
|
|
|
|
|
|
int body_number, const UTF8 *tolist, const UTF8 *time_str,
|
|
|
|
|
|
const UTF8 *subject, int read_flags, int64_t *pRowid);
|
|
|
|
|
|
virtual MUX_RESULT UpdateMailReadFlags(int64_t rowid, int read_flags);
|
|
|
|
|
|
virtual MUX_RESULT DeleteMailHeader(int64_t rowid);
|
|
|
|
|
|
virtual MUX_RESULT DeleteAllMailHeaders(int to_player);
|
|
|
|
|
|
virtual MUX_RESULT SyncMailBody(int number, const UTF8 *message);
|
|
|
|
|
|
virtual MUX_RESULT DeleteMailBody(int number);
|
|
|
|
|
|
virtual MUX_RESULT SyncMailAlias(int owner, const UTF8 *name,
|
|
|
|
|
|
const UTF8 *desc, int desc_width, const UTF8 *members);
|
|
|
|
|
|
virtual MUX_RESULT ClearMailAliases(void);
|
|
|
|
|
|
virtual MUX_RESULT ClearMailTables(void);
|
|
|
|
|
|
|
|
|
|
|
|
CMailStorage(void);
|
|
|
|
|
|
virtual ~CMailStorage();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint32_t m_cRef;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
CMailStorage::CMailStorage(void) : m_cRef(1) {}
|
|
|
|
|
|
CMailStorage::~CMailStorage() {}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid || IID_IMailStorage == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IMailStorage *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CMailStorage::AddRef(void) { m_cRef++; return m_cRef; }
|
|
|
|
|
|
uint32_t CMailStorage::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef) { delete this; return 0; }
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::LoadAllMailHeaders(PFN_MAIL_HEADER_CB pfn, void *context)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == pfn) return MUX_E_INVALIDARG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
bool ok = pDb->LoadAllMailHeaders(
|
2026-03-10 10:39:15 -06:00
|
|
|
|
[pfn, context](int64_t rowid, int to_player, int from_player,
|
|
|
|
|
|
int body_number, const UTF8 *tolist, const UTF8 *time_str,
|
|
|
|
|
|
const UTF8 *subject, int read_flags)
|
|
|
|
|
|
{
|
|
|
|
|
|
pfn(context, rowid, to_player, from_player, body_number,
|
|
|
|
|
|
tolist, time_str, subject, read_flags);
|
|
|
|
|
|
});
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::LoadAllMailBodies(PFN_MAIL_BODY_CB pfn, void *context)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == pfn) return MUX_E_INVALIDARG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
bool ok = pDb->LoadAllMailBodies(
|
2026-03-10 10:39:15 -06:00
|
|
|
|
[pfn, context](int number, const UTF8 *message)
|
|
|
|
|
|
{
|
|
|
|
|
|
pfn(context, number, message);
|
|
|
|
|
|
});
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::LoadAllMailAliases(PFN_MAIL_ALIAS_CB pfn, void *context)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == pfn) return MUX_E_INVALIDARG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
bool ok = pDb->LoadAllMailAliases(
|
2026-03-10 10:39:15 -06:00
|
|
|
|
[pfn, context](int owner, const UTF8 *name,
|
|
|
|
|
|
const UTF8 *desc, int desc_width, const UTF8 *members)
|
|
|
|
|
|
{
|
|
|
|
|
|
pfn(context, owner, name, desc, desc_width, members);
|
|
|
|
|
|
});
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::GetMeta(const UTF8 *key, int *pValue)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == key || nullptr == pValue) return MUX_E_INVALIDARG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
bool ok = pDb->GetMeta(
|
2026-03-10 10:39:15 -06:00
|
|
|
|
reinterpret_cast<const char *>(key), pValue);
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 08:33:42 -06:00
|
|
|
|
MUX_RESULT CMailStorage::PutMeta(const UTF8 *key, int value)
|
|
|
|
|
|
{
|
|
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == key) return MUX_E_INVALIDARG;
|
2026-07-25 08:31:08 -06:00
|
|
|
|
bool ok = pDb->PutMeta(
|
2026-07-25 08:04:59 -06:00
|
|
|
|
reinterpret_cast<const char *>(key), value);
|
2026-07-25 08:33:42 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
MUX_RESULT CMailStorage::InsertMailHeader(int to_player, int from_player,
|
|
|
|
|
|
int body_number, const UTF8 *tolist, const UTF8 *time_str,
|
|
|
|
|
|
const UTF8 *subject, int read_flags, int64_t *pRowid)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == pRowid) return MUX_E_INVALIDARG;
|
2026-07-25 08:00:15 -06:00
|
|
|
|
int64_t id = pDb->InsertMailHeaderReturningId(
|
2026-03-10 10:39:15 -06:00
|
|
|
|
to_player, from_player, body_number, tolist, time_str,
|
|
|
|
|
|
subject, read_flags);
|
|
|
|
|
|
if (id < 0) return MUX_E_FAIL;
|
|
|
|
|
|
*pRowid = id;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::UpdateMailReadFlags(int64_t rowid, int read_flags)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->UpdateMailReadFlags(rowid, read_flags);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::DeleteMailHeader(int64_t rowid)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->DeleteMailHeader(rowid);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::DeleteAllMailHeaders(int to_player)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->DeleteAllMailHeaders(to_player);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::SyncMailBody(int number, const UTF8 *message)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->SyncMailBody(number, message);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::DeleteMailBody(int number)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->DeleteMailBody(number);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::SyncMailAlias(int owner, const UTF8 *name,
|
|
|
|
|
|
const UTF8 *desc, int desc_width, const UTF8 *members)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->SyncMailAlias(owner, name, desc,
|
2026-03-10 10:39:15 -06:00
|
|
|
|
desc_width, members);
|
|
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::ClearMailAliases(void)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->ClearMailAliases();
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorage::ClearMailTables(void)
|
|
|
|
|
|
{
|
2026-07-25 08:00:15 -06:00
|
|
|
|
CSQLiteDB *pDb = sqlite_storage_db();
|
|
|
|
|
|
if (nullptr == pDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ok = pDb->ClearMailTables();
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return ok ? MUX_S_OK : MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CMailStorageFactory
|
|
|
|
|
|
//
|
|
|
|
|
|
CMailStorageFactory::CMailStorageFactory(void) : m_cRef(1) {}
|
|
|
|
|
|
CMailStorageFactory::~CMailStorageFactory() {}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorageFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid || mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CMailStorageFactory::AddRef(void) { m_cRef++; return m_cRef; }
|
|
|
|
|
|
uint32_t CMailStorageFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef) { delete this; return 0; }
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorageFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
|
|
|
|
|
|
MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(pUnknownOuter);
|
|
|
|
|
|
CMailStorage *p = nullptr;
|
|
|
|
|
|
try { p = new CMailStorage; } catch (...) { ; }
|
|
|
|
|
|
if (nullptr == p) return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
MUX_RESULT mr = p->QueryInterface(iid, ppv);
|
|
|
|
|
|
p->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CMailStorageFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 09:59:36 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CJITCompileFactory — Lua bytecode → native JIT compilation.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
2026-03-18 13:13:40 -06:00
|
|
|
|
#if defined(TINYMUX_JIT)
|
2026-03-18 09:59:36 -06:00
|
|
|
|
CJITCompileFactory::CJITCompileFactory(void) : m_cRef(1) {}
|
|
|
|
|
|
CJITCompileFactory::~CJITCompileFactory() {}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CJITCompileFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IUnknown *>(static_cast<mux_IClassFactory *>(this));
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CJITCompileFactory::AddRef(void) { m_cRef++; return m_cRef; }
|
|
|
|
|
|
uint32_t CJITCompileFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef) { delete this; return 0; }
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CJITCompileFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
|
|
|
|
|
|
MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != pUnknownOuter) return MUX_E_NOAGGREGATION;
|
|
|
|
|
|
return jit_compile_create_instance(iid, ppv);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CJITCompileFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
2026-03-18 13:13:40 -06:00
|
|
|
|
#endif // TINYMUX_JIT
|
2026-03-18 09:59:36 -06:00
|
|
|
|
|
2026-03-18 15:11:05 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CLuaModFactory — Lua 5.4 scripting (embedded in engine.so).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CLuaModFactory::CLuaModFactory(void) : m_cRef(1) {}
|
|
|
|
|
|
CLuaModFactory::~CLuaModFactory() {}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CLuaModFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IUnknown *>(static_cast<mux_IClassFactory *>(this));
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CLuaModFactory::AddRef(void) { m_cRef++; return m_cRef; }
|
|
|
|
|
|
uint32_t CLuaModFactory::Release(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cRef--;
|
|
|
|
|
|
if (0 == m_cRef) { delete this; return 0; }
|
|
|
|
|
|
return m_cRef;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CLuaModFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
|
|
|
|
|
|
MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != pUnknownOuter) return MUX_E_NOAGGREGATION;
|
|
|
|
|
|
return lua_mod_create_instance(iid, ppv);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CLuaModFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(bLock);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 12:34:51 -06:00
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
// COM Front-Door — engine.so exports only these 4 functions.
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
static MUX_CLASS_INFO engine_classes[] =
|
|
|
|
|
|
{
|
|
|
|
|
|
{ CID_Log },
|
|
|
|
|
|
{ CID_ServerEventsSource },
|
|
|
|
|
|
{ CID_QueryClient },
|
|
|
|
|
|
{ CID_Functions },
|
|
|
|
|
|
{ CID_LogPSFactory },
|
|
|
|
|
|
{ CID_Notify },
|
|
|
|
|
|
{ CID_ObjectInfo },
|
|
|
|
|
|
{ CID_AttributeAccess },
|
|
|
|
|
|
{ CID_Evaluator },
|
|
|
|
|
|
{ CID_Permissions },
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
{ CID_GameConfig },
|
2026-03-09 12:34:51 -06:00
|
|
|
|
{ CID_MailDelivery },
|
|
|
|
|
|
{ CID_HelpSystem },
|
|
|
|
|
|
{ CID_GameEngine },
|
|
|
|
|
|
{ CID_PlayerSession },
|
2026-03-10 10:39:15 -06:00
|
|
|
|
{ CID_ComsysStorage },
|
|
|
|
|
|
{ CID_MailStorage },
|
2026-03-18 13:13:40 -06:00
|
|
|
|
#if defined(TINYMUX_JIT)
|
2026-03-18 09:59:36 -06:00
|
|
|
|
{ CID_JITCompile },
|
2026-03-18 13:13:40 -06:00
|
|
|
|
#endif
|
2026-03-18 15:11:05 -06:00
|
|
|
|
{ CID_LuaMod },
|
2026-03-09 12:34:51 -06:00
|
|
|
|
};
|
|
|
|
|
|
#define NUM_ENGINE_CLASSES (sizeof(engine_classes)/sizeof(engine_classes[0]))
|
|
|
|
|
|
|
|
|
|
|
|
#define MAKE_FACTORY(cls, cid) \
|
|
|
|
|
|
if (cid == cid_arg) \
|
|
|
|
|
|
{ \
|
|
|
|
|
|
cls *pFactory = nullptr; \
|
|
|
|
|
|
try { pFactory = new cls; } catch (...) { ; } \
|
|
|
|
|
|
if (nullptr == pFactory) return MUX_E_OUTOFMEMORY; \
|
|
|
|
|
|
mr = pFactory->QueryInterface(iid, ppv); \
|
|
|
|
|
|
pFactory->Release(); \
|
|
|
|
|
|
return mr; \
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" MUX_RESULT DCL_EXPORT DCL_API mux_GetClassObject(MUX_CID cid_arg, MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
MUX_RESULT mr = MUX_E_CLASSNOTAVAILABLE;
|
|
|
|
|
|
|
|
|
|
|
|
MAKE_FACTORY(CLogFactory, CID_Log)
|
|
|
|
|
|
MAKE_FACTORY(CServerEventsSourceFactory, CID_ServerEventsSource)
|
|
|
|
|
|
MAKE_FACTORY(CQueryClientFactory, CID_QueryClient)
|
|
|
|
|
|
MAKE_FACTORY(CFunctionsFactory, CID_Functions)
|
|
|
|
|
|
MAKE_FACTORY(CLogPSFactory, CID_LogPSFactory)
|
|
|
|
|
|
MAKE_FACTORY(CNotifyFactory, CID_Notify)
|
|
|
|
|
|
MAKE_FACTORY(CObjectInfoFactory, CID_ObjectInfo)
|
|
|
|
|
|
MAKE_FACTORY(CAttributeAccessFactory, CID_AttributeAccess)
|
|
|
|
|
|
MAKE_FACTORY(CEvaluatorFactory, CID_Evaluator)
|
|
|
|
|
|
MAKE_FACTORY(CPermissionsFactory, CID_Permissions)
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
MAKE_FACTORY(CGameConfigFactory, CID_GameConfig)
|
2026-03-09 12:34:51 -06:00
|
|
|
|
MAKE_FACTORY(CMailDeliveryFactory, CID_MailDelivery)
|
|
|
|
|
|
MAKE_FACTORY(CHelpSystemFactory, CID_HelpSystem)
|
|
|
|
|
|
MAKE_FACTORY(CGameEngineFactory, CID_GameEngine)
|
|
|
|
|
|
MAKE_FACTORY(CPlayerSessionFactory, CID_PlayerSession)
|
2026-03-10 10:39:15 -06:00
|
|
|
|
MAKE_FACTORY(CComsysStorageFactory, CID_ComsysStorage)
|
|
|
|
|
|
MAKE_FACTORY(CMailStorageFactory, CID_MailStorage)
|
2026-03-18 13:13:40 -06:00
|
|
|
|
#if defined(TINYMUX_JIT)
|
2026-03-18 09:59:36 -06:00
|
|
|
|
MAKE_FACTORY(CJITCompileFactory, CID_JITCompile)
|
2026-03-18 13:13:40 -06:00
|
|
|
|
#endif
|
2026-03-18 15:11:05 -06:00
|
|
|
|
MAKE_FACTORY(CLuaModFactory, CID_LuaMod)
|
2026-03-09 12:34:51 -06:00
|
|
|
|
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static MUX_INTERFACE_INFO engine_interfaces[] =
|
|
|
|
|
|
{
|
|
|
|
|
|
{ IID_ILog, CID_LogPSFactory }
|
|
|
|
|
|
};
|
|
|
|
|
|
#define NUM_ENGINE_INTERFACES (sizeof(engine_interfaces)/sizeof(engine_interfaces[0]))
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" MUX_RESULT DCL_EXPORT DCL_API mux_Register(void)
|
|
|
|
|
|
{
|
2026-03-09 20:38:37 -06:00
|
|
|
|
MUX_RESULT mr = mux_RegisterClassObjects(NUM_ENGINE_CLASSES, engine_classes, nullptr);
|
2026-03-09 12:34:51 -06:00
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
mr = mux_RegisterInterfaces(NUM_ENGINE_INTERFACES, engine_interfaces);
|
|
|
|
|
|
}
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" MUX_RESULT DCL_EXPORT DCL_API mux_Unregister(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_RevokeInterfaces(NUM_ENGINE_INTERFACES, engine_interfaces);
|
|
|
|
|
|
return mux_RevokeClassObjects(NUM_ENGINE_CLASSES, engine_classes);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" MUX_RESULT DCL_EXPORT DCL_API mux_CanUnloadNow(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// The engine can never be unloaded.
|
|
|
|
|
|
return MUX_S_FALSE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|