Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,20 @@ if(BUILD_TESTING)
"${CMAKE_SOURCE_DIR}/src")
add_test(NAME btech_value_helpers COMMAND btech_value_helpers_test)

add_executable(btech_safe_helpers_test tests/btech_safe_helpers.c
${BTECH_SOURCE_DIR}/core/btech_text_builder.c
${MUX_SOURCE_DIR}/support/stringutil.c)
target_compile_definitions(btech_safe_helpers_test PRIVATE BTECH_INTERNAL=1)
target_compile_options(btech_safe_helpers_test PRIVATE -ffunction-sections)
target_link_options(btech_safe_helpers_test PRIVATE -Wl,--gc-sections)
target_link_libraries(btech_safe_helpers_test PRIVATE btmux_options)
target_include_directories(btech_safe_helpers_test PRIVATE
"${CMAKE_BINARY_DIR}/${BTECH_SOURCE_DIR}"
"${CMAKE_BINARY_DIR}"
${BTECH_PRIVATE_INCLUDE_DIRS}
"${CMAKE_SOURCE_DIR}/src")
add_test(NAME btech_safe_helpers COMMAND btech_safe_helpers_test)

add_executable(btech_caller_control_test tests/btech_caller_control.c
${BTECH_SOURCE_DIR}/ui/mech_notify_radio_config.c
${BTECH_SOURCE_DIR}/unit/mech_identity_state.c
Expand Down
18 changes: 14 additions & 4 deletions docs/content/en/docs/concepts/btech-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ Each domain owns both its state and the operations that change that state:
The context-owned gameplay generator is xoshiro256**, seeded once from Linux
OS entropy during BTech startup. Its runtime state is not persisted.

Internal command boundaries use the shared MUX `parse_*_checked` helpers for
numeric input. They reject empty values, overflow, non-finite floating-point
values, and trailing non-whitespace input. Presentation code that incrementally
builds text uses `BtechTextBuilder`, which always terminates a non-empty
destination and records truncation instead of writing past its capacity.

Map files are read and written as plain text. Compressed map files are not
supported, and BTech file handling never invokes a shell command.

Concrete `Mech`, `BattleMap`, `Autopilot`, and runtime-context layouts are
private. Cross-domain interfaces use forward declarations, database object
references, or domain operations rather than copying another domain's state.
Expand Down Expand Up @@ -111,7 +120,8 @@ types use PascalCase, and functions use subject-prefixed snake_case.
`special`, exposing only typed lookup and dispatch operations to callers.

The architecture check rejects files over 800 lines, dotted or generated-style
filenames, private unit or registry headers outside their owner, complete
`Mech` values outside `unit`, mutable or centralized command catalogues,
untyped command callbacks, disabled legacy code, and known legacy exported
names.
filenames and prototype banners, private unit or registry headers outside their
owner, complete `Mech` values outside `unit`, mutable or centralized command
catalogues, untyped command callbacks, disabled legacy code, shell-based BTech
file handling, weak numeric parsing at converted command boundaries, and known
legacy exported names.
3 changes: 1 addition & 2 deletions docs/content/en/docs/scripting/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ administrator should use the full flag name when issuing a command because
```

The `mux` Lua package does not currently expose direct flag getter or setter
functions. Lua logic can invoke an appropriate native command with
`mux.command`; queued commands run as God after the current callback finishes.
functions.

## Flag summary

Expand Down
10 changes: 4 additions & 6 deletions docs/content/en/docs/scripting/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,9 @@ exposed.

The `mux` table is the server interface exposed to Lua modules. Use
`mux.object(dbref)` for object properties, containment, locks, and typed
persistent state. Styled text, notifications, queued commands, connection
summaries, and interactive flows remain top-level `mux` operations. Queued
commands execute as `#1` after the current handler completes. See the
[`mux` package reference](packages/mux/) for the full API.
persistent state. Styled text, notifications, connection summaries, and
interactive flows remain top-level `mux` operations. See the [`mux` package
reference](packages/mux/) for the full API.

Lua has no filesystem, process, debug, FFI, coroutine, or dynamic-loading
APIs. The configured memory cap applies to the complete Lua state. Persistent
Expand All @@ -78,8 +77,7 @@ object state has separate per-value and per-object limits.
Native control is role-only: God controls everything; Wizards control themselves
and every non-Wizard object and player but cannot control God or another Wizard;
mortals control nothing, including themselves. Zones do not affect control. Lua
is trusted and uses the `mux` API, including commands queued as
`#1`, to manipulate any object.
is trusted and uses the `mux` API to manipulate objects.

## Validating and reloading

Expand Down
3 changes: 1 addition & 2 deletions docs/content/en/docs/scripting/powers.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,4 @@ stored `IDLE` power; the power name in this search form must currently be
lowercase.

The `mux` Lua package does not currently expose direct power getter or setter
functions. Privileged Lua logic can queue the native `@power` command with
`mux.command`; queued commands run as God after the current callback finishes.
functions.
2 changes: 1 addition & 1 deletion src/btech/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ set(BTECH_SOURCES
ui/mech_status_weapons_format.c
ui/menu.c
core/btech_event_core.c
core/btech_text_builder.c
unit/mech_advanced.c
unit/battle_value.c
unit/battle_value_legacy.c
Expand Down Expand Up @@ -274,7 +275,6 @@ set(BTECH_SOURCES
unit/template_commands.c
unit/template_flags.c
unit/template_format.c
unit/template_file.c
unit/template_internals.c
unit/template_inventory.c
unit/template_load.c
Expand Down
15 changes: 7 additions & 8 deletions src/btech/autopilot/autopilot_goto.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "mux/network/mux_event.h"
#include "mux/objects/db.h"
#include "mux/support/doubly_linked_list.h"
#include "mux/support/stringutil.h"
#include "registry_api.h"
#include "section_types.h"

Expand Down Expand Up @@ -72,8 +73,7 @@ void auto_goto_event(MuxEvent *e) {

/* Get the first argument - x coord */
argument = auto_get_command_arg(autopilot, 1, 1);
if (!argument || (!((tx) = atoi(argument)) && strcmp((argument), "0"))) {
/*! \todo {add a thing here incase the argument isn't a number} */
if (!argument || !parse_int_checked(argument, &tx)) {
free(argument);
auto_goto_next_command(autopilot, AUTOPILOT_NC_DELAY);
return;
Expand All @@ -82,8 +82,7 @@ void auto_goto_event(MuxEvent *e) {

/* Get the second argument - y coord */
argument = auto_get_command_arg(autopilot, 1, 2);
if (!argument || (!((ty) = atoi(argument)) && strcmp((argument), "0"))) {
/*! \todo {add a thing here incase the argument isn't a number} */
if (!argument || !parse_int_checked(argument, &ty)) {
free(argument);
auto_goto_next_command(autopilot, AUTOPILOT_NC_DELAY);
return;
Expand Down Expand Up @@ -209,7 +208,7 @@ void auto_dumbgoto_event(MuxEvent *muxevent) {
}

/* Read in the argument */
if ((!((tx) = atoi(argument)) && strcmp((argument), "0"))) {
if (!parse_int_checked(argument, &tx)) {

snprintf(error_buf, MBUF_SIZE,
"Internal AI Error - Attempting to"
Expand Down Expand Up @@ -241,7 +240,7 @@ void auto_dumbgoto_event(MuxEvent *muxevent) {
}

/* Read in the argument */
if ((!((ty) = atoi(argument)) && strcmp((argument), "0"))) {
if (!parse_int_checked(argument, &ty)) {

snprintf(error_buf, MBUF_SIZE,
"Internal AI Error - Attempting to"
Expand Down Expand Up @@ -381,7 +380,7 @@ void auto_astar_goto_event(MuxEvent *muxevent) {
}

/* Now change it into a number and make sure its valid */
if ((!((tx) = atoi(argument)) && strcmp((argument), "0"))) {
if (!parse_int_checked(argument, &tx)) {

snprintf(error_buf, MBUF_SIZE,
"Internal AI Error - Attempting to"
Expand Down Expand Up @@ -414,7 +413,7 @@ void auto_astar_goto_event(MuxEvent *muxevent) {
}

/* Read second argument into a number and make sure its ok */
if ((!((ty) = atoi(argument)) && strcmp((argument), "0"))) {
if (!parse_int_checked(argument, &ty)) {

snprintf(error_buf, MBUF_SIZE,
"Internal AI Error - Attempting to"
Expand Down
23 changes: 17 additions & 6 deletions src/btech/commands/mech_restrict.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "mux/support/doubly_linked_list.h"
#include "mux/support/formatting.h"
#include "mux/support/red_black_tree.h"
#include "mux/support/stringutil.h"
#include "registry_api.h"

void clear_mech_from_LOS(Mech *mech) {
Expand Down Expand Up @@ -102,8 +103,11 @@ void mech_Rsetxy(DbRef player, void *data, char *buffer) {
"Invalid number of arguments to SETXY!");
return;
}
x = atoi(args[0]);
y = atoi(args[1]);
if (!parse_int_checked(args[0], &x) || !parse_int_checked(args[1], &y)) {
mecha_notify(btech_context_evaluation(mech_context(mech)), player,
"Invalid coordinates!");
return;
}
if (x >= mech_map->map_width || y >= mech_map->map_height || x < 0 || y < 0) {
mecha_notify(btech_context_evaluation(mech_context(mech)), player,
"Invalid coordinates!");
Expand All @@ -122,7 +126,11 @@ void mech_Rsetxy(DbRef player, void *data, char *buffer) {
z = mech_position_z(mech);
mech_position_land_if_flying(mech);
} else {
z = atoi(args[2]);
if (!parse_int_checked(args[2], &z)) {
mecha_notify(btech_context_evaluation(mech_context(mech)), player,
"Invalid Z coordinate!");
return;
}
mech_position_z_set(mech, z);
mech_position_elevation_set(mech, map_elevation_get(mech_map, x, y));
}
Expand All @@ -148,8 +156,7 @@ void mech_Rsetmapindex(DbRef player, void *data, char *buffer) {
"Invalid number of arguments to SETMAPINDX!");
return;
}
newindex = atoi(args[0]);
if (newindex < -1) {
if (!parse_int_checked(args[0], &newindex) || newindex < -1) {
mecha_notify(btech_context_evaluation(mech_context(mech)), player,
"Invalid map index!");
return;
Expand Down Expand Up @@ -262,7 +269,11 @@ void mech_Rsetteam(DbRef player, void *data, char *buffer) {
"Invalid number of arguments!");
return;
}
team = atoi(args[0]);
if (!parse_int_checked(args[0], &team)) {
mecha_notify(btech_context_evaluation(mech_context(mech)), player,
"Invalid team!");
return;
}
if (team < 0)
team = 0;
mech_team_set(mech, team);
Expand Down
3 changes: 3 additions & 0 deletions src/btech/core/btech_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ typedef struct Autopilot Autopilot;
typedef void (*MuxEventCallback)(MuxEvent *event);
typedef void (*MuxEventVisitor)(MuxEvent *event, void *context);

/* Marker callback persisted for scheduled work whose skill roll failed. */
void mech_event_failure_marker(MuxEvent *event);

void mech_event_schedule(Mech *mech, MechEventType type,
MuxEventCallback callback, int delay, intptr_t data);
void autopilot_event_schedule(Autopilot *autopilot, MechEventType type,
Expand Down
67 changes: 67 additions & 0 deletions src/btech/core/btech_text_builder.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#include "btech_text_builder.h"

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

void btech_text_builder_initialize(BtechTextBuilder *builder, char *text,
size_t capacity) {
if (builder == nullptr)
return;
if (text == nullptr)
capacity = 0;
*builder = (BtechTextBuilder){
.text = text,
.capacity = capacity,
};
if (capacity > 0)
text[0] = '\0';
}

bool btech_text_builder_append(BtechTextBuilder *builder, const char *text) {
if (builder == nullptr || text == nullptr || builder->capacity == 0) {
if (builder != nullptr)
builder->truncated = true;
return false;
}

const size_t available = builder->capacity - builder->length;
const size_t requested = strlen(text);
const size_t copied = requested < available ? requested : available - 1;
memcpy(builder->text + builder->length, text, copied);
builder->length += copied;
builder->text[builder->length] = '\0';
if (copied != requested)
builder->truncated = true;
return !builder->truncated;
}

bool btech_text_builder_append_character(BtechTextBuilder *builder,
char character) {
const char text[] = {character, '\0'};
return btech_text_builder_append(builder, text);
}

bool btech_text_builder_append_format(BtechTextBuilder *builder,
const char *format, ...) {
if (builder == nullptr || format == nullptr || builder->capacity == 0) {
if (builder != nullptr)
builder->truncated = true;
return false;
}

const size_t available = builder->capacity - builder->length;
va_list arguments;
va_start(arguments, format);
const int count =
vsnprintf(builder->text + builder->length, available, format, arguments);
va_end(arguments);

if (count < 0 || (size_t)count >= available) {
builder->length = builder->capacity - 1;
builder->truncated = true;
return false;
}
builder->length += (size_t)count;
return true;
}
20 changes: 20 additions & 0 deletions src/btech/core/btech_text_builder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include <stdbool.h>
#include <stddef.h>

typedef struct BtechTextBuilder {
char *text;
size_t capacity;
size_t length;
bool truncated;
} BtechTextBuilder;

void btech_text_builder_initialize(BtechTextBuilder *builder, char *text,
size_t capacity);
bool btech_text_builder_append(BtechTextBuilder *builder, const char *text);
bool btech_text_builder_append_character(BtechTextBuilder *builder,
char character);
bool btech_text_builder_append_format(BtechTextBuilder *builder,
const char *format, ...)
__attribute__((format(printf, 2, 3)));
13 changes: 1 addition & 12 deletions src/btech/core/events_api.h
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@

/*
p.events.h

Automatically created by protomaker (C) 1998 Markus Stenberg (fingon@iki.fi)
Protomaker is actually only a wrapper script for cproto, but well.. I like
fancy headers and stuff :)
*/

/* Generated at Mon Feb 22 14:59:36 CET 1999 from legacy_events.c */

#pragma once

#include "mux/server/platform.h"

/* legacy_events.c */
/* Administrative command that reports currently scheduled BTech events. */
void debug_EventTypes(DbRef player, void *data, char *buffer);
14 changes: 10 additions & 4 deletions src/btech/integration/debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "mux/server/server_config.h"
#include "mux/support/formatting.h"
#include "mux/support/red_black_tree.h"
#include "mux/support/stringutil.h"
#include "registry_api.h"
#include "special_object.h"
#include "weapon_settings.h"
Expand Down Expand Up @@ -205,8 +206,13 @@ void debug_shutdown(DbRef player, void *data, char *buffer) {
int argc;

argc = mech_parseattributes(buffer, args, 3);
if (argc > 0)
ShutDownMap(debug->context, player, atoi(args[0]));
long map_number;
if (argc > 0 && parse_long_checked(args[0], &map_number)) {
ShutDownMap(debug->context, player, map_number);
} else {
mecha_notify(btech_context_evaluation(debug->context), player,
"Invalid map number!");
}
}

void debug_setvrt(DbRef player, void *data, char *buffer) {
Expand All @@ -220,7 +226,7 @@ void debug_setvrt(DbRef player, void *data, char *buffer) {
"Invalid arguments!");
return;
}
if ((!((vrt) = atoi(args[1])) && strcmp((args[1]), "0"))) {
if (!parse_int_checked(args[1], &vrt)) {
mecha_notify(btech_context_evaluation(debug->context), player,
"Invalid value!");
return;
Expand Down Expand Up @@ -267,7 +273,7 @@ void debug_setwbv(DbRef player, void *data, char *buffer) {
"Invalid arguments!");
return;
}
if ((!((bv) = atoi(args[1])) && strcmp((args[1]), "0"))) {
if (!parse_int_checked(args[1], &bv)) {
mecha_notify(btech_context_evaluation(debug->context), player,
"Invalid value!");
return;
Expand Down
Loading