SceneObj

From Mount&Blade Modding Wiki
Jump to: navigation, search

Mount&Blade Scenes are composed of two independent structures. First, every scene entry has a terrain key, provided by the built-in visual terrain generator.

Later on, through the in-game Scene Editor, which is enabled with the Edit Mode option in the launcher, the scene can be altered, adding flora, painting and modifying terrain's appearance or even adding scene props.

Contents

[edit] Terrain Keys

The structure of the Terrain Key, also known as Terrain Hash or Terrain Codes, is composed by five serialized chunks of 32 bits, encoded in hexadecimal notation where its properties are stored using bitwise operations.

As shown in the table, the first parts are for the noise generator's algorithm, water presence, and lastly size and terrain palette of the environment.

Decoded by courtesy of cmpxchg8b

value key no. shift num bits
seed_1 0 0 31
seed_2 1 0 31
deep_water 1 31 1
seed_3 2 0 31
size_x 3 0 10
size_y 3 10 10
shade_occluded 3 30 1
place_river 3 31 1
valley 4 0 7
hill_height 4 7 7
ruggedness 4 14 7
vegetation 4 21 7
region_type 4 28 4
region_detail 5 0 2
disable_grass 5 2 2

[edit] Terrain Code Generator | Client-side Browser Application

Exists an online clone of the in-game terrain generation window, made by Swyter.
As we're talking about a self-contained, downloadable, standard HTML web page, it can also be used offline without losing functionality.

It provides some advantages respect to the original implementation, like more options and customization ranges.
Avoids the tedious process of launching the game for simple tweaks. And works quite faster.

You can find it here: http://mbcommands.ollclan.eu/terrain/

[edit] SceneObj

The *.sco files, located in the /SceneObj folder of every module, are automatically generated by the in-game scene editor.
Those are the files that alters the default Terrain key orography, provides entry points (spawn) positions and repainted terrain or scene props and extra manually supplied plants (known internally as objects).

[edit] scoUtils

After some tweaks and additions [Disgrntld] released a tidied up version of the original parser shown below that he called scoUtils, accompanied by the screenshot of a example map and a procedural object (a roman coliseum) made with code.

First release: [Mirrored here]
Second release (includes headers): [Mirrored here] [Dropbox]

For more information take a look to the original thread:

http://forums.taleworlds.com/index.php/topic,221975.0.html


[edit] Heightmap Toolset

Later on a working, open source tested implementation of SCO to PNG heightmap import and export was released in binary form by Swyter, these two tiny tools can be found at Scene Heightmap Imp/export Tools. They include scopng, a drag-and-drop SceneObj to PNG/TGA/BMP conversion, and pngsco, its complementary friend that reimports back any modifications made to the images, supports PNG/PSD/TGA/BMP/JPG.


[edit] .SCO File Parser

Here's the original Standard C code for interacting with this file format, yet again courtesy of cmpxchg8b

#include <stdio.h>
#include <stdlib.h>
 
#define SCO_MAGIC -717
#define GROUND_PAINT_MAGIC -11873882
#define GROUND_PAINT_ELEVATION_MAGIC -7793
#define GROUND_PAINT_COLOR_MAGIC -12565
 
typedef struct vector
{
	float x;
	float y;
	float z;
} vector_t;
 
typedef struct matrix
{
	vector_t v0;
	vector_t v1;
	vector_t v2;
	vector_t o;
} matrix_t;
 
typedef struct ground_paint_layer
{
	char *ground_spec_id;
	int ground_spec_no;
	float *cells;
} ground_paint_layer_t;
 
typedef struct ground_paint
{
	int size_x;
	int size_y;
	int num_layers;
	ground_paint_layer_t *layers;
} ground_paint_t;
 
typedef struct ai_mesh_vertex
{
	vector_t position;
} ai_mesh_vertex_t;
 
typedef struct ai_mesh_edge
{
	int start_vertex;
	int end_vertex;
} ai_mesh_edge_t;
 
typedef struct ai_mesh_face
{
	int num_vertices;
	int vertices[4];
	int edges[4];
	int unknown;
} ai_mesh_face_t;
 
typedef struct ai_mesh
{
	int num_vertices;
	int num_edges;
	int num_faces;
	ai_mesh_vertex_t *vertices;
	ai_mesh_edge_t *edges;
	ai_mesh_face_t *faces;
} ai_mesh_t;
 
typedef struct mission_object
{
	char *id;
	int meta_type; // 0 = scene prop, 1 = entry point, 2 = scene item, 4 = flora, 5 = passage
	int sub_kind_no;
	int variation_id;
	int variation_id_2;
	matrix_t position;
	vector_t scale;
} mission_object_t;
 
typedef struct sco_file
{
	int version;
	int num_mission_objects;
	mission_object_t *mission_objects;
	ai_mesh_t *ai_mesh;
	ground_paint_t *ground_paint;
} sco_file_t;
 
void read(FILE *file, size_t size, void *dest)
{
	if (dest)
		fread(dest, size, 1, file);
}
 
void read_int(FILE *file, int *dest)
{
	read(file, 4, dest);
}
 
void read_char(FILE *file, char *dest)
{
	read(file, 1, dest);
}
 
void read_float(FILE *file, float *dest)
{
	read(file, 4, dest);
}
 
void read_string(FILE *file, char **dest)
{
	int length;
 
	read_int(file, &length);
	*dest = malloc(length + 1);
	read(file, length, *dest);
	(*dest)[length] = '\0';
}
 
void read_vector(FILE *file, vector_t *dest)
{
	read_float(file, &dest->x);
	read_float(file, &dest->y);
	read_float(file, &dest->z);
}
 
void read_matrix(FILE *file, matrix_t *dest)
{
	read_vector(file, &dest->v0);
	read_vector(file, &dest->v1);
	read_vector(file, &dest->v2);
	read_vector(file, &dest->o);
}
 
int main(int argc, char **argv)
{
	if (argc < 2)
	{
		printf("Usage: %s filename\n", argv[0]);
		return EXIT_FAILURE;
	}
 
	FILE *file = fopen(argv[1], "rb");
 
	if (!file)
	{
		printf("ERROR: file %s not found\n", argv[1]);
		return EXIT_FAILURE;
	}
 
	fseek(file, 0, SEEK_END);
 
	long file_size = ftell(file);
 
	fseek(file, 0, SEEK_SET);
 
	printf("Reading %s\n", argv[1]);
 
	sco_file_t sco_file;
	int magic;
 
	read_int(file, &magic);
 
	if (magic == SCO_MAGIC)
	{
		read_int(file, &sco_file.version);
		read_int(file, &sco_file.num_mission_objects);
	}
	else
	{
		sco_file.version = 0;
		sco_file.num_mission_objects = magic;
	}
 
	printf("SCO file version: %d\n", sco_file.version);
	printf("Mission object count: %d\n", sco_file.num_mission_objects);
 
	sco_file.mission_objects = malloc(sco_file.num_mission_objects * sizeof(mission_object_t));
 
	for (int i = 0; i < sco_file.num_mission_objects; ++i)
	{
		read_int(file, &sco_file.mission_objects[i].meta_type);
		read_int(file, &sco_file.mission_objects[i].sub_kind_no);
		fseek(file, 4, SEEK_CUR); // unused
		read_matrix(file, &sco_file.mission_objects[i].position);
		read_string(file, &sco_file.mission_objects[i].id);
		read_int(file, &sco_file.mission_objects[i].variation_id);
 
		if (sco_file.version >= 2)
			read_int(file, &sco_file.mission_objects[i].variation_id_2);
		else
			sco_file.mission_objects[i].variation_id_2 = 0;
 
		if (sco_file.version >= 3)
			read_vector(file, &sco_file.mission_objects[i].scale);
		else
		{
			sco_file.mission_objects[i].scale.x = 1.0f;
			sco_file.mission_objects[i].scale.y = 1.0f;
			sco_file.mission_objects[i].scale.z = 1.0f;
		}
	}
 
	if (sco_file.version >= 4)
	{
		int ai_mesh_size;
 
		read_int(file, &ai_mesh_size);
 
		long start_pos = ftell(file);
 
		sco_file.ai_mesh = malloc(sizeof(ai_mesh_t));
 
		read_int(file, &sco_file.ai_mesh->num_vertices);
		sco_file.ai_mesh->vertices = malloc(sco_file.ai_mesh->num_vertices * sizeof(ai_mesh_vertex_t));
 
		printf("AI mesh vertex count: %d\n", sco_file.ai_mesh->num_vertices);
 
		for (int i = 0; i < sco_file.ai_mesh->num_vertices; ++i)
		{
			read_vector(file, &sco_file.ai_mesh->vertices[i].position);
		}
 
		read_int(file, &sco_file.ai_mesh->num_edges);
		sco_file.ai_mesh->edges = malloc(sco_file.ai_mesh->num_edges * sizeof(ai_mesh_edge_t));
 
		printf("AI mesh edge count: %d\n", sco_file.ai_mesh->num_edges);
 
		for (int i = 0; i < sco_file.ai_mesh->num_edges; ++i)
		{
			fseek(file, 4, SEEK_CUR); // unused
			read_int(file, &sco_file.ai_mesh->edges[i].start_vertex);
			read_int(file, &sco_file.ai_mesh->edges[i].end_vertex);
			fseek(file, 8, SEEK_CUR); // unused
		}
 
		read_int(file, &sco_file.ai_mesh->num_faces);
		sco_file.ai_mesh->faces = malloc(sco_file.ai_mesh->num_faces * sizeof(ai_mesh_face_t));
 
		printf("AI mesh face count: %d\n", sco_file.ai_mesh->num_faces);
 
		for (int i = 0; i < sco_file.ai_mesh->num_faces; ++i)
		{
			read_int(file, &sco_file.ai_mesh->faces[i].num_vertices);
 
			for (int j = 0; j < sco_file.ai_mesh->faces[i].num_vertices; ++j)
			{
				read_int(file, &sco_file.ai_mesh->faces[i].vertices[j]);
			}
 
			for (int j = 0; j < sco_file.ai_mesh->faces[i].num_vertices; ++j)
			{
				read_int(file, &sco_file.ai_mesh->faces[i].edges[j]);
			}
 
			read_int(file, &sco_file.ai_mesh->faces[i].unknown);
 
			if (sco_file.ai_mesh->faces[i].unknown > 0)
				read_int(file, &sco_file.ai_mesh->faces[i].unknown);
			else
				sco_file.ai_mesh->faces[i].unknown = 0;
		}
 
		long end_pos = ftell(file);
 
		if (end_pos - start_pos != ai_mesh_size)
		{
			printf("ERROR: failed to read AI mesh\n");
			fclose(file);
			return EXIT_FAILURE;
		}
	}
	else
		sco_file.ai_mesh = NULL;
 
	if (ftell(file) != file_size)
	{
		read_int(file, &magic);
 
		if (magic != GROUND_PAINT_MAGIC)
		{
			printf("ERROR: wrong ground paint magic\n");
			fclose(file);
			return EXIT_FAILURE;
		}
 
		int ground_paint_size;
 
		read_int(file, &ground_paint_size);
 
		long start_pos = ftell(file);
 
		sco_file.ground_paint = malloc(sizeof(ground_paint_t));
 
		read_int(file, &sco_file.ground_paint->num_layers);
		sco_file.ground_paint->layers = malloc(sco_file.ground_paint->num_layers * sizeof(ground_paint_layer_t));
 
		printf("Ground paint layer count: %d\n", sco_file.ground_paint->num_layers);
 
		read_int(file, &sco_file.ground_paint->size_x);
		read_int(file, &sco_file.ground_paint->size_y);
 
		for (int i = 0; i < sco_file.ground_paint->num_layers; ++i)
		{
			read_int(file, &sco_file.ground_paint->layers[i].ground_spec_no);
			read_string(file, &sco_file.ground_paint->layers[i].ground_spec_id);
 
			int has_cells;
 
			read_int(file, &has_cells);
 
			if (has_cells)
			{
				sco_file.ground_paint->layers[i].cells = malloc(sco_file.ground_paint->size_x * sco_file.ground_paint->size_y * sizeof(float));
 
				int empty = 1;
				int count;
 
				read_int(file, &count);
 
				for (int y = 0; y < sco_file.ground_paint->size_y; ++y)
				{
					for (int x = 0; x < sco_file.ground_paint->size_x; ++x)
					{
						if (!count)
						{
							empty = !empty;
							read_int(file, &count);
						}
 
						count--;
 
						float value;
 
						if (empty)
						{
							if (sco_file.ground_paint->layers[i].ground_spec_no == GROUND_PAINT_COLOR_MAGIC)
								value = -1.0f;
							else
								value = 0.0f;
						}
						else
						{
							if (sco_file.ground_paint->layers[i].ground_spec_no < 0)
								read_float(file, &value);
							else
							{
								char cvalue;
 
								read_char(file, &cvalue);
								value = cvalue / 255.0f;
							}
						}
 
						sco_file.ground_paint->layers[i].cells[x * sco_file.ground_paint->size_y + y] = value;
					}
				}
			}
			else
				sco_file.ground_paint->layers[i].cells = NULL;
		}
 
		long end_pos = ftell(file);
 
		if (end_pos - start_pos != ground_paint_size)
		{
			printf("ERROR: failed to read ground paint\n");
			fclose(file);
			return EXIT_FAILURE;
		}
	}
	else
		sco_file.ground_paint = NULL;
 
	fclose(file);
	return EXIT_SUCCESS;
}

[edit] Notes

Code and analysis of the disassembled structures posted on the forums within the following thread: http://forums.taleworlds.com/index.php/topic,184470.html

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox