Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Disc.: Localized Update System utilizing llRemoteLoadScriptPin (Object Update System)

Darien Caldwell
Registered User
Join date: 12 Oct 2006
Posts: 3,127
03-21-2007 19:04
I think every person who sells scripted items realizes at some point or other they will have to eventually issue updates. After searching high and low for a good system, and finding none, I probably did what most do, write my own. But I think something like this is so basic there should be one out there for people to use and learn from. I putting this out into the ether, do with it as you please. It's basic, but functional, and could be heavily improved upon. But what do you expect for free? ;P

Code is in 4 parts:

Sample Call to update function: This shows how the update function would be invoked from your script. You just issue a simple linked message call with the keyword "UpdateRequest", and the Current Version number of the product. This linked message will be picked up by the Update_Handler Script (to follow):

CODE

// 101 is the version of the current item before update
integer current_version=101;

default
{
touch_start(integer total_number)
{
// Request an update check
llMessageLinked(LINK_SET,current_version, "UpdateRequest", NULL_KEY); }
}


The Update Handler: This scrip would also go in your scripted object. You could add this code directly into your script, but I think it's tidier to keep it seperate.

This script on state entry sets the Remote Script Access pin, which is needed to load new scripts into the object. Upon being invoked by a linked message, it opens a listener on the channel specified (-1000 in the example) and sends out a query to the Update Server on that same channel. Now obviously this means the server must be nearby. Otherwise it wouldn't hear the query. We set a timer as a timeout (8 seconds). If the server doesn't reply in that time, we tell the person where to go to find the updater (your store). This is nice way to bring people back into your store to see whats new and keep your brand identiy fresh (a wholly seperate topic). The server query we send out contains the product name, and the current version. The server checks this against what it knows to be the latest version, and if there is no update, it tells the object this. If there is an update, it gives the avatar the Update Box, and tells them to Rez it.

CODE

integer listen_id;
integer uPIN = 1971; // Update Pin. This should be the same in the handler and updater
integer listen_channel = -1000; // Communication Channel. This should be the same in the handler,updater, and giver.

// This is the product code, sent to the Server so it knows which product you are trying to update.
string product_id="UsefulWidget1";

default
{
on_rez(integer param) // Do the following when rezzed
{
llSetRemoteScriptAccessPin(uPIN); // Set the PIN
}
link_message(integer num,integer version_num,string str,key id)
{
if (str == "UpdateRequest") {
listen_id= llListen(listen_channel,"","","");
llWhisper(0,"Checking for Updates to your product...");
llSay(listen_channel,product_id+"+"+(string)version_num+"+"+(string)llGetOwner());

llSetTimerEvent(8); // see if we get a reply or not.
}

}


// This Listen processes commands sent by the update box, which the update server
// will give you if an update is available.
listen(integer channel, string name, key id, string message)
{
list tmplist;
string tmpcommand;
key tmpowner;
string tmpversion;
string tmpnotecard;
string tmpobject;

// The update box first sends a string listing what items are being updated.
// This portion could be configured to accept any number of any type of item.
// for this example, only a notecard and an object is shown. However it could easily
// be expanded to include any type of inventory item, or even multiples of the same type.
// if you don't want to update one particular type, you would leave that blank
// in the appropriate section in the update box, and this code would skip over it.
tmplist = llParseString2List(message,["+"],[]);
tmpcommand = llList2String(tmplist,0);
tmpowner = llList2String(tmplist,1);
tmpversion = llList2String(tmplist,2);
tmpnotecard = llList2String(tmplist,3);
tmpobject = llList2String(tmplist,4);

if (tmpowner == llGetOwner() ) { // only listen to replies from our update box.

if (tmpcommand == "Need_Update!") {
// the update giver has verified an update is needed so...
llSetTimerEvent(0); // turn off timer

}
if (tmpcommand == "NO_Update_Needed") {
// no update needed
llWhisper(0,"Your item is Up to Date.");
llSetTimerEvent(0); // turn off timer
llListenRemove(listen_id);
}
else if (tmpcommand=="Prep4Update") {
// we are about to start updating. get prepared
// delete old notecards/objects in preparation to recieve new ones
if (tmpnotecard != "") llRemoveInventory(tmpnotecard);
llSleep(0.2);
if (tmpobject != "") llRemoveInventory(tmpobject);
llSleep(0.2);
//Set the Description to the new version
llSetObjectDesc(tmpversion);

//send a reply so it knows the prim ID
llSleep(1);
llSay(listen_channel,"Ready4Update+"+(string)llGetOwner());
llListenRemove(listen_id); // not sure needed, but be safe.
}

}// end if this AV's Key
} // end listen


// if we don't get a reply from the server, tell them where to go.
timer()
{
llSay(0,"Sorry, the Update Server is not in range. Go to the Super Widget Main Store at YOURSIM 128, 128, 25 to use this update feature.");
llListenRemove(listen_id);
llSetTimerEvent(0); // turn off timer

}

}



