[Stages] Wall / Collision Data Edits

So, I'll try to make this post more friendly.
At the end of the "sDJ_dat.bin", i found the source code for a function, that loads elevation data for the "stage_AG1S"(the upper part of an unused variant of "AERIAL GARDEN", you can select this one by choosing "X Private Beach" at the npc girl with a gift-box in DoAOnline):
C++:
void sAG1N_ang_set (void)
{
/ * Stage grid size * /
stage_grid_size = 0.250000f;

/ * Stage reference position * /
angulation_mod = -0.383000f;

/ * Stage edge coordinate setting * /
stage_edge_min_x = -29.500000f;
stage_edge_min_z = -34.500000f;
stage_edge_max_x =  35.500000f;
stage_edge_max_z =  35.250000f;

/ * Set the number of grids on the stage * /
intersect_ct_x = 261;
intersect_ct_z = 280;

/ * Pointer setting to undulation data * /
stage_dat_tbl = (STG_TBL *) (stage_dat_addr [CmnSceneBank]);
angu = (unsigned short *) ((UINT32) stage_dat_tbl-> ang_tbl + stage_dat_addr [CmnSceneBank]);

/ * Field boundary edge setting * /
boundary_edge_ct = 169;

/ * Pointer setting to field boundary data * /
bdr_vertex = (BDR_VERTEX *) ((UINT32) stage_dat_tbl-> bdr_tbl + stage_dat_addr [CmnSceneBank]);
bdr_edge   =   (BDR_EDGE *) ((UINT32) stage_dat_tbl-> edg_tbl + stage_dat_addr [CmnSceneBank]);
}

So let's have a look into it. The elevation data is a grid on the floor, each intersection of the grid has a certain height. The variables describing this grid are hard-coded inside the game.
stage_dat_addr - is the pointer to the "stage_AG1S.bin" elevation block.

angu(undulation data) - is the data block from the first offset. It contains WORD values, each value is the height of a intersection of the elevation grid. The length of this block is = intersect_ct_x * intersect_ct_z = 261*280 = 73080 values

bdr_vertex(field boundary data) - is a block that consists of vertices (float x, float y, float z)

bdr_edge(field boundary data) - is a block that describes the walls, each data structure from this block contains two indices to vertices from the bdr_vertex, and also 4 bytes the third byte is the type of the wall. It's pretty much what usagiZ described.

An importer could be created that will load the collision data in a 3d program, and visualize it. But the hard-coded values should be extracted from the game's executable, and feed to the importer manually.

The hardcoded values for the elevation data explained:
Undulation_data.jpg
 
