Author Topic: [Xenogears] Level viewer in Python  (Read 26459 times)

Micky

  • *
  • Posts: 300
    • View Profile
[Xenogears] Level viewer in Python
« on: 2009-04-05 06:34:35 »
I dug out my old Xenogears field viewer again:

As you can see, some background parts and the walkmesh are rotated relative to each other. Has anyone seen a transform set, animations or a skeleton in the files? Or is that set from the script?

Cyberman

  • *
  • Posts: 1572
    • View Profile
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #1 on: 2009-04-05 16:35:51 »
Xenogears brings back memories. It's too bad Namco messed up Xenosaga 2 so badly that only one game release was left (3). 
12 year old games :D  (xenogears)

Interesting view, it looks familiar.

Cyb

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #2 on: 2009-04-05 19:16:08 »
Xenogears brings back memories. It's too bad Namco messed up Xenosaga 2 so badly that only one game release was left (3). 
12 year old games :D  (xenogears)
Who knows? Didn't many people resign during or after Xenosaga 2? That normally points to internal trouble.
The only thing I know is that Xenosaga 3 was a lot better, especially the battle systems. And it has a lot of Xenogears cameos and references. I was still hoping that they load the Zohar onto the Eldridge at the end, but it was just another cliffhanger. Sigh.
Quote
Interesting view, it looks familiar.
That's inside Aveh castle. It's an extreme case where one half of the meshes match the walk mesh, and the other half is rotated relative to the walkmesh. In most other levels all meshes have the same rotation relative to the walkmesh, and there are only a few where it matches.


Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #3 on: 2009-07-25 22:20:50 »
Sorry for the thread necromancy, but if anybody cares:

There is a displaylist between the model/data/text/script offset data and the beginning of the model data:
0x0184: u16: unknown
0x0186: u16: unknown
0x0188: u32: unknown
0x018c: u32: number of instances
0x0190: number * 16 byte instance data
instance:
u16 flags : mostly unknown, though items with value 480 may not be visible
u16 rot_x : 1024 units = 90 degree
u16 rot_y
u16 rot_z
s16 trans_x
s16 trans_y
s16 trans_z
u16 mesh_index : which mesh to draw from the model data
 
« Last Edit: 2009-07-25 22:24:27 by Micky »

halkun

  • Global moderator
  • *
  • Posts: 2097
  • NicoNico :)
    • View Profile
    • Q-Gears Homepage
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #4 on: 2009-07-26 00:10:37 »
You fixed it?

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #5 on: 2009-07-26 06:23:06 »
You fixed it?
That is what I was trying to say. I can display all the field maps with correct placement of individual meshes, though I'll have to work out the flags some time. So if anyone has a chance to update the Wiki (uploading Akari's X-Gears first)...

Akari

  • *
  • Posts: 766
    • View Profile
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #6 on: 2009-07-26 16:21:24 »
Hmmm, interesting. Is animations for field meshes also stores there? Or just idle?

halkun

  • Global moderator
  • *
  • Posts: 2097
  • NicoNico :)
    • View Profile
    • Q-Gears Homepage
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #7 on: 2009-07-26 19:36:15 »
What do the colored control lines mean green = walkmesh, yellow = camera?

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #8 on: 2009-07-26 20:56:29 »
Hmmm, interesting. Is animations for field meshes also stores there? Or just idle?
I haven't found that, yet. Maybe they just place a field model into the scene and animate that if they want an animation. Or the animation is controlled by the script, like doors. The table only contains the base transform for each instance.
What do the colored control lines mean green = walkmesh, yellow = camera?
The data is a list of meshes with connectivity, and I draw each one with a new colour. I assume the decision if they're a camera constraint or a walkmesh is done based on flags or from the script, but I haven't found that, yet. For areas where the character can walk on several levels there are actually several overlapping walkmeshes. It's possible they set the current walkmesh based on trigger zones.

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #9 on: 2009-08-16 10:40:52 »
Code: [Select]
// http://forums.qhimm.com/index.php?topic=5146.0
// http://forums.qhimm.com/index.php?topic=5423.msg70264#msg70264
// http://forums.qhimm.com/index.php?topic=5923.msg76045#msg76045

// OpenGL and GLUT
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <OpenGL/glext.h>

// c runtime
#include <cmath>
#include <wchar.h>
#include <locale.h>

// c++ stl
#include <map>

// utility
#include "file.h"
#include "vector.h"
#include "endian.h"

#include "lzsload.h"

#include "textMap.h"

using namespace std;

// ---------------------------------------------------------------------------

class XenoModel {
private:
struct TexInfo {
u16 status;
u16 clut;
u8 alpha;
bool abe;
bool operator<(const TexInfo & rhs) const {
if (status != rhs.status) {
return status < rhs.status;
}
if (clut != rhs.clut) {
return clut < rhs.clut;
}
if (alpha != rhs.alpha) {
return alpha < rhs.alpha;
}
return abe < rhs.abe;
}
};
map<TexInfo, int> texInfoMap;
u8 * m_model;
u8 * m_walkmesh;
u8 * m_vram;
u8 * m_archive;
public:
XenoModel(void * archive, void * model, void * walkmesh);
~XenoModel();
void draw();
void drawModel();
void drawWalkmesh();
void uploadTextures(void *data, int size);
void dumpVRAM();
int getTexture(u16 status, u16 clut, bool adb, int alpha);
};

XenoModel::XenoModel(void * archive, void * model, void * walkmesh): m_archive((u8*)archive), m_model((u8*)model) , m_walkmesh((u8*)walkmesh)
{
// generate virtual vram to make texture page calculations easier
m_vram = new u8[2048 * 512];
memset(m_vram, 0xAA, 2048 * 512);
}

XenoModel::~XenoModel()
{
delete m_vram;
}

int
XenoModel::getTexture(u16 status, u16 clut, bool abe, int alpha)
{
alpha = 128;
TexInfo texInfo;
texInfo.status = status;
texInfo.clut = clut;
texInfo.abe = abe;
texInfo.alpha = alpha;
if (texInfoMap.find(texInfo) != texInfoMap.end()) {
return texInfoMap[texInfo];
}

u8 *texture = new u8[256*256*4];

int tx = (status & 0xF) * 64 * 2;
int ty = ((status >> 4) & 1) * 256;
int tp = (status >> 7) & 3;

int px = clut & 63;
int py = clut >> 6;

switch (tp) {
case 0: {
for (int y=0; y<256; y++) {
for (int x=0; x<256; x++) {
u8 val = m_vram[(y + ty) * 2048 + (x/2) + tx];
int idx = (x & 1) ? val >> 4 : val & 0xF;
u16 col = get_u16le(m_vram + (py) * 2048 + idx * 2 + px * 2);
bool stp = (col & 0x8000) != 0;

int r = (((col     ) & 31) * 255 + 15) / 31;
int g = (((col >>  5) & 31) * 255 + 15) / 31;
int b = (((col >> 10) & 31) * 255 + 15) / 31;
int a = /*(idx == 0) || */(col == 0) ? 0 :  (abe && stp && ((col & 0x7FFF) != 0)) ? alpha : 255;

int addr = (y*256+x) * 4;
texture[addr+0] = r;
texture[addr+1] = g;
texture[addr+2] = b;
texture[addr+3] = a;
}
}
break;
}
case 1: {
for (int y=0; y<256; y++) {
for (int x=0; x<256; x++) {
int idx = m_vram[(y + ty) * 2048 + x + tx];
u16 col = get_u16le(m_vram + (py) * 2048 + idx * 2 + px * 2);
bool stp = (col & 0x8000) != 0;

int r = (((col     ) & 31) * 255 + 15) / 31;
int g = (((col >>  5) & 31) * 255 + 15) / 31;
int b = (((col >> 10) & 31) * 255 + 15) / 31;
int a = /* (idx == 0)  ||*/ (col == 0) ? 0 : (abe && stp && ((col & 0x7FFF) != 0)) ? alpha : 255;

int addr = (y*256+x) * 4;
texture[addr+0] = r;
texture[addr+1] = g;
texture[addr+2] = b;
texture[addr+3] = a;
}
}
break;
}
case 2: {
for (int y=0; y<256; y++) {
for (int x=0; x<256; x++) {
u16 col = get_u16le(m_vram + (y + ty) * 2048 + x * 2 + tx);
bool stp = (col & 0x8000) != 0;

int r = (((col     ) & 31) * 255 + 15) / 31;
int g = (((col >>  5) & 31) * 255 + 15) / 31;
int b = (((col >> 10) & 31) * 255 + 15) / 31;
int a = (col == 0) ? 0 : (abe && stp && ((col & 0x7FFF) != 0)) ? alpha : 255;

int addr = (y*256+x) * 4;
texture[addr+0] = r;
texture[addr+1] = g;
texture[addr+2] = b;
texture[addr+3] = a;
}
}
break;
}
}

GLuint textureId;
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
#if 0
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
#else
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#endif
//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, texture);
gluBuild2DMipmaps(GL_TEXTURE_2D, 4, 256, 256, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, texture);
delete texture;

texInfoMap[texInfo] = textureId;

return textureId;
}

void XenoModel::uploadTextures(void *data, int size)
{
// expand TIM into "vram"
int offset = 0;
while (offset < size) {
u8 * texture = ((u8*)data) + offset;
u32 type = get_u16le(texture + 0x00);
u16 pos_x = get_u16le(texture + 0x04);
u16 pos_y = get_u16le(texture + 0x06);
u16 move_x = get_u16le(texture + 0x08);
u16 move_y = get_u16le(texture + 0x0a);
u16 width = get_u16le(texture + 0x0c);
u16 height = get_u16le(texture + 0x0e);
u16 chunks = get_u16le(texture + 0x12);
if (width > 2048 || height > 512 || width == 0 || height == 0) {
return;
}
int blockSize = 0x1C + chunks * 2;
offset += (blockSize + 2047) & ~2047;
for (int i=0; i<chunks; i++) {
height = get_u16le(texture + 0x1C + i * 2);
for (int j=0; j < height; j++) {
memcpy(m_vram + (pos_y + move_y + j) * 2048 + (pos_x + move_x) * 2, ((u8*)data) + offset + j * width * 2, width * 2);
}
pos_y += height;
blockSize = width * height * 2;
offset += (blockSize + 2047) & ~2047;
}
}
}

void XenoModel::dumpVRAM()
{
save("vram.bin", m_vram, 2048 * 512);
}

void XenoModel::draw()
{
drawModel();
drawWalkmesh();
}

#if 0
bool first = true;
#endif

void XenoModel::drawWalkmesh()
{
glDisable(GL_CULL_FACE);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

glMatrixMode(GL_MODELVIEW);
// glLoadIdentity();

//glDisable(GL_DEPTH_TEST);
u32 block_count = get_u32le(m_walkmesh + 0);
//printf("drawWalkmesh, block_count = %i\n", block_count);
for (int i = 0; i < block_count; ++i) {
u32 block_size   = get_u32le(m_walkmesh + 0x04 + i * 0x04);
u32 block_start  = get_u32le(m_walkmesh + 0x18 + i * 0x08);
u32 block_vertex = get_u32le(m_walkmesh + 0x1c + i * 0x08);

// Walkmesh
glBegin(GL_TRIANGLES);
glColor3f((i + 1) & 1 ? 1 : 0, (i + 1) & 2 ? 1 : 0, (i + 1) & 4 ? 1 : 0);
for (int j = 0; j < block_size; j += 0x0E) {
for (int k=0; k<3; k++) {
u16 offset = block_vertex + get_u16le(m_walkmesh + block_start + j + k * 2) * 0x08;
int x =  (s16)get_u16le(m_walkmesh + 0x00 + offset);
int y =  (s16)get_u16le(m_walkmesh + 0x02 + offset);
int z =  (s16)get_u16le(m_walkmesh + 0x04 + offset);
glVertex3i(x, y, z);
#if 0
if (first) {
printf("%i: %x\n", j, get_u16le(m_walkmesh + 0x06 + offset));
}
#endif
}
}
glEnd();

// Connectivity
glBegin(GL_LINES);
glColor3f((i + 1) & 1 ? 0 : 1, (i + 1) & 2 ? 0 : 1, (i + 1) & 4 ? 0 : 1);
for (int j = 0; j < (block_size / 0x0E); j ++) {
#if 0
if (first) {
printf("%i: %x\n", j, get_u16le(m_walkmesh + block_start + 0x0C));
}
#endif
int sx = 0;
int sy = 0;
int sz = 0;
for (int k=0; k<3; k++) {
u16 offset = block_vertex + get_u16le(m_walkmesh + block_start + j * 0x0E + k * 2) * 0x08;
sx +=  (s16)get_u16le(m_walkmesh + 0x00 + offset);
sy +=  (s16)get_u16le(m_walkmesh + 0x02 + offset);
sz +=  (s16)get_u16le(m_walkmesh + 0x04 + offset);
}
sx /= 3;
sy /= 3;
sz /= 3;
for (int l=0; l<3; l++) {
int to = get_u16le(m_walkmesh + block_start + j * 0x0E + 0x06 + l * 2);
if (to != 0xFFFF) {
int tx = 0;
int ty = 0;
int tz = 0;
for (int k=0; k<3; k++) {
u16 offset = block_vertex + get_u16le(m_walkmesh + block_start + to * 0x0E + k * 2) * 0x08;
tx +=  (s16)get_u16le(m_walkmesh + 0x00 + offset);
ty +=  (s16)get_u16le(m_walkmesh + 0x02 + offset);
tz +=  (s16)get_u16le(m_walkmesh + 0x04 + offset);
}
tx /= 3;
ty /= 3;
tz /= 3;
glVertex3i(sx, sy, sz);
glVertex3i(tx, ty, tz);
}
}
}
glEnd();
}
//glEnable(GL_DEPTH_TEST);
//exit(0);
#if 0
first = false;
#endif
}

void XenoModel::drawModel()
{
glDisable(GL_CULL_FACE);
glPolygonMode(GL_FRONT, GL_FILL);
glPolygonMode(GL_BACK, GL_LINE);

#if 0
printf("Unknown:\n");
u16 unknown1 = get_u16le(((u8*)archiveData) + 0x0184);
u16 unknown2 = get_u16le(((u8*)archiveData) + 0x0186);
u32 unknown3 = get_u32le(((u8*)archiveData) + 0x0188);
u32 count = get_u32le(((u8*)archiveData) + 0x018C);
printf("??:%i ??:%i ??:%i count:%i\n", unknown1, unknown2, unknown3, count);
for (u32 i=0; i<count; i++) {
    void * base = ((u8*)archiveData) + 0x0190 + i * 16;
    for(u32 j=0; j<8; j++) {
int val = (s16)get_u16le(((u8*)base) + j * 2);
printf("%6i ", val);
    }
    printf("\n");
}
#endif
u32 item_count = get_u32le(((u8*)m_archive) + 0x018C);
for(u32 i=0; i<item_count; i++) {
void * item_base = ((u8*)m_archive) + 0x0190 + i * 16;

u32 model_count = get_u32le(m_model);
//printf("drawModel, model_count = %i\n", model_count);
//for (int m=0; m<model_count; m++) {
int item_flags = get_u16le(((u8*)item_base) + 0 * 2);
int item_rotx = get_u16le(((u8*)item_base) + 1 * 2);
int item_roty = get_u16le(((u8*)item_base) + 2 * 2);
int item_rotz = get_u16le(((u8*)item_base) + 3 * 2);
int item_posx = (s16)get_u16le(((u8*)item_base) + 4 * 2);
int item_posy = (s16)get_u16le(((u8*)item_base) + 5 * 2);
int item_posz = (s16)get_u16le(((u8*)item_base) + 6 * 2);

glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslatef(item_posx, item_posy, item_posz);
glRotatef(item_rotx * 90.0 / 1024.0, 1, 0, 0);
glRotatef(item_roty * 90.0 / 1024.0, 0, 1, 0);
glRotatef(item_rotz * 90.0 / 1024.0, 0, 0, 1);

if(item_flags != 480) {
int m = get_u16le(((u8*)item_base) + 7 * 2);
u32 offset_model = get_u32le(m_model + 4 + m * 4);
u8 * model = m_model + offset_model;
u32 number_blocks = get_u32le(model);
//printf("drawModel, number_blocks = %i\n", number_blocks);
for (int i=0; i<number_blocks; i++) {
u8 * block = model + 16 + i * 0x38;
u16 number_vertices = get_u16le(block + 2);
u16 number_mesh = get_u16le(block + 4);
u16 number_mesh_block = get_u16le(block + 6);
u32 offset_vertices = get_u32le(block + 8);
u32 offset_normals = get_u32le(block + 12);
u32 offset_mesh_blocks = get_u32le(block + 16);
u32 offset_displaylist = get_u32le(block + 20);
#if 0
if (first) {
printf("%i/%i\n", m, i);
for (int i=0; i<16; i++) {
s16 val = get_u16le(block + 24 + i*2);
printf("%5i ", val);
}
printf("\n");
}
#endif
u8 * vertices = model + offset_vertices;
u8 * mesh_blocks = model + offset_mesh_blocks;
u8 * normals = model + offset_normals;
u8 * displaylist = model + offset_displaylist;

int poly_available = 0;

glColor3f(1,1,1);

bool quad_block = false;
u16 status = 0;
u16 clut = 0;
while (poly_available > 0 || number_mesh_block > 0) {
// init the mesh block
if (poly_available == 0) {
quad_block = (mesh_blocks[0] & 16) != 0;
poly_available = get_u16le(mesh_blocks + 2);
mesh_blocks+=4;
number_mesh_block--;
}

// decode command
u32 cmd = get_u32le(displaylist);

bool hp = ((cmd >> 24) & 16) != 0; // geraud shading
bool quad = ((cmd >> 24) & 8) != 0; // quad or tri
bool tme = ((cmd >> 24) & 4) != 0; // texture mapping
bool abe = ((cmd >> 24) & 2) != 0; // semi transparency
bool fullbright = ((cmd >> 24) & 1) != 0; // bypass lighting

int srcAlpha = 255;
int dstAlpha = 255;

if (abe) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
switch ((status >> 3) & 7) {
case 0:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
break;
case 1:
glBlendFunc(GL_ONE, GL_ONE);
glBlendEquation(GL_FUNC_ADD);
break;
case 2:
glBlendFunc(GL_ONE, GL_ONE);
glBlendEquation(GL_FUNC_SUBTRACT);
break;
case 3:
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glBlendEquation(GL_FUNC_ADD);
break;
}
} else {
glDisable(GL_BLEND);
}

// printf("%8.8x\n", cmd);
displaylist += 4;
switch ((cmd >> 24) & ~(16|2|1)) { // (careful not to mask 0xc4 or 0xc8!)
case 0xC4: // texture page
status = cmd & 0xFFFF;
switch ((status >> 3) & 7) {
case 0:
srcAlpha = dstAlpha = 128;
break;
case 1:
srcAlpha = dstAlpha = 255;
break;
case 2:
srcAlpha = dstAlpha = 255;
break;
case 3:
srcAlpha = 64;
dstAlpha = 255;
break;
}
break;
case 0xC8: // clut
clut = cmd & 0xFFFF;
break;
case 0x24: { // triangle with texture
Vector::CVector2f uv[3];
uv[0][0] = displaylist[0];
uv[0][1] = displaylist[1];
uv[1][0] = displaylist[2];
uv[1][1] = displaylist[3];
uv[2][0] = cmd & 255;
uv[2][1] = (cmd >> 8) & 255;
displaylist += 4;

glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, getTexture(status, clut, abe, srcAlpha));
glBegin(GL_TRIANGLES);
for (int j=0; j<3; j++) {
int vtx = get_u16le(mesh_blocks + j * 2);
int x = (s16)get_u16le(vertices + vtx * 8 + 0);
int y = (s16)get_u16le(vertices + vtx * 8 + 2);
int z = (s16)get_u16le(vertices + vtx * 8 + 4);
if (hp) {
float nx = (s16)get_u16le(normals + vtx * 8 + 0) / 4096.0f;
float ny = (s16)get_u16le(normals + vtx * 8 + 2) / 4096.0f;
float nz = (s16)get_u16le(normals + vtx * 8 + 4) / 4096.0f;
glNormal3f(nx, ny, nz);
} else {
// no normal
glNormal3f(0.0f, 0.0f, 0.0f);
}
glTexCoord2i(uv[j][0],uv[j][1]);
glVertex3i(x, y, z);
}
glEnd();
glDisable(GL_TEXTURE_2D);

mesh_blocks += 8;
poly_available--;
break;
}
case 0x2c: { // quad with texture
Vector::CVector2f uv[4];
uv[0][0] = displaylist[0];
uv[0][1] = displaylist[1];
uv[1][0] = displaylist[2];
uv[1][1] = displaylist[3];
uv[2][0] = displaylist[4];
uv[2][1] = displaylist[5];
uv[3][0] = displaylist[6];
uv[3][1] = displaylist[7];
displaylist += 8;

glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, getTexture(status, clut, abe, srcAlpha));
glBegin(GL_QUADS);
const int quad_idx[] = { 0, 1, 3, 2 };
for (int j=0; j<4; j++) {
int vtx = get_u16le(mesh_blocks + quad_idx[j] * 2);
int x = (s16)get_u16le(vertices + vtx * 8 + 0);
int y = (s16)get_u16le(vertices + vtx * 8 + 2);
int z = (s16)get_u16le(vertices + vtx * 8 + 4);
glTexCoord2i(uv[quad_idx[j]][0],uv[quad_idx[j]][1]);
glVertex3i(x, y, z);
}
glEnd();
glDisable(GL_TEXTURE_2D);

mesh_blocks += 8;
poly_available--;
break;
}
case 0x20: { // monochrome triangle
glBegin(GL_TRIANGLES);
glColor4ub((cmd >> 16) & 255, (cmd >> 8) & 255, (cmd) & 255, srcAlpha);
for (int j=0; j<3; j++) {
int vtx = get_u16le(mesh_blocks + j * 2);
int x = (s16)get_u16le(vertices + vtx * 8 + 0);
int y = (s16)get_u16le(vertices + vtx * 8 + 2);
int z = (s16)get_u16le(vertices + vtx * 8 + 4);
glVertex3i(x, y, z);
}
glEnd();

mesh_blocks += 8;
poly_available--;
break;
}
case 0x28: { // monochrome quad
glBegin(GL_QUADS);
glColor4ub((cmd >> 16) & 255, (cmd >> 8) & 255, (cmd) & 255,  srcAlpha);
const int quad_idx[] = { 0, 1, 3, 2 };
for (int j=0; j<4; j++) {
int vtx = get_u16le(mesh_blocks + quad_idx[j] * 2);
int x = (s16)get_u16le(vertices + vtx * 8 + 0);
int y = (s16)get_u16le(vertices + vtx * 8 + 2);
int z = (s16)get_u16le(vertices + vtx * 8 + 4);
glVertex3i(x, y, z);
}
glEnd();

mesh_blocks += 8;
poly_available--;
break;
}
default:
printf("unknown cmd: %8.8x\n", cmd);
break;
}
number_mesh--;
}
}
glPopMatrix();
//}
}
}
// first = false;
}

// ---------------------------------------------------------------------------

XenoModel * xenoModel;
int rot_x, rot_y;
Vector::CVector3f position;

void ReshapeWindowFunc(int width, int height)
{
const double kFOVy = 0.57735f;
const double kZNear = 0.1f;
const double kZFar = 1000.0f;

glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLdouble aspect = ((GLfloat)width / (GLfloat)height) * (kZNear * kFOVy);
glFrustum(-aspect, aspect, -(kZNear * kFOVy), (kZNear * kFOVy), kZNear, kZFar);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void MainRenderLoop(void)
{
// render
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 1.0f/255.0f);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
        glDisable(GL_LIGHTING);
//glDisable(GL_BLEND);
//glEnable(GL_TEXTURE_2D);
//glEnable(GL_TEXTURE_RECTANGLE_EXT);

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef((float)rot_x * 360.0f / 256.0f, 1.0f, 0.0f, 0.0f);
glRotatef((float)rot_y * 360.0f / 256.0f, 0.0f, 1.0f, 0.0f);
glTranslatef(position[0], position[1], position[2]);
glScalef(1.0f/256.0f, -1.0f/256.0f, 1.0f/256.0f);

glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glScalef(1.0f/256.0f, 1.0f/256.0f, 0);
glTranslatef(0.5f, 0.5f, 0);

xenoModel->draw();

glFlush();
}

void SpecialHandler(int key, int x, int y)
{
switch (key) {
case GLUT_KEY_UP:
rot_x -= 4;
if (rot_x < -64) {
rot_x = -64;
}
break;
case GLUT_KEY_DOWN:
rot_x += 4;
if (rot_x > 64) {
rot_x = 64;
}
break;
case GLUT_KEY_LEFT:
rot_y -= 5;
break;
case GLUT_KEY_RIGHT:
rot_y += 5;
break;
}
glutPostRedisplay();
}

void KeyboardHandler(unsigned char key, int x, int y)
{
switch (key) {
case 'w': {
float sx = sin((float)rot_x * M_PI * 2.0f / 256.0f);
float cx = -cos((float)rot_x * M_PI * 2.0f / 256.0f);
float sy = sin((float)rot_y * M_PI * 2.0f / 256.0f);
float cy = -cos((float)rot_y * M_PI * 2.0f / 256.0f);
position[0] += sy * cx;
position[1] += sx;
position[2] += cy * cx;
break;
}
case 'x': {
float sx = sin((float)rot_x * M_PI * 2.0f / 256.0f);
float cx = -cos((float)rot_x * M_PI * 2.0f / 256.0f);
float sy = sin((float)rot_y * M_PI * 2.0f / 256.0f);
float cy = -cos((float)rot_y * M_PI * 2.0f / 256.0f);
position[0] -= sy * cx;
position[1] -= sx;
position[2] -= cy * cx;
break;
}
case 27:
exit(0);
break;
}
}

u32 getRawData(void * archiveData, int archiveSize, void ** data, int index)
{
u32 offset = get_u32le(((u8*)archiveData) + 0x0130 + index * 4);
u32 compLen = (index < 8 ? get_u32le(((u8*)archiveData) + 0x0134 + index * 4) : archiveSize) - offset;
*data = ((u8*)archiveData) + offset;
return compLen;
}

u32 getData(void * archiveData, int archiveSize, void ** data, int index)
{
u32 offset = get_u32le(((u8*)archiveData) + 0x0130 + index * 4);
u32 compLen = (index < 8 ? get_u32le(((u8*)archiveData) + 0x0134 + index * 4) : archiveSize) - offset;

u32 uncompLen = get_u32le(((u8*)archiveData) + 0x010c + index * 4);

u32 uncompLenHeader = get_u32le(((u8*)archiveData) + offset);
// printf("h:%4.4x b:%4.4x\n", uncompLen, uncompLenHeader);

*data = malloc(uncompLenHeader);
*data = decompress_lzs(((u8*)archiveData) + offset + 4, compLen, *data, uncompLenHeader);

return uncompLen;
}

void dumpText(void * textData, unsigned int textSize)
{
u32 textCount = get_u32le(((u8*)textData)) + 1;
for (int i=0; i<textCount; i++) {
u32 textOffset = get_u16le(((u8*)textData) + i * 2 + 4);
if (textOffset > 0) {
if (textOffset > textSize) {
printf("--- overflow ---\n");
return;
}
u8 textPosX = ((u8*)textData)[4 + textCount * 2 + i * 2];
u8 textPosY = ((u8*)textData)[4 + textCount * 2 + i * 2 + 1];
printf("%4.4x %i/%i\n", textOffset, textPosX, textPosY);
u8 * text = ((u8*)textData) + textOffset;
while (*text != 0) {
switch (*text) {
case 0x01: printf("\n"); break;
case 0x02: printf("\n[new dialog]\n"); break;
case 0x03: printf("[input]"); break;
case 0x0F: {
text++;
u8 opcode = *(text++);
u8 value = *text;
switch (opcode) {
case 0x00: printf("[delay %i]", value); break;
case 0x01: printf("[accelerator %i]", value); break;
case 0x02: printf("[delayed close %i]", value); break;
case 0x04: printf("[item %i]", value); break;
case 0x05: {
switch (value) {
case 7:
printf("[Chu Chu]");
break;
default:
printf("[char name %i]", value);
break;
}
break;
}
default: printf("[unknown %2.2x %i]", opcode, value); break;
}
break;
}
default:
if (textMap[*text] != 0) {
wprintf(L"%lc", textMap[*text]);
} else {
printf("[%2.2x]", *text);
}
break;
}
text++;
}
printf("\n----\n");
}
}
}

struct OpCode {
    u8 code;
    const char * name;
    const char * args;
};

const OpCode opCodeList[] = {
    { 0x00, "Return", "" },
    { 0x01, "Jump", "w" },
    { 0x02, "ConditionalJumpTo", "bbwbw" },
    { 0x0b, "SpriteSet", "bb" },
    { 0x19, "SpriteSetPosition", "wwb" },
    { 0x20, "SpriteSetSolid", "bb" },
    { 0x26, "Wait", "bb" },
    { 0x2C, "SpritePlayAnimation", "b" },
    { 0x36, "VariableSetTrue", "bb" },
    { 0x37, "VariableSetFalse", "bb" },
    { 0x4A, "SpriteGoToPosition", "wwb" },
    { 0x69, "SpriteSetDirection", "bb" },
    { 0x6b, "SpriteRotateClockwise", "bb" },
    { 0x6C, "SpriteRotateAntiClockwise", "bb" },
    { 0x6F, "SpriteRotateToEntity", "b" },
    { 0x98, "MapLoad", "wbb" },
    { 0xA8, "VariableRandom", "bbbb" },
    { 0xB3, "FadeOut", "bb" },
    { 0xB4, "FadeIn", "bb" },
    { 0xB5, "CameraSetDirection", "bb" },
    { 0xC7, "CameraRotate", "bb" },
    { 0xC8, "CameraRotate2", "bb" },
    { 0xD2, "DialogShow", "bw" },
    { 0x1e, "Unknown", "b"  },
    { 0x28, "Unknown", "b"  },
    { 0xC4, "Unknown", "b"  },
    { 0xC5, "Unknown", "b"  },
    { 0x3c, "Unknown", "b"  },
    { 0x5d, "Unknown", "bb"  },
    { 0xf4, "Unknown", "bb"  },
    { 0xfe, "Unknown", "bbb"  },
    { 0xA4, "Unknown", "bbbb"  },
    { 0x35, "Unknown", "w"  }
};

const unsigned opCodeCount = sizeof(opCodeList) / sizeof(opCodeList[0]);

void dumpScript(void * scriptData, unsigned int scriptSize)
{
    u16 entityCount = get_u32le((u8*)scriptData + 0x80);
    u8 * code = (u8*)scriptData + entityCount * 0x40 + 0x84;
    int codeLen = scriptSize - 0x84 - entityCount * 0x40;
    int codePtr = 0;
    while (codePtr < codeLen) {
for (int i=0; i<entityCount; i++) {
    u8 * entityStart = (u8*)scriptData + i * 0x40 + 0x84;
    for (int j=0; j<32; j++) {
u16 addr = get_u16le(entityStart + j * 2);
if (addr != 0 && addr == codePtr) {
    printf("e%is%i:\n", i, j);
}
    }
}
printf("[%4.4x] ", codePtr);
u8 cmd = code[codePtr++];
printf("%2.2x", cmd);
int opIndex = -1;
for(unsigned int i = 0; i < opCodeCount && opIndex < 0; i++) {
    if( opCodeList[i].code == cmd ) {
opIndex = i;
    }
}
if(opIndex < 0) {
    printf("                              Unknown\n");
} else {
    int cmdSize = 0;
    const char * arg = opCodeList[opIndex].args;
    while(*arg) {
switch(*arg) {
    case 'b': cmdSize += 1; break;
    case 'w': cmdSize += 2; break;
}
arg++;
    }
    for (int i = 0; i<cmdSize; i++) {
printf(" %2.2x", code[codePtr+i]);
    }
    for (int i = cmdSize; i<10; i++) {
printf("   ");
    }
    printf("%s(", opCodeList[opIndex].name);
    int argPos = 0;
    arg = opCodeList[opIndex].args;
    while(*arg) {
if(argPos > 0) {
    printf(", ");
}
switch(*arg) {
    case 'b':
printf("0x%2.2x", code[codePtr+argPos]);
argPos += 1;
break;
    case 'w':
printf("0x%4.4x", get_u16le(code + codePtr + argPos));
argPos += 2;
break;
}
arg++;
    }
    printf(")\n");
    codePtr += cmdSize;
}
    }
}

const char * section[] =
{
"unknown",
"walkmesh",
"models",
"unknown",
"unknown",
"scripts",
"unknown",
"text",
"unknown"
};

void dumpDisplaylist(void * archiveData)
{
    u16 unknown1 = get_u16le(((u8*)archiveData) + 0x0184);
    u16 unknown2 = get_u16le(((u8*)archiveData) + 0x0186);
    u32 unknown3 = get_u32le(((u8*)archiveData) + 0x0188);
    u32 count = get_u32le(((u8*)archiveData) + 0x018C);
    printf("??:%i ??:%i ??:%i count:%i\n", unknown1, unknown2, unknown3, count);
    for (u32 i=0; i<count; i++) {
void * base = ((u8*)archiveData) + 0x0190 + i * 16;
for(u32 j=0; j<8; j++) {
    int val = (s16)get_u16le(((u8*)base) + j * 2);
    printf("%6i ", val);
}
printf("\n");
    }
}

int main(int argc, char ** argv)
{
if (argc != 2) {
printf("view_level <index>\n");
} else {
#if 1
// init opengl and glut
glutInit(&argc, (char **) argv);
glutInitWindowSize(640, 480);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);
glutCreateWindow(argv[0]);
glutDisplayFunc(MainRenderLoop);
glutIdleFunc(MainRenderLoop);
glutReshapeFunc(ReshapeWindowFunc);
glutKeyboardFunc(KeyboardHandler);
glutSpecialFunc(SpecialHandler);
#endif
setlocale(LC_CTYPE, "en_US");
#if 0
for (int i=0; i<255; i++) {
wprintf(L"%lc", textCypher[i]);
if ((i & 15) == 15) { printf("\n"); }
}
#endif
// access files by index
//for (int i=0; i<730; i++) {
// printf("Room:%i\n", i);
int index = atoi(argv[1]);
int disk = 1;

char path[256];

// 730 rooms
// load data
void * archiveData = NULL;
sprintf(path, "disk%i/dir%i/file%i.bin", disk, 11, index * 2);
int archiveSize = load(path, &archiveData);

if (strncmp((char*)archiveData, "It's", 4) == 0) {
printf("%s\n", (char*)archiveData);
return 0;
}

void * textureData = NULL;
sprintf(path, "disk%i/dir%i/file%i.bin", disk, 11, index * 2 + 1);
int textureSize = load(path, &textureData);

// dumpDisplaylist(archiveData);

void * walkmeshData = NULL;
int walkmeshSize = getData(archiveData, archiveSize, &walkmeshData, 1);

void * modelData = NULL;
int modelSize = getData(archiveData, archiveSize, &modelData, 2);

void * scriptData = NULL;
int scriptSize = getData(archiveData, archiveSize, &scriptData, 5);

void * textData = NULL;
int textSize = getData(archiveData, archiveSize, &textData, 7);

#if 0
for (int i=0; i<9; i++) {
void * sectionData = NULL;
int sectionSize = getData(archiveData, archiveSize, &sectionData, i);
char name [256];
sprintf(name, "file%is%i-%s.bin", index, i, section[i]);
save(name, sectionData, sectionSize);
}
#endif
//dumpText(textData, textSize);

//dumpScript(scriptData, scriptSize);

// init camera
position[0] = 0;
position[1] = -1;
position[2] = 0;

rot_y = rot_x = 0;
#if 1
// setup viewing code
xenoModel = new XenoModel(archiveData, modelData, walkmeshData);

xenoModel->uploadTextures(textureData, textureSize);
//xenoModel->dumpVRAM();

// main (this will never return)
glutMainLoop();
#endif
//}
}
return 0;
}
I was asked for the code, so here it is... very hacky...Mostly based on Akari's information.

Cyberman

  • *
  • Posts: 1572
    • View Profile
Re: [Xenogears] Level transforms/animations/skeleton?
« Reply #10 on: 2009-08-16 22:44:09 »
It's always appreciated when people share Micky LOL

I haven't really done anything with FF7/8/9 in 2 years scarey huh?
However I may have some time to tinker.
I have WAY too many games... SIGH. :D

Cyb

Micky

  • *
  • Posts: 300
    • View Profile
[Xenogears] Level viewer in Python
« Reply #11 on: 2010-05-28 23:00:43 »
To avoid thread necromancy:
Code: (Python) [Select]
import sys, struct, array, math, os

from OpenGL.GL import *
from OpenGL.GLU import *

import pygame
from pygame.locals import *

def decompressLzs(data, size):
    """Decompress Xenogears-style compressed data. The data must exclude the initial size word"""
    ibuf = array.array("B", data)
    obuf = array.array("B")
   
    iofs = 0
    cmd = 0
    bit = 0
    while iofs < len(ibuf) and len(obuf) < size:
if bit == 0:
    cmd = ibuf[iofs]
    bit = 8
    iofs += 1
if cmd & 1:
    a = ibuf[iofs]
    iofs += 1
    b = ibuf[iofs]
    iofs += 1
   
    o = a | ((b & 0x0F) << 8)
    l = ((b & 0xF0) >> 4) + 3

    rofs = len(obuf) - o
    for j in xrange(l):
if rofs < 0:
    obuf.append(0)
else:
    obuf.append(obuf[rofs])
rofs += 1
else:
    obuf.append(ibuf[iofs])
    iofs += 1

cmd >>= 1
bit -= 1

    return obuf.tostring()

def loadLzs(name):
    """Read a compressed file from disc"""
    f = open(name, "rb")
    buf = f.read()
    f.close()   
    (size,) = struct.unpack_from("<I", buf, 0)
    return decompressLzs(buf[4:], size)

def getData(archiveData, index):
    """Get the compressed data block out of the field map archive"""
    (offset,) = struct.unpack_from("<I", archiveData, 0x0130 + index * 4)
    dataEnd = len(archiveData)
    if index < 8:
(dataEnd,) = struct.unpack_from("<I", archiveData, 0x0134 + index * 4)

    (size,) = struct.unpack_from("<I", archiveData, 0x010c + index * 4)

    dataStart = offset + 4
    return decompressLzs(archiveData[dataStart:dataEnd], size)

class ShaderBuilder:
    def __init__(self):
self.shaders = {}

    def getShader(self, data):
try:
    return self.shaders[data]
except KeyError:
    i = len(self.shaders)
    self.shaders[data] = i
    return i
   
    def getList(self):
items = [(val, key) for (key, val) in self.shaders.items()]
items.sort()
return [x for _,x in items]

abe_alpha = [128, 0, 0, 64]

def loadModel(data):
    shaderBuilder = ShaderBuilder()

    object = {}
    object["parts"] = []

    (partCount,) = struct.unpack_from("<I", data, 0)
    for partIndex in xrange(partCount):
(partOffset,) = struct.unpack_from("<I", data, 4 + partIndex * 4)

part = {}
part["blocks"] = []
(blockCount,) = struct.unpack_from("<I", data, partOffset)
for blockIndex in xrange(blockCount):
    blockOffset = partOffset + 16 + blockIndex * 0x38

    (vertexCount, meshCount, meshBlockCount, vertexOffset, normalOffset, meshBlockOffset, displayListOffset) = struct.unpack_from("<xxHHHIIII", data, blockOffset)

    tri_tex = []
    quad_tex = []
    tri_mono = []
    quad_mono = []

    status = 0
    clut = 0
    for meshBlockIndex in xrange(meshBlockCount):
# init the mesh block
quad_block, polyCount = struct.unpack_from("<BxH", data, partOffset + meshBlockOffset)
meshBlockOffset += 4

while polyCount > 0:

    # decode command
    (cmd,) = struct.unpack_from("<I", data, partOffset + displayListOffset)
   
    hp = ((cmd >> 24) & 16) != 0     # geraud shading
    quad = ((cmd >> 24) & 8) != 0     # quad or tri
    tme = ((cmd >> 24) & 4) != 0     # texture mapping
    abe = ((cmd >> 24) & 2) != 0     # semi transparency
    fullbright = ((cmd >> 24) & 1) != 0     # bypass lighting
    op = (cmd >> 24) & 255     # operator
    pop = op & ~(16|2|1)     # operator, with shading and lighting mask

    displayListOffset += 4
    if op == 0xC4: # texture page
status = cmd & 0xFFFF
    elif op == 0xC8: # clut
clut = cmd & 0xFFFF
    elif pop == 0x24: # triangle with texture
(ua, va, ub, vb) = struct.unpack_from("<BBBB", data, partOffset + displayListOffset)
uc = cmd & 255
vc = (cmd >> 8) & 255
displayListOffset += 4

shader = shaderBuilder.getShader((status, clut, abe, True))

vertex = []
for j in xrange(3):
    (vtx,) = struct.unpack_from("<H", data, partOffset + meshBlockOffset + j * 2)
    (x, y, z) = struct.unpack_from("<hhh", data, partOffset + vertexOffset + vtx * 8)
    if hp:
(nx, ny, nz) = struct.unpack_from("<hhh", data, partOffset + normalOffset + vtx * 8)
    else:
(nx, ny, nz) = (0, 0, 0)
    vertex.append(((x, y, z), (nx, ny, nz)))
   
tri_tex.append((shader, ((vertex[0][0], vertex[0][1], (ua, va)), (vertex[2][0], vertex[2][1], (uc, vc)), (vertex[1][0], vertex[1][1], (ub, vb)))))

meshBlockOffset += 8
polyCount -= 1
    elif pop == 0x2C: # quad with texture
(ua, va, ub, vb, uc, vc, ud, vd) = struct.unpack_from("<BBBBBBBB", data, partOffset + displayListOffset)
displayListOffset += 8

shader = shaderBuilder.getShader((status, clut, abe, True))

vertex = []
for j in xrange(4):
    (vtx,) = struct.unpack_from("<H", data, partOffset + meshBlockOffset + j * 2)
    (x, y, z) = struct.unpack_from("<hhh", data, partOffset + vertexOffset + vtx * 8)
    if hp:
(nx, ny, nz) = struct.unpack_from("<hhh", data, partOffset + normalOffset + vtx * 8)
    else:
(nx, ny, nz) = (0, 0, 0)
    vertex.append(((x, y, z), (nx, ny, nz)))

quad_tex.append((shader, ((vertex[1][0], vertex[1][1], (ub, vb)), (vertex[0][0], vertex[0][1], (ua, va)), (vertex[2][0], vertex[2][1], (uc, vc)), (vertex[3][0], vertex[3][1], (ud, vd)))))

meshBlockOffset += 8
polyCount -= 1
    elif pop == 0x20: # monochrome triangle
if abe:
    abr = (status >> 5) & 3
    alpha = abe_alpha[abr]
else:
    alpha = 255
   
col = ((cmd >> 16) & 255, (cmd >> 8) & 255, (cmd) & 255, alpha)

shader = shaderBuilder.getShader((status, clut, abe, False))

vertex = []
for j in xrange(3):
    (vtx,) = struct.unpack_from("<H", data, partOffset + meshBlockOffset + j * 2)
    (x, y, z) = struct.unpack_from("<hhh", data, partOffset + vertexOffset + vtx * 8)
    if hp:
(nx, ny, nz) = struct.unpack_from("<hhh", data, partOffset + normalOffset + vtx * 8)
    else:
(nx, ny, nz) = (0, 0, 0)
    vertex.append(((x, y, z), (nx, ny, nz)))

tri_mono.append((shader, ((vertex[0][0], vertex[0][1], col), (vertex[2][0], vertex[2][1], col), (vertex[1][0], vertex[1][1], col))))

meshBlockOffset += 8
polyCount -= 1
    elif pop == 0x28: # monochrome quad
if abe:
    abr = (status >> 5) & 3
    alpha = abe_alpha[abr]
else:
    alpha = 255

col = ((cmd >> 16) & 255, (cmd >> 8) & 255, (cmd) & 255, alpha)

shader = shaderBuilder.getShader((status, clut, abe, False))

vertex = []
for j in xrange(4):
    (vtx,) = struct.unpack_from("<H", data, partOffset + meshBlockOffset + j * 2)
    (x, y, z) = struct.unpack_from("<hhh", data, partOffset + vertexOffset + vtx * 8)
    if hp:
(nx, ny, nz) = struct.unpack_from("<hhh", data, partOffset + normalOffset + vtx * 8)
    else:
(nx, ny, nz) = (0, 0, 0)
    vertex.append(((x, y, z), (nx, ny, nz)))

quad_mono.append((shader, ((vertex[1][0], vertex[1][1], col), (vertex[0][0], vertex[0][1], col), (vertex[2][0], vertex[2][1], col), (vertex[3][0], vertex[3][1], col))))

meshBlockOffset += 8
polyCount -= 1
    else:
print("unknown cmd: %8.8x\n" % cmd)
    block = {}
    block["tri_tex"] = tri_tex
    block["quad_tex"] = quad_tex
    block["tri_mono"] = tri_mono
    block["quad_mono"] = quad_mono
    part["blocks"].append(block)
object["parts"].append(part)
    object["shaders"] = shaderBuilder.getList()
   
    return object

def getColour(col, abe, alpha):
    stp = (col & 0x8000) != 0
    r = (((col     ) & 31) * 255 + 15) / 31
    g = (((col >>  5) & 31) * 255 + 15) / 31
    b = (((col >> 10) & 31) * 255 + 15) / 31
    if (col & 0x7FFF) == 0:
if stp:
    a = 255
else:
    a = 0
    elif stp and abe:
a = alpha
    else:
a = 255
    return (r<<24)|(g<<16)|(b<<8)|a
   
def loadTextures(textureData, shaderList):
    vram = array.array("B", [0] * (2048 * 1024))

    # unpack MIM data into "VRAM"
    offset = 0
    while offset < len(textureData):
header = offset
(type, pos_x, pos_y, move_x, move_y, width, height, chunks) = struct.unpack_from("<IHHHHHHxxH", textureData, header)
# print (type, pos_x, pos_y, move_x, move_y, width, height, chunks)
blockSize = 0x1C + chunks * 2
offset += (blockSize + 2047) & ~2047
for i in xrange(chunks):
    (height,) = struct.unpack_from("<H", textureData, header + 0x1C)
    for j in xrange(height):
vramAddr = (pos_y + move_y + j) * 2048 + (pos_x + move_x) * 2
texAddr = offset + j * width * 2
for k in xrange(width * 2):
    vram[vramAddr] = ord(textureData[texAddr])
    vramAddr += 1
    texAddr += 1
    pos_y += height
    blockSize = width * height * 2
    offset += (blockSize + 2047) & ~2047
    if False:
f = open("vram.bin", "wb")
vram.tofile(f)
f.close()
   
    # convert textures with their palette
    textures = []
    for shader in shaderList:
(status, clut, abe, tme) = shader
if tme:
    tx = (status & 0xF) * 64 * 2
    ty = ((status >> 4) & 1) * 256
    abr = (status >> 5) & 3
    tp = (status >> 7) & 3
    px = (clut & 63) * 16
    py = clut >> 6
   
    if abe:
alpha = abe_alpha[abr]
    else:
alpha = 0
   
    image = array.array("I")
    if tp == 0: # 4-bit
pal = array.array("I")
for idx in xrange(16):
    vaddr = py * 2048 + idx * 2 + px * 2
    col = vram[vaddr] + vram[vaddr+1] * 256
    pal.append(getColour(col, abe, alpha))
for y in xrange(256):
    for x in xrange(256):
val = vram[(y + ty) * 2048 + (x/2) + tx]
if x & 1:
    idx = val >> 4
else:
    idx = val & 0xF
image.append(pal[idx])
del pal
    elif tp == 1:
pal = array.array("I")
for idx in xrange(256):
    vaddr = py * 2048 + idx * 2 + px * 2
    col = vram[vaddr] + vram[vaddr+1] * 256
    pal.append(getColour(col, abe, alpha))
for y in xrange(256):
    for x in xrange(256):
idx = vram[(y + ty) * 2048 + x + tx];
image.append(pal[idx])
del pal
    elif tp == 2:
for y in xrange(256):
    for x in xrange(256):
vaddr = (y + ty) * 2048 + x * 2 + tx
col = vram[vaddr] + vram[vaddr+1] * 256
image.append(getColour(col, abe, alpha))
   
    textures.append(image.tostring())
    del image
else:
    textures.append(None)

    del vram
    return textures

class OpenGLObject:
    def __init__(self, model):
self.model = model
self.drawNormals = False
self.list = None
self.abe = None
self.abr = None
self.texture = None

self.textureList = []
for t in model["textures"]:
    if t is not None:
texIndex = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texIndex)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, t)
if True:
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
else:
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE)
self.textureList.append(texIndex)
    else:
self.textureList.append(None)

    def setBlend(self, status, abe):
if abe:
    if self.abe != abe:
self.abe = abe
glEnable(GL_BLEND)
glDisable(GL_ALPHA_TEST)
    abr = (status >> 3) & 3
    if self.abr != abr:
self.abr = abr
if abr == 0:
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBlendEquation(GL_FUNC_ADD);
elif abr == 1:
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glBlendEquation(GL_FUNC_ADD);
elif abr == 2:
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glBlendEquation(GL_FUNC_SUBTRACT);
elif abr == 3:
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBlendEquation(GL_FUNC_ADD);
else:
    if self.abe != abe:
self.abe = abe
glDisable(GL_BLEND)
glEnable(GL_ALPHA_TEST)
glAlphaFunc(GL_GREATER, 0.0)
   
    def setTexture(self, texture):
if self.texture != texture:
    self.texture = texture
    glBindTexture(GL_TEXTURE_2D, texture)
   
    def drawPart(self, part, trans):
for block in part["blocks"]:
    glDisable(GL_TEXTURE_2D);
    # glColor4ub(255,255,255,255)
    if len(block["tri_mono"]) > 0:
for tri_mono in block["tri_mono"]:
    (status, _, abe, tme) = self.model["shaders"][tri_mono[0]]
    if (abe and trans) or (not abe and not trans):
self.setBlend(status, abe)
glBegin(GL_TRIANGLES)
for j in xrange(3):
    pos, normal, colour = tri_mono[1][j]
    glColor4ub(colour[0], colour[1], colour[2], colour[3])
    glNormal3f(normal[0] / 4096.0, normal[1] / 4096.0, normal[2] / 4096.0)
    glVertex3i(pos[0], pos[1], pos[2])
glEnd()
    if len(block["quad_mono"]) > 0:
for quad_mono in block["quad_mono"]:
    (status, _, abe, tme) = self.model["shaders"][quad_mono[0]]
    if (abe and trans) or (not abe and not trans):
self.setBlend(status, abe)
glBegin(GL_QUADS)
for j in xrange(4):
    pos, normal, colour = quad_mono[1][j]
    glColor4ub(colour[0], colour[1], colour[2], colour[3])
    glNormal3f(normal[0] / 4096.0, normal[1] / 4096.0, normal[2] / 4096.0)
    glVertex3i(pos[0], pos[1], pos[2])
glEnd()

    # glColor4ub(255,255,255,255)
    if True:
glEnable(GL_TEXTURE_2D)
if len(block["tri_tex"]) > 0:
    for tri_tex in block["tri_tex"]:
(status, _, abe, tme) = self.model["shaders"][tri_tex[0]]
if (abe and trans) or (not abe and not trans):
    self.setBlend(status, abe)
    self.setTexture(self.textureList[tri_tex[0]])
    glBegin(GL_TRIANGLES)
    for j in xrange(3):
pos, normal, uv = tri_tex[1][j]
glTexCoord2i(uv[0], uv[1])
glNormal3f(normal[0] / 4096.0, normal[1] / 4096.0, normal[2] / 4096.0)
glVertex3i(pos[0], pos[1], pos[2])
    glEnd()

if len(block["quad_tex"]) > 0:
    for quad_tex in block["quad_tex"]:
(status, _, abe, tme) = self.model["shaders"][quad_tex[0]]
if (abe and trans) or (not abe and not trans):
    self.setBlend(status, abe)
    self.setTexture(self.textureList[quad_tex[0]])
    glBegin(GL_QUADS)
    for j in xrange(4):
pos, normal, uv = quad_tex[1][j]
glTexCoord2i(uv[0], uv[1])
glNormal3f(normal[0] / 4096.0, normal[1] / 4096.0, normal[2] / 4096.0)
glVertex3i(pos[0], pos[1], pos[2])
    glEnd()
   
    def draw(self):
glMatrixMode(GL_TEXTURE)
glLoadIdentity()
glScalef(1.0/256.0, 1.0/256.0, 0)
glTranslatef(0.5, 0.5, 0.0)
   
glMatrixMode(GL_MODELVIEW)
glRotatef(180, 1, 0, 0)

if self.list is None:
    self.list = glGenLists(1)
    glNewList(self.list, GL_COMPILE_AND_EXECUTE)

    # render opaque objects
    glDepthMask(GL_TRUE)
    for item in self.model["nodes"]:
(flags, index, pos, rot) = item
if flags != 480:
    glPushMatrix()
    glTranslatef(pos[0], pos[1], pos[2])
    glRotatef(rot[0] * 90.0 / 1024.0, 1, 0, 0)
    glRotatef(rot[1] * 90.0 / 1024.0, 0, 1, 0)
    glRotatef(rot[2] * 90.0 / 1024.0, 0, 0, 1)

    self.drawPart(self.model["parts"][index], False)

    glPopMatrix()
   
    # render transparent objects
    glDepthMask(GL_FALSE)
    for item in self.model["nodes"]:
(flags, index, pos, rot) = item
if flags != 480:
    glPushMatrix()
    glTranslatef(pos[0], pos[1], pos[2])
    glRotatef(rot[0] * 90.0 / 1024.0, 1, 0, 0)
    glRotatef(rot[1] * 90.0 / 1024.0, 0, 1, 0)
    glRotatef(rot[2] * 90.0 / 1024.0, 0, 0, 1)

    self.drawPart(self.model["parts"][index], True)

    glPopMatrix()
    glDepthMask(GL_TRUE)
    glEndList()
else:
    glCallList(self.list)
   
    def clearList(self):
glDeleteLists(self.list, 1)
self.list = None

def getNodes(archiveData):
    nodes = []
    (itemCount,) = struct.unpack_from("<I", archiveData, 0x018C)
    for itemIndex in xrange(itemCount):
(flags, rot_x, rot_y, rot_z, pos_x, pos_y, pos_z, index) = struct.unpack_from("<HHHHhhhH", archiveData, 0x0190 + itemIndex * 16)
nodes.append((flags, index, (pos_x, pos_y, pos_z), (rot_x, rot_y, rot_z)))
    return nodes

def writeData(f, node):
    f.write("<%s" % node[0])
    items = node[1].items()
    items.sort()
    for key, value in items:
f.write(" %s=\"%s\"" % (key, value))
    if len(node[2]) > 0:
f.write(">")
x = []
for item in node[2]:
    if type(item) == tuple:
if len(x) > 0:
    f.write(" ".join(x))
    x = []
writeData(f, item)
    else:
x.append(str(item))
if len(x) > 0:
    f.write(" ".join(x))
    x = []
f.write("</%s>" % node[0])
    else:
f.write("/>\n")

def flattenBuffer(buffer):
    l = [(value,key) for key,value in buffer.items()]
    l.sort()
    v = []
    for x in [list(key) for _,key in l]:
v.extend(x)
    return v

def saveModel(name, model):
    basename = os.path.splitext(name)[0]
    print("saving textures...")
    for index,t in enumerate(model["textures"]):
if t is not None:
    n = "%s_%i.tga" % (basename, index)
    image = pygame.image.fromstring(t, (256, 256), "RGBA", True)
    pygame.image.save(image, n)
    del image

    print "saving model..."
    collada = ("COLLADA", {"xmlns":"http://www.collada.org/2005/11/COLLADASchema", "version":"1.4.0"}, [])

    library_visual_scenes = ("library_visual_scenes", {}, [])
    visual_scene = ("visual_scene", {"id":"scene", "name":"level"}, [])
    for i,n in enumerate(model["nodes"]):
(flags, partIndex, pos, rot) = n
nodeName = "node%i_%4.4x_%i" % (i, flags, partIndex)
node = ("node", {"name":nodeName, "id":nodeName}, [])
if pos[0] != 0 or pos[1] != 0 or pos[2] != 0:
    translate = ("translate", {}, [pos[0], pos[1], pos[2]])
    node[2].append(translate)
if rot[0] != 0:
    rotate = ("rotate", {}, [1,0,0,rot[0] * 90.0 / 1024.0])
    node[2].append(rotate)
if rot[1] != 0:
    rotate = ("rotate", {}, [0,1,0,rot[1] * 90.0 / 1024.0])
    node[2].append(rotate)
if rot[2] != 0:
    rotate = ("rotate", {}, [0,0,1,rot[2] * 90.0 / 1024.0])
    node[2].append(rotate)
if flags != 480:
    for blockIndex, block in enumerate(model["parts"][partIndex]["blocks"]):
shaders = set([shader for shader,_ in block["tri_tex"]]+[shader for shader,_ in block["quad_tex"]]+[shader for shader,_ in block["tri_mono"]]+[shader for shader,_ in block["quad_mono"]])
for s in shaders:
    meshName = "#mesh%i_%i_%i" % (partIndex, blockIndex,s)
    instance_geometry = ("instance_geometry", {"url":meshName}, [])
    bind_material = ("bind_material", {}, [])
    technique_common = ("technique_common", {}, [])

    shader = model["shaders"][s]
    instance_material = ("instance_material", {"symbol":"slot%i" % s, "target":"#material%i" % s}, [])
    if shader[3]:
bind_vertex_input = ("bind_vertex_input", {"semantic":"UVSET0", "input_semantic":"TEXCOORD", "input_set":0}, [])
instance_material[2].append(bind_vertex_input)
    else:
bind_vertex_input = ("bind_vertex_input", {"semantic":"DIFFUSE", "input_semantic":"COLOUR", "input_set":0}, [])
instance_material[2].append(bind_vertex_input)
    technique_common[2].append(instance_material)
    bind_material[2].append(technique_common)
    instance_geometry[2].append(bind_material)
    node[2].append(instance_geometry)
visual_scene[2].append(node)
    library_visual_scenes[2].append(visual_scene)
    collada[2].append(library_visual_scenes)

    library_images = ("library_images", {}, [])
    for index,t in enumerate(model["textures"]):
if t is not None:
    n = "%s_%i.tga" % (basename, index)
    image = ("image", {"id":"image%i" % index}, [
("init_from", {}, [n])
    ])
    library_images[2].append(image)
    collada[2].append(library_images)

    library_geometries = ("library_geometries", {}, [])
    for partIndex, part in enumerate(model["parts"]):
for blockIndex, block in enumerate(part["blocks"]):   
    # create separate mesh for each shader and for textured and untextured geometry
    meshlist = {}   
    for tri_tex in block["tri_tex"]:
(shader, prim) = tri_tex
try:
    d = meshlist[shader]
except KeyError:
    d = {"poly":[], "position":{}, "normal":{}, "colour":{}, "uv":{}}
    meshlist[shader] = d
poly = []
for vtx in prim:
    try:
p = d["position"][vtx[0]]
    except KeyError:
p = len(d["position"])
d["position"][vtx[0]] = p
    try:
n = d["normal"][vtx[1]]
    except KeyError:
n = len(d["normal"])
d["normal"][vtx[1]] = n
    try:
t = d["uv"][vtx[2]]
    except KeyError:
t = len(d["uv"])
d["uv"][vtx[2]] = t
    poly.append((p,n,t))
d["poly"].append(poly)

    for quad_tex in block["quad_tex"]:
(shader, prim) = quad_tex
try:
    d = meshlist[shader]
except KeyError:
    d = {"poly":[], "position":{}, "normal":{}, "colour":{}, "uv":{}}
    meshlist[shader] = d
poly = []
for vtx in prim:
    try:
p = d["position"][vtx[0]]
    except KeyError:
p = len(d["position"])
d["position"][vtx[0]] = p
    try:
n = d["normal"][vtx[1]]
    except KeyError:
n = len(d["normal"])
d["normal"][vtx[1]] = n
    try:
t = d["uv"][vtx[2]]
    except KeyError:
t = len(d["uv"])
d["uv"][vtx[2]] = t
    poly.append((p,n,t))
d["poly"].append(poly)
   
    for tri_mono in block["tri_mono"]:
(shader, prim) = tri_mono
try:
    d = meshlist[shader]
except KeyError:
    d = {"poly":[], "position":{}, "normal":{}, "colour":{}, "uv":{}}
    meshlist[shader] = d
poly = []
for vtx in prim:
    try:
p = d["position"][vtx[0]]
    except KeyError:
p = len(d["position"])
d["position"][vtx[0]] = p
    try:
n = d["normal"][vtx[1]]
    except KeyError:
n = len(d["normal"])
d["normal"][vtx[1]] = n
    try:
c = d["colour"][vtx[2]]
    except KeyError:
c = len(d["colour"])
d["colour"][vtx[2]] = c
    poly.append((p,n,c))
d["poly"].append(poly)
   
    for quad_mono in block["quad_mono"]:
(shader, prim) = quad_mono
try:
    d = meshlist[shader]
except KeyError:
    d = {"poly":[], "position":{}, "normal":{}, "colour":{}, "uv":{}}
    meshlist[shader] = d
poly = []
for vtx in prim:
    try:
p = d["position"][vtx[0]]
    except KeyError:
p = len(d["position"])
d["position"][vtx[0]] = p
    try:
n = d["normal"][vtx[1]]
    except KeyError:
n = len(d["normal"])
d["normal"][vtx[1]] = n
    try:
c = d["colour"][vtx[2]]
    except KeyError:
c = len(d["colour"])
d["colour"][vtx[2]] = c
    poly.append((p,n,c))
d["poly"].append(poly)

    for shader, meshdata in meshlist.items():
meshName = "mesh%i_%i_%i" % (partIndex, blockIndex, shader)
geometry = ("geometry", {"id":meshName}, [])
mesh = ("mesh", {}, [])

position = meshdata["position"]
if len(position) > 0:
    source = ("source", {"id":"%s-position" % meshName}, [])
    float_array = ("float_array", {"id":"%s-position-array" % meshName, "count":(len(position) * 3)}, [float(x) for x in flattenBuffer(position)])
    source[2].append(float_array)
    technique_common = ("technique_common", {}, [
("accessor", {"count":len(position), "source":"%s-position-array" % meshName, "stride":3}, [
    ("param", {"name":"X", "type":"float"}, []),
    ("param", {"name":"Y", "type":"float"}, []),
    ("param", {"name":"Z", "type":"float"}, [])
])
    ])
    source[2].append(technique_common)
    mesh[2].append(source)

normal = meshdata["normal"]
if len(normal) > 0:
    source = ("source", {"id":"%s-normal" % meshName}, [])
    float_array = ("float_array", {"id":"%s-normal-array" % meshName, "count":(len(normal) * 3)}, [float(x) / 4096.0 for x in flattenBuffer(normal)])
    source[2].append(float_array)
    technique_common = ("technique_common", {}, [
("accessor", {"count":len(normal), "source":"%s-normal-array" % meshName, "stride":3}, [
    ("param", {"name":"X", "type":"float"}, []),
    ("param", {"name":"Y", "type":"float"}, []),
    ("param", {"name":"Z", "type":"float"}, [])
])
    ])
    source[2].append(technique_common)
    mesh[2].append(source)

uv = meshdata["uv"]
if len(uv) > 0:
    source = ("source", {"id":"%s-uv" % meshName}, [])
    float_array = ("float_array", {"id":"%s-uv-array" % meshName, "count":(len(uv) * 2)}, [(float(x) + 0.5) / 256.0 for x in flattenBuffer(uv)])
    source[2].append(float_array)
    technique_common = ("technique_common", {}, [
("accessor", {"count":len(uv), "source":"%s-uv-array" % meshName, "stride":2}, [
    ("param", {"name":"U", "type":"float"}, []),
    ("param", {"name":"V", "type":"float"}, [])
])
    ])
    source[2].append(technique_common)
    mesh[2].append(source)

colour = meshdata["colour"]
if len(colour) > 0:
    source = ("source", {"id":"%s-colour" % meshName}, [])
    float_array = ("float_array", {"id":"%s-colour-array" % meshName, "count":(len(colour) * 4)}, [float(x) / 255.0 for x in flattenBuffer(colour)])
    source[2].append(float_array)
    technique_common = ("technique_common", {}, [
("accessor", {"count":len(colour), "source":"%s-colour-array" % meshName, "stride":4}, [
    ("param", {"name":"R", "type":"float"}, []),
    ("param", {"name":"G", "type":"float"}, []),
    ("param", {"name":"B", "type":"float"}, []),
    ("param", {"name":"A", "type":"float"}, [])
])
    ])
    source[2].append(technique_common)
    mesh[2].append(source)

vertices = ("vertices", {"id":"%s-vertex" % meshName}, [
    ("input", {"semantic":"POSITION", "source":"#%s-position" % meshName}, [])
])
mesh[2].append(vertices)
       
primitives = meshdata["poly"]
polylist = ("polylist", {"count":len(primitives), "material":"slot%i" % shader}, [])
input = ("input", {"semantic":"VERTEX", "source":"#%s-vertex" % meshName, "offset":0}, [])
polylist[2].append(input)
input = ("input", {"semantic":"NORMAL", "source":"#%s-normal" % meshName, "offset":1}, [])
polylist[2].append(input)
if len(uv) > 0:
    input = ("input", {"semantic":"TEXCOORD", "source":"#%s-uv" % meshName, "offset":2}, [])
    polylist[2].append(input)
if len(colour) > 0:
    input = ("input", {"semantic":"COLOUR", "source":"#%s-colour" % meshName, "offset":2}, [])
    polylist[2].append(input)
vcount = ("vcount", {}, [len(poly) for poly in primitives])
polylist[2].append(vcount)
p = ("p", {}, [])
for poly in primitives:
    for vtx in poly:
p[2].extend(list(vtx))
polylist[2].append(p)
mesh[2].append(polylist)

geometry[2].append(mesh)
library_geometries[2].append(geometry)
    collada[2].append(library_geometries)

    library_materials = ("library_materials", {}, [])
    for shaderIndex,shader in enumerate(model["shaders"]):
matName = "material%i" % shaderIndex
effName = "effect%i" % shaderIndex
material = ("material", {"id":matName}, [
    ("instance_effect", {"url":"#%s" % effName}, [])
])
library_materials[2].append(material)
    collada[2].append(library_materials)
   
    library_effects = ("library_effects", {}, [])
    for shaderIndex,shader in enumerate(model["shaders"]):
matName = "material%i" % shaderIndex
effName = "effect%i" % shaderIndex
effect = ("effect", {"id":effName}, [])
profile_COMMON = ("profile_COMMON", {}, [])

if shader[3]:
    newparam = ("newparam", {"sid":"surface%i" % shaderIndex}, [
("surface", {"type":"2D"}, [
    ("init_from", {}, ["image%i" % shaderIndex])
])
    ])
    profile_COMMON[2].append(newparam)
    newparam = ("newparam", {"sid":"sampler%i" % shaderIndex}, [
("sampler2D", {}, [
    ("source", {}, ["surface%i" % shaderIndex])
])
    ])
    profile_COMMON[2].append(newparam)
else:
    newparam = ("newparam", {"sid":"colour"}, [
("semantic", {}, ["DIFFUSE"]),
("modifier", {}, ["VARYING"])
    ])
    profile_COMMON[2].append(newparam)

technique = ("technique", {"sid":"COMMON"}, [])
lambert = ("lambert", {}, [])
if shader[3]:
    # textured
    diffuse = ("diffuse", {}, [
("texture", {"texture":"sampler%i" % shaderIndex, "texcoord":"UVSET0"}, [])
    ])
    lambert[2].append(diffuse)
else:
    # vertex colour
    diffuse = ("diffuse", {}, [
("param", {"ref":"colour"}, [])
    ])
    lambert[2].append(diffuse)

technique[2].append(lambert)
profile_COMMON[2].append(technique)
effect[2].append(profile_COMMON)

library_effects[2].append(effect)
    collada[2].append(library_effects)
   
    scene = ("scene", {}, [
("instance_visual_scene", {"url":"#scene"}, [])
    ])
    collada[2].append(scene)
   
    f = open(name, "wt")
    writeData(f, collada)
    f.close()
   
    print "done..."

def main(*argv):
    print "loading archive..."
    diskIndex = 1 # there are disk 1 and disk 2
    dirIndex = 11 # 0-based index
    fileIndex = int(argv[0]) # 0-based index

    archivePath = os.path.join("disk%i" % diskIndex, "dir%i" % dirIndex, "file%i.bin" % (fileIndex * 2))
   
    f = open(archivePath, "rb")
    archiveData = f.read()
    f.close()
   
    if archiveData[:4] == "It's":
# file was removed from disk image
print "This file was removed from the disk image. Most likely it is a room that is not reachable any more."
return 0

    modelData = getData(archiveData, 2)

    print "loading texture..."
    texturePath = os.path.join("disk%i" % diskIndex, "dir%i" % dirIndex, "file%i.bin" % (fileIndex * 2 + 1))
   
    f = open(texturePath, "rb")
    textureData = f.read()
    f.close()

    print "converting meshes..."
    model = loadModel(modelData)
   
    print "converting textures..."
    model["textures"] = loadTextures(textureData, model["shaders"])
   
    print "getting nodes..."
    model["nodes"] = getNodes(archiveData)

    print "starting OpenGL..."
    pygame.init()
   
    screen = pygame.display.set_mode((640, 480), HWSURFACE|DOUBLEBUF|OPENGL)

    glViewport(0, 0, 640, 480)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    kFOVy = 0.57735
    kZNear = 10.0
    kZFar = 10000.0
    aspect = (640.0 / 480.0) * kZNear * kFOVy
    glFrustum(-aspect, aspect, -(kZNear * kFOVy), (kZNear * kFOVy), kZNear, kZFar)

    glEnable(GL_DEPTH_TEST)
    glDepthFunc(GL_LEQUAL)
    glDisable(GL_CULL_FACE)
    glDisable(GL_LIGHTING)
    glDisable(GL_BLEND)
    glPolygonMode(GL_FRONT, GL_FILL)
    glPolygonMode(GL_BACK, GL_LINE)

    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
    wireframe = False
   
    clock = pygame.time.Clock()
    key_up = key_down = key_left = key_right = key_front = key_back = False
    rot_x = 0.0
    rot_y = 0.0
    pos_x = 0.0
    pos_y = 0
    pos_z = -2000
    object = OpenGLObject(model)
    while True:
for event in pygame.event.get():
    if event.type == QUIT:
exit()
    elif event.type == KEYDOWN:
if event.key == pygame.K_UP:
    key_up = True
elif event.key == pygame.K_DOWN:
    key_down = True
elif event.key == pygame.K_LEFT:
    key_left = True
elif event.key == pygame.K_RIGHT:
    key_right = True
elif event.key == pygame.K_w:
    key_front = True
elif event.key == pygame.K_s:
    key_back = True
elif event.key == pygame.K_ESCAPE:
    exit()
elif event.key == pygame.K_d:
    wireframe = not wireframe
    if wireframe:
        glPolygonMode(GL_FRONT, GL_LINE)
    else:
        glPolygonMode(GL_FRONT, GL_FILL)
elif event.key == pygame.K_c:
    saveModel("level%i.dae" % fileIndex, model)
    elif event.type == KEYUP:
if event.key == pygame.K_UP:
    key_up = False
elif event.key == pygame.K_DOWN:
    key_down = False
elif event.key == pygame.K_LEFT:
    key_left = False
elif event.key == pygame.K_RIGHT:
    key_right = False
elif event.key == pygame.K_w:
    key_front = False
elif event.key == pygame.K_s:
    key_back = False
   
if key_up:
    rot_x -= 1.0
elif key_down:
    rot_x += 1.0
   
if key_left:
    rot_y -= 1.0
elif key_right:
    rot_y += 1.0
   
if key_front:
    sx = math.sin(rot_x * math.pi * 2.0 / 256.0)
    cx = -math.cos(rot_x * math.pi * 2.0 / 256.0)
    sy = math.sin(rot_y * math.pi * 2.0 / 256.0)
    cy = -math.cos(rot_y * math.pi * 2.0 / 256.0)
    pos_x += sy * cx * 16
    pos_y += sx * 16
    pos_z += cy * cx * 16
elif key_back:
    sx = math.sin(rot_x * math.pi * 2.0 / 256.0)
    cx = -math.cos(rot_x * math.pi * 2.0 / 256.0)
    sy = math.sin(rot_y * math.pi * 2.0 / 256.0)
    cy = -math.cos(rot_y * math.pi * 2.0 / 256.0)
    pos_x -= sy * cx * 16
    pos_y -= sx * 16
    pos_z -= cy * cx * 16

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glFrustum(-aspect, aspect, -(kZNear * kFOVy), (kZNear * kFOVy), kZNear, kZFar)
if False:
    glTranslatef(0, 0, -2000)
    glRotatef(rot_y * 360.0 / 256.0, 0.0, 1.0, 0.0)
    glRotatef(rot_x * 360.0 / 256.0, 1.0, 0.0, 0.0)
else:
    glRotatef(rot_x * 360.0 / 256.0, 1.0, 0.0, 0.0)
    glRotatef(rot_y * 360.0 / 256.0, 0.0, 1.0, 0.0)
    glTranslatef(pos_x, pos_y, pos_z)

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
   
object.draw()
   
clock.tick(60)
pygame.display.flip()

if __name__ == "__main__":
    main(*sys.argv[1:])
I wrote a Python version of my Xenogears level viewer. It requires pygame and the python opengl bindings.
It takes a single argument with the room number [<=729], and it expects the data relative to the current directory in disk1/dir11/file<index>.bin. If you're unpacking your ISO, the directory index and file index are 0-based.
There is still a lot to decode, to debug and to optimise. But this version should handle transparencies a lot better then the last version. Texture conversion is a bit slow, though.

Update: Endian fix for Windows
Update: Better endian fix, mipmap fix, (slightly)faster texture conversion
Update: Collada Exporter
« Last Edit: 2010-07-03 11:48:32 by Micky »

Covarr

  • Covarr-Let
  • Administrator
  • *
  • Posts: 3941
  • Just Covarr. No "n".
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #12 on: 2010-05-28 23:08:39 »
I don't think anybody would fault you for necro'ing your own thread with something completely on-topic.

I'd check this out if I had Xenogears.

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #13 on: 2010-05-29 07:26:01 »
Showing off transparency:

I think this is from the Zeboim-era Fei and Elly talking about making Esmeralda.

halkun

  • Global moderator
  • *
  • Posts: 2097
  • NicoNico :)
    • View Profile
    • Q-Gears Homepage
Re: [Xenogears] Level viewer in Python
« Reply #14 on: 2010-05-29 09:46:02 »
wait, I don't have my copy of Xenogears infront of me.. You have to unpack an archive off the disk to see that, right?

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #15 on: 2010-05-29 09:54:15 »
wait, I don't have my copy of Xenogears infront of me.. You have to unpack an archive off the disk to see that, right?
Yes. The ISO filesystem only contains the executable and system.cnf file. But LUCKY for you I ported my extraction tool to python as well this morning:

Moved to a separate thread.

You need raw images of your disk (a plain dump of all data, 2352 byte per sector). Just run it with your images as input, and it will create folders in your current directory. And it even automatically detects if your image is disk 1 or disk 2, or not a playstation or xenogears image at all.
I'm using the NTSC U/C version.
« Last Edit: 2010-06-30 08:00:24 by Micky »

nikfrozty

  • *
  • Posts: 1215
  • Cloud kicks Sephiroth's Butt Anytime
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #16 on: 2010-05-29 10:05:26 »
Showing off transparency:

I think this is from the Zeboim-era Fei and Elly talking about making Esmeralda.
That looks nice!! I wished it was available to do that in ff7 game though. :-\

Mako

  • *
  • Posts: 669
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #17 on: 2010-05-29 22:43:13 »
This is perfect!!!!!!. Python!?!?. Damn how many languages are there!...Just kidding, python is the one book I skipped at the library...does it just view the scene or can I dump it somewhere in a 3d format of any kind?.
« Last Edit: 2010-05-29 22:48:32 by Mako »

pyrozen

  • *
  • Posts: 791
  • Team Avalanche Member
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #18 on: 2010-05-30 01:07:14 »
are there currently any programs similar to this for FF7 battle fields?

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #19 on: 2010-05-30 06:53:08 »
are there currently any programs similar to this for FF7 battle fields?
I'm sure Akari has a better one by now, but you could try this: http://forums.qhimm.com/index.php?topic=3152.0
Quote from: Mako
This is perfect!!!!!!. Python!?!?. Damn how many languages are there!...Just kidding, python is the one book I skipped at the library...does it just view the scene or can I dump it somewhere in a 3d format of any kind?.
At the moment it is only a viewer, but I arranged the code in a way that I can easily write out the model and textures.

Update: I tried it on Windows and noticed there is an endian problem. And gluBuild2DMipmaps didn't work. The version in the first message should be fixed now.
« Last Edit: 2010-05-30 08:45:23 by Micky »

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #20 on: 2010-05-31 17:53:54 »
Just a preview:

It'll take a bit more work until I can release the code. It looks like I'm hitting an internal Blender limit and it needs to be more user friendly.
« Last Edit: 2010-05-31 23:07:18 by Micky »

fulansujin

  • *
  • Posts: 2
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #21 on: 2010-06-03 11:09:23 »


Hello and congratulations for your programming skills.
I have tried it on Windows XP but I always get this error message.
I am using Pygame 1.9.1 and Python 3.1.2.
I have also tried on a Macintosh platform but I also have error returns.
Finally I used the script function in Blender but the same problem occurs.
I have an ISO of the game dumped with Clone CD, should I redump it with Alcool 120 to precise the 2352 option ?

Danke ^_^

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #22 on: 2010-06-03 21:40:52 »
I'm using Python 2.5 and pygame 1.9.1. Additionally the OpenGL bindings 3.0.1. From the screenshot it looks like you're trying to run the viewer and not the extractor. And no version runs inside Blender, my new (unreleased) version creates a Collada 1.4 file.
The extractor complains if the file is not the correct format. The ISO should be identical between clonecd and alcohol, if it is raw and 2352 byte per sector.

I'm not sure what the error complains about, the indentation looks fine. Are you running the script from the command line or from explorer?

Update: The error is new in python 3. Try 2.5.
Something like this on the command line (in the directory with the iso):
Code: [Select]
c:\python2.5\python extract.py image1filename image2filenameand then
Code: [Select]
c:\python2.5\python view.py 666
« Last Edit: 2010-06-03 21:51:12 by Micky »

fulansujin

  • *
  • Posts: 2
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #23 on: 2010-06-17 21:13:22 »
Hello there Micky, still no luck in working your program, something must be wrong with my configuration.

Anyway I have a software that allows to extract all 42 folders of a standard ISO image and also extract all of its content as you can see on the picture.

You mentionned directory 11 and the file 729, which can be extracted among the others.

My question is: on the basis of your code, is it possible to write a collada plugin working this Blender so as to view these 3D levels ?



« Last Edit: 2010-06-17 21:15:36 by fulansujin »

Micky

  • *
  • Posts: 300
    • View Profile
Re: [Xenogears] Level viewer in Python
« Reply #24 on: 2010-06-18 07:29:40 »
Hello there Micky, still no luck in working your program, something must be wrong with my configuration.
If you don't tell me what is wrong I can't help you. Maybe you should start with a command line tutorial, so you know what you're doing?
Quote
Anyway I have a software that allows to extract all 42 folders of a standard ISO image and also extract all of its content as you can see on the picture.

You mentionned directory 11 and the file 729, which can be extracted among the others.
All levels are pairs of files, one for the textures and one for the level. The script should pick the correct files if you give it the room number.
Quote
My question is: on the basis of your code, is it possible to write a collada plugin working this Blender so as to view these 3D levels ?
Yes, of course, but I wouldn't make it a Blender plugin. Collada plugin doesn't make sense, collada is a file format.
It would probably be easier to contact the authors of your xenostudio program and tell them about this code. Then they can include it in xenostudio.
« Last Edit: 2010-06-18 07:36:36 by Micky »