The Update Server: As described above, this objects sits at the store, waiting for update queries. In the example I show it set up for two products, but the number of products it could server is up to you, just repeat the blocks of code per product. When the Server receives an update request, it checks it against the product and version numbers stored in the script, if the query version number is less, it hands the Avatar the update box and a Revision History notecard. Otherwise it sends a reply that there is no update available. Very simple.

CODE

integer listen_channel = -1000; // Communication Channel. This should be the same in the handler,updater, and giver.

// These define the various products and their respective revisions
string product_id1="UsefulWidget1";
integer product_ver1=102;

string product_id2="UsefulWidget2";
integer product_ver2=101;


default
{
on_rez(integer param)
{
llResetScript();
}


state_entry()
{
// this listener runs always, waiting for a version check request.
llListen(listen_channel,"","","");
}


// This Listener handles requests for updates, tells the item if it's up to date or not,
// and giving an updater if out of date.
listen(integer channel, string name, key id, string message)
{
list tmplist;
integer tmpversion;
string tmpcommand;
key tmpowner;


tmplist = llParseString2List(message,["+"],[]);
tmpcommand = llList2String(tmplist,0);
tmpversion = llList2Integer(tmplist,1);
tmpowner = llList2String(tmplist,2);

////////////////////////////////////////////////////////////////////////////////////////////////
// This block can be repeated as many times as needed, depending on how many products you have.
//////////////////////////////////////////////////////////////////////////////////////////////////

if (tmpcommand == product_id1) // item asking to check version
{
if (product_ver1 > tmpversion) { // needs an updater
llSay(listen_channel,"Need_Update!+"+(string)tmpowner);
llSleep(1);
llSay(0,"Accept the Updater object and Rez it on the ground. Be sure not to move out of range or remove your item!");
// give the person requesting the update the appropriate update box from inventory, and
// hey, why not a Rev. history card too?
llGiveInventory(tmpowner,"Widget1 Updater");
llGiveInventory(tmpowner,"Widget1 Revision History");
}
else {// already up to date.
llSay(listen_channel,"NO_Update_Needed+"+(string)tmpowner); // we are good.
}
} // end if (tmpcommand == product_id1)

/////////////////////////////////////////////////////////////////////////////////////////////////

else if (tmpcommand == product_id2) // item asking to check version
{
if (product_ver2 > tmpversion) { // needs an updater
llSay(listen_channel,"Need_Update!+"+(string)tmpowner);
llSleep(1);
llSay(0,"Accept the Updater object and Rez it on the ground. Be sure not to move out of range or remove your item!");
// give the person requesting the update the appropriate update box from inventory, and
// hey, why not a Rev. history card too?
llGiveInventory(tmpowner,"Widget2 Updater");
llGiveInventory(tmpowner,"Widget2 Revision History");
}
else {// already up to date.
llSay(listen_channel,"NO_Update_Needed+"+(string)tmpowner); // we are good.
}
} // end if (tmpcommand == product_id1)


}
}




