Skip to content

Commit

Permalink
everything for this release (in particular the CPX extension for game…
Browse files Browse the repository at this point in the history
… path/etc.)
  • Loading branch information
20kdc committed Jun 19, 2022
1 parent f57ee1c commit 8fa51d9
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 27 deletions.
46 changes: 32 additions & 14 deletions caosproxy/spec.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ CPX: CAOS Proxy For Cross-Language And Operating System Use

specification version number

2206182044
2206191904

license of specification

Expand Down Expand Up @@ -33,6 +33,35 @@ critical notes on the shared memory interface
the 32-bit little-endian status code (1 for error text, which inhibits other output) is at offset 8
and the response length (including null byte) is at offset 12

command set (standard)

The normal C2E command handling essentially reads a null-terminated string, and that's the command.
The command type is determined by the string-separated words of the first line.

Commands are as follows:
"execute\n" : Executes the (following) CAOS. Output is the result.
"scrp X Y Z W\n" : Adds script with classifier X Y Z W, which follows. Don't add an "endm", it's implied.

command set (CPX extension)

These commands are similar to engine commands, but they have stricter requirements on spacing as they're implemented in the CPX server.
They are treated as "starts with this set of bytes", but will still expect null termination if they have a parameter.
All CPX extended command start with the first four bytes "cpx-", and this is used to detect them.
They are as follows:
"cpx-fwd:" : This is used as an "escape command" to say you REALLY want to bypass this CPX server.
"cpx-ver\n" : Output is the CPX server version.
"cpx-gamepath\n" : Output is the game path, i.e: C:\Program Files (x86)\Docking Station\

notes on extensions

Extensions to this protocol, should they ever be required, should pretend to be engine commands.
It's not like the engine is ever going to change.
This is why the standardized API design is careful about the raw request API.

Do be aware extensions should not be considered a substitute for manual configuration.
Returning the game path should not mean applications should only operate if the game path is correctly returned.
***Extensions should absolutely not be abused to prevent manual configuration!!!***

standardized API design

Following this part of the specification is not required but recommended.
Expand Down Expand Up @@ -63,17 +92,6 @@ standardized API design
This means no truncating based on the null terminator.
However, where the API is using exceptions, errors can have their null terminator stripped and be decoded as Latin-1.

notes on extensions

Extensions to this protocol, should they ever be required, should pretend to be engine commands.
It's not like the engine is ever going to change.
This is why the standardized API design is careful about the raw request API.

Do be aware extensions should not be considered a substitute for manual configuration.
Returning the "game path" sounds portable until a virtual machine or separate computer is involved.
Then it comes down to manual configuration.
***Extensions should absolutely not be abused to prevent manual configuration!!!***

server error handling

It's possible for an error to occur in the CPX Server, rather than in the engine (say, if the game hasn't been started yet)
Expand Down Expand Up @@ -128,9 +146,9 @@ design rationale

implementation status (from most important to least)

CPX Server Windows : PUBLIC BETA
CPX Server Windows : OK
Example CPX Client : OK
Storyteller PR : SORT OF
Storyteller : HANDLED BY UPSTREAM
CPX Server Unix : BUGGY ALPHA
CPX -> SHM : NO

Expand Down
25 changes: 25 additions & 0 deletions caosproxy/tools/cpxinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python3
# CAOS Terminal

# caosprox - CPX server reference implementation
# Written starting in 2022 by 20kdc
# To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
# You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

import socket
import readline
import sys
import struct
import traceback

import libcpx

def rrdo(t: bytes) -> str:
return libcpx.raw_request_default(t)[:-1].decode("latin1")

print("server:", rrdo(b"cpx-ver\n\0"))
print("game path:", rrdo(b"cpx-gamepath\n\0"))
print("game name:", rrdo(b"execute\nouts gnam\0"))
print("engine version:", rrdo(b"execute\noutv vmjr outs \".\" outv vmnr\0"))
print("engine modules:", rrdo(b"execute\nouts modu\0"))

4 changes: 3 additions & 1 deletion caosproxy/tools/index.mk
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
rel: caosproxy/tools/caosterm.py caosproxy/tools/libcpx.py caosproxy/tools/maprooms.py caosproxy/tools/maptypes.py caosproxy/tools/bdmptest.py
rel: caosproxy/tools/libcpx.py
rel: caosproxy/tools/caosterm.py caosproxy/tools/cpxinfo.py
rel: caosproxy/tools/bdmptest.py caosproxy/tools/maprooms.py caosproxy/tools/maptypes.py

1 change: 1 addition & 0 deletions caosproxy/w32/cpxservg.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// returns non-zero on error
extern int cpxservi_serverInit(int host, int port);
extern const char * cpxservi_gameID;
// this uses stdio, so we need to be sure we stop using it from main thread if in UI mode!
extern void cpxservi_serverLoop();

static HWND globalWindow;
Expand Down
75 changes: 63 additions & 12 deletions caosproxy/w32/cpxservi.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ int cpxservi_serverInit(int host, int port) {
}

const char * cpxservi_gameID = NULL;
const char * cpxservi_gamePath = NULL;

static int sgetc(SOCKET s) {
char chr;
Expand Down Expand Up @@ -77,23 +78,43 @@ static void sputa(SOCKET s, const void * target, int len) {


static char gameIDBuffer[8192];
static char gamePathBuffer[8192];
static char tmpBuffer[8200]; // above + 8 chars for _request

static const char * findGameID() {
if (cpxservi_gameID)
return cpxservi_gameID;
static const char * findRegKey(HKEY hive, const char * base, const char * sub, const char * def, char * buffer) {
HKEY key;
if (!RegOpenKeyA(HKEY_CURRENT_USER, "Software\\CyberLife Technology\\Creatures Engine", &key)) {
LONG gameIDBufferLen = 8191;
int result = RegQueryValueA(key, "Default Game", gameIDBuffer, &gameIDBufferLen);
if (!RegOpenKeyA(hive, base, &key)) {
LONG bufferLen = 8191;
DWORD throwaway; // I feel like there's a story to this being required.
int result = RegQueryValueExA(key, sub, NULL, &throwaway, buffer, &bufferLen);
RegCloseKey(key);
if (!result) {
// only try this if it succeeds - ERROR_MORE_DATA will cause it to run off the end of the buffer
gameIDBuffer[gameIDBufferLen] = 0;
return gameIDBuffer;
buffer[bufferLen] = 0;
return buffer;
}
printf("caosprox registry warning, found \"%s\" but asking for \"%s\" got %x\n", base, sub, result);
} else {
printf("caosprox registry warning, failed to find \"%s\":\"%s\"\n", base, sub);
}
return "Docking Station";
return def;
}

static const char * findGameID() {
if (cpxservi_gameID)
return cpxservi_gameID;
return findRegKey(HKEY_CURRENT_USER, "Software\\CyberLife Technology\\Creatures Engine", "Default Game", "Docking Station", gameIDBuffer);
}

static const char * findGamePath(const char * gameID) {
if (cpxservi_gamePath)
return cpxservi_gamePath;
const char * base = "Software\\CyberLife Technology\\";
int baseLen = strlen(base);
char name[baseLen + strlen(gameID) + 1];
strcpy(name, base);
strcpy(name + baseLen, gameID);
return findRegKey(HKEY_LOCAL_MACHINE, name, "Main Directory", "C:\\Program Files (x86)\\Docking Station\\", gameIDBuffer);
}

typedef struct {
Expand All @@ -107,6 +128,18 @@ static void transferAreaToClient(SOCKET client, const transfer_t * area, int hdr
sputa(client, area, hdrOnly ? 24 : (area->sizeBytes + 24));
}

static void sendStringResponse(SOCKET client, const char * text) {
transfer_t * tmpErr = (transfer_t *) tmpBuffer;
memcpy(tmpErr->magic, "c2e@", 4);
strcpy(tmpErr->data, text);
tmpErr->pid = 0;
tmpErr->resultCode = 0;
tmpErr->sizeBytes = strlen(tmpErr->data) + 1;
tmpErr->maxSizeBytes = 8192;
tmpErr->padding = 0;
transferAreaToClient(client, tmpErr, 0);
}

// Note! doubleSend is 1 if we haven't sent the initial 24-byte header.
static void internalError(SOCKET client, const char * text, int doubleSend) {
transfer_t * tmpErr = (transfer_t *) tmpBuffer;
Expand All @@ -122,7 +155,7 @@ static void internalError(SOCKET client, const char * text, int doubleSend) {
transferAreaToClient(client, tmpErr, 0);
}

static void handleClientWithEverything(SOCKET client, transfer_t * shm, HANDLE resultEvent, HANDLE requestEvent, HANDLE process) {
static void handleClientWithEverything(SOCKET client, const char * gameID, transfer_t * shm, HANDLE resultEvent, HANDLE requestEvent, HANDLE process) {
// send SHM state to client
transferAreaToClient(client, shm, 1);
// now we want a size back
Expand All @@ -141,7 +174,25 @@ static void handleClientWithEverything(SOCKET client, transfer_t * shm, HANDLE r
internalError(client, "failed to get request body", 0);
return;
}
// actually run the request
// WAIT! This could be intended for CPX.
if ((size >= 8) && !memcmp(shm->data, "cpx-fwd:", 8)) {
// The client wants us to forward this along as if nothing happened.
memmove(shm->data, shm->data + 8, size - 8);
// fallthrough
} else if ((size >= 8) && !memcmp(shm->data, "cpx-ver\n", 8)) {
// The client wants us to give an identifier
sendStringResponse(client, "CPX Server W32");
return;
} else if ((size >= 8) && !memcmp(shm->data, "cpx-gamepath\n", 13)) {
// The client wants us to give the game path
sendStringResponse(client, findGamePath(gameID));
return;
} else if ((size >= 4) && !memcmp(shm->data, "cpx-", 4)) {
// It's intended for CPX but we don't recognize it.
internalError(client, "Unrecognized CPX extension command.", 0);
return;
}
// It's intended for the engine
ResetEvent(resultEvent);
PulseEvent(requestEvent);
HANDLE waitHandles[2] = {process, resultEvent};
Expand Down Expand Up @@ -177,7 +228,7 @@ static void handleClientWithSHM(SOCKET client, const char * gameID, transfer_t *
CloseHandle(process);
return;
}
handleClientWithEverything(client, shm, resultEvent, requestEvent, process);
handleClientWithEverything(client, gameID, shm, resultEvent, requestEvent, process);
CloseHandle(requestEvent);
CloseHandle(resultEvent);
}
Expand Down
2 changes: 2 additions & 0 deletions ciesetup/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ciesetup

*STATUS: Incomplete*

ciesetup is a process designed to take one of three files:

+ `dockingstation.run` (MD5: 5a175535e104e6150005c1f3213aebc3 )
Expand Down

0 comments on commit 8fa51d9

Please sign in to comment.