Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to compile .nss scripts, created using nsbparse/nsbparse2 to nsb+map? #15

Open
paxidently opened this issue Feb 20, 2015 · 3 comments

Comments

@paxidently
Copy link

When I trying decompile and compile script using nsbparse2 and nsbcompile2:

$ nsbparse2 sg09_01.nsb sg09_01.nss en_US.UTF-8
$ nsbcompile2 sg09_01.nss en_US.UTF-8
Error: syntax error
zsh: abort (core dumped)  nsbcompile2 sg09_01.nss en_US.UTF-8

When I trying to decompile and compile again using nsbparse and nsbcompile I can't create .map file for sg09_01.new.nsb:

$ nsbparse sg09_01.nsb en_US.UTF-8
$ nsbcompile sg09_01.nss sg09_01.new.nsb en_US.UTF-8
$ ls *.map
sg09_01.map

When I trying to decompile sg09_01.new.nsb using sg09_01.map, strings apparently become broken (I haven;t tested neither FGRE nor SG with this files):

sg09_01.nss:

<....>
00173 ParseText(text00010, @box00, <PRE @box00>[text00010]
--The feeling of another warm hand.

Slips through my fingers with a strong sense of vertigo.

</PRE>);
<....>

sg09_01.new.nss:

<....>
00173 CallFunction(00173ParseText, text00010, @box00, <PRE@box00>[text00010]--Thefeelingofanotherwarmhand.Slipsthroughmyfingerswithastrongsenseofvertigo.</PRE>);
<....>

Is there right way to decompile nsb, edit it and compile to nss+map?

P.S. I used Fuwanovel version.

@krofna
Copy link
Member

krofna commented Feb 20, 2015

nsbcompile is an obsolete tool which was dropped in 0.5.0. As for nsbparse2/nsbcompile2, they are very buggy and are currently useful only for development purposes. I currently do not have free time so I'm afraid it will not be fixed any time soon 😢

@paxidently
Copy link
Author

Hmm.. Is there any drafts / docs / specs on nsb+map format and nsbparse2's nss language?

@krofna
Copy link
Member

krofna commented Feb 20, 2015

AFAIK there are no docs on those formats apart the ones in my head, so here it is:
nsb and map are actually very simple formats which are fully implemented in libnpa/src/scripfile.cpp ScriptFile::ReadNsb. nsb is basically a list of commands:

[command]

  • 4 bytes wasted on a value which monotonically increases (1, 2, 3 etc)
  • 2 bytes of magic which identifies operation. (see: libnpa/src/nsbmagic.hpp) I've figured out most of them, the UNK (unknown) ones are mostly Steins;Gate phone related.
  • 2 byte parameter list size. Depends on magic. For example, MAGIC_DELETE always has 1 but MAGIC_ARRAY has arbitrary.
  • N parameters (format below)

[parameter]

  • 4 byte string size
  • string (value of parameter)

Example:
1C000000 - 28th command in this script
D000 - MAGIC_LITERAL
0200 - 2 parameters
06000000 - 6 characters
535452494E47 - "STRING"
09000000 - 9 characters
4E4F5F544152474554 - "NO_TARGET"

Translates to (nsbparse):
Literal(STRING, NO_TARGET);

Looking at some context (note that nsbparse starts counting at zero):

00027 Literal(STRING, NO_TARGET);
00028 Assign($SW_RNDMAIL_RUK);
00029 ClearParams();

Which translates to (nsbparse2):

$SW_RNDMAIL_RUK = "NO_TARGET";

Note that integer literals are also written as strings, but have an "INT" parameter rather than "STRING".

The syntax used by nsbparse2 mimics the language used by older Nitroplus games (before they switched to binary scripts) like Chaos;Head, so you can use that as sort of a reference.

I have never bothered to document parameters used by each command, although I've figured out most of them and you can see them in libnpengine-new/src/NSBInterpreter.cpp. There is also no documentation of syntax (i.e. how do combinations like Literal+Assign work or how are combinations like Variable + Variable + CmpLogicalAnd + If evaluated, but you can see that either from NSBInterpreter or nsbparse2. But the general principle is that each operation has N arguments it affects, starting from the one with lowest ID (i.e. it's the opposite of what you would expect on an x86 cdecl stack). For example:

01615 Literal(INT, 126);
01616 Literal(INT, 12);
01617 SubExpression();

Means (126 - 12), and after that operation 126 and 12 are removed from "stack", and 114 is pushed.

Number of arguments each command affects is in this table. There is also bunch of other quirks like position parameters. But I will not get into that now (unless you want me to explain). Also variable identifiers are overly complex to explain in this post (it's getting quite big), but you can try reading this code just to get the taste of it. It's basically spaghetti recursive hell.

ANYWAY; map files are simpler. They basically tell where if/while/select/function/scene/chapter/jump/createprocess are supposed to jump. It also contains list of includes (although I suspect it's broken and unused).

[map entry]

  • 4 byte offset into nsb file pointing at the ID (i.e. the wasted 4 bytes) of "landing point"
  • 2 bytes of string size
  • string containing label (for example: label.if.311 or chapter.main)

nsbparse2 is supposed to use .map to determine else if's and else's but it's currently broken. However, branching works in libnpengine-new.

So yeah, it's a very simple format but it's very powerful and represents lots of complex stuff (like for example array-related magic, event loops, etc). If you have any additional questions feel free to ask, I'll be glad to help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants