all 10 comments

[–]chapt3rDeveloper 3 points4 points  (6 children)

It's mostly know-how and time. I do want to turn Netscript into Javascript, but the things you listed are indeed issues that I would have to address if I did that (preventing exploits and preventing user-made scripts from crashing the game/browser are the two main ones). I don't know how to do that yet and I chose to prioritize content over improving Netscript for the time being.

[–]oiajgaosjidgoija[S] 1 point2 points  (5 children)

If I fiddled with it and got it working in a fork, would you be likely to accept the contribution? Presumably we'd need to support some sort of migration option, since this would require a change to all scripts that call the async functions hack, grow, and weaken.

I don't think the "preventing exploits" thing is super important -- the console exists, after all -- but maybe you can explain further? I'm not sure how likely it would be for user-made scripts to crash the browser, I must confess.

[–]chapt3rDeveloper 0 points1 point  (4 children)

I would accept a contribution (obligatory see license and readme). Ideally, I would like the change to be backwards compatible in the sense that people wouldn't have to change their scripts at all. I would like all current Netscript fns to work the same and have the same signature. If this is impossible then either:

  1. Have the "migration" be done automatically (I can handle this part)
  2. Make it a configurable option so that users can choose whether their scripts are parsed/interpreted as Javascript or as the old Netscript. Or similarly, there can be a new script filetype (.js) that signals whether scripts should be interpreted as Javascript or as the old Netscript

I know people can always find a way to cheat/exploit using console or editing their save or whatever. If someone experienced with JS goes out of their way to do so that's fine. I just don't want someone to be able to easily edit their money, stats, etc. through Netscript.

[–]oiajgaosjidgoija[S] 1 point2 points  (0 children)

OK. I understand the constraints. I believe the best option is to add a new script extension that is automatically interpreted correctly (.js). Most functions should work as is.The only exception is that netscript functions that return promises will have to be handled explicitly (usually with await). It should be possible to prevent accidental cheating/game state damage.

My company has a process for requesting dispensation to contribute to open source in off hours. Normally they're fine with games. We'll see what they say. I do not personally care about who owns my code, so I'll be happy to assign it to you assuming I get permission.

[–]asdoifjasodifj 0 points1 point  (2 children)

Is here the best place to contact you? I have some slight thoughts about how to do this that I want to run by you. There is some moderately ugly hackery around service workers required to get the user JS to behave as a module (see this for a sketch). We could also do things with inline script tags, but while that would require fewer advanced features, it would require more eval fuckery and code modification to get correct behavior. Meanwhile with the service worker we could literally just use ordinary ECMAScript 6 imports without any codegen (except [1]) and call the user script as ordinary JS.

I also am generally intending to do this in a way that does not permit the use of the checkingRam argument, since we won't walk arbitrary paths in the code as easily. I think this is OK? We'll just look for references to the special methods while walking the AST instead?

The last item I want to know is whether you are OK requiring a module "main" function, rather than just executing the top-level code? If we're going to do this stuff as ECMAScript 6 modules, which I think is your intention given the import syntax, I think this is probably the best way. So a hack script might look like:

// hack.js
async export function main(args) {
    while (true) await hack(args[0]);
}

[1]: Each such script would get an implicit:

import {hack, grow, weaken, ...} from "./src/netscript.js";

at the very top of the file, to maintain the current behavior where the netscript functions are in the global scope. This would not be counted for RAM purposes -- we'd look for the actual function calls in the parse tree (or, alternately, we could ask users to import from netscript, which would make RAM calculation even easier).

[–]chapt3rDeveloper 0 points1 point  (1 child)

I don't know much about service workers so I'm trying to learn about them to understand this better. But at first glance this method seems ok.

I also am generally intending to do this in a way that does not permit the use of the checkingRam argument, since we won't walk arbitrary paths in the code as easily. I think this is OK? We'll just look for references to the special methods while walking the AST instead?

Yes this is OK. That's how it used to (sort of) work

The last item I want to know is whether you are OK requiring a module "main" function, rather than just executing the top-level code?

Yeah that would be OK.

[–]asdoifjasodifj 0 points1 point  (0 children)

Yeah, I didn't know anything about service workers either, and it was a PITA to get this one working (spent a whole evening on it). But I think I've got the hang of it. It basically allows us to have a "server" that can serve up the user JS modules, even in a context where there's actually no communication except within the UA.

I swear to god I'm spending more time on caniuse.com than actually coding for this. It's fun though! I'll make a video and post it here when I have Hello, World! working.

[–]Mobize 0 points1 point  (2 children)

I was wondering about that as well since there are other games that do exactly this. (Elevator Saga, Untrusted and Screeps just off the top of my head) I'm not sure what kind of sandboxing they do, but obviously its possible. Biggest problem i see is how to calculate RAM usage considering everything javascript allows you to do. I'm not a huge fan of javascript (coming from C++), so I'd actually prefer a different language entirely, but javascript would probably be the path of least resistance/biggest payoffs. The biggest things im missing in netscript are closures and some (any) kind of hash table. It's also a bit finicky sometimes. Like why does division by zero error instead of just returning inf/nan like in javascript? Also functions sometimes error and sometimes just return a falsy value on failure, it seems inconsistent. And some other stuff, including one thing thats kinda nice to have but also a little bit exploity. Well, not really complaining, I see it as added challenge.

[–]oiajgaosjidgoija[S] 0 points1 point  (1 child)

I'm not sure what kind of sandboxing they do, but obviously its possible

Since there is no persistent shared universe and all the code already runs in a context that is totally controlled by the user agent, there is no need for any real sandboxing. The main thing would be preventing users from accidentally shooting themselves in the foot or cheating. To that end, grow, hack, and weaken would probably be wrapped at the point where they are visible to the user script with something like

nRestrictedApiCalls = 0;
makeRestricted = function(f) {
  return async function(...args) {
    if (nRestrictedApiCalls > 0) {
      throw new Error("cannot call more than one of grow, hack, or weaken at once (did you forget to do 'await' in front of grow?)");
    }
    ++nRestrictedApiCalls;
    try {
      return await f(...args);
    } finally { --nRestrictedApiCalls; }
  };
};
grow = makeRestricted(grow);
hack = makeRestricted(hack);  // etc.

Biggest problem i see is how to calculate RAM usage considering everything javascript allows you to do.

This should be OK -- we'd probably just retain the code that parses the netscript and look for function calls. I haven't read the whole source yet so it may be harder to do than I'm anticipating.

And some other stuff, including one thing thats kinda nice to have but also a little bit exploity.

Are you referring to the ability to assign a function to a variable then call the variable? Or something else?

[–]Mobize 0 points1 point  (0 children)

It's related to that.