The Update Box: This is really the part that actually performs the update. Once the Avatar drops this on the ground, it contacts the object to be updated, and tells the Update Handler script what to expect, which items to delete in preparation of being replaced, and what the new version number will be. The Update Handler Script will then delete these items (in the example an object, and a notecard. The Update Handler will also change the objects description to the new version string. The Update Handler then replies back, letting the Update Box know it's done preparing. The Update Box then sends the updated object, notecard, and script over to the object to be updated. After a 2 second delay, the update box deletes itself. Update Complete!

CODE

string gTargetScript = "TestScript"; // script to update
string TargetNote ="TestNote"; // notecard to update
string TargetObject = "TestObject"; // Object to update
string new_version="1.02"; // this is the new version to list in the description field of the object.
// sent to the update handler.

integer uPIN = 1971; // Update Pin. This should be the same in the handler and updater
integer listen_channel = -1000; // Communication Channel. This should be the same in the handler,updater, and giver.


// This function is the workhorse, it sends all the new scripts and objects to the item being updated.
send_update(key id)
{
llSay(0,"Starting Update.... Don't go anywhere!");
llGiveInventory(id,TargetNote);
llSleep(0.3);
llGiveInventory(id,TargetObject);
llSleep(0.3);
llRemoteLoadScriptPin(id, gTargetScript, uPIN, TRUE, 1);
llSetText("Update Completed.", <1,0,0>, 1.0);
llSleep(0.3);
llSay(0,"Update Complete.... Deleting Update Prim.");
llSleep(2);
llDie(); // delete the update box, it's not needed anymore.

}

default
{
on_rez(integer param)
{
llResetScript();
}


state_entry()
{
llListen(listen_channel,"","","");
llSetText("", <1,0,0>, 1.0); // clear text on the box

// let the item know we are going to update it.
// we send a list of the names of any non-script items to delete before the update starts.
// good for changing out notecards, animations, objects, whatever.
// you could leave TargetNote a null string, and it would skip deleting that item.
// we tag everything with the owner's key, because you could have multiple people updaing
// multiple items in the same area. Crosstalk=bad.
llSay(listen_channel,"Prep4Update+"+(string)llGetOwner()+"+"+new_version+"+"+TargetNote+"+"+TargetObject);
llSay(0,"Sending Handshake...");
}


// This listener waits for the update handler to signal that it's done preparing for the update
// and is ready to proceed.

listen(integer channel, string name, key id, string message)
{
list tmplist;
string tmpcommand;
key tmpowner;

tmplist = llParseString2List(message,["+"],[]);
tmpcommand = llList2String(tmplist,0);
tmpowner = llList2String(tmplist,1);

if (tmpowner == llGetOwner() ) { // only listen to our replies
//llSay(0,"Verified Owner...");
if (tmpcommand == "Ready4Update")
{
llSay(0,"Verified Update Command...");
llSetText("Updating...Please wait.", <1,0,0>, 1.0);
send_update(id);
}


}
}
}



Caveats: The update box must contain all the new items being updated. Any scripts being updated should be set to a non-running state. It's recommended the updater box be no-copy at least, to make sure one doesn't stay in your customer's inventory. As written, the old items deleted during the update must be the same name as the new updated items. Some scripting and this could be changed. All variables are hard coded, some scripting could change this. The example only changes out 1 object, 1 notecard, and 1 script. This too could be changed... I've tried to cover this as well as I can, but If anyone has questions I'll do my best to answer. Enjoy. :)
Nada Epoch
The Librarian
Join date: 4 Nov 2002
Posts: 1,423
Original Thread
03-22-2007 07:41
/15/8e/172797/1.html
_____________________
i've got nothing. ;)
Daisy Rimbaud
Registered User
Join date: 12 Oct 2006
Posts: 764
07-24-2007 01:07
Many thinks for posting this - I'll try it out. One technical question: are negative channels secure? Is there any way to spy on the communications and then try to spoof them?
Squirrel Wood
Nuteater. Beware!
Join date: 14 Jun 2006
Posts: 471
07-24-2007 02:00
Regardless what channels you use. encrypt your communication.
Yumi Murakami
DoIt!AttachTheEarOfACat!
Join date: 27 Sep 2005
Posts: 6,860
07-24-2007 07:42
Unfortunately this is vulnerable to the following attack (assuming a no copy/transfer item - but if your object is copy/no transfer, you don't need to do script-only updates):

1. Remove the "update handler" script from the item (you can do this even if the item is no modify)
2. Make a new prim and put the update handler in it
3. Forge a script which sends a link message, indicating version 0, to the update handler
4. Since version 0 needs an update, the update handler will helpfully contact the server for you, which will load a nice fresh copy of those no-copy scripts into the plywood cube you just rezzed
5. Repeat steps 2-5 as necessary to make as many copies as you like, then sell them

NEVER, EVER make a script which engages in secure activity on command of a link message. It almost inevitably means you are vulnerable to a proxy attack of this type. If you must do it, the link message needs to be at least as secure as the other activity to prevent this kind of thing, and that usually makes it pointless to use a seperate script.
Darien Caldwell
Registered User
Join date: 12 Oct 2006
Posts: 3,127
07-24-2007 13:01
Yes, it's not meant to be secure, as I don't consider updating to be in need of security. Personally I only do diff updates, there may be 20 scripts but I only may change 1. so they would never get the full item even if they tried this. not to mention nobody would want to buy a cube no matter how cool it was scripted. :)

But yes, add the security as you see fit, that's why its free :)
_____________________
Darien Caldwell
Registered User
Join date: 12 Oct 2006
Posts: 3,127
07-24-2007 13:03
From: Daisy Rimbaud
Many thinks for posting this - I'll try it out. One technical question: are negative channels secure? Is there any way to spy on the communications and then try to spoof them?


Only as script can talk on a negative channel, however any script can listen. but you have to know what channel to listen on to begin with. As others have said, if you are really worried about security, there are many encryption methods available. Thats beyond the scope of this example script.

There are some other things you could do as well, such as have the update handler verify the object it resides in was created by you, maybe verify # of linked prims etc. or even name a child prim with a code and read that it's set before allowing update. all of these could be faked with varying degrees of effort, but my motto has always been "where there's a will, there's a way". If someone wants to steal from you, they will no matter what. But you don't have to make it easy :)
_____________________
Daisy Rimbaud
Registered User
Join date: 12 Oct 2006
Posts: 764
07-24-2007 15:06
From: Yumi Murakami
Unfortunately this is vulnerable to the following attack (assuming a no copy/transfer item - but if your object is copy/no transfer, you don't need to do script-only updates):

1. Remove the "update handler" script from the item (you can do this even if the item is no modify)
2. Make a new prim and put the update handler in it
3. Forge a script which sends a link message, indicating version 0, to the update handler
4. Since version 0 needs an update, the update handler will helpfully contact the server for you, which will load a nice fresh copy of those no-copy scripts into the plywood cube you just rezzed
5. Repeat steps 2-5 as necessary to make as many copies as you like, then sell them


I'm thinking out loud here, but -

Can one not get round such an attack the following way? Supposing the item to be updated is no-copy. The update script is also no-copy, and contains an llDie() call. Then whenever an update is performed, the user is sent a fresh copy of the item, but the old copy deletes itself. You can't then repeat steps 2-5 above, as the update script is deleted the first time. I suppose someone could move any other scripts out of the object, but if the update function was part of another script rather than standalone, would that really be useful?
Yumi Murakami
DoIt!AttachTheEarOfACat!
Join date: 27 Sep 2005
Posts: 6,860
07-25-2007 09:34
From: Daisy Rimbaud

Can one not get round such an attack the following way? Supposing the item to be updated is no-copy. The update script is also no-copy, and contains an llDie() call. Then whenever an update is performed, the user is sent a fresh copy of the item, but the old copy deletes itself.


If you're going to use llDie() (to remove the entire object) then the update would have to be a completely new copy of the object - not just the scripts. The script given above is for script-only updates.

To actually address your point, no, just that change wouldn't make it secure. Someone can still remove the update script from the original item, place it in a plywood cube, so than when the llDie occurs it only kills the plywood cube and then they have two copies of the item. Of course there are ways the update script could make sure it isn't in a plywood cube, but those weren't included in the scripts posted here. :)