Schthack map viewer ported to Nodejs


Garbage Human
There are still a lot of issues to work on, but I managed to port Schthack's map viewer source from Delphi/Pascal to a Nodejs script which outputs an .obj file. Textures seem to be in the right place for the most part, but the UV's are wrong. All of models in the map can be exported, but the rotations and translations seem to be really wrong. So all of the pieces are there, they're just not in the right place it seems. I checked and this seems to be the same output for what was in the MapViewer program as far as that goes.

The Quest Editor has a map viewer, so I'm not sure if that has an updated viewer or fixed code, but if anyone has any information on that, it would definitely be nice to have more to go off of. I'm tempted to take a detour to try and document the file structure for .nj to see if it would be possible to take a stab at .xj models once I was familiar with the file format. Though, I'd like to finish maps before I get too side tracked working on other stuff. The code is all up on github if anyone wants to take a look and fork or clone anything.

Link: ... /edit08.js


Very nice! I'll definitely be taking a look at this :D

On a side note, do you have the original map viewer from schthack? I tried to download it the other day and all I found was a dead link :/

- Andy


Garbage Human
Spent today looking at Pso Version 1 n.rel stage files. It's kind of confusing, I'm going to try writing out everything I know so far to try and have something to refer back to. Maybe this will help someone else.

I'm going to be using "map_boss03an.rel" as an example since it's only about a kb in size and there's only one section. For longer files the same pattern is just repeated across each section.

For the most part the file goes bottom to top. We goto the last line labeled with "A" and look at EOF (end of file) minus 16 bytes and read the pointer to the header. 0x0E98 in this case.

We then goto "B" the header which has four items in it. The number of sections 0x01, some value which almost always seems to be "4844", pointer to section_list 0x0E5C and pointer to texture_list 0x0364. Texture list is super easy, more on that later.

Next we follow the section_list pointer to "C" for the section_entry. Not entirely sure about this yet, but my best guess for this struct is:
Section_no : dword
Position_x : single
Position_y : single
Position_z : single
Rotation_x : int
Rotation_y : int
Rotation_z : int
Radius : single
Pointer_A : dword
Pointer_B : dword
Pointer_C : dword
Pointer_A_num_entries : dword
Pointer_B_num_entries : dword
Pointer_C_num_entries : dword
Const : 0x1000
And the number of structs is an array for how many sections we listed in this header.

So let's follow Pointer_B to 0x0D78 which is labeled with "D". There were 0x13 (19) entries listed for it, and we can see there are 19 entries of "00000000 00000000 00100000". I have no idea what this is for but it's there.

Next let's follow Pointer_A to 0x0E5C labeled by "E", we have what appears to be another super useless struct. It has a pointer to an NJS_OBJ, generally 10 entries of zeros (broken by a 0x01 in this case) followed by what seems to be some kind of flag at the end. So it's basically a pointer to a pointer which is kind of redundant. Not sure what the flags mean, it might be a good idea to mess with those and load up the game. Also, the number of these is given in the section_list struct, given by 1 in this case.

So skipping on by, let's follow the pointer 0x0D14 from the pointer in the last struct to come to section "F". Now we're in familiar territory, this is just an NJS_Object listed in the Ninja_GD.pdf documentation.
typedef struct obj {
Uint32 evalflags; /* Evaluation method optimization */
NJS_MODEL *model; /* Model structure */
Float pos[3]; /* Parallel motion */
Angle ang[3]; /* Rotation */
Float scl[3]; /* Scale */
struct obj *child; /* Child pointer */
struct obj *sibling; /* Sibling pointer */

Following the NJS_MODEL pointer we come to section G which is also the same as in NJ files. We have the pointer to the vertices 0x0730 and the pointer to the normals 0x036C. Not sure what the other four items in this struct are as of yet.

The next image looks a lot scarier than it actually is.

Following the pointer to the vertices from the NJS model we come to section "G". The fist 2 items are 4 words, material, something, zero and 0x5C for the number of vertices (highlighted in red). Then all you have to do is read the x,y,z,rgba data from there for the given number of vertices highlighted in orange.

Normals work the exact same way. Following the pointer from the NJS_MODEL to the normal list we get the normal list, labeled by "I" and outlined by orange. I think it's the same number of vertices as given before. But the end is also given by the start of the normal list and the array is marked closed by 0xFF000000.

And lastly I said textures are stupidly simple are they are. The texture pointer in the file header (B) points to the area outlined in red which gives a pointer to the start of the vertex list and the number of entries.

The vertex list is labeled with "J" and outlined in purple. If you look at the top of the file, you will notice that the file names are given for the textures. The texture entry list is just a list of pointers which gives the starting position for the string. It is followed by two dwords of zeros which are attribute entries which are generally not used.

So it's kind of wonky, but not too bad of a file structure. Basically it's a list of sections, each section is a group of NJCM models. Though I at this point I'm more familiar with .xj files types than .nj so it might take some more time and playing with to see what happens.


  • stages.png
    109.4 KB · Views: 116
  • verts.png
    150.7 KB · Views: 114
  • norms.png
    169.4 KB · Views: 107

Aleron Ives

I've been waiting years for somebody to figure those out. :p Every post-Dreamcast version of PSO has a bunch of broken animations, and figuring out how the n.rel files work would allow us to fix them by importing animations from working versions/regions into versions/regions that are broken.