tinymux/mux/modules/engine/engine_com.cpp
Stephen Dennis 95977dc7d4 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

6164 lines
160 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*! \file engine_com.cpp
* \brief Engine-side COM class implementations and COM front-door.
*
* These classes implement the server-provided COM interfaces that
* 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
* mux_CanUnloadNow. All other symbols have hidden visibility.
* g_debug_cmd lives in libmux.so (shared crash breadcrumb).
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#include "sqlite_backend.h"
#include "mguests.h"
#include "engine_api.h"
#include "routing.h"
#include "walk.h"
// g_debug_cmd moved to libmux.cpp — shared by all layers.
// ---------------------------------------------------------------------------
// 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)
DEFINE_ENGINE_FACTORY(CGameConfigFactory)
DEFINE_ENGINE_FACTORY(CMailDeliveryFactory)
DEFINE_ENGINE_FACTORY(CHelpSystemFactory)
DEFINE_ENGINE_FACTORY(CGameEngineFactory)
DEFINE_ENGINE_FACTORY(CPlayerSessionFactory)
DEFINE_ENGINE_FACTORY(CComsysStorageFactory)
DEFINE_ENGINE_FACTORY(CMailStorageFactory)
#if defined(TINYMUX_JIT)
// 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)
#endif
// 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)
// 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)
{
}
ServerEventsSinkNode *g_pServerEventsSinkListHead = nullptr;
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)
{
// #1878: never trust field lengths without an end
// bound. A truncated or hostile blob used to
// memcpy/advance past m_pBlob + m_nBlob.
//
int i, j;
UTF8 *p = m_pBlob;
UTF8 *const pEnd = m_pBlob + m_nBlob;
bool bRowError = false;
for (i = 0; i < m_nRows && !bRowError; i++)
{
m_pRows[i] = p;
for (j = 0; j < m_nFields; j++)
{
if (static_cast<size_t>(pEnd - p) < sizeof(size_t))
{
bRowError = true;
break;
}
size_t n;
memcpy(&n, p, sizeof(size_t));
p += sizeof(size_t);
if (static_cast<size_t>(pEnd - p) < n)
{
bRowError = true;
break;
}
p += n;
}
}
if (!bRowError && p == pEnd && i == m_nRows)
{
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)
{
// #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)
{
return nullptr;
}
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;
}
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;
}
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);
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);
virtual MUX_RESULT PayFor(dbref who, int cost, bool *pPaid);
virtual MUX_RESULT GiveTo(dbref who, int amount);
virtual MUX_RESULT GetPureName(dbref obj, const UTF8 **ppName);
virtual MUX_RESULT DecodeFlags(dbref player, dbref obj, UTF8 **ppStr);
virtual MUX_RESULT UnparseObject(dbref player, dbref target,
bool bObeyMyopic, UTF8 *pBuf, size_t nBufMax);
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);
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,
int64_t anFields[4]);
virtual MUX_RESULT PutConnectionInfoFields(dbref player,
int64_t anFields[4], CLinearTimeAbsolute &ltaNow);
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)
{
if (nullptr == pValid)
{
return MUX_E_INVALIDARG;
}
*pValid = Good_obj(obj);
return MUX_S_OK;
}
MUX_RESULT CObjectInfo::GetName(dbref obj, const UTF8 **ppName)
{
if (nullptr == ppName)
{
return MUX_E_INVALIDARG;
}
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)
{
if (nullptr == pOwner)
{
return MUX_E_INVALIDARG;
}
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)
{
if (nullptr == pLocation)
{
return MUX_E_INVALIDARG;
}
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)
{
if (nullptr == pType)
{
return MUX_E_INVALIDARG;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
MUX_RESULT CObjectInfo::AtrAddRaw(dbref obj, int attrnum, const UTF8 *value)
{
// 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.
//
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)
{
if ( nullptr == pValue
|| nullptr == pOwner
|| nullptr == pFlags
|| 0 == nValueMax)
{
return MUX_E_INVALIDARG;
}
if (!Good_obj(obj))
{
*pValue = '\0';
return MUX_E_INVALIDARG;
}
LBuf p = LBuf_Adopt(atr_get("com_bridge", obj, attrnum, pOwner, pFlags));
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)
{
if ( nullptr == pValue
|| nullptr == pOwner
|| nullptr == pFlags
|| 0 == nValueMax)
{
return MUX_E_INVALIDARG;
}
if (!Good_obj(obj))
{
*pValue = '\0';
return MUX_E_INVALIDARG;
}
LBuf p = LBuf_Adopt(atr_pget(obj, attrnum, pOwner, pFlags));
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;
}
*pResult = lookup_player(executor, pName, bConnected);
return MUX_S_OK;
}
MUX_RESULT CObjectInfo::FetchConnectionInfoFields(dbref player,
int64_t anFields[4])
{
if (!Good_obj(player))
{
return MUX_E_INVALIDARG;
}
fetch_ConnectionInfoFields(player, anFields);
return MUX_S_OK;
}
MUX_RESULT CObjectInfo::PutConnectionInfoFields(dbref player,
int64_t anFields[4], CLinearTimeAbsolute &ltaNow)
{
if (!Good_obj(player))
{
return MUX_E_INVALIDARG;
}
put_ConnectionInfoFields(player, anFields, ltaNow);
return MUX_S_OK;
}
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)
{
if (nullptr == pValue || 0 == nValueMax || nullptr == pAttrName)
{
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;
LBuf buf = LBuf_Src("GetFormatCmd");
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)
{
if (!Good_obj(obj) || nullptr == pAttrName)
{
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);
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);
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.
//
LBuf buf = LBuf_Src("EvalExpr");
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;
}
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;
}
// ---------------------------------------------------------------------------
// 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);
virtual MUX_RESULT CouldDoit(dbref who, dbref what, int atr, bool *pResult);
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;
}
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;
}
// ---------------------------------------------------------------------------
// 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;
}
// 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;
LBuf str = LBuf_Adopt(atr_pget(target, A_MFAIL, &aowner, &aflags));
if (*str)
{
LBuf str2 = LBuf_Src("mail_delivery.check");
UTF8 *bp = str2.get();
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);
raw_notify(player, tprintf(M_("MAIL: Reject message from %s: %s"),
Moniker(target), str2.get()));
raw_notify(target, tprintf(M_("[%d:%02d] MAIL: Reject message sent to %s."),
ft.iHour, ft.iMinute, Moniker(player)));
}
}
else
{
raw_notify(player, tprintf(M_("Sorry, %s is not accepting mail."),
Moniker(target)));
}
*pResult = false;
return MUX_S_OK;
}
if (!could_doit(target, player, A_LMAIL))
{
if (Wizard(player))
{
raw_notify(player, tprintf(
M_("Warning: %s cant return your mail."),
Moniker(target)));
*pResult = true;
}
else
{
raw_notify(player, tprintf(
M_("Sorry, %s cant return your mail."),
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)
{
raw_notify(sender, tprintf(M_("MAIL: You sent your message to %s."),
Moniker(target)));
}
raw_notify(target, tprintf(
M_("MAIL: You have a new message from %s. Subject: %s"),
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.
//
LBuf buff = LBuf_Src("CHelpSystem.LookupTopic");
UTF8 *bufc = buff.get();
help_helper(executor, iHelpfile, pTopic, buff, &bufc);
*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 &ltaNow);
virtual MUX_RESULT UpdateQuotas(CLinearTimeAbsolute &ltaLast,
const CLinearTimeAbsolute &ltaCurrent);
virtual MUX_RESULT WhenNext(CLinearTimeAbsolute *pltaWhen);
virtual MUX_RESULT HasPendingUserTasks(bool *pbResult);
virtual MUX_RESULT DumpDatabase(void);
virtual MUX_RESULT Shutdown(void);
virtual MUX_RESULT DbConvert(const UTF8 *infile, const UTF8 *outfile,
const UTF8 *basename, bool bCheck, bool bLoad, bool bUnload,
const UTF8 *comsys_file, const UTF8 *mail_file, bool bForce);
virtual MUX_RESULT GetConfig(DRIVER_CONFIG *pConfig);
virtual MUX_RESULT MarkConnected(dbref player);
virtual MUX_RESULT DumpChildExited(int child_pid);
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);
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 &ltWhen,
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 &ltaWhen, 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);
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;
}
// alloc.cpp callback for @list buffers output.
//
static void engine_alloc_notify(dbref player, const UTF8 *text)
{
notify(player, text);
}
// ---------------------------------------------------------------------------
// 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)
{
mudstate.pIComsysControl = nullptr;
mudstate.pIMailControl = nullptr;
if (nullptr == g_pSQLiteBackend)
{
STARTLOG(LOG_ALWAYS, "INI", "MOD");
log_text(T("Skipping comsys/mail module init: SQLite backend not open."));
ENDLOG;
return;
}
MUX_RESULT mr;
// Comsys module.
//
mr = mux_CreateInstance(CID_Comsys, nullptr, UseSameProcess,
IID_IComsysControl,
reinterpret_cast<void **>(&mudstate.pIComsysControl));
if (MUX_SUCCEEDED(mr) && nullptr != mudstate.pIComsysControl)
{
mux_IComsysStorage *pComsysStorage = nullptr;
mr = mux_CreateInstance(CID_ComsysStorage, nullptr, UseSameProcess,
IID_IComsysStorage,
reinterpret_cast<void **>(&pComsysStorage));
if (MUX_SUCCEEDED(mr) && nullptr != pComsysStorage)
{
mr = mudstate.pIComsysControl->Initialize(pComsysStorage);
pComsysStorage->Release();
pComsysStorage = nullptr;
if (MUX_SUCCEEDED(mr))
{
STARTLOG(LOG_ALWAYS, "INI", "MOD");
log_text(T("Comsys module initialized (via engine storage)."));
ENDLOG;
}
else
{
STARTLOG(LOG_ALWAYS, "INI", "MOD");
log_printf(T("Comsys module Initialize failed (mr=%d); using built-in."), mr);
ENDLOG;
mudstate.pIComsysControl->Release();
mudstate.pIComsysControl = nullptr;
}
}
else
{
STARTLOG(LOG_ALWAYS, "INI", "MOD");
log_printf(T("Comsys storage interface creation failed (mr=%d); using built-in."), mr);
ENDLOG;
mudstate.pIComsysControl->Release();
mudstate.pIComsysControl = nullptr;
}
}
else
{
mudstate.pIComsysControl = nullptr;
}
// 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");
// Mail module.
//
mr = mux_CreateInstance(CID_Mail, nullptr, UseSameProcess,
IID_IMailControl,
reinterpret_cast<void **>(&mudstate.pIMailControl));
if (MUX_SUCCEEDED(mr) && nullptr != mudstate.pIMailControl)
{
mux_IMailStorage *pMailStorage = nullptr;
mr = mux_CreateInstance(CID_MailStorage, nullptr, UseSameProcess,
IID_IMailStorage,
reinterpret_cast<void **>(&pMailStorage));
if (MUX_SUCCEEDED(mr) && nullptr != pMailStorage)
{
mr = mudstate.pIMailControl->Initialize(pMailStorage,
mudconf.mail_expiration, mudconf.mail_max_per_player);
pMailStorage->Release();
pMailStorage = nullptr;
if (MUX_SUCCEEDED(mr))
{
STARTLOG(LOG_ALWAYS, "INI", "MOD");
log_text(T("Mail module initialized (via engine storage)."));
ENDLOG;
}
else
{
STARTLOG(LOG_ALWAYS, "INI", "MOD");
log_printf(T("Mail module Initialize failed (mr=%d); using built-in."), mr);
ENDLOG;
mudstate.pIMailControl->Release();
mudstate.pIMailControl = nullptr;
}
}
else
{
STARTLOG(LOG_ALWAYS, "INI", "MOD");
log_printf(T("Mail storage interface creation failed (mr=%d); using built-in."), mr);
ENDLOG;
mudstate.pIMailControl->Release();
mudstate.pIMailControl = nullptr;
}
}
else
{
mudstate.pIMailControl = nullptr;
}
// 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");
}
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);
}
// A config file that cannot be opened is fatal (#1601).
//
// 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.
//
// 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.
//
// stderr: STARTLOG is not visible at this phase under muxscript, which is
// half of why the defect stayed hidden. Callers also name the file.
//
if (0 != cf_read())
{
fprintf(stderr,
"FATAL: cannot read configuration file '%s' -- refusing to start "
"on compiled-in defaults.\n",
reinterpret_cast<const char *>(mudconf.config_file));
STARTLOG(LOG_ALWAYS, "CNF", "LOAD");
log_printf(T("Fatal: configuration file '%s' could not be read."),
mudconf.config_file);
ENDLOG;
return MUX_E_NOTFOUND;
}
// 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.
//
// 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);
mux_nls_refresh_messages();
// 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;
// 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))
{
mudstate.pILuaControl->SetLimits(
mudconf.lua_instruction_limit,
mudconf.lua_memory_limit);
STARTLOG(LOG_ALWAYS, "INI", "MOD");
log_text(T("Lua scripting module discovered."));
ENDLOG;
}
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");
log_printf(T("Couldnt connect sink to server (%d)."), mr);
ENDLOG;
}
}
else
{
mudstate.pIQueryControl->Release();
mudstate.pIQueryControl = nullptr;
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
log_printf(T("Couldnt create Query Sink (%d)."), mr);
ENDLOG;
}
}
else
{
mudstate.pIQueryControl->Release();
mudstate.pIQueryControl = nullptr;
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
log_printf(T("Couldnt connect to Query Server (%d)."), mr);
ENDLOG;
}
}
else
{
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
log_text(T("Couldnt create interface to Query Server."));
ENDLOG;
}
#if defined(INLINESQL)
init_sql();
#endif // INLINESQL
fcache_init();
helpindex_init();
// PlayerSession interface is now created by the driver after LoadGame.
// Open or create the SQLite database.
//
if (bMinDB)
{
// Remove the SQLite database to start fresh.
//
// 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.
//
char sqlitefile[SIZEOF_PATHNAME];
if (derive_sqlite_path(sqlitefile, sizeof(sqlitefile), mudconf.indb))
{
RemoveFile((UTF8 *)sqlitefile);
}
else
{
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
log_text(T("input_database too long to derive a .sqlite path; not removed."));
ENDLOG;
}
}
int ccPageFile = init_dbfile(mudconf.indb);
if (HF_OPEN_STATUS_ERROR == ccPageFile)
{
STARTLOG(LOG_ALWAYS, "INI", "LOAD");
log_text(T("Couldnt open storage backend."));
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;
// 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);
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."));
ENDLOG
}
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")
log_text(T("Couldnt load: "));
log_text(mudconf.indb);
ENDLOG
return MUX_E_FAIL;
}
}
// Repair any out-of-range object field dbrefs from the loaded database
// (#810). No-op for a valid DB; prevents OOB at use (e.g. DOLIST chain
// 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();
// 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);
mux_fclose(f);
}
}
}
// #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();
return MUX_S_OK;
}
MUX_RESULT CGameEngine::Startup(void)
{
// Set up the COM bridge so engine-side code can reach the driver's
// connection manager through free-function stubs.
//
conn_bridge_init();
Guest.StartUp();
// Do a consistency check and set up the freelist.
//
do_dbck(NOTHING, NOTHING, NOTHING, 0, 0);
route_init();
walk_init();
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 &ltaNow)
{
scheduler.RunTasks(ltaNow);
return MUX_S_OK;
}
MUX_RESULT CGameEngine::UpdateQuotas(CLinearTimeAbsolute &ltaLast,
const CLinearTimeAbsolute &ltaCurrent)
{
update_quotas(ltaLast, ltaCurrent);
return MUX_S_OK;
}
MUX_RESULT CGameEngine::WhenNext(CLinearTimeAbsolute *pltaWhen)
{
if (nullptr == pltaWhen)
{
return MUX_E_INVALIDARG;
}
// 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();
return MUX_S_OK;
}
MUX_RESULT CGameEngine::DumpDatabase(void)
{
dump_database();
return MUX_S_OK;
}
#if defined(TINYMUX_JIT)
extern void dbt_compile_cleanup(void);
#endif
MUX_RESULT CGameEngine::Shutdown(void)
{
#if defined(TINYMUX_JIT)
dbt_compile_cleanup();
#endif
walk_shutdown();
route_shutdown();
conn_bridge_final();
local_shutdown();
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
while (nullptr != p)
{
p->pSink->shutdown();
p = p->pNext;
}
return MUX_S_OK;
}
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;
pConfig->login_fail_limit = mudconf.login_fail_limit;
pConfig->login_fail_period = mudconf.login_fail_period;
pConfig->max_lastsite_cnt = mudconf.max_lastsite_cnt;
pConfig->min_con_attempt = mudconf.min_con_attempt;
pConfig->nospam_connect = mudconf.nospam_connect;
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;
pConfig->input_limit = mudconf.input_limit;
pConfig->max_preauth_sitecons = mudconf.max_preauth_sitecons;
pConfig->proto_detect_window = mudconf.proto_detect_window;
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();
pConfig->start_time_utc = mudstate.start_time.ReturnSeconds();
// 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;
}
MUX_RESULT CGameEngine::MarkConnected(dbref player)
{
if ( Good_obj(player)
&& isPlayer(player))
{
s_Connected(player);
return MUX_S_OK;
}
return MUX_E_INVALIDARG;
}
MUX_RESULT CGameEngine::DumpChildExited(int child_pid)
{
#if defined(HAVE_WORKING_FORK)
if (!mudstate.dumping)
{
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))
{
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;
#else
UNUSED_PARAMETER(child_pid);
return MUX_S_FALSE;
#endif
}
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;
}
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)
{
// #1871: surface scheduler OOM/insert failure to the driver bridge.
//
if (!scheduler.DeferImmediateTask(iPriority, fpTask, arg_voidptr, arg_Integer))
{
return MUX_E_OUTOFMEMORY;
}
return MUX_S_OK;
}
MUX_RESULT CGameEngine::DeferTask(const CLinearTimeAbsolute &ltWhen,
int iPriority, void (*fpTask)(void *, int), void *arg_voidptr,
int arg_Integer)
{
if (!scheduler.DeferTask(ltWhen, iPriority, fpTask, arg_voidptr, arg_Integer))
{
return MUX_E_OUTOFMEMORY;
}
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 &ltaWhen, 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;
}
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
&& (flags & MANDFLAGS_V4) != MANDFLAGS_V4)
|| ( 5 == ver
&& (flags & MANDFLAGS_V5) != MANDFLAGS_V5))
{
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,
const UTF8 *comsys_file, const UTF8 *mail_file, bool bForce)
{
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));
pcache_init();
cf_init();
// dbconvert path: write-through attribute cache immediately (no
// scheduler). Cleared before return if the process continues (#1046).
//
mudstate.bStandAlone = true;
// 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)
{
mux_fprintf(stderr, T("Cant open SQLite database.\n"));
return MUX_E_FAIL;
}
// 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)
{
if (setflags == OUTPUT_FLAGS && !bForce)
{
// 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).
//
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;
}
}
}
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"));
mux_fclose(fpIn);
return MUX_E_FAIL;
}
mudstate.bSQLiteLoading = true;
if (db_read(fpIn, &db_format, &db_ver, &db_flags) < 0)
{
mudstate.bSQLiteLoading = false;
sqldb.Rollback();
// 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"));
mux_fclose(fpIn);
mudstate.bStandAlone = false;
return MUX_E_FAIL;
}
mudstate.bSQLiteLoading = false;
if (!sqldb.Commit())
{
sqldb.Rollback();
cache_discard_writes();
mux_fprintf(stderr, T("SQLite attribute import commit failed.\n"));
mux_fclose(fpIn);
mudstate.bStandAlone = false;
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);
}
mux_fclose(fpIn);
}
// 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"));
}
// 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);
mux_fclose(fpMail);
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);
mux_fclose(fpMail);
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);
// #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);
mux_fclose(fpOut);
if (nWritten < 0)
{
CLOSE;
#ifdef SELFCHECK
db_free();
#endif
mudstate.bStandAlone = false;
return MUX_E_FAIL;
}
}
CLOSE;
#ifdef SELFCHECK
db_free();
#endif
mudstate.bStandAlone = false;
return MUX_S_OK;
}
// ---------------------------------------------------------------------------
// 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,
const UTF8 *username, const UTF8 *ipaddr, int *pTimeout,
int64_t *pConnlogId);
virtual MUX_RESULT AnnounceDisconnect(dbref player, int numConnections,
bool isSuspect, bool wasAutoDark, const UTF8 *reason,
int64_t connlogId);
virtual MUX_RESULT FcacheSend(DESC *d, int num);
virtual MUX_RESULT FcacheRawSend(SOCKET fd, int num);
virtual MUX_RESULT CreateGuest(DESC *d, const UTF8 **ppName);
virtual MUX_RESULT CheckGuest(dbref player, bool *pResult);
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,
const UTF8 *username, const UTF8 *ipaddr, int *pTimeout,
int64_t *pConnlogId)
{
// Preload attributes for the player and nearby rooms.
//
cache_preload_nearby(player, mudconf.cache_preload_depth);
// 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)
{
LBuf buf = LBuf_Src("AnnounceConnect.timeout");
dbref aowner;
int aflags;
size_t nLen;
atr_pget_str_LEN(buf, player, A_TIMEOUT, &aowner, &aflags, &nLen);
if (nLen)
{
*pTimeout = mux_atoi64(buf);
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))
{
raw_notify(player, M_("*** Logins are disabled."));
}
}
// Page lock warning.
//
{
LBuf buf = LBuf_Src("AnnounceConnect.lpage");
dbref aowner;
int aflags;
size_t nLen;
atr_get_str_LEN(buf, player, A_LPAGE, &aowner, &aflags, &nLen);
if (nLen)
{
raw_notify(player, M_("Your PAGE LOCK is set. You may be unable to receive some pages."));
}
}
// 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)
{
pRoomAnnounceFmt = M_("%s has connected.");
do_comconnect(player);
if ( Hidden(player)
&& Can_Hide(player))
{
pMonitorAnnounceFmt = M_("GAME: %s has DARK-connected.");
}
else
{
pMonitorAnnounceFmt = M_("GAME: %s has connected.");
}
if ( Suspect(player)
|| isSuspect)
{
raw_broadcast(WIZARD, M_("[Suspect] %s has connected."),
Moniker(player));
}
}
else
{
pRoomAnnounceFmt = M_("%s has reconnected.");
pMonitorAnnounceFmt = M_("GAME: %s has reconnected.");
if ( Suspect(player)
|| isSuspect)
{
raw_broadcast(WIZARD, M_("[Suspect] %s has reconnected."),
Moniker(player));
}
}
LBuf buf = LBuf_Src("AnnounceConnect.room");
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);
// 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);
}
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,
const UTF8 *reason, int64_t connlogId)
{
// 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);
}
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)
{
raw_broadcast(WIZARD, M_("[Suspect] %s has disconnected."),
Moniker(player));
}
LBuf buf = LBuf_Src("AnnounceDisconnect.only");
mux_sprintf(buf, LBUF_SIZE, M_("%s has disconnected."),
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);
raw_broadcast(MONITOR, M_("GAME: %s has disconnected. <%s>"),
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,
M_("[Suspect] %s has partially disconnected."),
Moniker(player));
}
UTF8 *mbuf = alloc_mbuf("AnnounceDisconnect.partial");
mux_sprintf(mbuf, MBUF_SIZE, M_("%s has partially disconnected."),
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
raw_broadcast(MONITOR, M_("GAME: %s has partially disconnected."),
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;
}
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;
}
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;
}
// ---------------------------------------------------------------------------
// 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;
}
// ===========================================================================
// 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);
virtual MUX_RESULT SetChannelAttr(const UTF8 *channel_name,
const UTF8 *pAttrName, const UTF8 *pValue);
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;
}
// #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();
}
MUX_RESULT CComsysStorage::LoadAllChannels(PFN_CHANNEL_CB pfn, void *context)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
if (nullptr == pfn) return MUX_E_INVALIDARG;
bool ok = pDb->LoadAllChannels(
[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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
if (nullptr == pfn) return MUX_E_INVALIDARG;
bool ok = pDb->LoadAllChannelUsers(
[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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
if (nullptr == pfn) return MUX_E_INVALIDARG;
bool ok = pDb->LoadAllPlayerChannels(
[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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->SyncChannel(name, header, type,
temp1, temp2, charge, charge_who, amount_col, num_messages, chan_obj);
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);
}
return ok ? MUX_S_OK : MUX_E_FAIL;
}
// 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;
}
struct channel *ch = select_channel(channel_name);
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;
}
MUX_RESULT CComsysStorage::SyncChannelUser(const UTF8 *channel_name, int who,
bool is_on, bool comtitle_status, bool gag_join_leave, const UTF8 *title)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->SyncChannelUser(channel_name, who,
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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->SyncPlayerChannel(who, alias,
channel_name);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CComsysStorage::DeleteChannel(const UTF8 *name)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->DeleteChannel(name);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CComsysStorage::DeleteChannelUser(const UTF8 *channel_name, int who)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->DeleteChannelUser(channel_name, who);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CComsysStorage::DeletePlayerChannel(int who, const UTF8 *alias)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->DeletePlayerChannel(who, alias);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CComsysStorage::DeleteAllPlayerChannels(int who)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->DeleteAllPlayerChannels(who);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CComsysStorage::ClearComsysTables(void)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->ClearComsysTables();
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);
virtual MUX_RESULT PutMeta(const UTF8 *key, int value);
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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
if (nullptr == pfn) return MUX_E_INVALIDARG;
bool ok = pDb->LoadAllMailHeaders(
[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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
if (nullptr == pfn) return MUX_E_INVALIDARG;
bool ok = pDb->LoadAllMailBodies(
[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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
if (nullptr == pfn) return MUX_E_INVALIDARG;
bool ok = pDb->LoadAllMailAliases(
[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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
if (nullptr == key || nullptr == pValue) return MUX_E_INVALIDARG;
bool ok = pDb->GetMeta(
reinterpret_cast<const char *>(key), pValue);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
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;
bool ok = pDb->PutMeta(
reinterpret_cast<const char *>(key), value);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
if (nullptr == pRowid) return MUX_E_INVALIDARG;
int64_t id = pDb->InsertMailHeaderReturningId(
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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->UpdateMailReadFlags(rowid, read_flags);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CMailStorage::DeleteMailHeader(int64_t rowid)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->DeleteMailHeader(rowid);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CMailStorage::DeleteAllMailHeaders(int to_player)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->DeleteAllMailHeaders(to_player);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CMailStorage::SyncMailBody(int number, const UTF8 *message)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->SyncMailBody(number, message);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CMailStorage::DeleteMailBody(int number)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->DeleteMailBody(number);
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)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->SyncMailAlias(owner, name, desc,
desc_width, members);
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CMailStorage::ClearMailAliases(void)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->ClearMailAliases();
return ok ? MUX_S_OK : MUX_E_FAIL;
}
MUX_RESULT CMailStorage::ClearMailTables(void)
{
CSQLiteDB *pDb = sqlite_storage_db();
if (nullptr == pDb)
{
return MUX_E_FAIL;
}
bool ok = pDb->ClearMailTables();
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;
}
// ---------------------------------------------------------------------------
// CJITCompileFactory — Lua bytecode → native JIT compilation.
// ---------------------------------------------------------------------------
#if defined(TINYMUX_JIT)
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;
}
#endif // TINYMUX_JIT
// ---------------------------------------------------------------------------
// 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;
}
// ===========================================================================
// 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 },
{ CID_GameConfig },
{ CID_MailDelivery },
{ CID_HelpSystem },
{ CID_GameEngine },
{ CID_PlayerSession },
{ CID_ComsysStorage },
{ CID_MailStorage },
#if defined(TINYMUX_JIT)
{ CID_JITCompile },
#endif
{ CID_LuaMod },
};
#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)
MAKE_FACTORY(CGameConfigFactory, CID_GameConfig)
MAKE_FACTORY(CMailDeliveryFactory, CID_MailDelivery)
MAKE_FACTORY(CHelpSystemFactory, CID_HelpSystem)
MAKE_FACTORY(CGameEngineFactory, CID_GameEngine)
MAKE_FACTORY(CPlayerSessionFactory, CID_PlayerSession)
MAKE_FACTORY(CComsysStorageFactory, CID_ComsysStorage)
MAKE_FACTORY(CMailStorageFactory, CID_MailStorage)
#if defined(TINYMUX_JIT)
MAKE_FACTORY(CJITCompileFactory, CID_JITCompile)
#endif
MAKE_FACTORY(CLuaModFactory, CID_LuaMod)
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)
{
MUX_RESULT mr = mux_RegisterClassObjects(NUM_ENGINE_CLASSES, engine_classes, nullptr);
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;
}