Last edited:
More on collisions:
Every stage with a '-' doesn't have a function to set angulation data. Tina's EGX_TF has one but makes two checks to load it or not.
index , SHOrt_name, -has_no_angulation, /has_no_files , "official_name", <developer_name>, {comments}
Code:
00  GL1     "THE WHITE STORM" <THE WHITE STORM>
01 AG1N     "THE AERIAL GARDEN (NIGHT)" <THE NIGHT OF FIREFLY>
02  DS2 -    <THE DANGER ZONE 2>     {DANGERZONE survival bonusesDS2}
03  GL2     "THE WHITE STORM2" <THE BLUE CAVE>
04  X04 - /  <THE GREAT OPERA>
05   WA -    <THE DEATH VALLEY>
06  TST - / "TEST1" <MOTION EDIT>     {test grid}
07  WAL     "WALL_TEST" <UNDULATION CHECK>     {test wals}
08  KL1     "The Kowloon" <THE CRIMSON>
09  DS1     "THE DANGERZONE" <THE DANGER ZONE>
0A   OH     "THE GREAT OPERA(FRAME)" <THE GREAT OPERA>
0B  OH2     "THE GREAT OPERA" <THE GREAT OPERA>
0C   SN     "THE DEMON'S CHURCH" <THE DEMON'S CHURCH>     {42+}
0D  SN2     "THE DEMON'S CHURCH2" <THE DEMON'S CHURCH>
0E   AM -    <THE BIO LAB.>
0F AG1D     "THE AERIAL GARDEN" <THE AERIAL GARDEN>
10  AG2     "THE AERIAL GARDEN2" <THE WATER FALL>
11   EL -    <ELEVATOR>     {city elevator}
12   MI     "THE MIYAMA" <THE MIYAMA>
13   NK     "THE KOKU AN" <THE KOKU AN>
14   MO -    <THE SPIRAL>
15   PO     "THE DRAGON HILL" <THE DRAGON HILLS>
16  X22 - /  <THE MIYAMA>
17   BP     "EGX_BP" <UNKNOWN>     {bass tina's poster}
18   BL     "THE DOATEC GERMANY" <THE BIO LAB.>
19 GL1Z     "NIX_GL1Z" <UNKNOWN>     {leon's desert}
1A AG2N     "THE AERIAL GARDEN (NIGHT)2" <THE WATER FALL>
1B MISP      <UNKNOWN>     {miama spring deprecated}
1C   GC     "EGX_GC" <UNKNOWN>     {young tina's PRAIRIE}
1D   TF     "EGX_TF" <UNKNOWN>     {tina tv}
1E  PO2     "THE DRAGON HILL2" <THE DRAGON HILLS>
1F   PR     "THE PRAIRIE" <THE PRAIRIE>
20   TR -   "Trailer" <UNKNOWN>     {bass' truck}
21  KL2     "The Kowloon2" <THE CRIMSON>
22 AG1S      <THE AERIAL GARDEN>
23 AG2S      <THE WATER FALL>
24   AL -    <THE D OCTAGON>
25  COL -    <THE PANCRATIUM>
26   OL -    <THE IRON HELL>
27   SA -    <THE BLANCA>
28   KK     "The L's Castle" <THE L'S CASTLE>
29   TE     "THE BURAIZENIN" <THE BURAI ZENIN>
2A  TE2      <THE BURAI ZENIN>
2B   HT -    <UNKNOWN>     {bamboo forest}
2C  OL2 -    <THE IRON HELL>
2D  TF2 -   "EGX_TF2" <UNKNOWN>     {tina tv}
2E   DJ -   "EGX_DJ"      {dojo}
2F   KM -         {montane flower}
30   NC     "THE YOZAKURA" 
31   BC     "THE ISLAND" 
32   FC     "THE SHRINE" 
33   LT     "THE RAY HOUSE" 
34   SV     "THE SAFARI" 
35   LC     "THE GREAT WALL" 
36  SEA     "THE AQUARIUM" 
37   DT     "THE DOWNTOWN" 
38   SP     "THE CYCLOTRON" 
39   SB     "THE SUSPENSION BRIDGE" 
3A  EFC -   "Effect Check1" 
3B  EF2 -   "Effect Check2" 
3C  EAR -   "EARTH" 
3D  WL2     "WALL_TEST2" 
3E  WL3 -    
3F MISM     "THE MIYAMA(SPRING)" 
40 MIAU     "THE MIYAMA(AUTUMN)" 
41 MIWI     "THE MIYAMA(WINTER)"

-Open blender.
-Change the timeline to text editor(Shift+F11).
-Press the [+ New] button.
-Paste the text of this script.
-Change the stg_path to the s??_dat.bin of the stage you want to preview the collision edges(walls only)
-Press the [Run script] button. The collision data will be loaded.
-Doau's DJ stage and the stages from doa3 are messed up, for some stages there are only cloud points. others look right.
Python:
#-------------------------------------------------------------------------------
# import collision data from doau/doa3 stages to blender (script)
#-------------------------------------------------------------------------------
import struct,os
stg_path = r"C:\WORK\doaol\STG-bin\sDJ_dat.bin"

stg_dir = r"C:\WORK\doaol\STG-bin"

parse_dir = 0

imin_blender = 1

if not parse_dir:
    stages_bin = [stg_path]
else:
    stages_bin = [os.path.join(stg_dir, stg_name) for stg_name in os.listdir(stg_dir)]

if imin_blender:
    import bpy
    new_collection = None

testlist = []

for stg_path in stages_bin:
    if os.path.isfile(stg_path) and '.bin' in stg_path:
        with open(stg_path,'rb') as f:
            #get a list of offsets to colision data objects in a stage (not the angulation data, only collision edges)
            head_size = struct.unpack('<L', f.read(4))[0]
            if head_size in [0x14242, 0xc0e29548, 0x4b4e4c, 0xd0027c0, 0xd0027c0, 0x168]: #doa2: HT/TR are unknown data; doa3: LNK data, TST/TS2 are 4b long; doa2 sSV_mot.bin
                continue
            print(hex(head_size),stg_path)
            colobjects = []
            is_single_object = False
            if head_size in (0x10, 0x18):
                f.seek(head_size)
                dprobe = struct.unpack('<L', f.read(4))[0]
                if dprobe not in (0x10, 0x18, 0x6c, 0x3, 0x10310) or dprobe == 0xf000f000: #this bin is a single object
                    colobjects.append(0)
                    is_single_object = True
                    head_size = 0
            if not is_single_object:
                f.seek(0)
                for i in range(head_size//4):
                    f.seek(i*4)
                    boffset = struct.unpack('<L', f.read(4))[0]
                    f.seek(boffset)
                    dprobe = struct.unpack('<L', f.read(4))[0]
                    if dprobe in (0x10, 0x18):
                        colobjects.append(boffset)
            #go throug all collision objects and import the data
            for coloffset in colobjects:
                f.seek(coloffset)
                angulation_offset, boundry_vertex_offset, boundry_edge_offset, boundry_edge_end_offset = struct.unpack('<LLLL', f.read(0x10))
                boundry_edge_count = (boundry_edge_end_offset - boundry_edge_offset) // 8
                f.seek(boundry_edge_offset + coloffset)
                edges_with_attribs = [struct.unpack('<HHHH', f.read(8)) for i in range(boundry_edge_count)]
                edges = [e[:2] for e in edges_with_attribs]

                print(stg_path, f.tell(), colobjects, angulation_offset, boundry_vertex_offset, boundry_edge_offset, boundry_edge_end_offset, boundry_edge_count)
    ##            boundry_vertex_count = max([index for edge in edges for index in edge]) + 1
                boundry_vertex_count = (boundry_edge_offset - boundry_vertex_offset) // 12
                f.seek(boundry_vertex_offset + coloffset)
                print(stg_path, f.tell(), colobjects, angulation_offset, boundry_vertex_offset, boundry_edge_offset, boundry_edge_end_offset, boundry_edge_count, boundry_vertex_count)
                vertices = [struct.unpack('<3f', f.read(12)) for i in range(boundry_vertex_count)]
                #print(vertices)
                #print(edges)
                stg_name_short = os.path.basename(stg_path)


    ##            vertices = [(0, 0, 0),(1,1,1)]
    ##            edges = [(0,1)]
                faces = []
                if imin_blender:
                    new_mesh = bpy.data.meshes.new(stg_name_short +'_mesh_')
                    new_mesh.from_pydata(vertices, edges, faces)
                    new_mesh.update()
                    # make object from mesh
                    new_object = bpy.data.objects.new(stg_name_short + '_object_', new_mesh)
                    # make collection
                    if not new_collection:
                        new_collection = bpy.data.collections.new('stags_col')
                        bpy.context.scene.collection.children.link(new_collection)
                    # add object to scene collection
                    new_collection.objects.link(new_object)
 
Last edited:

Matt Ponton

Founder
Staff member
Administrator
Standard Donor
-Open blender.
-Change the timeline to text editor(Shift+F11).
-Press the [+ New] button.
-Paste the text of this script.
-Change the stg_path to the s??_dat.bin of the stage you want to preview the collision edges(walls only)
-Press the [Run script] button. The collision data will be loaded.

I tried this, sadly it didn't load anything (attempted sAG1N_dat.bin), and no console errors given. Used v2.93.1.

2fPNJq.jpg
 
I tried this, sadly it didn't load anything (attempted sAG1N_dat.bin), and no console errors given. Used v2.93.1.

2fPNJq.jpg
It didn't like the "\2" in the path. I fixed the script to support ms windows paths. Try it now, it should work.
Also added 'sSV_mot.bin' to ignore.
You can change the 'stg_dir =' to the folder with all your stages bins, and make 'parse_dir = 1', and it will load all collisions from all stages.

I extracted the data embedded in executable that complements the collision data inside the bin files. I'll try to parse it and extend the importer to load also the terrain data. Also not sure if the walls data is shown correctly, need to look more into it.
 
Last edited:

Matt Ponton

Founder
Staff member
Administrator
Standard Donor
It didn't like the "\2" in the path. I fixed the script to support ms windows paths. Try it now, it should work.
Also added 'sSV_mot.bin' to ignore.
You can change the 'stg_dir =' to the folder with all your stages bins, and make 'parse_dir = 1', and it will load all collisions from all stages.

I extracted the data embedded in executable that complements the collision data inside the bin files. I'll try to parse it and extend the importer to load also the terrain data. Also not sure if the walls data is shown correctly, need to look more into it.

Thanks, seems to import some linework (wall vertical edges?) now.

RlBxYp.jpg


6T3fwH.jpg
 
Python script to preview angulation data in blender(2.93). [version 2]:
Python:
#-------------------------------------------------------------------------------
# import collision data from doau stages(only original names) to blender (script) [ver.2]
#-------------------------------------------------------------------------------
stg_path = r"E:\--\@stages\doauJPstages\sAG1N_dat.bin"

stg_dir = r"E:\--\@stages\doauJPstages"

parse_dir = 0

imin_blender = 1

terrain_area_flags = 0b1000         #(default:0b1111) {non_shoreline?,water?,solid_ground?,playable_area?} 0b0000 0b0001 0b0100 0b0101 0b0110 0b0111 0b1000 0b1001 0b1100 0b1111  //bc fc (gl1-gl2 has no ground?) kl2 lc wl2 (gl1 has flag1)

################################################################################
##use stg_path='path/to/stage_folder/sSTAGE_dat.bin' and parse_dir=0, to parse a single stage
##use stg_dir='path/to/stages_folder/' and parse_dir=1, to parse all stages in the folder
##imin_blender = 0 for testing the script outside blender / imin_blender = 1 for importing data in blender
##terrain_area_flags - set the flags for terrain area types; all null is non playable area, others are unknown, like the snow and water have their combination of flags
##note: I used in this script only the most common for all stages, embedded collision parameters and layers of data
##limitations: only two types of terrain can be visualized at the same time, no properties of the walls are displayed; maybe convert edges to grease_pencil and use color-coding?
##???
## TF and WL3 both have collision data but it's deprecated inside the game
## MISP is processed with all miyamas but has a bin of a test stage(edges count and angulation count is different)
## GL1_1,OH_3,WL2_5,OH2_3,GL1Z_0 are empty?
## TE2 is doa3 wall data type?
################################################################################
import struct,os
terrain_area_flags = terrain_area_flags << 12

if parse_dir:stages_bin = [os.path.join(stg_dir, stg_name) for stg_name in os.listdir(stg_dir)]
else:        stages_bin = [stg_path]

if imin_blender: import bpy


testlist = []
embedded_angs = {
0x00:[(0.25,1024.0,-1.719,19.0,26.25,159,172,90),(0.25,512.0,-5.7249999,7.25,22.25,93,99,0)],
0x01:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x0F:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x03:[(0.25,1024.0,-0.102,25.25,28.5,177,200,103)],
0x07:[(0.25,1024.0,0.0,10.25,10.25,83,83,124)],
0x08:[(0.25,1024.0,-0.19400001,17.5,14.25,137,140,132),(0.25,1024.0,0.0,16.5,1.75,136,141,73),(0.25,1024.0,-0.75,16.5,1.75,136,141,69)],
0x09:[(0.25,1024.0,-0.090999998,16.25,16.25,131,131,24)],
0x0A:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0B:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0C:[(0.25,1024.0,-0.206,15.5,19.25,124,129,96)],
0x0D:[(0.25,1024.0,-0.414,25.75,40.75,207,269,141)],
0x10:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x1A:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x12:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x1B:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x3F:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x40:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x41:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x13:[(0.25,1024.0,0.015,23.5,17.5,221,192,137),(0.25,1024.0,-1.001,15.75,9.75,156,180,99)],
0x15:[(0.5,1024.0,0.0,17.5,16.5,70,67,40),(0.5,1024.0,-12.543,16.5,25.0,69,68,78),(0.375,1024.0,-69.033997,12.375,28.5,62,63,16),(0.25,256.0,-29.403999,28.5,14.75,85,51,8),(0.375,1024.0,-31.601999,47.625,38.25,145,133,145),(0.25,256.0,-29.403999,-7.75,14.75,85,51,8),(0.375,1024.0,-29.478001,-10.5,24.0,88,88,74)],
0x17:[(0.5,1024.0,0.0,50.5,50.5,203,203,16)],
0x18:[(0.25,1024.0,-0.52399999,18.5,34.5,191,205,169)],
0x19:[(0.25,1024.0,-0.52399999,33.5,46.0,280,368,0)],
0x1C:[(0.5,1024.0,-1.765,27.0,22.5,104,93,47)],
0x1E:[(0.25,1024.0,0.62400001,17.5,17.25,140,143,84)],
0x1F:[(0.5,1024.0,-1.765,27.0,22.5,104,93,50),(0.5,1024.0,-7.0430002,37.0,21.0,171,147,76),(0.5,1024.0,-7.152,38.5,49.5,167,145,68),(0.5,512.0,-12.416,3.5,36.0,112,137,135),(0.5,512.0,-5.6170001,32.5,10.0,128,92,27),(0.5,512.0,-11.283,16.0,33.5,106,96,26)],
0x21:[(0.25,1024.0,0.31999999,33.25,12.0,170,149,177),(0.25,1024.0,0.31999999,33.25,12.0,170,149,185)],
0x22:[(0.25,1024.0,-0.38299999,35.5,35.25,261,280,169)],
0x23:[(0.25,1024.0,-0.18799999,16.25,16.75,144,133,79)],
0x28:[(0.5,1024.0,0.048999999,15.5,15.5,63,63,28)],
0x29:[(0.175,1024.0,-0.001,25.375,24.15,290,254,162)],
0x2A:[(0.25,1024.0,-1.6,24.0,26.5,203,171,113)],
0x30:[(0.25,1024.0,-0.001,19.5,20.0,177,226,135)],
0x31:[(0.44999999,1024.0,-0.22,66.150002,54.0,302,212,101)],
0x32:[(0.25,1024.0,15.05,17.0,-12.75,133,151,120),(0.25,128.0,-0.491,7.75,2.5,63,130,18),(0.25,1024.0,-0.741,17.5,20.0,141,146,84)],
0x33:[(0.1,1024.0,0.0,19.4,19.4,389,389,44)],
0x34:[(0.25,819.20001,-0.15899999,25.25,24.0,209,204,67),(0.25,512.0,-3.678,25.25,4.5,103,66,66),(0.5,1024.0,-4.152,48.5,38.5,133,136,129),(0.5,1024.0,-4.1999998,18.0,38.5,148,136,124)],
0x35:[(0.25,1024.0,0.0,16.75,16.75,133,137,75),(0.25,256.0,-19.9,10.75,-5.25,78,114,114),(0.25,1024.0,-21.048,25.0,-14.5,157,149,97),(0.25,256.0,-33.764999,16.75,-28.0,122,136,112),(0.25,1024.0,-33.799999,16.5,-34.25,187,184,71),(0.25,256.0,-34.618,39.0,-25.5,121,93,114),(0.25,512.0,-37.717999,58.5,-7.0,182,300,144),(0.25,204.8,-24.667999,14.25,44.75,96,161,113),(0.25,1024.0,-24.664,37.5,78.25,208,223,119)],
0x36:[(0.25,1024.0,0.0,20.25,20.25,163,163,65)],
0x37:[(0.25,1024.0,-0.18799999,20.25,20.25,163,163,19)],
0x38:[(0.5,1024.0,-0.99900001,19.5,19.5,79,79,32)],
0x39:[(0.5,1024.0,-0.67799997,23.5,10.5,95,43,42),(0.5,512.0,-20.073,18.5,10.0,76,82,74),(0.5,512.0,-26.941999,17.5,17.0,71,55,58),(0.5,341.33334,-30.17,19.5,43.5,78,99,84),(0.75,256.0,-30.17,19.5,43.5,53,98,208)],
0x3D:[(0.25,1024.0,0.0,16.25,17.5,131,141,19),(0.25,256.0,-4.0,20.25,12.25,71,99,12),(0.25,1024.0,-4.0,44.25,37.75,179,303,8),(0.25,256.0,-8.0,12.25,-2.75,99,98,12),(0.25,1024.0,-8.0,20.25,-9.5,163,162,8),(0.25,256.0,-4.0,-3.75,12.25,53,99,0),(0.25,1024.0,-14.066,6.25,37.75,207,303,11)]}
stg_short_names = ["GL1","AG1N","DS2","GL2","X04","WA","TST","WAL","KL1","DS1","OH","OH2","SN","SN2","AM","AG1D","AG2","EL","MI","NK","MO","PO","X22","BP","BL","GL1Z","AG2N","MISP","GC","TF","PO2","PR","TR","KL2","AG1S","AG2S","AL","COL","OL","SA","KK","TE","TE2","HT","OL2","TF2","DJ","KM","NC","BC","FC","LT","SV","LC","SEA","DT","SP","SB","EFC","EF2","EAR","WL2","WL3","MISM","MIAU","MIWI","X66","X67","X68","X69","X70"]

for stg_path in stages_bin:
##    print('hey', os.path.isfile(stg_path) , '.bin' in stg_path)
    if os.path.isfile(stg_path) and '.bin' in stg_path:
        with open(stg_path,'rb') as f:
            #try to detect the offsets that contain angulation data
            head_size = struct.unpack('<L', f.read(4))[0]
            if head_size in [0x14242, 0xc0e29548, 0x4b4e4c, 0xd0027c0, 0x168]: #doa2: HT/TR are unknown data; doa3: LNK data, TST/TS2 are 4b long; doao sSV_mot.bin
                continue
            colobjects = []
            is_single_object = False
            if head_size in (0x10, 0x18):
                f.seek(head_size)
                dprobe = struct.unpack('<L', f.read(4))[0]
                if dprobe not in (0x10, 0x18, 0x6c, 0x3, 0x10310) or dprobe == 0xf000f000: #this bin is a single object
                    colobjects.append(0)
                    is_single_object = True
                    head_size = 0
            if not is_single_object:
                f.seek(0)
                for i in range(head_size//4):
                    f.seek(i*4)
                    boffset = struct.unpack('<L', f.read(4))[0]
                    f.seek(boffset)
                    dprobe = struct.unpack('<L', f.read(4))[0]
                    if dprobe in (0x10, 0x18):
                        colobjects.append(boffset)

            srtname = os.path.basename(stg_path)[1:-8]
            if srtname in stg_short_names:
                stgidx = stg_short_names.index(srtname)
            else:
                print(srtname, 'IS NOT RECOGNIZED')
                continue
            if stgidx not in embedded_angs:
                print(srtname, 'HAS NO COMPLEMENTARY ANGULATION DATA', colobjects)
                continue
            colobjects = colobjects[:len(embedded_angs[stgidx])] #removing the falsedetected collision blocks

##            print(srtname, len(colobjects), len(embedded_angs[stgidx]))

            #go throug all collision objects and import the data
            for k in range(len(colobjects)):
                coloffset = colobjects[k]
                stage_grid_size,angulation_scale,angulation_mod, stage_edge_max_x,stage_edge_max_z, intersect_ct_x,intersect_ct_z, boundary_edge_ct = embedded_angs[stgidx][k]
                stage_edge_min_x = stage_edge_max_x - stage_grid_size * (intersect_ct_x - 1)
                stage_edge_min_z = stage_edge_max_z - stage_grid_size * (intersect_ct_z - 1)

                f.seek(coloffset)
                angulation_offset, boundry_vertex_offset, boundry_edge_offset, boundry_edge_end_offset = struct.unpack('<LLLL', f.read(0x10))
                boundry_edge_count = (boundry_edge_end_offset - boundry_edge_offset) // 8
                f.seek(boundry_edge_offset + coloffset)
                edges_with_attribs = [struct.unpack('<HHHH', f.read(8)) for i in range(boundry_edge_count)]
                edges = [e[:2] for e in edges_with_attribs]
                boundry_vertex_count = (boundry_edge_offset - boundry_vertex_offset) // 12
                f.seek(boundry_vertex_offset + coloffset)
                vertices = [struct.unpack('<3f', f.read(12)) for i in range(boundry_vertex_count)]

                #terrain
                angulation_count = (boundry_vertex_offset - angulation_offset) // 2
                if angulation_count > intersect_ct_x * intersect_ct_z: angulation_count = intersect_ct_x * intersect_ct_z
                f.seek(angulation_offset + coloffset)
##                angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_x, f.read(intersect_ct_x*2)) for i in range(intersect_ct_z)]
                angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_z, f.read(intersect_ct_z*2)) for i in range(intersect_ct_x)]
                trn_vecs = []
                trn_edges = []
                for z in range(intersect_ct_z):
                    for x in range(intersect_ct_x):
                        angval = angulation_tbl1[x][z]
                        trn_vecs.append([x*stage_grid_size+stage_edge_min_x, float(angval & 0xFFF) / angulation_scale + angulation_mod + 1, z*stage_grid_size+stage_edge_min_z])
                        if x != intersect_ct_x - 1:
                            if angval & terrain_area_flags:
                                if angulation_tbl1[x+1][z] & terrain_area_flags:
                                    trn_edges.append([z*intersect_ct_x+x, z*intersect_ct_x+x+1])
                            else:
                                if z != intersect_ct_z - 1:
                                    if not angulation_tbl1[x][z+1] & terrain_area_flags:
                                        trn_edges.append([z*intersect_ct_x+x, (z+1)*intersect_ct_x+x])



                faces = []
                if imin_blender:
                    new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_wall')
                    new_mesh.from_pydata(vertices, edges, faces)
                    new_mesh.update()
                    # make object from mesh
                    new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_wall', new_mesh)
                    # make collection
                    if srtname + '_col' not in bpy.data.collections:
                        new_collection = bpy.data.collections.new(srtname + '_col')
                        bpy.context.scene.collection.children.link(new_collection)
                    # add object to scene collection
                    new_collection.objects.link(new_object)

                    new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_terrain')
                    new_mesh.from_pydata(trn_vecs, trn_edges, [])
                    new_mesh.update()
                    # make object from mesh
                    new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_terrain', new_mesh)

                    # add object to scene collection
                    new_collection.objects.link(new_object)

If you import all the stages at once, you can right click on the 'Scene Collection' in the blender's 'Outliner', and select 'View'->'Hide one level'. And then click on a 'eye' button in a collection, with the 'Ctrl' key pressed, to show only that collection. This way you can go faster through all stages, there is some impressive imagery to see(like the collisions for the GreatWall).

Some ancient footage:
doesn't work in a spoiler
 
Last edited:

Matt Ponton

Founder
Staff member
Administrator
Standard Donor
Python script to preview angulation data in blender(2.93). [version 2]:
Python:
#-------------------------------------------------------------------------------
# import collision data from doau stages(only original names) to blender (script) [ver.2]
#-------------------------------------------------------------------------------
stg_path = r"E:\--\@stages\doauJPstages\sAG1N_dat.bin"

stg_dir = r"E:\--\@stages\doauJPstages"

parse_dir = 0

imin_blender = 1

terrain_area_flags = 0b1000         #(default:0b1111) {non_shoreline?,water?,solid_ground?,playable_area?} 0b0000 0b0001 0b0100 0b0101 0b0110 0b0111 0b1000 0b1001 0b1100 0b1111  //bc fc (gl1-gl2 has no ground?) kl2 lc wl2 (gl1 has flag1)

################################################################################
##use stg_path='path/to/stage_folder/sSTAGE_dat.bin' and parse_dir=0, to parse a single stage
##use stg_dir='path/to/stages_folder/' and parse_dir=1, to parse all stages in the folder
##imin_blender = 0 for testing the script outside blender / imin_blender = 1 for importing data in blender
##terrain_area_flags - set the flags for terrain area types; all null is non playable area, others are unknown, like the snow and water have their combination of flags
##note: I used in this script only the most common for all stages, embedded collision parameters and layers of data
##limitations: only two types of terrain can be visualized at the same time, no properties of the walls are displayed; maybe convert edges to grease_pencil and use color-coding?
##???
## TF and WL3 both have collision data but it's deprecated inside the game
## MISP is processed with all miyamas but has a bin of a test stage(edges count and angulation count is different)
## GL1_1,OH_3,WL2_5,OH2_3,GL1Z_0 are empty?
## TE2 is doa3 wall data type?
################################################################################
import struct,os
terrain_area_flags = terrain_area_flags << 12

if parse_dir:stages_bin = [os.path.join(stg_dir, stg_name) for stg_name in os.listdir(stg_dir)]
else:        stages_bin = [stg_path]

if imin_blender: import bpy


testlist = []
embedded_angs = {
0x00:[(0.25,1024.0,-1.719,19.0,26.25,159,172,90),(0.25,512.0,-5.7249999,7.25,22.25,93,99,0)],
0x01:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x0F:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x03:[(0.25,1024.0,-0.102,25.25,28.5,177,200,103)],
0x07:[(0.25,1024.0,0.0,10.25,10.25,83,83,124)],
0x08:[(0.25,1024.0,-0.19400001,17.5,14.25,137,140,132),(0.25,1024.0,0.0,16.5,1.75,136,141,73),(0.25,1024.0,-0.75,16.5,1.75,136,141,69)],
0x09:[(0.25,1024.0,-0.090999998,16.25,16.25,131,131,24)],
0x0A:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0B:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0C:[(0.25,1024.0,-0.206,15.5,19.25,124,129,96)],
0x0D:[(0.25,1024.0,-0.414,25.75,40.75,207,269,141)],
0x10:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x1A:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x12:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x1B:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x3F:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x40:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x41:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x13:[(0.25,1024.0,0.015,23.5,17.5,221,192,137),(0.25,1024.0,-1.001,15.75,9.75,156,180,99)],
0x15:[(0.5,1024.0,0.0,17.5,16.5,70,67,40),(0.5,1024.0,-12.543,16.5,25.0,69,68,78),(0.375,1024.0,-69.033997,12.375,28.5,62,63,16),(0.25,256.0,-29.403999,28.5,14.75,85,51,8),(0.375,1024.0,-31.601999,47.625,38.25,145,133,145),(0.25,256.0,-29.403999,-7.75,14.75,85,51,8),(0.375,1024.0,-29.478001,-10.5,24.0,88,88,74)],
0x17:[(0.5,1024.0,0.0,50.5,50.5,203,203,16)],
0x18:[(0.25,1024.0,-0.52399999,18.5,34.5,191,205,169)],
0x19:[(0.25,1024.0,-0.52399999,33.5,46.0,280,368,0)],
0x1C:[(0.5,1024.0,-1.765,27.0,22.5,104,93,47)],
0x1E:[(0.25,1024.0,0.62400001,17.5,17.25,140,143,84)],
0x1F:[(0.5,1024.0,-1.765,27.0,22.5,104,93,50),(0.5,1024.0,-7.0430002,37.0,21.0,171,147,76),(0.5,1024.0,-7.152,38.5,49.5,167,145,68),(0.5,512.0,-12.416,3.5,36.0,112,137,135),(0.5,512.0,-5.6170001,32.5,10.0,128,92,27),(0.5,512.0,-11.283,16.0,33.5,106,96,26)],
0x21:[(0.25,1024.0,0.31999999,33.25,12.0,170,149,177),(0.25,1024.0,0.31999999,33.25,12.0,170,149,185)],
0x22:[(0.25,1024.0,-0.38299999,35.5,35.25,261,280,169)],
0x23:[(0.25,1024.0,-0.18799999,16.25,16.75,144,133,79)],
0x28:[(0.5,1024.0,0.048999999,15.5,15.5,63,63,28)],
0x29:[(0.175,1024.0,-0.001,25.375,24.15,290,254,162)],
0x2A:[(0.25,1024.0,-1.6,24.0,26.5,203,171,113)],
0x30:[(0.25,1024.0,-0.001,19.5,20.0,177,226,135)],
0x31:[(0.44999999,1024.0,-0.22,66.150002,54.0,302,212,101)],
0x32:[(0.25,1024.0,15.05,17.0,-12.75,133,151,120),(0.25,128.0,-0.491,7.75,2.5,63,130,18),(0.25,1024.0,-0.741,17.5,20.0,141,146,84)],
0x33:[(0.1,1024.0,0.0,19.4,19.4,389,389,44)],
0x34:[(0.25,819.20001,-0.15899999,25.25,24.0,209,204,67),(0.25,512.0,-3.678,25.25,4.5,103,66,66),(0.5,1024.0,-4.152,48.5,38.5,133,136,129),(0.5,1024.0,-4.1999998,18.0,38.5,148,136,124)],
0x35:[(0.25,1024.0,0.0,16.75,16.75,133,137,75),(0.25,256.0,-19.9,10.75,-5.25,78,114,114),(0.25,1024.0,-21.048,25.0,-14.5,157,149,97),(0.25,256.0,-33.764999,16.75,-28.0,122,136,112),(0.25,1024.0,-33.799999,16.5,-34.25,187,184,71),(0.25,256.0,-34.618,39.0,-25.5,121,93,114),(0.25,512.0,-37.717999,58.5,-7.0,182,300,144),(0.25,204.8,-24.667999,14.25,44.75,96,161,113),(0.25,1024.0,-24.664,37.5,78.25,208,223,119)],
0x36:[(0.25,1024.0,0.0,20.25,20.25,163,163,65)],
0x37:[(0.25,1024.0,-0.18799999,20.25,20.25,163,163,19)],
0x38:[(0.5,1024.0,-0.99900001,19.5,19.5,79,79,32)],
0x39:[(0.5,1024.0,-0.67799997,23.5,10.5,95,43,42),(0.5,512.0,-20.073,18.5,10.0,76,82,74),(0.5,512.0,-26.941999,17.5,17.0,71,55,58),(0.5,341.33334,-30.17,19.5,43.5,78,99,84),(0.75,256.0,-30.17,19.5,43.5,53,98,208)],
0x3D:[(0.25,1024.0,0.0,16.25,17.5,131,141,19),(0.25,256.0,-4.0,20.25,12.25,71,99,12),(0.25,1024.0,-4.0,44.25,37.75,179,303,8),(0.25,256.0,-8.0,12.25,-2.75,99,98,12),(0.25,1024.0,-8.0,20.25,-9.5,163,162,8),(0.25,256.0,-4.0,-3.75,12.25,53,99,0),(0.25,1024.0,-14.066,6.25,37.75,207,303,11)]}
stg_short_names = ["GL1","AG1N","DS2","GL2","X04","WA","TST","WAL","KL1","DS1","OH","OH2","SN","SN2","AM","AG1D","AG2","EL","MI","NK","MO","PO","X22","BP","BL","GL1Z","AG2N","MISP","GC","TF","PO2","PR","TR","KL2","AG1S","AG2S","AL","COL","OL","SA","KK","TE","TE2","HT","OL2","TF2","DJ","KM","NC","BC","FC","LT","SV","LC","SEA","DT","SP","SB","EFC","EF2","EAR","WL2","WL3","MISM","MIAU","MIWI","X66","X67","X68","X69","X70"]

for stg_path in stages_bin:
##    print('hey', os.path.isfile(stg_path) , '.bin' in stg_path)
    if os.path.isfile(stg_path) and '.bin' in stg_path:
        with open(stg_path,'rb') as f:
            #try to detect the offsets that contain angulation data
            head_size = struct.unpack('<L', f.read(4))[0]
            if head_size in [0x14242, 0xc0e29548, 0x4b4e4c, 0xd0027c0, 0x168]: #doa2: HT/TR are unknown data; doa3: LNK data, TST/TS2 are 4b long; doao sSV_mot.bin
                continue
            colobjects = []
            is_single_object = False
            if head_size in (0x10, 0x18):
                f.seek(head_size)
                dprobe = struct.unpack('<L', f.read(4))[0]
                if dprobe not in (0x10, 0x18, 0x6c, 0x3, 0x10310) or dprobe == 0xf000f000: #this bin is a single object
                    colobjects.append(0)
                    is_single_object = True
                    head_size = 0
            if not is_single_object:
                f.seek(0)
                for i in range(head_size//4):
                    f.seek(i*4)
                    boffset = struct.unpack('<L', f.read(4))[0]
                    f.seek(boffset)
                    dprobe = struct.unpack('<L', f.read(4))[0]
                    if dprobe in (0x10, 0x18):
                        colobjects.append(boffset)

            srtname = os.path.basename(stg_path)[1:-8]
            if srtname in stg_short_names:
                stgidx = stg_short_names.index(srtname)
            else:
                print(srtname, 'IS NOT RECOGNIZED')
                continue
            if stgidx not in embedded_angs:
                print(srtname, 'HAS NO COMPLEMENTARY ANGULATION DATA', colobjects)
                continue
            colobjects = colobjects[:len(embedded_angs[stgidx])] #removing the falsedetected collision blocks

##            print(srtname, len(colobjects), len(embedded_angs[stgidx]))

            #go throug all collision objects and import the data
            for k in range(len(colobjects)):
                coloffset = colobjects[k]
                stage_grid_size,angulation_scale,angulation_mod, stage_edge_max_x,stage_edge_max_z, intersect_ct_x,intersect_ct_z, boundary_edge_ct = embedded_angs[stgidx][k]
                stage_edge_min_x = stage_edge_max_x - stage_grid_size * (intersect_ct_x - 1)
                stage_edge_min_z = stage_edge_max_z - stage_grid_size * (intersect_ct_z - 1)

                f.seek(coloffset)
                angulation_offset, boundry_vertex_offset, boundry_edge_offset, boundry_edge_end_offset = struct.unpack('<LLLL', f.read(0x10))
                boundry_edge_count = (boundry_edge_end_offset - boundry_edge_offset) // 8
                f.seek(boundry_edge_offset + coloffset)
                edges_with_attribs = [struct.unpack('<HHHH', f.read(8)) for i in range(boundry_edge_count)]
                edges = [e[:2] for e in edges_with_attribs]
                boundry_vertex_count = (boundry_edge_offset - boundry_vertex_offset) // 12
                f.seek(boundry_vertex_offset + coloffset)
                vertices = [struct.unpack('<3f', f.read(12)) for i in range(boundry_vertex_count)]

                #terrain
                angulation_count = (boundry_vertex_offset - angulation_offset) // 2
                if angulation_count > intersect_ct_x * intersect_ct_z: angulation_count = intersect_ct_x * intersect_ct_z
                f.seek(angulation_offset + coloffset)
##                angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_x, f.read(intersect_ct_x*2)) for i in range(intersect_ct_z)]
                angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_z, f.read(intersect_ct_z*2)) for i in range(intersect_ct_x)]
                trn_vecs = []
                trn_edges = []
                for z in range(intersect_ct_z):
                    for x in range(intersect_ct_x):
                        angval = angulation_tbl1[x][z]
                        trn_vecs.append([x*stage_grid_size+stage_edge_min_x, float(angval & 0xFFF) / angulation_scale + angulation_mod + 1, z*stage_grid_size+stage_edge_min_z])
                        if x != intersect_ct_x - 1:
                            if angval & terrain_area_flags:
                                if angulation_tbl1[x+1][z] & terrain_area_flags:
                                    trn_edges.append([z*intersect_ct_x+x, z*intersect_ct_x+x+1])
                            else:
                                if z != intersect_ct_z - 1:
                                    if not angulation_tbl1[x][z+1] & terrain_area_flags:
                                        trn_edges.append([z*intersect_ct_x+x, (z+1)*intersect_ct_x+x])



                faces = []
                if imin_blender:
                    new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_wall')
                    new_mesh.from_pydata(vertices, edges, faces)
                    new_mesh.update()
                    # make object from mesh
                    new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_wall', new_mesh)
                    # make collection
                    if srtname + '_col' not in bpy.data.collections:
                        new_collection = bpy.data.collections.new(srtname + '_col')
                        bpy.context.scene.collection.children.link(new_collection)
                    # add object to scene collection
                    new_collection.objects.link(new_object)

                    new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_terrain')
                    new_mesh.from_pydata(trn_vecs, trn_edges, [])
                    new_mesh.update()
                    # make object from mesh
                    new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_terrain', new_mesh)

                    # add object to scene collection
                    new_collection.objects.link(new_object)

If you import all the stages at once, you can right click on the 'Scene Collection' in the blender's 'Outliner', and select 'View'->'Hide one level'. And then click on a 'eye' button in a collection, with the 'Ctrl' key pressed, to show only that collection. This way you can go faster through all stages, there is some impressive imagery to see(like the collisions for the GreatWall).

Some ancient footage:
doesn't work in a spoiler

Great job! So cool to look at.

FYI though, and I'm not sure which is 'correct' or not. But I've noticed that when running the script (both just the walls before, and the angulation+walls now) when I import the stage modeling to Blender, the angulation+walls is offset from the stage model by (0,2,-1). Could have my axes (X, Y, Z) mixed up there as I rotate each by 90* on the X after import, then I shift the XPR by -2 units on the Y to align its origin with global orgin, then I lower the angulation+walls by -1 on the Z to line the ground terrain with the angulation plane.

This seems to align the wall 'height' and ground plane well:
VqgFPI.jpg


I'm thinking the offset my be due to the XPR importer and not your script. seeing that the center of the WAL XPR is offset on import from global center. or it's a combination of both scripts being slightly offset? I'm sure they're just reading the data in as they can from the hex so it's no big deal.

Just something to note.
 
Last edited:
Great job! So cool to look at.
Thanks
when I import the stage modeling to Blender, the angulation+walls is offset from the stage model by (0,2,-1).
I shift the XPR by -2 units on the Y to align its origin with global orgin, then I lower the angulation+walls by -1 on the Z to line the ground terrain with the angulation plane.
The floor is created by a formula, but the geometry for the walls is loaded as it is. The game engine could have it's own perception of the things. Anyway for previz is no biggie to shift everything to look good. Well, I checked myself, the xpr importer is making an offset of 2.0. Will try to export angulation data as mesh to see if it's offset is wrong or not.
 
Last edited:
Interesting
Not really. I thought that maybe if I export the angulation from blender as xpr, and load it in the game, some alpha stages could become more playable. Well, that's not the case...
I used this script to import the angulation of AG1S as mesh to blender 2 93 1
#-------------------------------------------------------------------------------
# import collision data from doau stages(only original names) to blender (script) [ver.2]
#-------------------------------------------------------------------------------
stg_path = r"E:\--\@stages\doauJPstages\sAG1S_dat.bin"

stg_dir = r"E:\--\@stages\doauJPstages"

parse_dir = 0

imin_blender = 1

terrain_area_flags = 0b1000 #(default:0b1111) {non_shoreline?,water?,solid_ground?,playable_area?} 0b0000 0b0001 0b0100 0b0101 0b0110 0b0111 0b1000 0b1001 0b1100 0b1111 //bc fc (gl1-gl2 has no ground?) kl2 lc wl2 (gl1 has flag1)

################################################################################
##use stg_path='path/to/stage_folder/sSTAGE_dat.bin' and parse_dir=0, to parse a single stage
##use stg_dir='path/to/stages_folder/' and parse_dir=1, to parse all stages in the folder
##imin_blender = 0 for testing the script outside blender / imin_blender = 1 for importing data in blender
##terrain_area_flags - set the flags for terrain area types; all null is non playable area, others are unknown, like the snow and water have their combination of flags
##note: I used in this script only the most common for all stages, embedded collision paramameters and layers of data
##limitations: only two types of terrain can be visualized at the same time, no properties of the walls are displayed; maybe convert edges to grease_pencil and use colorcoding?
##???
## TF and WL3 both have collision data but it's deprecated inside the game
## MISP is processed with all miyamas but has a bin of a test stage(edges count and angulation count is different)
## GL1_1,OH_3,WL2_5,OH2_3,GL1Z_0 are empty?
## TE2 is doa3 wall data type?
################################################################################
import struct,os
terrain_area_flags = terrain_area_flags << 12

if parse_dir:stages_bin = [os.path.join(stg_dir, stg_name) for stg_name in os.listdir(stg_dir)]
else: stages_bin = [stg_path]

if imin_blender: import bpy


testlist = []
embedded_angs = {
0x00:[(0.25,1024.0,-1.719,19.0,26.25,159,172,90),(0.25,512.0,-5.7249999,7.25,22.25,93,99,0)],
0x01:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x0F:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x03:[(0.25,1024.0,-0.102,25.25,28.5,177,200,103)],
0x07:[(0.25,1024.0,0.0,10.25,10.25,83,83,124)],
0x08:[(0.25,1024.0,-0.19400001,17.5,14.25,137,140,132),(0.25,1024.0,0.0,16.5,1.75,136,141,73),(0.25,1024.0,-0.75,16.5,1.75,136,141,69)],
0x09:[(0.25,1024.0,-0.090999998,16.25,16.25,131,131,24)],
0x0A:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0B:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0C:[(0.25,1024.0,-0.206,15.5,19.25,124,129,96)],
0x0D:[(0.25,1024.0,-0.414,25.75,40.75,207,269,141)],
0x10:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x1A:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x12:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x1B:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x3F:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x40:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x41:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x13:[(0.25,1024.0,0.015,23.5,17.5,221,192,137),(0.25,1024.0,-1.001,15.75,9.75,156,180,99)],
0x15:[(0.5,1024.0,0.0,17.5,16.5,70,67,40),(0.5,1024.0,-12.543,16.5,25.0,69,68,78),(0.375,1024.0,-69.033997,12.375,28.5,62,63,16),(0.25,256.0,-29.403999,28.5,14.75,85,51,8),(0.375,1024.0,-31.601999,47.625,38.25,145,133,145),(0.25,256.0,-29.403999,-7.75,14.75,85,51,8),(0.375,1024.0,-29.478001,-10.5,24.0,88,88,74)],
0x17:[(0.5,1024.0,0.0,50.5,50.5,203,203,16)],
0x18:[(0.25,1024.0,-0.52399999,18.5,34.5,191,205,169)],
0x19:[(0.25,1024.0,-0.52399999,33.5,46.0,280,368,0)],
0x1C:[(0.5,1024.0,-1.765,27.0,22.5,104,93,47)],
0x1E:[(0.25,1024.0,0.62400001,17.5,17.25,140,143,84)],
0x1F:[(0.5,1024.0,-1.765,27.0,22.5,104,93,50),(0.5,1024.0,-7.0430002,37.0,21.0,171,147,76),(0.5,1024.0,-7.152,38.5,49.5,167,145,68),(0.5,512.0,-12.416,3.5,36.0,112,137,135),(0.5,512.0,-5.6170001,32.5,10.0,128,92,27),(0.5,512.0,-11.283,16.0,33.5,106,96,26)],
0x21:[(0.25,1024.0,0.31999999,33.25,12.0,170,149,177),(0.25,1024.0,0.31999999,33.25,12.0,170,149,185)],
0x22:[(0.25,1024.0,-0.38299999,35.5,35.25,261,280,169)],
0x23:[(0.25,1024.0,-0.18799999,16.25,16.75,144,133,79)],
0x28:[(0.5,1024.0,0.048999999,15.5,15.5,63,63,28)],
0x29:[(0.175,1024.0,-0.001,25.375,24.15,290,254,162)],
0x2A:[(0.25,1024.0,-1.6,24.0,26.5,203,171,113)],
0x30:[(0.25,1024.0,-0.001,19.5,20.0,177,226,135)],
0x31:[(0.44999999,1024.0,-0.22,66.150002,54.0,302,212,101)],
0x32:[(0.25,1024.0,15.05,17.0,-12.75,133,151,120),(0.25,128.0,-0.491,7.75,2.5,63,130,18),(0.25,1024.0,-0.741,17.5,20.0,141,146,84)],
0x33:[(0.1,1024.0,0.0,19.4,19.4,389,389,44)],
0x34:[(0.25,819.20001,-0.15899999,25.25,24.0,209,204,67),(0.25,512.0,-3.678,25.25,4.5,103,66,66),(0.5,1024.0,-4.152,48.5,38.5,133,136,129),(0.5,1024.0,-4.1999998,18.0,38.5,148,136,124)],
0x35:[(0.25,1024.0,0.0,16.75,16.75,133,137,75),(0.25,256.0,-19.9,10.75,-5.25,78,114,114),(0.25,1024.0,-21.048,25.0,-14.5,157,149,97),(0.25,256.0,-33.764999,16.75,-28.0,122,136,112),(0.25,1024.0,-33.799999,16.5,-34.25,187,184,71),(0.25,256.0,-34.618,39.0,-25.5,121,93,114),(0.25,512.0,-37.717999,58.5,-7.0,182,300,144),(0.25,204.8,-24.667999,14.25,44.75,96,161,113),(0.25,1024.0,-24.664,37.5,78.25,208,223,119)],
0x36:[(0.25,1024.0,0.0,20.25,20.25,163,163,65)],
0x37:[(0.25,1024.0,-0.18799999,20.25,20.25,163,163,19)],
0x38:[(0.5,1024.0,-0.99900001,19.5,19.5,79,79,32)],
0x39:[(0.5,1024.0,-0.67799997,23.5,10.5,95,43,42),(0.5,512.0,-20.073,18.5,10.0,76,82,74),(0.5,512.0,-26.941999,17.5,17.0,71,55,58),(0.5,341.33334,-30.17,19.5,43.5,78,99,84),(0.75,256.0,-30.17,19.5,43.5,53,98,208)],
0x3D:[(0.25,1024.0,0.0,16.25,17.5,131,141,19),(0.25,256.0,-4.0,20.25,12.25,71,99,12),(0.25,1024.0,-4.0,44.25,37.75,179,303,8),(0.25,256.0,-8.0,12.25,-2.75,99,98,12),(0.25,1024.0,-8.0,20.25,-9.5,163,162,8),(0.25,256.0,-4.0,-3.75,12.25,53,99,0),(0.25,1024.0,-14.066,6.25,37.75,207,303,11)]}
stg_short_names = ["GL1","AG1N","DS2","GL2","X04","WA","TST","WAL","KL1","DS1","OH","OH2","SN","SN2","AM","AG1D","AG2","EL","MI","NK","MO","PO","X22","BP","BL","GL1Z","AG2N","MISP","GC","TF","PO2","PR","TR","KL2","AG1S","AG2S","AL","COL","OL","SA","KK","TE","TE2","HT","OL2","TF2","DJ","KM","NC","BC","FC","LT","SV","LC","SEA","DT","SP","SB","EFC","EF2","EAR","WL2","WL3","MISM","MIAU","MIWI","X66","X67","X68","X69","X70"]

for stg_path in stages_bin:
## print('hey', os.path.isfile(stg_path) , '.bin' in stg_path)
if os.path.isfile(stg_path) and '.bin' in stg_path:
with open(stg_path,'rb') as f:
#try to detect the offsets that contain angulation data
head_size = struct.unpack('<L', f.read(4))[0]
if head_size in [0x14242, 0xc0e29548, 0x4b4e4c, 0xd0027c0, 0x168]: #doa2: HT/TR are unknown data; doa3: LNK data, TST/TS2 are 4b long; doao sSV_mot.bin
continue
colobjects = []
is_single_object = False
if head_size in (0x10, 0x18):
f.seek(head_size)
dprobe = struct.unpack('<L', f.read(4))[0]
if dprobe not in (0x10, 0x18, 0x6c, 0x3, 0x10310) or dprobe == 0xf000f000: #this bin is a single object
colobjects.append(0)
is_single_object = True
head_size = 0
if not is_single_object:
f.seek(0)
for i in range(head_size//4):
f.seek(i*4)
boffset = struct.unpack('<L', f.read(4))[0]
f.seek(boffset)
dprobe = struct.unpack('<L', f.read(4))[0]
if dprobe in (0x10, 0x18):
colobjects.append(boffset)

srtname = os.path.basename(stg_path)[1:-8]
if srtname in stg_short_names:
stgidx = stg_short_names.index(srtname)
else:
print(srtname, 'IS NOT RECOGNIZED')
continue
if stgidx not in embedded_angs:
print(srtname, 'HAS NO COMPLEMENTARY ANGULATION DATA', colobjects)
continue
colobjects = colobjects[:len(embedded_angs[stgidx])] #removing the falsedetected collision blocks

## print(srtname, len(colobjects), len(embedded_angs[stgidx]))

#go throug all collision objects and import the data
for k in range(len(colobjects)):
coloffset = colobjects[k]
stage_grid_size,angulation_scale,angulation_mod, stage_edge_max_x,stage_edge_max_z, intersect_ct_x,intersect_ct_z, boundary_edge_ct = embedded_angs[stgidx][k]
stage_edge_min_x = stage_edge_max_x - stage_grid_size * (intersect_ct_x - 1)
stage_edge_min_z = stage_edge_max_z - stage_grid_size * (intersect_ct_z - 1)

f.seek(coloffset)
angulation_offset, boundry_vertex_offset, boundry_edge_offset, boundry_edge_end_offset = struct.unpack('<LLLL', f.read(0x10))
boundry_edge_count = (boundry_edge_end_offset - boundry_edge_offset) // 8
f.seek(boundry_edge_offset + coloffset)
edges_with_attribs = [struct.unpack('<HHHH', f.read(8)) for i in range(boundry_edge_count)]
edges = [e[:2] for e in edges_with_attribs]
boundry_vertex_count = (boundry_edge_offset - boundry_vertex_offset) // 12
f.seek(boundry_vertex_offset + coloffset)
vertices = [struct.unpack('<3f', f.read(12)) for i in range(boundry_vertex_count)]

#terrain
angulation_count = (boundry_vertex_offset - angulation_offset) // 2
if angulation_count > intersect_ct_x * intersect_ct_z: angulation_count = intersect_ct_x * intersect_ct_z
f.seek(angulation_offset + coloffset)
## angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_x, f.read(intersect_ct_x*2)) for i in range(intersect_ct_z)]
angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_z, f.read(intersect_ct_z*2)) for i in range(intersect_ct_x)]
trn_verts = []
trn_edges = []
trn_faces = []
for z in range(intersect_ct_z):
for x in range(intersect_ct_x):
angval = angulation_tbl1[x][z]
abits = angval & 0xF000
trn_verts.append([x*stage_grid_size+stage_edge_min_x, float(angval & 0xFFF) / angulation_scale + angulation_mod + 1, z*stage_grid_size+stage_edge_min_z])
if x != intersect_ct_x - 1 and z != intersect_ct_z - 1:
trn_faces.append([z*intersect_ct_x+x, z*intersect_ct_x+x+1, (z+1)*intersect_ct_x+x+1, (z+1)*intersect_ct_x+x])


## if x != intersect_ct_x - 1:
## if angval & terrain_area_flags:
## if angulation_tbl1[x+1][z] & terrain_area_flags:
## trn_edges.append([z*intersect_ct_x+x, z*intersect_ct_x+x+1])
## else:
## if z != intersect_ct_z - 1:
## if not angulation_tbl1[x][z+1] & terrain_area_flags:
## trn_edges.append([z*intersect_ct_x+x, (z+1)*intersect_ct_x+x])
#### flags = (angval & 0xF000) >> 12
#### if not flags in testlist:
#### testlist.append(flags)
#### print(srtname, hex(stgidx), k, bin(flags), hex(flags))


faces = []
if imin_blender:
new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_wall')
new_mesh.from_pydata(vertices, edges, faces)
new_mesh.update()
# make object from mesh
new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_wall', new_mesh)
# make collection
if srtname + '_col' not in bpy.data.collections:
new_collection = bpy.data.collections.new(srtname + '_col')
bpy.context.scene.collection.children.link(new_collection)
# add object to scene collection
new_collection.objects.link(new_object)

new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_terrain')
new_mesh.from_pydata(trn_verts, trn_edges, trn_faces)
new_mesh.update()
# make object from mesh
new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_terrain', new_mesh)

# add object to scene collection
new_collection.objects.link(new_object)

##testlist.sort()
##print([bin(v) for v in testlist])
##print([hex(v) for v in testlist])
Then from blender 2 93, i exported it as obj and imported in blender 2 69(in the import options forward:-Y, up:-Z, will fix the xpr rotation)
Then I tried to export it as xpr and this hanged blender, the mesh was to big to create vertex winding for it. I added the decimate modifier with a value of 0.05. and in edit mode used extrude to make a wall out of the collision edge. And then exported it as xpr, converted to emp using a tool from wasaaaaa's pack, and loaded to doao. The result was confusing. I think this stage is an early attempt to remake the fireflies stage from doa2. When i swapped the stage with the AG1N stage, the waterfall was at least in the same place, though the AG1S is much larger.
Also a thing i found interesting, is the TE2 stage. In this list - https://www.freestepdodge.com/threads/stages-wall-collision-data-edits.8243/post-417695, it's called buraizenin, the buraizenin stage is called TE, and this one could be a second part or something. The angulation looks like some stairs, could be doa2 original stage, didn't check yet. Also in my post with the stage's names, you can see names of doa3 stages and doa2 stages that were not released with doau. It's interesting how much of these stages is left inside the game, and if it's possible to bring them back to life.
 

Attachments

  • stage_AG1S.zip
    103.8 KB · Views: 134
Yeah, strange, since the XPR looks either empty or just a shader. But angulation looks like the second tier of Burai Zenin from DOA2:HC.
The_Burai_Zenin_1.jpg
Yeah, is there a way to convert the geometry of this stage to xpr? Could happen that this is a playable stage...
Added to the script attributes to load stages that have angulation data but it's not loaded in the game. These would be:
WA AM MO AL COL OL SA OL2 DJ KM EFC EF2 WL3 MISP

Python:
#-------------------------------------------------------------------------------
# import collision data from doau stages(only original names) to blender (script) [ver.2]
#-------------------------------------------------------------------------------
stg_path = r"E:\--\@stages\doauJPstages\sAG1S_dat.bin"
stg_path = r"E:\--\@stages\1\doaOstages\sOH2_dat.bin"

stg_dir = r"E:\--\@stages\doauJPstages"

parse_dir = 0

imin_blender = 1

terrain_as_edges = 0

terrain_area_flags = 0b1000         #(default:0b1111) {non_shoreline?,water?,solid_ground?,playable_area?} 0b0000 0b0001 0b0100 0b0101 0b0110 0b0111 0b1000 0b1001 0b1100 0b1111  //bc fc (gl1-gl2 has no ground?) kl2 lc wl2 (gl1 has flag1)

## recreate the lost angulation data: 5_WA-350, E_AM-266, 14_MO-594, 24_AL-266, 25_COL-342, 26_OL-266, 27_SA-326, 2C_OL2-266, 2E_DJ-560, 2F_KM-560, 3A_EFC-532, 3B_EF2-532, 3E_WL3-606
################################################################################
##use stg_path='path/to/stage_folder/sSTAGE_dat.bin' and parse_dir=0, to parse a single stage
##use stg_dir='path/to/stages_folder/' and parse_dir=1, to parse all stages in the folder
##imin_blender = 0 for testing the script outside blender / imin_blender = 1 for importing data in blender
##terrain_area_flags - set the flags for terrain area types; all null is non playable area, others are unknown, like the snow and water have their combination of flags
##note: I used in this script only the most common for all stages, embedded collision paramameters and layers of data
##limitations: only two types of terrain can be visualized at the same time, no properties of the walls are displayed; maybe convert edges to grease_pencil and use colorcoding?
##???
## created comlementary data to load WA,AM,MO,AL,COL,OL,SA,OL2,DJ,KM,EFC,EF2,WL3
## TF and WL3 both have collision data but it's deprecated inside the game
## MISP is processed with all miyamas but has a bin of a test stage(edges count and angulation count is different)
## GL1_1,OH_3,WL2_5,OH2_3,GL1Z_0 are empty?
## TE2 is doa3 wall data type?
################################################################################
import struct,os
terrain_area_flags = terrain_area_flags << 12

if parse_dir:stages_bin = [os.path.join(stg_dir, stg_name) for stg_name in os.listdir(stg_dir)]
else:        stages_bin = [stg_path]

if imin_blender: import bpy


testlist = []
embedded_angs = {
0x00:[(0.25,1024.0,-1.719,19.0,26.25,159,172,90),(0.25,512.0,-5.7249999,7.25,22.25,93,99,0)],
0x01:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x0F:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x03:[(0.25,1024.0,-0.102,25.25,28.5,177,200,103)],
0x07:[(0.25,1024.0,0.0,10.25,10.25,83,83,124)],
0x08:[(0.25,1024.0,-0.19400001,17.5,14.25,137,140,132),(0.25,1024.0,0.0,16.5,1.75,136,141,73),(0.25,1024.0,-0.75,16.5,1.75,136,141,69)],
0x09:[(0.25,1024.0,-0.090999998,16.25,16.25,131,131,24)],
0x0A:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0B:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0C:[(0.25,1024.0,-0.206,15.5,19.25,124,129,96)],
0x0D:[(0.25,1024.0,-0.414,25.75,40.75,207,269,141)],
0x10:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x1A:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x12:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
##0x1B:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x3F:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x40:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x41:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x13:[(0.25,1024.0,0.015,23.5,17.5,221,192,137),(0.25,1024.0,-1.001,15.75,9.75,156,180,99)],
0x15:[(0.5,1024.0,0.0,17.5,16.5,70,67,40),(0.5,1024.0,-12.543,16.5,25.0,69,68,78),(0.375,1024.0,-69.033997,12.375,28.5,62,63,16),(0.25,256.0,-29.403999,28.5,14.75,85,51,8),(0.375,1024.0,-31.601999,47.625,38.25,145,133,145),(0.25,256.0,-29.403999,-7.75,14.75,85,51,8),(0.375,1024.0,-29.478001,-10.5,24.0,88,88,74)],
0x17:[(0.5,1024.0,0.0,50.5,50.5,203,203,16)],
0x18:[(0.25,1024.0,-0.52399999,18.5,34.5,191,205,169)],
0x19:[(0.25,1024.0,-0.52399999,33.5,46.0,280,368,0)],
0x1C:[(0.5,1024.0,-1.765,27.0,22.5,104,93,47)],
0x1E:[(0.25,1024.0,0.62400001,17.5,17.25,140,143,84)],
0x1F:[(0.5,1024.0,-1.765,27.0,22.5,104,93,50),(0.5,1024.0,-7.0430002,37.0,21.0,171,147,76),(0.5,1024.0,-7.152,38.5,49.5,167,145,68),(0.5,512.0,-12.416,3.5,36.0,112,137,135),(0.5,512.0,-5.6170001,32.5,10.0,128,92,27),(0.5,512.0,-11.283,16.0,33.5,106,96,26)],
0x21:[(0.25,1024.0,0.31999999,33.25,12.0,170,149,177),(0.25,1024.0,0.31999999,33.25,12.0,170,149,185)],
0x22:[(0.25,1024.0,-0.38299999,35.5,35.25,261,280,169)],
0x23:[(0.25,1024.0,-0.18799999,16.25,16.75,144,133,79)],
0x28:[(0.5,1024.0,0.048999999,15.5,15.5,63,63,28)],
0x29:[(0.175,1024.0,-0.001,25.375,24.15,290,254,162)],
0x2A:[(0.25,1024.0,-1.6,24.0,26.5,203,171,113)],
0x30:[(0.25,1024.0,-0.001,19.5,20.0,177,226,135)],
0x31:[(0.44999999,1024.0,-0.22,66.150002,54.0,302,212,101)],
0x32:[(0.25,1024.0,15.05,17.0,-12.75,133,151,120),(0.25,128.0,-0.491,7.75,2.5,63,130,18),(0.25,1024.0,-0.741,17.5,20.0,141,146,84)],
0x33:[(0.1,1024.0,0.0,19.4,19.4,389,389,44)],
0x34:[(0.25,819.20001,-0.15899999,25.25,24.0,209,204,67),(0.25,512.0,-3.678,25.25,4.5,103,66,66),(0.5,1024.0,-4.152,48.5,38.5,133,136,129),(0.5,1024.0,-4.1999998,18.0,38.5,148,136,124)],
0x35:[(0.25,1024.0,0.0,16.75,16.75,133,137,75),(0.25,256.0,-19.9,10.75,-5.25,78,114,114),(0.25,1024.0,-21.048,25.0,-14.5,157,149,97),(0.25,256.0,-33.764999,16.75,-28.0,122,136,112),(0.25,1024.0,-33.799999,16.5,-34.25,187,184,71),(0.25,256.0,-34.618,39.0,-25.5,121,93,114),(0.25,512.0,-37.717999,58.5,-7.0,182,300,144),(0.25,204.8,-24.667999,14.25,44.75,96,161,113),(0.25,1024.0,-24.664,37.5,78.25,208,223,119)],
0x36:[(0.25,1024.0,0.0,20.25,20.25,163,163,65)],
0x37:[(0.25,1024.0,-0.18799999,20.25,20.25,163,163,19)],
0x38:[(0.5,1024.0,-0.99900001,19.5,19.5,79,79,32)],
0x39:[(0.5,1024.0,-0.67799997,23.5,10.5,95,43,42),(0.5,512.0,-20.073,18.5,10.0,76,82,74),(0.5,512.0,-26.941999,17.5,17.0,71,55,58),(0.5,341.33334,-30.17,19.5,43.5,78,99,84),(0.75,256.0,-30.17,19.5,43.5,53,98,208)],
0x3D:[(0.25,1024.0,0.0,16.25,17.5,131,141,19),(0.25,256.0,-4.0,20.25,12.25,71,99,12),(0.25,1024.0,-4.0,44.25,37.75,179,303,8),(0.25,256.0,-8.0,12.25,-2.75,99,98,12),(0.25,1024.0,-8.0,20.25,-9.5,163,162,8),(0.25,256.0,-4.0,-3.75,12.25,53,99,0),(0.25,1024.0,-14.066,6.25,37.75,207,303,11)],
0x05:[[0.25, 1024, 0, 0, 0, 175, 175, 52]], #these are made up, they have angulation but the game doesn't use it and do not have complementary data for them.
0x14:[[0.25, 1024, 0, 0, 0, 142, 297, 98]],
0x25:[[0.25, 1024, 0, 0, 0, 171, 171, 128]],
0x27:[[0.25, 1024, 0, 0, 0, 163, 163, 82]],
0x26:[[0.25, 1024, 0, 0, 0, 153, 133, 54]],
0x2C:[[0.25, 1024, 0, 0, 0, 153, 133, 54]],
0x2E:[[0.25, 1024, 0, 0, 0, 261, 280, 169]],
0x2F:[[0.25, 1024, 0, 0, 0, 261, 280, 169]],
0x3E:[[0.25, 1024, 0, 0, 0, 163, 303, 11]],
0x1B:[[0.25, 1024, 0, 0, 0, 163, 303, 11]],
0x3A:[[0.25, 1024, 0, 0, 0, 144, 133, 79]],
0x3B:[[0.25, 1024, 0, 0, 0, 144, 133, 79]],
0x24:[[0.25, 1024, 0, 0, 0, 144, 133, 79]],
0x0E:[[0.25, 1024, 0, 0, 0, 144, 133, 79]]}

stg_short_names = ["GL1","AG1N","DS2","GL2","X04","WA","TST","WAL","KL1","DS1","OH","OH2","SN","SN2","AM","AG1D","AG2","EL","MI","NK","MO","PO","X22","BP","BL","GL1Z","AG2N","MISP","GC","TF","PO2","PR","TR","KL2","AG1S","AG2S","AL","COL","OL","SA","KK","TE","TE2","HT","OL2","TF2","DJ","KM","NC","BC","FC","LT","SV","LC","SEA","DT","SP","SB","EFC","EF2","EAR","WL2","WL3","MISM","MIAU","MIWI","X66","X67","X68","X69","X70"]

##add materials to the scene, for the floor will be the four bits 1111, first theree i'll make rgb and the fourth will be for darkness, and for the walls i'll need only color tones?


for stg_path in stages_bin:
##    print('hey', os.path.isfile(stg_path) , '.bin' in stg_path)
    if os.path.isfile(stg_path) and '.bin' in stg_path:
        with open(stg_path,'rb') as f:
            #try to detect the offsets that contain angulation data
            head_size = struct.unpack('<L', f.read(4))[0]
            if head_size in [0x14242, 0xc0e29548, 0x4b4e4c, 0xd0027c0, 0x168]: #doa2: HT/TR are unknown data; doa3: LNK data, TST/TS2 are 4b long; doao sSV_mot.bin
                continue
            colobjects = []
            is_single_object = False
            if head_size in (0x10, 0x18):
                f.seek(head_size)
                dprobe = struct.unpack('<L', f.read(4))[0]
                if dprobe not in (0x10, 0x18, 0x6c, 0x3, 0x10310) or dprobe == 0xf000f000: #this bin is a single object
                    colobjects.append(0)
                    is_single_object = True
                    head_size = 0
            if not is_single_object:
                f.seek(0)
                for i in range(head_size//4):
                    f.seek(i*4)
                    boffset = struct.unpack('<L', f.read(4))[0]
                    f.seek(boffset)
                    dprobe = struct.unpack('<L', f.read(4))[0]
                    if dprobe in (0x10, 0x18):
                        colobjects.append(boffset)

            srtname = os.path.basename(stg_path)[1:-8]
            print(srtname)
            if srtname == 'WA': colobjects.append(8)
##            if not srtname in ["WA","AM","MO","AL","COL","OL","SA","OL2","DJ","KM","EFC","EF2","WL3","MISP"]: # DELETE THIS NOW!!!!!!!!!!!!!!!!!!!!!!!!!!
##                continue
            if srtname in stg_short_names:
                stgidx = stg_short_names.index(srtname)
            else:
                print(srtname, 'IS NOT RECOGNIZED')
                continue
            if stgidx not in embedded_angs:
                print(srtname, 'HAS NO COMPLEMENTARY ANGULATION DATA', colobjects)
                continue
            colobjects = colobjects[:len(embedded_angs[stgidx])] #removing the falsedetected collision blocks

##            print(srtname, len(colobjects), len(embedded_angs[stgidx]))

            #go throug all collision objects and import the data
            for k in range(len(colobjects)):
                coloffset = colobjects[k]
                stage_grid_size,angulation_scale,angulation_mod, stage_edge_max_x,stage_edge_max_z, intersect_ct_x,intersect_ct_z, boundary_edge_ct = embedded_angs[stgidx][k]
                stage_edge_min_x = stage_edge_max_x - stage_grid_size * (intersect_ct_x - 1)
                stage_edge_min_z = stage_edge_max_z - stage_grid_size * (intersect_ct_z - 1)

                f.seek(coloffset)
                angulation_offset, boundry_vertex_offset, boundry_edge_offset, boundry_edge_end_offset = struct.unpack('<LLLL', f.read(0x10))
                boundry_edge_count = (boundry_edge_end_offset - boundry_edge_offset) // 8
                f.seek(boundry_edge_offset + coloffset)
                edges_with_attribs = [struct.unpack('<HHHH', f.read(8)) for i in range(boundry_edge_count)]
                edges = [e[:2] for e in edges_with_attribs]
                boundry_vertex_count = (boundry_edge_offset - boundry_vertex_offset) // 12
                f.seek(boundry_vertex_offset + coloffset)
                vertices = [struct.unpack('<3f', f.read(12)) for i in range(boundry_vertex_count)]

                #terrain
                angulation_count = (boundry_vertex_offset - angulation_offset) // 2
                if angulation_count > intersect_ct_x * intersect_ct_z: angulation_count = intersect_ct_x * intersect_ct_z
                f.seek(angulation_offset + coloffset)
##                angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_x, f.read(intersect_ct_x*2)) for i in range(intersect_ct_z)]
                angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_z, f.read(intersect_ct_z*2)) for i in range(intersect_ct_x)]
                trn_verts = []
                trn_edges = []
                trn_faces = []
                for z in range(intersect_ct_z):
                    for x in range(intersect_ct_x):
                        angval = angulation_tbl1[x][z]
##                        trn_verts.append([x*stage_grid_size+stage_edge_min_x, float(angval & 0xFFF) / angulation_scale + angulation_mod + 1, z*stage_grid_size+stage_edge_min_z]) #is +1 for intersection?
                        trn_verts.append([x*stage_grid_size+stage_edge_min_x, float(angval & 0xFFF) / angulation_scale + angulation_mod, z*stage_grid_size+stage_edge_min_z])

                        if not terrain_as_edges:
                            if x != intersect_ct_x - 1 and z != intersect_ct_z - 1:
                                trn_faces.append([z*intersect_ct_x+x, z*intersect_ct_x+x+1, (z+1)*intersect_ct_x+x+1, (z+1)*intersect_ct_x+x])
                        else:
                            if x != intersect_ct_x - 1:
                                if angval & terrain_area_flags:
                                    if angulation_tbl1[x+1][z] & terrain_area_flags:
                                        trn_edges.append([z*intersect_ct_x+x, z*intersect_ct_x+x+1])
                                else:
                                    if z != intersect_ct_z - 1:
                                        if not angulation_tbl1[x][z+1] & terrain_area_flags:
                                            trn_edges.append([z*intersect_ct_x+x, (z+1)*intersect_ct_x+x])
                        flags = (angval & 0xF000) >> 12


                faces = []
                if imin_blender:
                    new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_wall')
                    new_mesh.from_pydata(vertices, edges, faces)
                    new_mesh.update()
                    # make object from mesh
                    new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_wall', new_mesh)
                    # make collection
                    if srtname + '_col' not in bpy.data.collections:
                        new_collection = bpy.data.collections.new(srtname + '_col')
                        bpy.context.scene.collection.children.link(new_collection)
                    # add object to scene collection
                    new_collection.objects.link(new_object)

                    new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_terrain')
                    new_mesh.from_pydata(trn_verts, trn_edges, trn_faces)
                    new_mesh.update()
                    # make object from mesh
                    new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_terrain', new_mesh)

                    # add object to scene collection
                    new_collection.objects.link(new_object)

##DODO: add materials to assign colors to show the collision's type.
Just some placeholder angulation data, and the stage "14 MO - <THE SPIRAL> "
has angulation data for THE SPIRAL.

Code:
00  THE WHITE STORM (UPPER)
01  THE AERIAL GARDEN (NIGHT UPPER)
02  THE DANGER ZONE 2
03  THE WHITE STORM (LOWER)
04  -THE GREAT OPERA (BURNING UPPER)
05  -THE DEATH VALLEY (UPPER)
06  -EDITOR STAGE
07  -UNDULATION CHECK
08  THE CRIMSON (UPPER)
09  THE DANGER ZONE
0A  THE GREAT OPERA (UPPER)
0B  THE GREAT OPERA (LOWER)
0C  THE DEMON'S CHURCH (UPPER)
0D  THE DEMON'S CHURCH (LOWER)
0E  +AMOEBA
0F  THE AERIAL GARDEN (DAY UPPER)
10  THE AERIAL GARDEN (DAY LOWER)
11  +ELEVATOR
12  THE MIYAMA
13  THE KOKU AN
14  -THE SPIRAL
15  THE DRAGON HILLS (UPPER)
16  -THE MIYAMA (DUMMY)
17  +BASS POSTER
18  THE BIO LAB
19  +DESSERT OF DEATH
1A  THE AERIAL GARDEN (NIGHT LOWER)
1B  -THE MIYAMA (WINTER)
1C  +GRAND CANYON
1D  +TINA'S FASHION SHOW
1E  THE DRAGON HILLS (LOWER)
1F  THE PRAIRIE
20  +TRAILER TRUCK
21  THE CRIMSON (LOWER)
22  -THE AERIAL GARDEN (EVENING UPPER)
23  -THE AERIAL GARDEN (EVENING LOWER)
24  -THE D OCTAGON
25  -THE PANCRATIUM
26  -THE IRON HELL (EVENING)
27  -THE BLANCA
28  -THE L'S CASTLE
29  THE BURAI ZEN-IN (UPPER)
2A  -THE BURAI ZEN-IN (LOWER)
2B  +BAMBOO THICHET
2C  -THE IRON HELL (NIGHT)
+1                            {- tina show} (DJ stage is the last one for DoA2:HC)
2E-  +CHOPPING BEER BOTTLE
2F-  -WALL TEST                {+montane flower}
30-  -LORELEI                {THE YOZAKURA}
31-  -DOATEC HK (UPPER)        {THE ISLAND}
32-  -LOST WORLD (UPPER)    {THE SHRINE}
33-  -ICE CAVE                {THE RAY HOUSE}
34-  -BEACH (EVENING)        {THE SAFARI}
35-  -AZUCHI (COURTYARD)    {THE GREAT WALL}
36-  -FOREST                {THE AQUARIUM}
37-  -TAG : IRON HELL        {THE DOWNTOWN}
38-  -DOATEC HK (LOWER)        {THE CYCLOTRON}
39-  -TAG : DANGER ZONE        {THE SUSPENSION BRIDGE}
3A-  -TAG : PANCRATIUM        {Effect Check1}
3B-  -AZUCHI (UPPER)        {Effect Check2}
3C-  -LOST WORLD (LOWER)    {EARTH}
3D-  -TAG : X OCTAGON        {WALL_TEST2}
3E-  -LORELEI (INDOOR)        {WALL_TEST3}
3F-  -AZUCHI (LOWER)        {THE MIYAMA(SPRING)}
40-  -TEST2                    {THE MIYAMA(AUTUMN)}
41-  -BEACH (DAY)            {THE MIYAMA(WINTER)}
42-  -TAG : AQUA PALACE        {/}
43-  -GENRA'S RAID            {/}
I made a script for unpacking files from doa2:hc japan to see how many stages it has, it's 45 stages, in hex that would be 0x2D, both doa3 and doa2u are keeping this list intact and add their stages that do not fit in that list after 0x2D.
this script will extract the file table from inside the executable file.
Python:
#-------------------------------------------------------------------------------
# unpack dead or alive 2 hardcore - ROM_00.BIN
#-------------------------------------------------------------------------------

SLPS_250_path = r"E:\--\doa2hcJP\DOA2 - Hardcore (Japan) (En,Ja,Fr,De,Es,It)\SLPS_250.26"
ROM_00_path   = r"E:\--\doa2hcJP\DOA2 - Hardcore (Japan) (En,Ja,Fr,De,Es,It)\ROM_00.BIN"
out_path      = r"E:\--\doa2hcJP\DOA2 - Hardcore (Japan) (En,Ja,Fr,De,Es,It)\ROM_00"

import os, struct
filetable_offset = 0x0030C6D0
ram_offset = 0x0050B6D0 - filetable_offset
file_count = 1722
all_out_paths = []
def main():
    with open(SLPS_250_path, 'rb') as f:
        f.seek(filetable_offset)
        fnames_offsets = struct.unpack('<%dL'%(file_count*2), f.read(file_count*8))
        foffsets_sizes = struct.unpack('<%dL'%(file_count*2), f.read(file_count*8))
        fnames = []
        with open(ROM_00_path, 'rb') as fr:
            for i in range(file_count):
                f.seek(fnames_offsets[i*2+1] - ram_offset)
                bname = list(f.read(9))
                while bname[-1]:
                    bname.append(f.read(1)[0])
                name = bytearray(bname[6:-1]).decode('ASCII')
                save_path = os.path.join(out_path,name)
                save_dirs = os.path.dirname(save_path)
                if save_dirs not in all_out_paths:
                    all_out_paths.append(save_dirs)
                    os.makedirs(save_dirs)
                foffset = foffsets_sizes[i*2]
                fsize   = foffsets_sizes[i*2+1]
                with open(save_path, 'wb') as fs:
                    fr.seek(foffset)
                    fs.write(fr.read(fsize))
        print('finished')

if __name__ == '__main__':
    main()
 

Alpha-001

Member
Yeah, is there a way to convert the geometry of this stage to xpr? Could happen that this is a playable stage...
Added to the script attributes to load stages that have angulation data but it's not loaded in the game. These would be:
WA AM MO AL COL OL SA OL2 DJ KM EFC EF2 WL3 MISP

Python:
#-------------------------------------------------------------------------------
# import collision data from doau stages(only original names) to blender (script) [ver.2]
#-------------------------------------------------------------------------------
stg_path = r"E:\--\@stages\doauJPstages\sAG1S_dat.bin"
stg_path = r"E:\--\@stages\1\doaOstages\sOH2_dat.bin"

stg_dir = r"E:\--\@stages\doauJPstages"

parse_dir = 0

imin_blender = 1

terrain_as_edges = 0

terrain_area_flags = 0b1000         #(default:0b1111) {non_shoreline?,water?,solid_ground?,playable_area?} 0b0000 0b0001 0b0100 0b0101 0b0110 0b0111 0b1000 0b1001 0b1100 0b1111  //bc fc (gl1-gl2 has no ground?) kl2 lc wl2 (gl1 has flag1)

## recreate the lost angulation data: 5_WA-350, E_AM-266, 14_MO-594, 24_AL-266, 25_COL-342, 26_OL-266, 27_SA-326, 2C_OL2-266, 2E_DJ-560, 2F_KM-560, 3A_EFC-532, 3B_EF2-532, 3E_WL3-606
################################################################################
##use stg_path='path/to/stage_folder/sSTAGE_dat.bin' and parse_dir=0, to parse a single stage
##use stg_dir='path/to/stages_folder/' and parse_dir=1, to parse all stages in the folder
##imin_blender = 0 for testing the script outside blender / imin_blender = 1 for importing data in blender
##terrain_area_flags - set the flags for terrain area types; all null is non playable area, others are unknown, like the snow and water have their combination of flags
##note: I used in this script only the most common for all stages, embedded collision paramameters and layers of data
##limitations: only two types of terrain can be visualized at the same time, no properties of the walls are displayed; maybe convert edges to grease_pencil and use colorcoding?
##???
## created comlementary data to load WA,AM,MO,AL,COL,OL,SA,OL2,DJ,KM,EFC,EF2,WL3
## TF and WL3 both have collision data but it's deprecated inside the game
## MISP is processed with all miyamas but has a bin of a test stage(edges count and angulation count is different)
## GL1_1,OH_3,WL2_5,OH2_3,GL1Z_0 are empty?
## TE2 is doa3 wall data type?
################################################################################
import struct,os
terrain_area_flags = terrain_area_flags << 12

if parse_dir:stages_bin = [os.path.join(stg_dir, stg_name) for stg_name in os.listdir(stg_dir)]
else:        stages_bin = [stg_path]

if imin_blender: import bpy


testlist = []
embedded_angs = {
0x00:[(0.25,1024.0,-1.719,19.0,26.25,159,172,90),(0.25,512.0,-5.7249999,7.25,22.25,93,99,0)],
0x01:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x0F:[(0.25,1024.0,0.0,-0.25,24.25,187,175,122),(0.25,1024.0,-0.41100001,22.0,15.25,222,221,251)],
0x03:[(0.25,1024.0,-0.102,25.25,28.5,177,200,103)],
0x07:[(0.25,1024.0,0.0,10.25,10.25,83,83,124)],
0x08:[(0.25,1024.0,-0.19400001,17.5,14.25,137,140,132),(0.25,1024.0,0.0,16.5,1.75,136,141,73),(0.25,1024.0,-0.75,16.5,1.75,136,141,69)],
0x09:[(0.25,1024.0,-0.090999998,16.25,16.25,131,131,24)],
0x0A:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0B:[(0.30000001,1024.0,0.0,21.0,25.500002,140,149,51),(0.5,1024.0,-2.825,17.0,3.5,68,50,23),(0.5,1024.0,-3.7,25.0,7.5,101,84,25),(0.5,1024.0,-3.7,25.0,25.5,101,120,0)],
0x0C:[(0.25,1024.0,-0.206,15.5,19.25,124,129,96)],
0x0D:[(0.25,1024.0,-0.414,25.75,40.75,207,269,141)],
0x10:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x1A:[(0.1,1024.0,6.6929998,12.8,21.1,311,310,110),(0.25,1024.0,-0.338,21.25,18.75,178,156,204),(0.25,256.0,4.0900002,7.75,17.25,65,70,110)],
0x12:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
##0x1B:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x3F:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x40:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x41:[(0.25,1024.0,-2.77,20.75,21.0,173,193,158)],
0x13:[(0.25,1024.0,0.015,23.5,17.5,221,192,137),(0.25,1024.0,-1.001,15.75,9.75,156,180,99)],
0x15:[(0.5,1024.0,0.0,17.5,16.5,70,67,40),(0.5,1024.0,-12.543,16.5,25.0,69,68,78),(0.375,1024.0,-69.033997,12.375,28.5,62,63,16),(0.25,256.0,-29.403999,28.5,14.75,85,51,8),(0.375,1024.0,-31.601999,47.625,38.25,145,133,145),(0.25,256.0,-29.403999,-7.75,14.75,85,51,8),(0.375,1024.0,-29.478001,-10.5,24.0,88,88,74)],
0x17:[(0.5,1024.0,0.0,50.5,50.5,203,203,16)],
0x18:[(0.25,1024.0,-0.52399999,18.5,34.5,191,205,169)],
0x19:[(0.25,1024.0,-0.52399999,33.5,46.0,280,368,0)],
0x1C:[(0.5,1024.0,-1.765,27.0,22.5,104,93,47)],
0x1E:[(0.25,1024.0,0.62400001,17.5,17.25,140,143,84)],
0x1F:[(0.5,1024.0,-1.765,27.0,22.5,104,93,50),(0.5,1024.0,-7.0430002,37.0,21.0,171,147,76),(0.5,1024.0,-7.152,38.5,49.5,167,145,68),(0.5,512.0,-12.416,3.5,36.0,112,137,135),(0.5,512.0,-5.6170001,32.5,10.0,128,92,27),(0.5,512.0,-11.283,16.0,33.5,106,96,26)],
0x21:[(0.25,1024.0,0.31999999,33.25,12.0,170,149,177),(0.25,1024.0,0.31999999,33.25,12.0,170,149,185)],
0x22:[(0.25,1024.0,-0.38299999,35.5,35.25,261,280,169)],
0x23:[(0.25,1024.0,-0.18799999,16.25,16.75,144,133,79)],
0x28:[(0.5,1024.0,0.048999999,15.5,15.5,63,63,28)],
0x29:[(0.175,1024.0,-0.001,25.375,24.15,290,254,162)],
0x2A:[(0.25,1024.0,-1.6,24.0,26.5,203,171,113)],
0x30:[(0.25,1024.0,-0.001,19.5,20.0,177,226,135)],
0x31:[(0.44999999,1024.0,-0.22,66.150002,54.0,302,212,101)],
0x32:[(0.25,1024.0,15.05,17.0,-12.75,133,151,120),(0.25,128.0,-0.491,7.75,2.5,63,130,18),(0.25,1024.0,-0.741,17.5,20.0,141,146,84)],
0x33:[(0.1,1024.0,0.0,19.4,19.4,389,389,44)],
0x34:[(0.25,819.20001,-0.15899999,25.25,24.0,209,204,67),(0.25,512.0,-3.678,25.25,4.5,103,66,66),(0.5,1024.0,-4.152,48.5,38.5,133,136,129),(0.5,1024.0,-4.1999998,18.0,38.5,148,136,124)],
0x35:[(0.25,1024.0,0.0,16.75,16.75,133,137,75),(0.25,256.0,-19.9,10.75,-5.25,78,114,114),(0.25,1024.0,-21.048,25.0,-14.5,157,149,97),(0.25,256.0,-33.764999,16.75,-28.0,122,136,112),(0.25,1024.0,-33.799999,16.5,-34.25,187,184,71),(0.25,256.0,-34.618,39.0,-25.5,121,93,114),(0.25,512.0,-37.717999,58.5,-7.0,182,300,144),(0.25,204.8,-24.667999,14.25,44.75,96,161,113),(0.25,1024.0,-24.664,37.5,78.25,208,223,119)],
0x36:[(0.25,1024.0,0.0,20.25,20.25,163,163,65)],
0x37:[(0.25,1024.0,-0.18799999,20.25,20.25,163,163,19)],
0x38:[(0.5,1024.0,-0.99900001,19.5,19.5,79,79,32)],
0x39:[(0.5,1024.0,-0.67799997,23.5,10.5,95,43,42),(0.5,512.0,-20.073,18.5,10.0,76,82,74),(0.5,512.0,-26.941999,17.5,17.0,71,55,58),(0.5,341.33334,-30.17,19.5,43.5,78,99,84),(0.75,256.0,-30.17,19.5,43.5,53,98,208)],
0x3D:[(0.25,1024.0,0.0,16.25,17.5,131,141,19),(0.25,256.0,-4.0,20.25,12.25,71,99,12),(0.25,1024.0,-4.0,44.25,37.75,179,303,8),(0.25,256.0,-8.0,12.25,-2.75,99,98,12),(0.25,1024.0,-8.0,20.25,-9.5,163,162,8),(0.25,256.0,-4.0,-3.75,12.25,53,99,0),(0.25,1024.0,-14.066,6.25,37.75,207,303,11)],
0x05:[[0.25, 1024, 0, 0, 0, 175, 175, 52]], #these are made up, they have angulation but the game doesn't use it and do not have complementary data for them.
0x14:[[0.25, 1024, 0, 0, 0, 142, 297, 98]],
0x25:[[0.25, 1024, 0, 0, 0, 171, 171, 128]],
0x27:[[0.25, 1024, 0, 0, 0, 163, 163, 82]],
0x26:[[0.25, 1024, 0, 0, 0, 153, 133, 54]],
0x2C:[[0.25, 1024, 0, 0, 0, 153, 133, 54]],
0x2E:[[0.25, 1024, 0, 0, 0, 261, 280, 169]],
0x2F:[[0.25, 1024, 0, 0, 0, 261, 280, 169]],
0x3E:[[0.25, 1024, 0, 0, 0, 163, 303, 11]],
0x1B:[[0.25, 1024, 0, 0, 0, 163, 303, 11]],
0x3A:[[0.25, 1024, 0, 0, 0, 144, 133, 79]],
0x3B:[[0.25, 1024, 0, 0, 0, 144, 133, 79]],
0x24:[[0.25, 1024, 0, 0, 0, 144, 133, 79]],
0x0E:[[0.25, 1024, 0, 0, 0, 144, 133, 79]]}

stg_short_names = ["GL1","AG1N","DS2","GL2","X04","WA","TST","WAL","KL1","DS1","OH","OH2","SN","SN2","AM","AG1D","AG2","EL","MI","NK","MO","PO","X22","BP","BL","GL1Z","AG2N","MISP","GC","TF","PO2","PR","TR","KL2","AG1S","AG2S","AL","COL","OL","SA","KK","TE","TE2","HT","OL2","TF2","DJ","KM","NC","BC","FC","LT","SV","LC","SEA","DT","SP","SB","EFC","EF2","EAR","WL2","WL3","MISM","MIAU","MIWI","X66","X67","X68","X69","X70"]

##add materials to the scene, for the floor will be the four bits 1111, first theree i'll make rgb and the fourth will be for darkness, and for the walls i'll need only color tones?


for stg_path in stages_bin:
##    print('hey', os.path.isfile(stg_path) , '.bin' in stg_path)
    if os.path.isfile(stg_path) and '.bin' in stg_path:
        with open(stg_path,'rb') as f:
            #try to detect the offsets that contain angulation data
            head_size = struct.unpack('<L', f.read(4))[0]
            if head_size in [0x14242, 0xc0e29548, 0x4b4e4c, 0xd0027c0, 0x168]: #doa2: HT/TR are unknown data; doa3: LNK data, TST/TS2 are 4b long; doao sSV_mot.bin
                continue
            colobjects = []
            is_single_object = False
            if head_size in (0x10, 0x18):
                f.seek(head_size)
                dprobe = struct.unpack('<L', f.read(4))[0]
                if dprobe not in (0x10, 0x18, 0x6c, 0x3, 0x10310) or dprobe == 0xf000f000: #this bin is a single object
                    colobjects.append(0)
                    is_single_object = True
                    head_size = 0
            if not is_single_object:
                f.seek(0)
                for i in range(head_size//4):
                    f.seek(i*4)
                    boffset = struct.unpack('<L', f.read(4))[0]
                    f.seek(boffset)
                    dprobe = struct.unpack('<L', f.read(4))[0]
                    if dprobe in (0x10, 0x18):
                        colobjects.append(boffset)

            srtname = os.path.basename(stg_path)[1:-8]
            print(srtname)
            if srtname == 'WA': colobjects.append(8)
##            if not srtname in ["WA","AM","MO","AL","COL","OL","SA","OL2","DJ","KM","EFC","EF2","WL3","MISP"]: # DELETE THIS NOW!!!!!!!!!!!!!!!!!!!!!!!!!!
##                continue
            if srtname in stg_short_names:
                stgidx = stg_short_names.index(srtname)
            else:
                print(srtname, 'IS NOT RECOGNIZED')
                continue
            if stgidx not in embedded_angs:
                print(srtname, 'HAS NO COMPLEMENTARY ANGULATION DATA', colobjects)
                continue
            colobjects = colobjects[:len(embedded_angs[stgidx])] #removing the falsedetected collision blocks

##            print(srtname, len(colobjects), len(embedded_angs[stgidx]))

            #go throug all collision objects and import the data
            for k in range(len(colobjects)):
                coloffset = colobjects[k]
                stage_grid_size,angulation_scale,angulation_mod, stage_edge_max_x,stage_edge_max_z, intersect_ct_x,intersect_ct_z, boundary_edge_ct = embedded_angs[stgidx][k]
                stage_edge_min_x = stage_edge_max_x - stage_grid_size * (intersect_ct_x - 1)
                stage_edge_min_z = stage_edge_max_z - stage_grid_size * (intersect_ct_z - 1)

                f.seek(coloffset)
                angulation_offset, boundry_vertex_offset, boundry_edge_offset, boundry_edge_end_offset = struct.unpack('<LLLL', f.read(0x10))
                boundry_edge_count = (boundry_edge_end_offset - boundry_edge_offset) // 8
                f.seek(boundry_edge_offset + coloffset)
                edges_with_attribs = [struct.unpack('<HHHH', f.read(8)) for i in range(boundry_edge_count)]
                edges = [e[:2] for e in edges_with_attribs]
                boundry_vertex_count = (boundry_edge_offset - boundry_vertex_offset) // 12
                f.seek(boundry_vertex_offset + coloffset)
                vertices = [struct.unpack('<3f', f.read(12)) for i in range(boundry_vertex_count)]

                #terrain
                angulation_count = (boundry_vertex_offset - angulation_offset) // 2
                if angulation_count > intersect_ct_x * intersect_ct_z: angulation_count = intersect_ct_x * intersect_ct_z
                f.seek(angulation_offset + coloffset)
##                angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_x, f.read(intersect_ct_x*2)) for i in range(intersect_ct_z)]
                angulation_tbl1 = [struct.unpack('<%dH'%intersect_ct_z, f.read(intersect_ct_z*2)) for i in range(intersect_ct_x)]
                trn_verts = []
                trn_edges = []
                trn_faces = []
                for z in range(intersect_ct_z):
                    for x in range(intersect_ct_x):
                        angval = angulation_tbl1[x][z]
##                        trn_verts.append([x*stage_grid_size+stage_edge_min_x, float(angval & 0xFFF) / angulation_scale + angulation_mod + 1, z*stage_grid_size+stage_edge_min_z]) #is +1 for intersection?
                        trn_verts.append([x*stage_grid_size+stage_edge_min_x, float(angval & 0xFFF) / angulation_scale + angulation_mod, z*stage_grid_size+stage_edge_min_z])

                        if not terrain_as_edges:
                            if x != intersect_ct_x - 1 and z != intersect_ct_z - 1:
                                trn_faces.append([z*intersect_ct_x+x, z*intersect_ct_x+x+1, (z+1)*intersect_ct_x+x+1, (z+1)*intersect_ct_x+x])
                        else:
                            if x != intersect_ct_x - 1:
                                if angval & terrain_area_flags:
                                    if angulation_tbl1[x+1][z] & terrain_area_flags:
                                        trn_edges.append([z*intersect_ct_x+x, z*intersect_ct_x+x+1])
                                else:
                                    if z != intersect_ct_z - 1:
                                        if not angulation_tbl1[x][z+1] & terrain_area_flags:
                                            trn_edges.append([z*intersect_ct_x+x, (z+1)*intersect_ct_x+x])
                        flags = (angval & 0xF000) >> 12


                faces = []
                if imin_blender:
                    new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_wall')
                    new_mesh.from_pydata(vertices, edges, faces)
                    new_mesh.update()
                    # make object from mesh
                    new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_wall', new_mesh)
                    # make collection
                    if srtname + '_col' not in bpy.data.collections:
                        new_collection = bpy.data.collections.new(srtname + '_col')
                        bpy.context.scene.collection.children.link(new_collection)
                    # add object to scene collection
                    new_collection.objects.link(new_object)

                    new_mesh = bpy.data.meshes.new(srtname + "_%X_"%stgidx + str(k) +'_terrain')
                    new_mesh.from_pydata(trn_verts, trn_edges, trn_faces)
                    new_mesh.update()
                    # make object from mesh
                    new_object = bpy.data.objects.new(srtname + "_%X_"%stgidx + str(k) + '_terrain', new_mesh)

                    # add object to scene collection
                    new_collection.objects.link(new_object)

##DODO: add materials to assign colors to show the collision's type.
Just some placeholder angulation data, and the stage "14 MO - <THE SPIRAL> "
has angulation data for THE SPIRAL.

Code:
00  THE WHITE STORM (UPPER)
01  THE AERIAL GARDEN (NIGHT UPPER)
02  THE DANGER ZONE 2
03  THE WHITE STORM (LOWER)
04  -THE GREAT OPERA (BURNING UPPER)
05  -THE DEATH VALLEY (UPPER)
06  -EDITOR STAGE
07  -UNDULATION CHECK
08  THE CRIMSON (UPPER)
09  THE DANGER ZONE
0A  THE GREAT OPERA (UPPER)
0B  THE GREAT OPERA (LOWER)
0C  THE DEMON'S CHURCH (UPPER)
0D  THE DEMON'S CHURCH (LOWER)
0E  +AMOEBA
0F  THE AERIAL GARDEN (DAY UPPER)
10  THE AERIAL GARDEN (DAY LOWER)
11  +ELEVATOR
12  THE MIYAMA
13  THE KOKU AN
14  -THE SPIRAL
15  THE DRAGON HILLS (UPPER)
16  -THE MIYAMA (DUMMY)
17  +BASS POSTER
18  THE BIO LAB
19  +DESSERT OF DEATH
1A  THE AERIAL GARDEN (NIGHT LOWER)
1B  -THE MIYAMA (WINTER)
1C  +GRAND CANYON
1D  +TINA'S FASHION SHOW
1E  THE DRAGON HILLS (LOWER)
1F  THE PRAIRIE
20  +TRAILER TRUCK
21  THE CRIMSON (LOWER)
22  -THE AERIAL GARDEN (EVENING UPPER)
23  -THE AERIAL GARDEN (EVENING LOWER)
24  -THE D OCTAGON
25  -THE PANCRATIUM
26  -THE IRON HELL (EVENING)
27  -THE BLANCA
28  -THE L'S CASTLE
29  THE BURAI ZEN-IN (UPPER)
2A  -THE BURAI ZEN-IN (LOWER)
2B  +BAMBOO THICHET
2C  -THE IRON HELL (NIGHT)
+1                            {- tina show} (DJ stage is the last one for DoA2:HC)
2E-  +CHOPPING BEER BOTTLE
2F-  -WALL TEST                {+montane flower}
30-  -LORELEI                {THE YOZAKURA}
31-  -DOATEC HK (UPPER)        {THE ISLAND}
32-  -LOST WORLD (UPPER)    {THE SHRINE}
33-  -ICE CAVE                {THE RAY HOUSE}
34-  -BEACH (EVENING)        {THE SAFARI}
35-  -AZUCHI (COURTYARD)    {THE GREAT WALL}
36-  -FOREST                {THE AQUARIUM}
37-  -TAG : IRON HELL        {THE DOWNTOWN}
38-  -DOATEC HK (LOWER)        {THE CYCLOTRON}
39-  -TAG : DANGER ZONE        {THE SUSPENSION BRIDGE}
3A-  -TAG : PANCRATIUM        {Effect Check1}
3B-  -AZUCHI (UPPER)        {Effect Check2}
3C-  -LOST WORLD (LOWER)    {EARTH}
3D-  -TAG : X OCTAGON        {WALL_TEST2}
3E-  -LORELEI (INDOOR)        {WALL_TEST3}
3F-  -AZUCHI (LOWER)        {THE MIYAMA(SPRING)}
40-  -TEST2                    {THE MIYAMA(AUTUMN)}
41-  -BEACH (DAY)            {THE MIYAMA(WINTER)}
42-  -TAG : AQUA PALACE        {/}
43-  -GENRA'S RAID            {/}
I made a script for unpacking files from doa2:hc japan to see how many stages it has, it's 45 stages, in hex that would be 0x2D, both doa3 and doa2u are keeping this list intact and add their stages that do not fit in that list after 0x2D.
this script will extract the file table from inside the executable file.
Python:
#-------------------------------------------------------------------------------
# unpack dead or alive 2 hardcore - ROM_00.BIN
#-------------------------------------------------------------------------------

SLPS_250_path = r"E:\--\doa2hcJP\DOA2 - Hardcore (Japan) (En,Ja,Fr,De,Es,It)\SLPS_250.26"
ROM_00_path   = r"E:\--\doa2hcJP\DOA2 - Hardcore (Japan) (En,Ja,Fr,De,Es,It)\ROM_00.BIN"
out_path      = r"E:\--\doa2hcJP\DOA2 - Hardcore (Japan) (En,Ja,Fr,De,Es,It)\ROM_00"

import os, struct
filetable_offset = 0x0030C6D0
ram_offset = 0x0050B6D0 - filetable_offset
file_count = 1722
all_out_paths = []
def main():
    with open(SLPS_250_path, 'rb') as f:
        f.seek(filetable_offset)
        fnames_offsets = struct.unpack('<%dL'%(file_count*2), f.read(file_count*8))
        foffsets_sizes = struct.unpack('<%dL'%(file_count*2), f.read(file_count*8))
        fnames = []
        with open(ROM_00_path, 'rb') as fr:
            for i in range(file_count):
                f.seek(fnames_offsets[i*2+1] - ram_offset)
                bname = list(f.read(9))
                while bname[-1]:
                    bname.append(f.read(1)[0])
                name = bytearray(bname[6:-1]).decode('ASCII')
                save_path = os.path.join(out_path,name)
                save_dirs = os.path.dirname(save_path)
                if save_dirs not in all_out_paths:
                    all_out_paths.append(save_dirs)
                    os.makedirs(save_dirs)
                foffset = foffsets_sizes[i*2]
                fsize   = foffsets_sizes[i*2+1]
                with open(save_path, 'wb') as fs:
                    fr.seek(foffset)
                    fs.write(fr.read(fsize))
        print('finished')

if __name__ == '__main__':
    main()
What is a '+AMOEBA'?
 

dee4doa

Member
Wait so does that mean that this patch comes from a SEGA NAOMI DOA2 SDK build?

with this patch, can you load single stages in tag/sparring mode or load certain costumes/stages?

when editing the stage lighting or adding more lights, are they saved to the game or memory card? I really hope with this debug tool we can easily create new stages and our own dat collision stage data.
 
ALL DOA6 DOA5 DOA4 DOA3 DOA2U DOAD
Top