Alhexx' Final Fantasy VII .p Format Description 1.51 Last Update: 2004-03-18 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= News in Version 1.51: --------------------- - Added "General File Structure" Diagram - Fixed TexCoord-Normal-Bug* - Renamed "Pool"s to "Chunk"s - Changed Hex-Offsets to C/C++ Style (e.g. 0x80) News in Version 1.5: -------------------- - Hacked "Unknown" Pool -> Normal Index Table - Changed "int"s to "long"s - Renamed members to new, better names ;) - Removed VB Types - Fixed some "bugs" -* TexCoord-Normal-Bug: -------------------- All the time I believed that the Texture coords were stored before the Normals in the .p Files. However, that isn't so. The Normals are strored before the Texture Coords. That's one of the Reasons I've added the "General File Structure" Diagram to this doc. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- First I've got to say that this description contains parts of Ficedula's and mirex' descriptions, but I don't think they've got anything against that I use them... Okay, I'm not going to explain the basics of Polygon and 3D models like Fice. If you don't know what a polygon is, you should close this file. I've also got to say, that this file will be updated and there may be a lot of bugs in it, but I think if you're trying to hack the .p format, it's going to be useful. Oh, and I'm using Hex-Offsets, too. I will explain values this way: 0x80 The lengths are in decimal (as in Fice's description) One thing, before I begin: This file can be 'splitted' into 2 parts: The First part (I.x) describes how the File is build together. The Second part (II.x) describes how the model is splitted into different Groups. Okay, here we go: -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- O. General File Structure ========================= Here's a short Diagriam of the File Structure .p-File | +- Header | +- Vertices[] | (+- Normals[]) | (+- Texture Coords[]) | +- Vertice Colors[] | +- Polygon Colors[] | +- Edges[] | +- Polygons[] | +- Hundrets[] | +- Groups[] | +- BoundingBox | +- Normal Index Table[] [] = a variable-sized array -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- I. File Structure ================= I.I .P File Header ------------------ The .p files have a 128-Byte-long Header. We know 'till know to decode the first 64 Bytes. Perhaps the other 64 Bytes aren't really a header, perhaps they're a part of data. Dunno. Here's what I know: (You should know that from a hex-editor) Off| 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ---+--------------------------------------------------------- 00 | 01 00 00 00 01 00 00 00 VertexColor NumVerts 10 | NumNormals 00 00 00 00 NumTexCs NumNormInds 20 | NumEdges NumPolys 00 00 00 00 00 00 00 00 30 | mirex_h NumGroups mirex_g 01 00 00 00 All Values, that can be Read out in this part of the header are 4-Byte-Integers typedef struct { long off00; long off04; long VertexColor; long NumVerts; long NumNormals; long off14; long NumTexCs; long NumNormInds; long NumEdges; long NumPolys; long off28; long off2c; long mirex_h; long NumGroups; long mirex_g; long off3c; long unknown[16]; } t_p_header; Here are the meanings: VertexColor specifies if Vertex-Colors are used (0=no,1=yes; default: 1) NumVerts Count of Vertices NumNormals Count of Normals (always 0 in Battle files) NumTexCs Count of Texture Coords NumNormInds Count of Normal Indices NumEdges Count of Lines for WireFrame-Model NumPolys Count of Polygons mirex_h Count of Hundrets Chunk Entries (Textures?) NumGroups Count of Groups mirex_g ? (sometimes 0 or 1)(but usually 1) All the other values, which are mostly the same, are used, too, but I don't know for what. If you change one of these 1 in the header, FF7 will crash. I've tried it. -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Here's written down, how the parts of the .p file are put together. There are 11 Parts after the Header, in which the .p file can be splitted: IMPORTANT: This Part of this documentation shows only how the structures look like and how they ahve to be interpreted. How a .p Model is build up from these parts is written in Chapter II. I.II. Vertex Chunk ------------------ Okay, now I'm going to tell you what I know 'bout the vertex Chunk: Start Offset: 0x80 Length: NumVerts * 12 Here's how it is put together: Additional + Numverts are the count of the vertexes saved in the .p file. Every Vertex consists of 12 Bytes, 4 for each coord (X/Y/Z). Every coord is a 4-Byte Float The C/C++ Struct: typedef struct { float x; float y; float z; } t_p_vertex; INFO: Of course, you could also use a simple Array with 3 elements, one element for every coord, but I like that X, Y, Z thingy :P -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.III Normals Chunk ------------------- Start Offset: 0x80 + (NumVerts * 12) Length: NumNormals * 12 That Chunk stores the normals for Field Models. The structure is the same as in the Vertex Chunk. -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.IV Texture Coord Chunk ------------------------ Start Offset: 0x80 + (NumVerts + NumNormals * 12) Length: NumTexCs * 8 These are the Texture coords. They're build up similar to the Vertex type, just with 2 elements: C/C++: typedef struct { float x; float y; } t_p_texturecoord; X and Y are the offsets on the texture relative to the texture size. The values can between 0.0 and 1.0. So if a TexCoord has X = 0 and Y = 0, it is in the upper left corner, if it has X = 1 and Y = 1 it is in the lower right corner (or at least I hope so...) Simetimes, the values may be even higher than 1.0. In that case, i.e. 1.3 is the same as 0.3. So if a value exceeds 1.0, simply subtract 1.0 from it. -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.V Vertex Color Chunk ---------------------- Start Offset: 0x80 + (NumVerts + NumNormals) * 12 + (NumTexCs * 8) Length: NumVerts * 4 That are the Color Values. Every Vertex has its own Vertex Color. They're saved as 4 x 1-Byte-Integers (a.c.:Byte). The Values are saved in a 'standard RGB-QUAD': typedef struct { unsigned char blue; unsigned char green; unsigned char red; unsigned char reserved; } t_p_color; I'm not sure 'bout the Reserved Byte, it maybe alpha, but it's usually 255. So I don't think it's worthy thinking 'bout it. -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.VI Polygon Color Chunk ------------------------ Start Offset: 0x80 + (NumVerts + NumNormals) * 12 + (NumTexCs * 8) + NumVerts * 4 Length: NumPolys * 4 As every Vertex has its own Color, every Polygon also has its own. The Type/Struct is the same as in I.V -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.VII Edge Chunk ---------------- Start Offset: 0x80 + (NumVerts + NumNormals) * 12 + (NumTexCs * 8) + (NumVerts + NumPolys) * 4 Length: NumEdges * 4 The Edge Chunk consists of a couple of 2-Byte Integers. The Edge Chunk saves the Wireframe model of the model. Every Entry is one line of the Model. C/C++: typedef struct { short vertex[2]; } t_p_edge; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.VIII Polygon Chunk -------------------- Start Offset: 0x80 + (NumVerts + NumNormals) * 12 + (NumTexCs * 8) + (NumVerts + NumPolys + NumEdges) * 4 Length: Numpolys * 24 As you can see, every polygon consists of 24-Bytes. I'm first going to explain the Type... typedef struct { short Tag1; short Vertex[3]; short Normal[3]; short Edge[3]; long Tag2; } t_p_polygon; Now I'm going to explain what I know 'bout the parts: Tag Unknown, but always 0 Vertex[*] Vertex * for the polygon Normal[*] Normal* for the polygon (always 0 in Battle Files) Edge[*] Edge * for WireFrame-Model Tag2 Unknown, but always $0C FC EA 00 Important: The Normal Indices are absolute, not related to the Group! -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.IX Hundrets Chunk ------------------- Start Offset: 0x80 + (NumVerts + NumNormals) * 12 + (NumTexCs * 8) + (NumVerts + NumPolys + NumEdges) * 4 + (NumPolys * 24) Length: Mirex-H * 100 I don't really know what is stored here, but I suppose it has to do with the textures... well, dunno. In the C/C++ Struct, there is the info how this struct is usually filled. So if you want to create a new Hundrets Entry, you should use these values. Here is the (very long) type: C/C++: typedef struct { // 1st 2nd 3rd long off00; // 0x00000001 long off04; // 0x00000001 char off08; // 0x00 char off09; // 0x82 0x86 0x86 short off0a; // 0x0003 char off0c; // 0x02 char off0d; // 0x00 0x04 0x04 short off0e; // 0x0002 long off10; // 0x00000000 0x00000000 0x00000001 long off14; // 0x00000000 long off18; // 0x00000000 0x00000001 0x00000001 long off1c; // 0x00000000 0x00000025 0x00000025 char off20; // 0x00 x00 x40 x80 x00 x40 x80 char off21; // 0x00 0x78 0x78 short off22; // 0x0000 long off24; // 0x00000002 0x00000001 0x00000001 long off28; // 0xFFFFFFFF long off2c; // 0x00000000 long off30; // 0x00000000 long off34; // 0x00000002 0x00000005 0x00000005 long off38; // 0x00000001 0x00000006 0x00000006 long off3c; // 0x00000002 long off40; // 0x00000000 long off44; // 0x00000004 0x00000000 0x00000000 long off48; // 0x00000000 long off4c; // 0x00000000 long off50; // 0x00000000 long off54; // 0x00000000 long off58; // 0x00000000 long off5c; // 0x000000FF 0x00000080 0x00000080 long off60; // 0x00000000 } t_p_hundrets; 1st, 2nd and 3rd tells you how the * Hundrets Chunk looks like. Here are some ideas: off18 Index of Texture(?) off5c Width/Height (?) -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.X Group Chunk --------------- Start Offset: 0x80 + (NumVerts + NumNormals) * 12 + (NumTexCs * 8) + (NumVerts + NumPolys + NumEdges) * 4 + (NumPolys * 24) + Mirex-H * 100 Length: NumGroups * 56 I was hacking quite long, to find out how this Chunk's calculated... After looooooong time of hacking I finally found out for what this Chunk is good for... it's a Group Chunk. It divides the .p Model into several Parts. typedef struct { long polyType; long offPoly; long numPoly; long offVert; long numVert; long offEdge; long numEdge; long off18; // 0 long off1c; // 0 long off20; // 0 long off24; // 0 long offTex; long texFlag; long texID; } t_p_group; Explanation: polyType: Specifies the Polygon Type for this Group: 1 - nontextured Polygons 2 - textured Polygons with normals 3 - textured Polygons without normals offPoly: The First Polygon used in this Group numPoly: Number of Polygons used in this Group offVert: see above numVert: see above offEdge: The First Edge used in this Group numEdge: see below offTex: The first Texture Coord used in this Group texFlag: Texture Flag: 0 - No texture on this Group 1 - Textured texID: Index of Texture (see below) Now it's gettin' more complicated... The numEdge value is usually 0. So if you want to know how many Edges are used in Chunk i, then you'll have to see the offEdge in Chunk i+1 and get the difference ... blabla ... ahhh, take a look at this: t_fiftysix[i].numEdge = t_fiftysix[i+1].offEdge - t_fiftysix[i].offEdge BUT: If you want to get the numEdge for the LAST Group, then, of course, you'll have to do this: t_fiftysix[i].numEdge = t_header.NumEdges - t_fiftysix[i].offEdge I hope you know what I mean. BTW: If you're going to generate you own Groups, you CAN save the numEdge values to the struct; FF7 won't crash. As for the texID entry: In Field files, this is the Index used by the RSD Files. In Battle files it *should* be like this: (Example: Yuffie: RX**) 1 - rxac 2 - rxad 3 - rxae ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.XI BoundingBox ----------------- Start Offset: 0x80 + (NumVerts + NumNormals) * 12 + (NumTexCs * 8) + (NumVerts + NumPolys + NumEdges) * 4 + (NumPolys * 24) + (Mirex-H * 100) + (NumGroups * 56) + 4 Length: 24 This is the model's bounding box. typedef struct { float max_x; float max_y; float max_z; float min_x; float min_y; float min_z; } t_p_boundingbox; - OR maybe even better: - typedef struct { t_p_vertex max; t_p_vertex min; } t_p_boundingbox; It can be simply generated by going through the whole Vertex Array and looking for values. -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= I.XII Normal Index Table ------------------------ Start Offset: 0x80 + (NumVerts + NumNormals) * 12 + (NumTexCs * 8) + (NumVerts + NumPolys + NumEdges) * 4 + (NumPolys * 24) + (Mirex-H * 100) + (NumGroups * 56) + 4 + 24 Length: NumNormInds * 4 Heh, it took me a long time top hack these values, too! But it's quite simple! This is just a normal index table! It's just an array of 32Bit integers. That means if you want to know which normal is used by vertex *, you will have to take a look into that table. So let's say: NormalIndex[0] = 4 NormalIndex[1] = 7 NormalIndex[2] = 2 ... So the Vertex 0 uses Normal 4, ... Vertex 1 uses Normal 7, ... Vertex 2 uses Normal 2, and so on... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- II. Grouping ============ Since a part from the Group Chunk is decoded, we should now take a look at the Groups in the p File Format, since it isn't that easy. However, I hope I'm able to describe it understandably... INFO: If you want to have a look at what I'm talking here, download Ultima 0.40 from my site, load a Model and click on let Ultima generate an Info file. Or take a look att two of them at: http://www.alhexx.com/fyuffie.txt http://www.alhexx.com/byuffie.txt II.I General ------------ Like I said, the p Model is splitted up into Groups. Usually only models which are textured have more than one group, but I'm sure that there are non-textured models which are grouped, too. Well, almost every part in the p File is splitted up into Groups, except the Header and, but this is not sure yet: Hundrets Chunk, Normals Chunk and Texture Coords. I'm sure that the Tex Coords ARE grouped, too, but I haven't found any Grouping behavior yet. Since I don't have enough time to check that now, I will do that later and update this document. There is something unusual about the Groups in the p Format. Usually, in a model there is, to keep it simple, only a Vertex, a Polygon and a Group Chunk. There are let's say 4 Verts and 2 Polygons in 2 Groups. Now every Polygon point to the absoulte Vertex index, not the index of the Vertex within this Group... let me guess, you don't have an idea what I'm talking about, huh? So here's a small "image" to demonstate it. STEP 1: ------- Here we've got a basic "Model": 1 /|\ / | \ / P|Q \ / | \ 2----3----4 \ | / \ R|S / \ | / \|/ 5 Here are 5 Vertices, numerated from 1 to 5: (I used 2 Coordinates per vertex, since this document is 2D and not 3D) Vert X Y ----------------- 1 1 0 2 0 -1 3 0 0 4 0 1 5 -1 0 ...and we've got 4 Polygons from P to S: Poly Vertex1 V2 V3 -------------------------- P 1 2 3 Q 1 3 4 R 3 2 5 S 3 5 4 I think this is easy to understand. STEP 2: ------- Now we've got to add something that isn't sooo important, but I will need this in STEP 4. We'll get some names for those Polygon lines (Edges). 2.1 1 /|\ a b c / P|Q \ / | \ 2-d--3--e-4 \ | / \ R|S / f g h \|/ 5 You see, we'ge got 8 Lines. Line V1 V2 ------------------ a 1 2 b 1 3 c 1 5 d 2 3 e 3 4 f 2 5 g 3 5 h 4 5 STEP 3: ------- Now let's divide the model into 2 Groups: 3.1 1 /|\ a.b~c /.P|Q~\ /...|~~~\ 2-d--3--e-4 \...|~~~/ \.R|S~/ f.g~h \|/ 5 The Vertex Coords and Polygon Indices are the same as in STEP 1. But we've got 2 Groups now: DOT-Group: Vertices: 1, 2, 3, 5 Polygons: P, R Lines : a, b, d, f, g TILDE-Group: Vertices: 1, 3, 4, 5 Polygons: Q, S Lines : b, c, e, g, h That's the way a "normal" Model Format would group the Model. The Group tells you that it uses these Vertices, Polygons and Lines. STEP 4: ------- But now the p Format isn't a "normal" Format :-| I think Square wanted to create a riddle for us hackers... ;) The FF7 engine will "violently" rip this model into 2 DIFFERENT groups... Every Vertex and Edge, that is used by both Groups, will be "cloned", so every Group is independent from each other. Now the Indices of the Polygons and Lines will have to be corrected... 4.1 DOT-Group: 1 /| a b / P| / | 2-c--3 \ | \ Q| d e \| 4 4.2 TILDE-Group: 1 |\ a-b |P \ | \ 2--c-3 | / |Q / d-e |/ 4 As you can see, every Group now has its own Indices for Vertices, Polygons and Lines. But there is only 1 Vertex Chunk, 1 Polygon Chunk and 1 Edge Chunk in the p Model... So let's now draw both Groups at once: (I've had to let space between the two groups, in fact the Vertices 1 and 5 are on excatly the same coords...) 4.3 ABSOLUTE INDICES: 1 5 /| |\ a b f g / P| |R \ / | | \ 2-c--3 6--h-7 \ | | / \ Q| |S / d e i j \| |/ 4 8 So every Vertex, Polygon and Edge can be "called" in 2 ways: By its absolute Index and its Index within the Group. Here's a listing: Absolute DOT TILDE ------------------------------ 1 1 - 2 2 - 3 3 - 4 4 5 - 1 6 - 2 7 - 3 8 - 4 a a - b b - c c - d d - e e - f - a g - b h - c i - d j - e P P - Q Q - R - P S - Q In other Words: Vertex 1 of the DOT-Group is Vertex 1 in the (absolute) Vertex Chunk, and Vertex 1 of the TILDE-Group is Vertex 5 in the (abs.) Vertex Chunk. The Same Thing it with the Polygons and Edges. STEP 5: ------- Now, in the FF7 p Format, there is another "problem". Take a look at the TILDE-Group in Image 4.2 and 4.3 Do you see it? Let's take Polygon P from 4.2. It is Polygon R in 4.3. But that's not the problem. The Problem is that in 4.2 the Polygon uses Vertices 1, 2 and 3 (from this Group) and in 4.3, the Polygon uses Vertices 5, 6 and 7. In 4.2 the Polygon consists of the Lines a, b and c, in 4.3 it consists of f, g and h. This is the way the Indices are handled in the FF7 p format. So in the Vertex Chunk there are all vertices of Group 1 first, then all of Group 2 and so on. So, if you want to add a Vertex to Group 1, you will have to correct the Vertex Offset of Group 2, 3, ... INFO: You can now take a look at those Ultima Info Files to see what I mean. Scroll throught the file until you reach the end of the Polygon Chunk. The Indices of the Vertices and Edges rise and rise, but then, suddenly, they start from 0 again. This is where a new group begins. BUT(T): There is another problem, which is, at least in my opinion, totally unlogical. In the same file, scroll down (or up) to the end of the Edges Chunk. And? Hm ... you won't see any groups there. IMPORTANT: This is b'cause the Edges Chunk hold the ABSOLUTE Vertex Indices, not the Group-Ones !!! So if you want to add a Vertex to Group 1, you will not only have to correct the Edges offset, but also the Vertex Indices in the Edges of Group 2. I hope this was understandable... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- I will keep on working on the format, and I hope that I will be soon able to generate new p Model that will be accepted by FF7 without crashing. Keep on checkin' my site, there *should* be an update soon... If you find out anything else 'bout the .p format, or if you find any bugs in this document, tell me. ~~ Special Thanx: ~~ Ficedula mirex NeutopiaW phaeron Qhimm ~~ Greetings Fly Out To: ~~ Darkness Ficedula Kaddy #17 Kaoru S. Night Mirex Phaeron Qhimm Sephiroth 3D ShinRa Inc. The SaiNt ... and everyone I've forgotten! See you on Qhimm's Messageboard ..or on mine ;) Home : http://www.alhexx.com Forum : http://forums.alhexx.com Mail : alhexx@alhexx.com - Alhexx 21:53 18.03.2004