Compare commits
10 Commits
97ba953170
...
2842ba291c
| Author | SHA1 | Date |
|---|---|---|
|
|
2842ba291c | |
|
|
16369fadd7 | |
|
|
19f9442495 | |
|
|
760f9b9d95 | |
|
|
1ebbe44786 | |
|
|
02a71d1866 | |
|
|
e64d33d217 | |
|
|
491f624b6b | |
|
|
2097267b9a | |
|
|
5fb3ac714e |
|
|
@ -203,6 +203,11 @@ activate with:
|
||||||
|
|
||||||
// 209-245: available
|
// 209-245: available
|
||||||
|
|
||||||
|
#define GAME_BOARD_START 210
|
||||||
|
|
||||||
|
// 210-229: Tetris grid
|
||||||
|
// 230: Tetris score. 123-126 also used.
|
||||||
|
|
||||||
#define PERFMON_EVENTS 246
|
#define PERFMON_EVENTS 246
|
||||||
#define PERFMON_CPU 247
|
#define PERFMON_CPU 247
|
||||||
#define PERFMON_DRAW 248
|
#define PERFMON_DRAW 248
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
/* =========================================================================
|
||||||
|
*
|
||||||
|
* Nanite Systems Advanced Research Encapsulation System
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025–2026 Nanite Systems Corporation
|
||||||
|
*
|
||||||
|
* =========================================================================
|
||||||
|
*
|
||||||
|
* Instructor Application
|
||||||
|
*
|
||||||
|
* This program is covered under the terms of the ARES Software Copyright
|
||||||
|
* License, Section 3 (ASCL-iii). It may be redistributed or used as the
|
||||||
|
* basis of commercial, closed-source products so long as steps are taken
|
||||||
|
* to ensure proper attribution as defined in the text of the license.
|
||||||
|
*
|
||||||
|
* To see the full text of the ASCL, type 'help license' on any standard
|
||||||
|
* ARES distribution, or visit http://nanite-systems.com/ASCL for the
|
||||||
|
* current version.
|
||||||
|
*
|
||||||
|
* DISCLAIMER
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS
|
||||||
|
* IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
* PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
*
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT
|
||||||
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* =========================================================================
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ARES/a>
|
||||||
|
#include <ARES/api/interface.consts.h.lsl>
|
||||||
|
|
||||||
|
#define CLIENT_VERSION "1.0.0"
|
||||||
|
#define CLIENT_VERSION_TAGS "release"
|
||||||
|
|
||||||
|
#define DEFAULT_INTERVAL 30
|
||||||
|
#define DEFAULT_ANCHOR CLADDING_BACKDROP
|
||||||
|
|
||||||
|
integer enabled = FALSE;
|
||||||
|
integer interval = DEFAULT_INTERVAL;
|
||||||
|
string section = "instruction";
|
||||||
|
integer random = FALSE;
|
||||||
|
integer anchor = DEFAULT_ANCHOR;
|
||||||
|
integer offset = 0;
|
||||||
|
|
||||||
|
main(integer src, integer n, string m, key outs, key ins, key user) {
|
||||||
|
if(n == SIGNAL_INVOKE) {
|
||||||
|
list argv = split(m, " ");
|
||||||
|
integer argc = count(argv);
|
||||||
|
string msg = "";
|
||||||
|
string action = gets(argv, 1);
|
||||||
|
if(argc == 1 || action == "help") {
|
||||||
|
msg = "Usage: " + PROGRAM_NAME + " ...\n\n "
|
||||||
|
+ PROGRAM_NAME + " help: this message\n "
|
||||||
|
+ PROGRAM_NAME + " anchor <n>: set anchor prim (currently: " + (string)anchor + "); 0 for chat\n "
|
||||||
|
+ PROGRAM_NAME + " on|off|toggle: enable/disable/toggle instructor (currently: " + gets(["off", "on"], enabled) + ")\n "
|
||||||
|
+ PROGRAM_NAME + " interval <n>: set interval to <n> sec (currently: " + (string)interval + " sec)\n "
|
||||||
|
+ PROGRAM_NAME + " random|sequential: set random message selection on/off (currently: " + gets(["off", "on"], random) + ")\n "
|
||||||
|
+ PROGRAM_NAME + " from <section>: use LSD:<section> as input (currently: " + section + ")"
|
||||||
|
;
|
||||||
|
} else if(action == "anchor") {
|
||||||
|
setdbl("instructor", ["anchor"], (string)(anchor = (integer)gets(argv, 2)));
|
||||||
|
if(anchor < 0)
|
||||||
|
anchor = 0;
|
||||||
|
|
||||||
|
} else if(action == "on") {
|
||||||
|
setdbl("instructor", ["enabled"], (string)(enabled = TRUE));
|
||||||
|
set_timer("instruct", interval);
|
||||||
|
|
||||||
|
} else if(action == "off") {
|
||||||
|
setdbl("instructor", ["enabled"], (string)(enabled = FALSE));
|
||||||
|
set_timer("instruct", 0);
|
||||||
|
|
||||||
|
} else if(action == "toggle") {
|
||||||
|
setdbl("instructor", ["enabled"], (string)(enabled = !enabled));
|
||||||
|
if(enabled)
|
||||||
|
set_timer("instruct", interval);
|
||||||
|
else
|
||||||
|
set_timer("instruct", 0);
|
||||||
|
|
||||||
|
} else if(action == "interval") {
|
||||||
|
integer new_interval = (integer)gets(argv, 2);
|
||||||
|
if(new_interval >= 6) {
|
||||||
|
setdbl("instructor", ["interval"], (string)(interval = new_interval));
|
||||||
|
if(enabled)
|
||||||
|
set_timer("instruct", interval);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
msg = "Interval must be at least 6 sec. Use '@" + PROGRAM_NAME + " off' to disable instruction.";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(action == "random") {
|
||||||
|
setdbl("instructor", ["random"], (string)(random = TRUE));
|
||||||
|
|
||||||
|
} else if(action == "sequential") {
|
||||||
|
setdbl("instructor", ["random"], (string)(random = FALSE));
|
||||||
|
|
||||||
|
} else if(action == "from") {
|
||||||
|
string new_section = gets(argv, 2);
|
||||||
|
if(llOrd(llLinksetDataRead(new_section), 0) == 0x5b) { // '['
|
||||||
|
setdbl("instructor", ["section"], section = new_section);
|
||||||
|
} else {
|
||||||
|
msg = "Section '" + new_section + "' does not contain a JSON array.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg != "")
|
||||||
|
print(outs, user, msg);
|
||||||
|
} else if(n == SIGNAL_INIT) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
echo("[" + PROGRAM_NAME + "] init event");
|
||||||
|
#endif
|
||||||
|
enabled = (integer)getdbl("instructor", ["enabled"]);
|
||||||
|
random = (integer)getdbl("instructor", ["random"]);
|
||||||
|
interval = (integer)getdbl("instructor", ["interval"]);
|
||||||
|
string raw_anchor = getdbl("instructor", ["anchor"]);
|
||||||
|
if(raw_anchor == JSON_INVALID) {
|
||||||
|
setdbl("instructor", ["anchor"], (string)(anchor = DEFAULT_ANCHOR));
|
||||||
|
} else {
|
||||||
|
anchor = (integer)raw_anchor;
|
||||||
|
}
|
||||||
|
if(anchor < 0)
|
||||||
|
anchor = 0;
|
||||||
|
|
||||||
|
section = getdbl("instructor", ["section"]);
|
||||||
|
|
||||||
|
if(interval == 0)
|
||||||
|
setdbl("instructor", ["interval"], (string)(interval = DEFAULT_INTERVAL));
|
||||||
|
|
||||||
|
if(enabled)
|
||||||
|
set_timer("instruct", interval);
|
||||||
|
|
||||||
|
} else if(n == SIGNAL_TIMER) {
|
||||||
|
if(enabled) {
|
||||||
|
integer line_count = count(js2list(llLinksetDataRead(section)));
|
||||||
|
if(!line_count) {
|
||||||
|
echo("[" + PROGRAM_NAME + "] no usable data in " + section + "; please fix");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(random)
|
||||||
|
offset = (integer)llFrand(0x7fffffff) % line_count;
|
||||||
|
else
|
||||||
|
offset = (offset + 1) % line_count;
|
||||||
|
|
||||||
|
string message = getdbl(section, [offset]);
|
||||||
|
|
||||||
|
if(!anchor) {
|
||||||
|
echo(getdbl(section, [offset]));
|
||||||
|
} else {
|
||||||
|
vector color = getv(getp(2, [PRIM_COLOR, 1]), 0);
|
||||||
|
|
||||||
|
float fade = 0;
|
||||||
|
while(fade < 1.0) {
|
||||||
|
if(fade >= 0.9375)
|
||||||
|
fade = 1.0;
|
||||||
|
setp(anchor, [
|
||||||
|
PRIM_TEXT, message + "\n \n \n ", color, fade
|
||||||
|
]);
|
||||||
|
fade += 0.0625;
|
||||||
|
llSleep(0.022);
|
||||||
|
}
|
||||||
|
|
||||||
|
fade = 1.0;
|
||||||
|
|
||||||
|
llSleep(interval * 0.5 - 2);
|
||||||
|
|
||||||
|
while(fade > 0.0) {
|
||||||
|
if(fade < 0.0625)
|
||||||
|
fade = 0.0;
|
||||||
|
setp(anchor, [
|
||||||
|
PRIM_TEXT, message + "\n \n \n ", color, fade
|
||||||
|
]);
|
||||||
|
fade -= 0.0625;
|
||||||
|
llSleep(0.022);
|
||||||
|
}
|
||||||
|
|
||||||
|
setp(anchor, [
|
||||||
|
PRIM_TEXT, "", ZV, 0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
set_timer("instruct", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(n == SIGNAL_UNKNOWN_SCRIPT) {
|
||||||
|
echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)");
|
||||||
|
} else {
|
||||||
|
echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <ARES/program>
|
||||||
|
|
@ -841,7 +841,7 @@ default
|
||||||
edefaultrez = llListen(2046820352, "", E, "");
|
edefaultrez = llListen(2046820352, "", E, "");
|
||||||
g_ = 0;
|
g_ = 0;
|
||||||
H = "";
|
H = "";
|
||||||
llMessageLinked(1, 3840, LslUserScript + "\n" + "0.5.4" + " " + "beta 3", "");
|
llMessageLinked(1, 3840, LslUserScript + "\n" + "0.2.6" + " " + "lang " + "0.2.3", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(integer llListen, string llFrand, key llGetListEntryType, string llJsonValueType)
|
listen(integer llListen, string llFrand, key llGetListEntryType, string llJsonValueType)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
*
|
||||||
|
* Nanite Systems Advanced Research Encapsulation System
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025–2026 Nanite Systems Corporation
|
||||||
|
*
|
||||||
|
* =========================================================================
|
||||||
|
*
|
||||||
|
* Salvage Utility
|
||||||
|
*
|
||||||
|
* This program is covered under the terms of the ARES Software Copyright
|
||||||
|
* License, Section 3 (ASCL-iii). It may be redistributed or used as the
|
||||||
|
* basis of commercial, closed-source products so long as steps are taken
|
||||||
|
* to ensure proper attribution as defined in the text of the license.
|
||||||
|
*
|
||||||
|
* To see the full text of the ASCL, type 'help license' on any standard
|
||||||
|
* ARES distribution, or visit http://nanite-systems.com/ASCL for the
|
||||||
|
* current version.
|
||||||
|
*
|
||||||
|
* DISCLAIMER
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS
|
||||||
|
* IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
* PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
*
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT
|
||||||
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* =========================================================================
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <ARES/a>
|
||||||
|
#define CLIENT_VERSION "1.0"
|
||||||
|
#define CLIENT_VERSION_TAGS "release"
|
||||||
|
|
||||||
|
main(integer src, integer n, string m, key outs, key ins, key user) {
|
||||||
|
if(n == SIGNAL_INVOKE) {
|
||||||
|
list argv = splitnulls(m, " ");
|
||||||
|
integer argc = count(argv);
|
||||||
|
string msg = "";
|
||||||
|
string action = gets(argv, 1);
|
||||||
|
if(argc == 1 || action == "help") {
|
||||||
|
msg = PROGRAM_NAME + " version " + CLIENT_VERSION + " (" + CLIENT_VERSION_TAGS + ")"
|
||||||
|
+ "\nUsage: " + PROGRAM_NAME + " <action> [<arguments>]"
|
||||||
|
+ "\nSupported actions:"
|
||||||
|
+ "\n help: this message"
|
||||||
|
+ "\n dbread <entry>: read from LSD storage"
|
||||||
|
+ "\n dbwrite <entry> <value>: write to LSD storage (self only)"
|
||||||
|
+ "\n dbdelete <entry>: delete from LSD storage (self only)"
|
||||||
|
+ "\n dbdrop <section>: delete a whole section from LSD storage (self only)"
|
||||||
|
+ "\n dblist: list all LSD entries"
|
||||||
|
+ "\n invdrop on|off: enable/disable inventory drop (self only)"
|
||||||
|
;
|
||||||
|
} else if(action == "dbread") {
|
||||||
|
string path = gets(argv, 2);
|
||||||
|
list ks = splitnulls(path, ".");
|
||||||
|
msg = getdbl(gets(ks, 0), delitem(ks, 0));
|
||||||
|
if(msg == "")
|
||||||
|
msg = "(empty)";
|
||||||
|
else if(msg == JSON_INVALID)
|
||||||
|
msg = "(undefined)";
|
||||||
|
|
||||||
|
} else if(action == "dbwrite") {
|
||||||
|
if(user != avatar) {
|
||||||
|
msg = "Only " + (string)avatar + " may issue the " + action + " command.";
|
||||||
|
} else {
|
||||||
|
string path = gets(argv, 2);
|
||||||
|
list ks = splitnulls(path, ".");
|
||||||
|
string h = gets(ks, 0);
|
||||||
|
ks = delitem(ks, 0);
|
||||||
|
|
||||||
|
string old = getdbl(h, ks);
|
||||||
|
string new = concat(delrange(argv, 0, 2), " ");
|
||||||
|
|
||||||
|
string ksname = concat(ks, ".");
|
||||||
|
if(ksname == "")
|
||||||
|
ksname = "<root>";
|
||||||
|
|
||||||
|
tell(user, 0, "Writing database entry at (" + h + ", " + ksname + "):");
|
||||||
|
|
||||||
|
string test = llLinksetDataRead(h);
|
||||||
|
if(test != JSON_INVALID && setjs(test, ks, new) == JSON_INVALID) {
|
||||||
|
tell(user, 0, " !!! Cannot write database entry: result would be invalid JSON. Check for unbalanced {} and [] characters.");
|
||||||
|
} else {
|
||||||
|
setdbl(h, ks, new);
|
||||||
|
|
||||||
|
if(old == "")
|
||||||
|
old = "(empty)";
|
||||||
|
else if(old == JSON_INVALID)
|
||||||
|
old = "(undefined)";
|
||||||
|
|
||||||
|
tell(user, 0, " --- old value: " + old);
|
||||||
|
|
||||||
|
if(new == "")
|
||||||
|
new = "(empty)";
|
||||||
|
else if(new == JSON_INVALID)
|
||||||
|
new = "(undefined)";
|
||||||
|
|
||||||
|
tell(user, 0, " --- new value: " + new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(action == "dbdelete") {
|
||||||
|
if(user != avatar) {
|
||||||
|
msg = "Only " + (string)avatar + " may issue the " + action + " command.";
|
||||||
|
} else {
|
||||||
|
string path = gets(argv, 2);
|
||||||
|
list ks = splitnulls(path, ".");
|
||||||
|
string h = gets(ks, 0);
|
||||||
|
ks = delitem(ks, 0);
|
||||||
|
|
||||||
|
string old = getdbl(h, ks);
|
||||||
|
string ksname = concat(ks, ".");
|
||||||
|
if(ksname == "")
|
||||||
|
ksname = "<root>";
|
||||||
|
|
||||||
|
if(old == JSON_INVALID) {
|
||||||
|
msg = "Cannot delete (" + h + ", " + ksname + "): it does not exist.";
|
||||||
|
} else {
|
||||||
|
tell(user, 0, "Deleting database entry at (" + h + ", " + ksname + "):");
|
||||||
|
|
||||||
|
string test = llLinksetDataRead(h);
|
||||||
|
if(test != JSON_INVALID && setjs(test, ks, JSON_DELETE) == JSON_INVALID) {
|
||||||
|
tell(user, 0, " !!! Cannot write database entry: result would be invalid JSON. Check for unbalanced {} and [] characters.");
|
||||||
|
} else {
|
||||||
|
setdbl(h, ks, JSON_DELETE);
|
||||||
|
|
||||||
|
if(old == "")
|
||||||
|
old = "(empty)";
|
||||||
|
|
||||||
|
tell(user, 0, " --- old value: " + old);
|
||||||
|
tell(user, 0, " --- deleted successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(action == "dbdrop") {
|
||||||
|
if(user != avatar) {
|
||||||
|
msg = "Only " + (string)avatar + " may issue the " + action + " command.";
|
||||||
|
} else {
|
||||||
|
string k = gets(argv, 2);
|
||||||
|
list candidates = llLinksetDataFindKeys(k, 0, 0);
|
||||||
|
if(contains(candidates, k) || gets(argv, 3) == "force") {
|
||||||
|
tell(user, 0, "Deleting key " + k);
|
||||||
|
tell(user, 0, "Contents were: " + llLinksetDataRead(k));
|
||||||
|
llLinksetDataDelete(k);
|
||||||
|
} else {
|
||||||
|
msg = "No key found: " + k + "\nIs it an accidental regex? If so, try: @" + PROGRAM_NAME + " dbdrop " + k + " force";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(action == "dblist") {
|
||||||
|
integer lsmax = llLinksetDataCountKeys();
|
||||||
|
integer li = 0;
|
||||||
|
while(li < lsmax) {
|
||||||
|
string k = gets(llLinksetDataListKeys(li, 1), 0);
|
||||||
|
tell(user, 0, " - " + k + " (" + (string)strlen(llLinksetDataRead(k)) + " bytes)");
|
||||||
|
++li;
|
||||||
|
llSleep(0.044);
|
||||||
|
}
|
||||||
|
tell(user, 0, (string)lsmax + " key(s), " + (string)llLinksetDataAvailable() + " byte(s) free");
|
||||||
|
|
||||||
|
} else if(action == "invdrop") {
|
||||||
|
if(user != avatar) {
|
||||||
|
msg = "Only " + (string)avatar + " may issue the " + action + " command.";
|
||||||
|
} else {
|
||||||
|
integer new = index((["off", "on"]), gets(argv, 2));
|
||||||
|
if(new == 1) {
|
||||||
|
inventory_drop(TRUE);
|
||||||
|
msg = "Inventory drop enabled.";
|
||||||
|
} else if(new == 0) {
|
||||||
|
inventory_drop(FALSE);
|
||||||
|
msg = "Inventory drop disabled.";
|
||||||
|
} else {
|
||||||
|
msg = "Invalid option. Syntax must be: @" + PROGRAM_NAME + " invdrop on|off";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msg = "Unknown action: " + action + ". For usage, see: @" + PROGRAM_NAME + " help";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg != "")
|
||||||
|
tell(user, 0, msg);
|
||||||
|
} else if(n == SIGNAL_INIT) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
echo("[" + PROGRAM_NAME + "] init event");
|
||||||
|
#endif
|
||||||
|
} else if(n == SIGNAL_UNKNOWN_SCRIPT) {
|
||||||
|
echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)");
|
||||||
|
} else {
|
||||||
|
echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <ARES/program>
|
||||||
|
|
@ -44,7 +44,11 @@ main(integer src, integer n, string m, key outs, key ins, key user) {
|
||||||
string msg = "";
|
string msg = "";
|
||||||
string action = gets(argv, 1);
|
string action = gets(argv, 1);
|
||||||
if(argc == 1 || action == "help") {
|
if(argc == 1 || action == "help") {
|
||||||
msg = "Syntax: " + PROGRAM_NAME + " <action>";
|
msg = PROGRAM_NAME + " version " + CLIENT_VERSION + " (" + CLIENT_VERSION_TAGS + ")"
|
||||||
|
+ "\nUsage: " + PROGRAM_NAME + " <action> [<arguments>]"
|
||||||
|
+ "\nSupported actions:"
|
||||||
|
+ "\n help: this message"
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(msg != "")
|
if(msg != "")
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ default {
|
||||||
L_KERNEL = llListen(C_UNASSIGNED, "", KERNEL, "");
|
L_KERNEL = llListen(C_UNASSIGNED, "", KERNEL, "");
|
||||||
PROGRAM_NUMBER = 0;
|
PROGRAM_NUMBER = 0;
|
||||||
E_PROGRAM_NUMBER = "";
|
E_PROGRAM_NUMBER = "";
|
||||||
linked(R_KERNEL, SIGNAL_SOLICIT_ADDRESS, PROGRAM_NAME + "\n" + ARES_VERSION + " " + ARES_VERSION_TAGS, "");
|
linked(R_KERNEL, SIGNAL_SOLICIT_ADDRESS, PROGRAM_NAME + "\n" + CLIENT_VERSION + " " + CLIENT_VERSION_TAGS, "");
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
echo("[" + PROGRAM_NAME + "] waiting for PID");
|
echo("[" + PROGRAM_NAME + "] waiting for PID");
|
||||||
|
|
|
||||||
|
|
@ -453,6 +453,7 @@ main(integer src, integer n, string m, key outs, key ins, key user) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(e == EVENT_INTERFACE) {
|
} else if(e == EVENT_INTERFACE) {
|
||||||
|
setp(1, [PRIM_TEXT, "", ZV, 0]);
|
||||||
power_on = (integer)getdbl("status", ["on"]);
|
power_on = (integer)getdbl("status", ["on"]);
|
||||||
position_all(FALSE);
|
position_all(FALSE);
|
||||||
} else if(e == EVENT_WARNING) {
|
} else if(e == EVENT_WARNING) {
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,8 @@ main(integer src, integer n, string m, key outs, key ins, key user) {
|
||||||
selfname + " autolock time <secs>: automatically lock after <secs> time\n" +
|
selfname + " autolock time <secs>: automatically lock after <secs> time\n" +
|
||||||
selfname + " lock: lock local commands and menu, preventing access\n" +
|
selfname + " lock: lock local commands and menu, preventing access\n" +
|
||||||
selfname + " unlock <password>: unlock\n" +
|
selfname + " unlock <password>: unlock\n" +
|
||||||
selfname + " password <password>: change lock password\n";
|
selfname + " password <password>: change lock password\n" +
|
||||||
|
selfname + " password none|NONE|clear|remove: remove lock password\n";
|
||||||
llSleep(0.5);
|
llSleep(0.5);
|
||||||
print(outs, user, msg);
|
print(outs, user, msg);
|
||||||
msg =
|
msg =
|
||||||
|
|
@ -207,10 +208,14 @@ main(integer src, integer n, string m, key outs, key ins, key user) {
|
||||||
|
|
||||||
} else if(action == "lock") {
|
} else if(action == "lock") {
|
||||||
string password = getdbl("policy", ["password"]);
|
string password = getdbl("policy", ["password"]);
|
||||||
if(password == "" || password == JSON_INVALID) {
|
if(password == JSON_INVALID)
|
||||||
|
password = "";
|
||||||
|
|
||||||
|
/*if(password == "" || password == JSON_INVALID) {
|
||||||
m = "* unlock";
|
m = "* unlock";
|
||||||
jump restart_main;
|
jump restart_main;
|
||||||
} else {
|
} else */
|
||||||
|
{
|
||||||
setdbl("policy", ["lock"], "1");
|
setdbl("policy", ["lock"], "1");
|
||||||
msg = "Unit locked.";
|
msg = "Unit locked.";
|
||||||
|
|
||||||
|
|
@ -222,13 +227,17 @@ main(integer src, integer n, string m, key outs, key ins, key user) {
|
||||||
} else if(action == "unlock") {
|
} else if(action == "unlock") {
|
||||||
string password = getdbl("policy", ["password"]);
|
string password = getdbl("policy", ["password"]);
|
||||||
string attempt = concat(delrange(argv, 0, 1), " ");
|
string attempt = concat(delrange(argv, 0, 1), " ");
|
||||||
if(password == "" || password == JSON_INVALID) {
|
if(password == JSON_INVALID)
|
||||||
|
password = "";
|
||||||
|
|
||||||
|
/*if(password == "" || password == JSON_INVALID) {
|
||||||
msg = "Cannot lock console: no password set.";
|
msg = "Cannot lock console: no password set.";
|
||||||
setdbl("policy", ["lock"], "0");
|
setdbl("policy", ["lock"], "0");
|
||||||
announce("lock-0");
|
announce("lock-0");
|
||||||
io_tell(NULL_KEY, C_LIGHT_BUS, "unlocked");
|
io_tell(NULL_KEY, C_LIGHT_BUS, "unlocked");
|
||||||
notify_program("security power", outs, NULL_KEY, user);
|
notify_program("security power", outs, NULL_KEY, user);
|
||||||
} else if(attempt != password) {
|
} else */
|
||||||
|
if(attempt != password) {
|
||||||
msg = "Incorrect password.";
|
msg = "Incorrect password.";
|
||||||
announce("denied");
|
announce("denied");
|
||||||
io_tell(NULL_KEY, C_LIGHT_BUS, "locked");
|
io_tell(NULL_KEY, C_LIGHT_BUS, "locked");
|
||||||
|
|
@ -341,7 +350,7 @@ main(integer src, integer n, string m, key outs, key ins, key user) {
|
||||||
}
|
}
|
||||||
} else if(action == "password") {
|
} else if(action == "password") {
|
||||||
string password = concat(delrange(argv, 0, 1), " ");
|
string password = concat(delrange(argv, 0, 1), " ");
|
||||||
if(password == "NONE" || password == "none")
|
if(password == "NONE" || password == "none" || password == "clear" || password == "remove")
|
||||||
password = "";
|
password = "";
|
||||||
setdbl("policy", ["password"], password);
|
setdbl("policy", ["password"], password);
|
||||||
if(password == "") {
|
if(password == "") {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
|
||||||
|
PATH PROTOCOL VERSION 2.0
|
||||||
|
|
||||||
|
2026-01-23
|
||||||
|
|
||||||
|
To be introduced in ARES 0.5.7, the PATH protocol (PHASE Augmented Through HTTP) is a hybrid filesystem model for data storage systems in Second Life. It builds on the PHASE (Prim-Handle Abstract Storage Engine) protocol and the WEBFS file access mechanism originated in ARES 0.4.4.
|
||||||
|
|
||||||
|
The main benefits of PATH over PHASE are as follows:
|
||||||
|
|
||||||
|
- Storage devices track their accessor systems and can reach out to the accessor in the event of an address change
|
||||||
|
- Data transfer over HTTP re-uses the WEBFS protocol, alleviating the need for the ARES _storage daemon to support two fully independent filesystems (the main reason for the change)
|
||||||
|
- Data transfer over HTTP supports larger pagination chunks (4K), further allowing for increased speed where memory is available
|
||||||
|
- Storage devices can be accessed from outside of SL if desired
|
||||||
|
|
||||||
|
As with PHASE 1.x, all standard communications happen on channel 1608011905. The message format is:
|
||||||
|
|
||||||
|
<handle> <command> <argument ...>
|
||||||
|
|
||||||
|
<handle> is an arbitrary string, typically a UUID. The supported <command>s are:
|
||||||
|
|
||||||
|
system-to-storage: connect disconnect
|
||||||
|
|
||||||
|
storage-to-system: version reset access deleted new update denied
|
||||||
|
|
||||||
|
The <argument>s are <command>-specific.
|
||||||
|
|
||||||
|
No side channels are required for PATH. All directory and file I/O occurs via HTTP.
|
||||||
|
|
||||||
|
Although the PHASE and PATH protocols both use the same external interface, the 'access' message provided by the PATH service specifies the HTTP URL that should be used. A PATH storage device should send the 'denied' message if any unsupported messages are encountered.
|
||||||
|
|
||||||
|
In practice it is unlikely that incompatibility between PHASE and PATH will be encountered by end users, since as of this writing PHASE is exclusively used by ARES local storage.
|
||||||
|
|
||||||
|
|
||||||
|
PATH MESSAGES
|
||||||
|
|
||||||
|
<handle> connect <mode>
|
||||||
|
|
||||||
|
system -> storage
|
||||||
|
|
||||||
|
where <mode> is one of: ro rd rw
|
||||||
|
|
||||||
|
Initiates a connection with the specified access level. The storage device should respond with an "access" command (below), and store the host UUID and granted level of permission for future reference. The modes are defined as "read only" (ro), "read and delete" (rd), or "full access" (rw).
|
||||||
|
|
||||||
|
At this point, the storage device should also check through all existing connections and confirm that the hosts involved still exist (that is, llGetOwnerKey(id) != id). Failing that, delete those connections.
|
||||||
|
|
||||||
|
However, if <handle> is not an identifiable object UUID, the system is simply pinging and does not want to alter the state of any existing connections. A storage device that wants to be seen by the inquiring party (e.g. for public use) should respond with a "version" message; otherwise, no response is required.
|
||||||
|
|
||||||
|
|
||||||
|
<handle> version <version> <label>
|
||||||
|
|
||||||
|
storage -> system
|
||||||
|
|
||||||
|
Sent in response to "connect" when <handle> is NULL_KEY. The reply handle should also be NULL_KEY.
|
||||||
|
|
||||||
|
<version> is an implementation-specific string up to 64 characters in length with no spaces, e.g. "ARES-_path-0.5.7"
|
||||||
|
|
||||||
|
|
||||||
|
<label> is a human-readable (space-containing) descriptor of the volume.
|
||||||
|
|
||||||
|
|
||||||
|
<handle> reset <label>
|
||||||
|
|
||||||
|
storage -> system
|
||||||
|
|
||||||
|
The storage device has been rebooted and has lost its active connections list. Any system that recognizes the storage device's address should re-send the "connect" message (above).
|
||||||
|
|
||||||
|
Under PATH, the system should iterate through the list of known mounts to see if the originating object's UUID matches a volume that is currently believed to be mounted. This solves the general problem of storage getting demounted after login/rez. The provided <label>, if present, may be used to more precisely identify the source.
|
||||||
|
|
||||||
|
This message should also be generated during the PATH driver's on_rez() and changed(CHANGED_REGION) events, to prompt accessors to request a new URL.
|
||||||
|
|
||||||
|
|
||||||
|
<handle> access <mode> <unit> <label> <URL>
|
||||||
|
|
||||||
|
storage -> system
|
||||||
|
|
||||||
|
Responds to a request to a "connect" command (above).
|
||||||
|
|
||||||
|
where <mode> is one of: ro rd rw
|
||||||
|
|
||||||
|
The storage device should indicate the level of access it is willing/capable of giving the system. It should also store the host UUID and granted level of permission for future reference. The useful modes are defined as "read only" (ro), "read and delete" (rd), or "full access" (rw). A mode of "none" indicates the connection was rejected and not stored.
|
||||||
|
|
||||||
|
where <unit> is one of: b p l
|
||||||
|
|
||||||
|
If the unit is b, then size measurements are in bytes.
|
||||||
|
|
||||||
|
If the unit is p, then size measurements are in pages. Each page can hold up to 1024 bytes. Pages may be less than this size, in which case they are called "unsaturated" or "ragged".
|
||||||
|
|
||||||
|
If the unit is l, then size measurements are in lines. This is the same as pages but the system should always assume lines are separated by a linebreak character. Writing to line-based storage should be done one line at a time.
|
||||||
|
|
||||||
|
Since PATH uses the HTTP 'Range' header for data selection, use of any unit other than 'b' is strongly discouraged, as this will cause incompatibility with external (non-SL) clients.
|
||||||
|
|
||||||
|
<label> is a freeform text label that describes the volume.
|
||||||
|
|
||||||
|
If no access is granted, the device should send the "denied" command.
|
||||||
|
|
||||||
|
<URL> is the HTTP(S) address to which the client system should refer for file access. It must implement the portions of `webfs` used by the current storage mode. The `ro` mode is equivalent to the `static` format. Other implementation details are described in the next section.
|
||||||
|
|
||||||
|
In normal operation the system will proceed to query <URL> to retrieve a directory listing.
|
||||||
|
|
||||||
|
|
||||||
|
<handle> disconnect
|
||||||
|
|
||||||
|
system -> storage
|
||||||
|
|
||||||
|
The system no longer wishes to access the device. Forget the session.
|
||||||
|
|
||||||
|
|
||||||
|
<handle> deleted <filename>
|
||||||
|
|
||||||
|
storage -> system
|
||||||
|
|
||||||
|
A file has been removed from the system.
|
||||||
|
|
||||||
|
The storage device may send this spontaneously in response to an outside action. The handle should be NULL_KEY in this case.
|
||||||
|
|
||||||
|
|
||||||
|
<handle> new <filename>
|
||||||
|
|
||||||
|
storage -> system
|
||||||
|
|
||||||
|
A file has been added from the system.
|
||||||
|
|
||||||
|
The storage device may send this spontaneously in response to an outside action. The handle should be NULL_KEY in this case.
|
||||||
|
|
||||||
|
|
||||||
|
<handle> update
|
||||||
|
|
||||||
|
storage -> system
|
||||||
|
|
||||||
|
A file has changed in stat information (size, type, desc, etc).
|
||||||
|
|
||||||
|
|
||||||
|
<handle> denied
|
||||||
|
|
||||||
|
storage -> system
|
||||||
|
|
||||||
|
The requested action exceeds the permissions granted.
|
||||||
|
|
||||||
|
The storage device may send this spontaneously in response to an outside action. The handle should be NULL_KEY in this case.
|
||||||
|
|
||||||
|
For most actions, 'denied' is not used by PATH, as the webfs operation will fail with a 403 error code.
|
||||||
|
|
||||||
|
|
||||||
|
WEBFS
|
||||||
|
|
||||||
|
Under PATH, the following standard operations are defined as taking place over HTTP using a webfs endpoint: stat, read, delete, write, append.
|
||||||
|
|
||||||
|
The webfs base URL must end in a / -- this can either be the raw llRequestURL() or suffixed with a subpath given only to the system, for higher security.
|
||||||
|
|
||||||
|
Directory listings are accessed at the webfs base URL, in plain text, with the format:
|
||||||
|
|
||||||
|
<filename> <size>
|
||||||
|
<filename> <size>
|
||||||
|
<filename> <size>
|
||||||
|
<filename> <size>
|
||||||
|
|
||||||
|
This may be subject to partial access, as defined in the next section.
|
||||||
|
|
||||||
|
|
||||||
|
Reading files
|
||||||
|
|
||||||
|
To retrieve a file, or portions of a file, the system will transmit the 'Range' header and request a specific document, e.g. <base URL>foobar.txt (remembering that <base URL> ends in '/').
|
||||||
|
|
||||||
|
Partial access: 'Range' is not implemented by SL and must be manually interpreted from the results of llGetHTTPHeader(query, "range") -- if 'Range' is parsed successfully, then the response code for either a directory listing or file content changes from HTTP 200 to HTTP 206.
|
||||||
|
|
||||||
|
|
||||||
|
Deleting files
|
||||||
|
|
||||||
|
Deletions are accomplished by calling:
|
||||||
|
|
||||||
|
<base URL>?action=delete&file=<filename>
|
||||||
|
|
||||||
|
On success the server returns an HTTP 204 code and webfs will send a 'deleted' message to any connected system. On failure the server returns a HTTP 403 code (if denied) or HTTP 404 code (if the file doesn't exist), but does NOT generate a 'denied' message (as that would have to be filtered out). If both failures would apply, 403 takes precedence over 404.
|
||||||
|
|
||||||
|
|
||||||
|
Writing files
|
||||||
|
|
||||||
|
The client sends a POST message to:
|
||||||
|
|
||||||
|
<base URL>?action=write&file=<filename>
|
||||||
|
|
||||||
|
The POST body is the text to commit to the file.
|
||||||
|
|
||||||
|
On success the webfs will send an 'updated' message and an HTTP 204 code, or a 'new' message and HTTP 201 code (if the file did not exist) to any connected system. On failure the server returns a HTTP 403 code, but does NOT generate a 'denied' message (as that would have to be filtered out).
|
||||||
|
|
||||||
|
|
||||||
|
Appending to files
|
||||||
|
|
||||||
|
The client sends a POST message to:
|
||||||
|
|
||||||
|
<base URL>?action=append&file=<filename>
|
||||||
|
|
||||||
|
The POST body is the text to append to the file.
|
||||||
|
|
||||||
|
On success the webfs will send an 'updated' message and an HTTP 204 code, or a 'new' message and HTTP 201 code (if the file did not exist) to any connected system. On failure the server returns a HTTP 403 code, but does NOT generate a 'denied' message (as that would have to be filtered out).
|
||||||
|
|
||||||
|
This is identical to a file write, but with action=append instead of action=write.
|
||||||
|
|
||||||
|
webfs is tolerant of minor misdeeds like appending to a nonexistent file.
|
||||||
|
|
||||||
|
|
||||||
|
Statting files
|
||||||
|
|
||||||
|
If the client sends:
|
||||||
|
|
||||||
|
<base URL>?action=stat&file=<filename>
|
||||||
|
|
||||||
|
Then the server should return an HTTP 200 or 206 code and a directory listing consisting only of the information for the specified file.
|
||||||
|
|
||||||
|
A stat of the special filename * should return information about the directory listing itself:
|
||||||
|
|
||||||
|
<base URL>?action=stat&file=*
|
||||||
|
|
||||||
|
Statting a nonexistent file or invalid filename should return an HTTP 404 code.
|
||||||
|
|
||||||
|
|
||||||
|
Subdirectories
|
||||||
|
|
||||||
|
Future versions of webfs might support subdirectories, in which case a stat of the bare directory name or the directory name followed by /* should be interpreted as an attempt to stat the directory:
|
||||||
|
|
||||||
|
<base URL>?action=stat&file=subdir
|
||||||
|
<base URL>?action=stat&file=subdir/*
|
||||||
|
|
||||||
|
Statting a nonexistent file or invalid filename should return an HTTP 404 code.
|
||||||
|
|
||||||
|
|
@ -13,7 +13,9 @@ Except where otherwise specified, each control message consists of the format:
|
||||||
|
|
||||||
Where <handle> is an arbitrary string (typically a UUID) generated by the OS for each interaction, <command> is one of:
|
Where <handle> is an arbitrary string (typically a UUID) generated by the OS for each interaction, <command> is one of:
|
||||||
|
|
||||||
connect disconnect access size read write ready delete deleted new update close
|
system-to-storage: connect disconnect stat read write append delete close
|
||||||
|
|
||||||
|
storage-to-system: version reset access stat ready deleted new update denied
|
||||||
|
|
||||||
and the <argument>s are <command>-specific.
|
and the <argument>s are <command>-specific.
|
||||||
|
|
||||||
|
|
@ -81,9 +83,11 @@ MESSAGES
|
||||||
|
|
||||||
storage -> system
|
storage -> system
|
||||||
|
|
||||||
|
Responds to a request to a "connect" command (above).
|
||||||
|
|
||||||
where <mode> is one of: ro rd rw
|
where <mode> is one of: ro rd rw
|
||||||
|
|
||||||
Responds to a request to a "connect" command (above). The storage device should indicate the level of access it is willing/capable of giving the system. It should also store the host UUID and granted level of permission for future reference. The useful modes are defined as "read only" (ro), "read and delete" (rd), or "full access" (rw). A mode of "none" indicates the connection was rejected and not stored.
|
The storage device should indicate the level of access it is willing/capable of giving the system. It should also store the host UUID and granted level of permission for future reference. The useful modes are defined as "read only" (ro), "read and delete" (rd), or "full access" (rw). A mode of "none" indicates the connection was rejected and not stored.
|
||||||
|
|
||||||
where <unit> is one of: b p l
|
where <unit> is one of: b p l
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
* *
|
* *
|
||||||
* UTILS COMPONENT *
|
* UTILS COMPONENT *
|
||||||
* *
|
* *
|
||||||
* Copyright, (C) Nanite Systems Corp., 1984-85, 2017-25 *
|
* Copyright, (C) Nanite Systems Corp., 1984-85, 2017-26 *
|
||||||
* *
|
* *
|
||||||
* Copyright, (C) University of Michigan 1977-1981 *
|
* Copyright, (C) University of Michigan 1977-1981 *
|
||||||
* *
|
* *
|
||||||
|
|
@ -406,4 +406,7 @@ string hex(integer bits) {
|
||||||
// SOUND_DEFAULT: a placeholder sound, meant to complement SL's TEXTURE_DEFAULT
|
// SOUND_DEFAULT: a placeholder sound, meant to complement SL's TEXTURE_DEFAULT
|
||||||
#define SOUND_DEFAULT "f1adc36c-4c3f-081b-4ab2-fa5105d80561"
|
#define SOUND_DEFAULT "f1adc36c-4c3f-081b-4ab2-fa5105d80561"
|
||||||
|
|
||||||
|
// mem_sum(): string describing current memory state
|
||||||
|
#define mem_sum() ("[" + llGetScriptName() + "] " + (string)llGetFreeMemory() + " bytes free; " + (string)llGetUsedMemory() + " used (" + (string)((integer)(100 * llGetUsedMemory() / llGetMemoryLimit())) + "%)")
|
||||||
|
|
||||||
#endif // UTILS
|
#endif // UTILS
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue