Jump to content

kion

Members
  • Content Count

    62
  • Joined

  • Last visited

  • Days Won

    9

kion last won the day on December 24 2019

kion had the most liked content!

Community Reputation

51 Guardian

About kion

  • Rank
    2★

Console Information

  • Xbox 360
    No
  • Playstation 2
    No
  • PC/JP
    No

Recent Profile Visitors

101 profile views
  1. I guess I should also establish a schedule. My visa expires on 3/17, it's 1/12 now. I kind of want to use January to get all of the documents together, and then be ready to get the application in early February. That way if I need to scramble for any last papers, then I can still safely finish by February, and be done before things get crazy in March. Though I still need to keep searching to kind the papers I need. It would be nice if the application process was clearly explained on the Japanese Ministry of Justice website, but I guess this country can't be expected to clearly define it's laws or rules. Otherwise how would the lawyers get their bribes? I should probably first try to think of some strategies for finding where to find the list of documents I need for the application. 1. Search around on the MOJ site more 2. Try to search google for the application form + sames of other documents that I probably need 3. Search around on more english websites 4. Ask on reddit? (that actually might be a good idea) 5. Look for youtube videos with people describing the process and see if they have a list of documents
  2. I think .xnj might be pretty similar to .unj in terms of general structure. I think the main difference is that .unj seems to be a strip structure generated from the vertices directly and .xnj has a vertex list and faces are created by a list of indices. But that's only speculation from what little i've seen of the file type. For .xna, i think that's the bone names and it should look extremely similar to the NJTL texture list as I think they effectively use the same layout. And .xnm/.unm I think are actually the same file and layout. Though even after fixing the rig, it didn't make the animation look any better, so there is the possibility that the rotation angles need to be multiplied by the bind matrix or something like that. For now I'm really tempted to keep playing around .unm, but I have renew my visa. So I think I'll have to hold off on PSP models for a week or two to do paperwork so I don't get deported from my country of residence. Edit: While it's generally off topic in the sense of model formats. It's kind of on topic for me as it helps my sleep deprived brain by writing everything else. I moved to Shizuoka last year, which means the place I need to get my visa renewed is: 静岡入国管理局 (i think), which is weird because it's not listed on the Japanese Immigration site, but then again it might not be because that's immigration and not visa (extension). And then my visa type is : 技術・人部知識・国際業務. So first I need to find out 1) where I need to go, and 2) I need to find a list of documents needed for extending my visa. Edit 2: For the place to go. I think it's actually 静岡出張所 and then 静岡入国管理局 is probably the visa department inside the general immigration building. In terms of location, it's only about 5-10 minutes from Shizuoka station, which shouldn't be too bad to get there and back. I'll just have to plan on going super early to avoid waiting too long: http://www.immi-moj.go.jp/soshiki/kikou/map/nagoya_shizuoka.html As for documentation required, this is what screwed me over last time as the only page listed on this internet is this: http://www.moj.go.jp/nyuukokukanri/kouhou/nyuukokukanri07_00095.html, which basically just says that you need a passport, to fill out a form and a photograph. Which I totally know is bullshit because when I updated my visa three years ago, that's the same page I found and I walked into the immigration office with those three things, and then they sent me over to window where I had to wait for 4 hours. And that's when they told me the the documents I actually needed which included the obvious things like proof of employment and proof of residence and stuff like that. I asked the rep at the window how to find that page from the internet and he completely froze up, so I probably need to keep looking on the internet to find the actual list of documents I need. Edit 3: This page actually describes what I need: https://www.tokyoimmigration.jp/?p=169 If you are working for a company, you need a certificate of the registered matters of the company, and matters reported to the taxation office, a certificate of employment, a contract of employment, a certificate of tax payment (national tax as well as residential tax) and others. From April 2010, it is recommended that you submit a copy of your health care card as well. It is not compulsory to have a social health care card for the visa purpose, ... Though this is weird though. I can't really find any site that gives a clear list of documents that need to be submitted. All of the sites seem to be focused on forcing people to get the services of a lawyer for the application. Which seems scummy, because the only difference is the lawyer is going to know which documents need to be submitted, force you to provide them, and then charge you for the privilege of hiring them. I guess I'll start making a list of documents I know I'll probably need, and a list of documents I definitely need. Definitely need: 1. 在留期間更新許可申請書 Application for change of status of visa http://www.moj.go.jp/content/001290202.pdf 2. 3cm width x 4cm height portrait photo 3. Residence Card 4. Passport 5. Marriage Certificate (for wife as a dependent) 6. Health Insurance card Probably Need: 1. Proof of residence 2. Proof of employment 3. Proof of taxes (local) 4. Proof of taxes (national) 5. Income slip 6. Proof of company registration (Toukijikou Shoumeisho)
  3. I was pretty frustrated this afternoon of trying tons of different combinations and coming up short every time. Writing the recursive function approach actually helped a lot in getting me to turn a corner. Up until that point I was using the prs values and thinking "this is close, I just need to tweak this", and I ended up focusing on the prs values too much. After writing the recursive approach and getting the exact same result it made me realize that I shouldn't be trusting the prs values, and that I should take a look at the included matrix again. For the included matrix I wasn't exactly sure what it was, but knowing that the root bone isn't going to be multiplied by a parent, i went ahead and compared the prs values to the included matrix and found that I could get the prs matrix by transposing and inversing it. If I could calculate the matrix that way, it means that I could possibly get the local, or world and then local matrix for the other bones and worked my way through it. To be honest now that we have some working values, it's potentially viable to use the same approach as .nj using the prs values. Since we have a local matrix to check against, it's possible to look at the prs values, compare with the decomposed local matrix and then figure out which flags need to be set when and why. But as far as displaying the model, the inverse approach ended up being pretty simple, so I'm not in any particular hurry to look into it.
  4. After loosing my sanity it finally worked!!! const bindMat = new THREE.Matrix4(); bindMat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); bindMat.transpose(); const childWorldMat = new THREE.Matrix4(); childWorldMat.getInverse(bindMat, true); ofs += 0x90; let num = i.toString(); while (num.length < 3) { num = "0" + num; } const bone = new THREE.Bone(); bone.name = "bone_" + num; let localMatrix = new THREE.Matrix4(); const parentBone = this.MEM.bones[unj_bone_t.parent_id]; if (parentBone) { localMatrix.getInverse(parentBone.matrixWorld, true); parentBone.add(bone); } localMatrix.multiply(childWorldMat); bone.applyMatrix(localMatrix); bone.updateMatrix(); bone.updateMatrixWorld(); this.MEM.bones.push(bone); So this is kind of nuts. The way it works is that the bone includes a 4x4 inverse bind matrix. To convert the bind matrix to the child's world matrix you transpose and then invert this matrix. Once we have the child's world matrix, we then need to derive the local matrix. In the case of the root bone, the world and local matrix are the same value. So we can get ahead and set as-is. In the case the child has a parent bone, then we need to use another inverse matrix. So the child's world matrix is going to be the parent's world matrix by child's local matrix. We have the child's world matrix, and we have the parent's world matrix. We inverse the parent's world matrix and multiply that by the child's world matrix and that gives us the local matrix, which means we have our bone!
  5. Recursive approach using similar style as nj: readBones() { let ofs = this.header.bone_ofs; const tmpBones = []; for (let i = 0; i < this.header.bone_count; i++) { const unj_bone_t = { flags: this.view.getUint32(ofs + 0x00, true); bone_id: this.view.getInt16(ofs + 0x04, true); parent_id: this.view.getInt16(ofs + 0x06, true); child_id: this.view.getInt16(ofs + 0x08, true); sibling_id: this.view.getInt16(ofs + 0x0a, true); pos: { x: this.view.getFloat32(ofs + 0x0c, true), y: this.view.getFloat32(ofs + 0x10, true), z: this.view.getFloat32(ofs + 0x14, true) }, rot: { x: this.view.getInt32(ofs + 0x18, true) * Math.PI / 0x8000, y: this.view.getInt32(ofs + 0x1c, true) * Math.PI / 0x8000, z: this.view.getInt32(ofs + 0x20, true) * Math.PI / 0x8000 }, scl: { x: this.view.getFloat32(ofs + 0x24, true), y: this.view.getFloat32(ofs + 0x28, true), z: this.view.getFloat32(ofs + 0x2c, true) } } const mat = new THREE.Matrix4(); mat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); mat.transpose(); unj_bone_t.mat = mat; ofs += 0x90; tmpBones.push(unj_bone_t); } tmpBones.forEach(bone => { bone.child = tmpBones[bone.child_id]; bone.sibling = tmpBones[bone.sibling_id]; }); multiplyBones(tmpBones[0]); } multiplyBones(unj_bone_t, parentBone) { let num = this.MEM.bones.length.toString(); while (num.length < 3) { num = "0" + num; } let bone = new THREE.Bone(); bone.name = "bone_" + num; this.MEM.bones.push(bone); bone.scale.x = unj_bone_t.scl.x; bone.scale.y = unj_bone_t.scl.y; bone.scale.z = unj_bone_t.scl.z; let xRotMatrix = new THREE.Matrix4(); xRotMatrix.makeRotationX(unj_bone_t.rot.x); bone.applyMatrix(xRotMatrix); let yRotMatrix = new THREE.Matrix4(); yRotMatrix.makeRotationY(unj_bone_t.rot.y); bone.applyMatrix(yRotMatrix); let zRotMatrix = new THREE.Matrix4(); zRotMatrix.makeRotationZ(unj_bone_t.rot.z); bone.applyMatrix(zRotMatrix); bone.position.x = unj_bone_t.pos.x; bone.position.y = unj_bone_t.pos.y; bone.position.z = unj_bone_t.pos.z; bone.updateMatrix(); bone.updateMatrixWorld(); if (parentBone) { parentBone.add(bone); bone.updateMatrix(); bone.updateMatrixWorld(); } if (unj_bone_t.child) { multiplyBones(unj_bone_t.child, bone); } if (unj_bone_t.sibling) { multiplyBones(unj_bone_t.sibling, parentBone); } } Result: Looks like there's something else going on.
  6. Can't say I need pointers on .nj, I have a fully capable ninja tool with stages and characters with animations and exports. The whole issue that I've having is that the same approach and code I used for NJ DOESN'T WORK. And other alternatives that i've tried ARE NOT WORKING. Try writing some code for UNJ and see how that works. I would be very grateful to have some working code to compare against. My Phantasy Star Portable code is available here: https://gitlab.com/dashgl/psp2i for this current project being working on and the example files are included in the dat folder.
  7. With transpose and pre-multiplied by inverse parent world: const mat = new THREE.Matrix4(); mat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); mat.transpose(); ofs += 0x90; const bone = new THREE.Bone(); bone.name = "bone_" + i.toString(); bone.userData = new THREE.Matrix4(); bone.userData.getInverse(mat, true); if (parent_id !== -1) { let parentBone = this.MEM.bones[parent_id]; mat.premultiply(parentBone.userData); parentBone.add(bone); } bone.applyMatrix(mat); this.MEM.bones.push(bone);
  8. Without transpose (skeleton doesn't even show up) const mat = new THREE.Matrix4(); mat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); //mat.transpose(); ofs += 0x90; let num = i.toString(); while (num.length < 3) { num = "0" + num; } const bone = new THREE.Bone(); bone.name = "bone_" + num; bone.userData = new THREE.Matrix4(); bone.userData.getInverse(mat, true); if (parent_id !== -1) { let parentBone = this.MEM.bones[parent_id]; mat.multiply(parentBone.userData); parentBone.add(bone); } bone.applyMatrix(mat); bone.updateMatrix(); this.MEM.bones.push(bone);
  9. I guess I might as well keep track of the insanity that are these god-forsaken bones. If we can't get the prs values to play nice, then maybe we can take the included world matrix, and then multiply it by the inverse of the parent's world matrix to try and get the local transforms that way. Good approach maybe? Code: const mat = new THREE.Matrix4(); mat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); mat.transpose(); ofs += 0x90; let num = i.toString(); while (num.length < 3) { num = "0" + num; } const bone = new THREE.Bone(); bone.name = "bone_" + num; bone.userData = new THREE.Matrix4(); bone.userData.getInverse(mat, true); if (parent_id !== -1) { let parentBone = this.MEM.bones[parent_id]; mat.multiply(parentBone.userData); parentBone.add(bone); } bone.applyMatrix(mat); bone.updateMatrix(); this.MEM.bones.push(bone); Result:
  10. I figured it would be a good idea to go back a document the bone struct, there's not a lot going on. But might as well. (Red) The first dword is the flags. Agrajag says that he's able to calculate the bone position while ignoring these flags. So this is probably more for debug, and bones in the game should probably be encoded as "ready to go" (Green) Following the flags are the bone indexes, which is made up of four signed short (int16_t) values. We have bone id (unique or -1), parent index (-1 for none), child index(id?) and sibling index(id?). Though in general the inclusion of children and siblings seems like a waste of time, since any given bone can have several children, but only a single parent. So the easiest way is to look at the parent index. (Orange) For orange we have floats for the XYZ local position. (Dark Blue) For Dark blue we have signed ints (int32_t) for the rotation angles. This should work the same way as pso where 0x10000 represents 360 degrees. So to get to degrees you multiply the original int value by 360 and divide by 0x10000 to get degrees, and to get to radians you divide the original int value by 0x8000 and multiply by PI. (Purple) For purple we have floats for the XYZ scale (Light blue) For light blue we have an included 4x4 matrix, in what seems to be column-major structure, as if you look at 0x60, 0x64, 0x68 these are the tx, ty, tz value which are listed on the right in row-major orientation. For the purpose of this matrix, I think the included position, rotation angles and scale represent the matrix's local transforms, and this matrix is the world position. Which means that it's the local transformation matrix multiplied through to the parents. If you're doing things right, you should be able to calculate this matrix and possibly use it for confirmation.
  11. I was able to confirm the hierarchy information with Agrajag. The id's are kind of a red herring. The first value is the bone_id, which is a unique number otherwise it's -1. For the parent_id, it's actually the parent_index. So you have a bone_id of 15 with a parent index of 15 (the previous bone). So my original implementation for the bone hierarchy was correct. Which means that I'll have to keep digging into the local transformation values and included matrix to try and debug where there is an issue with how or why the bone structure doesn't look like as intended. Here's the screenshot provided by Agrajag, and here's a debug log of my program confirming this structure works with the implementation of bone indexes instead of bone_id's. Bone Index: 0, Parent Index -1 Bone Index: 1, Parent Index 0 Bone Index: 2, Parent Index 1 Bone Index: 3, Parent Index 2 Bone Index: 4, Parent Index 3 Bone Index: 5, Parent Index 4 Bone Index: 6, Parent Index 5 Bone Index: 7, Parent Index 4 Bone Index: 8, Parent Index 7 Bone Index: 9, Parent Index 8 Bone Index: 10, Parent Index 9 Bone Index: 11, Parent Index 4 Bone Index: 12, Parent Index 11 Bone Index: 13, Parent Index 12 Bone Index: 14, Parent Index 13 Bone Index: 15, Parent Index 1 Bone Index: 16, Parent Index 15 Bone Index: 17, Parent Index 16 Bone Index: 18, Parent Index 1 Bone Index: 19, Parent Index 18 Bone Index: 20, Parent Index 19 Bone Index: 21, Parent Index 0 I also created a debug of all of the values for the local transformation values and included world matrix: https://pastebin.com/ZcgCYmyd
  12. I went back to start printing values to try and figure out where the skeleton was going wrong, and I think I discovered the issue. It ended up not being able multiplication, or multiplication order, it was that I was assuming the id definitions would be in order. So when I went back to double-check the position, rotation and scale values I was completely caught off-guard that the hierarchy values don't make any sense at all. The order for the values is bone_id, parent_id, child_id and sibling_id. So we look at bone 8, which has bone 10 listed as its child, and then we have bone 10, which has bone 4 listed as its parent. So which one is supposed to take priority? There's also a lot of other problems like a bone id being listed as -1, but then we also have bones 20 listed as a child and bone 21 listed as a sibling, even though these bone id's are never actually declared. It would be nice if these -1 bones weren't needed in the hierarchy, but then the second bone in the list has bone 2 listed as it's child. And then bones 2, 3, 4, 7, 8, 9, 11, 12, 13, 15, 16 and 18 all have themselves listed as their own parent values. We need a way not only to interpret these values, and then to manage the references for the bone_ids as they're not in order.
  13. Nailed it!!! bandicam 2020-01-08 13-25-24-495.mp4 Just kidding. I've had pretty good luck up to this point, now seems like I need to go cry in the corner. We'll call it "Kion's corner". But looking back, it does look like my base skeleton is probably wrong. Referring to the .nj booma the knees bend forward, so I should spend some more time on bones until that works, Seems like I should try to be methodical with how I approach the bone transformations. I've tried a lot of different options, like using the included pos,scl, rot values, changing the order these are multiplied in, using the included world matrix, multiplying by the inverse of the parent, multiplying by the parent, multiplying by the inverse of the world matrix, and probably a lot more. Like I've really tried a lot and using the prs values seems to have gotten the closest to something that looks like it works, but not correct. So I should probably try and be more specific about what I've tried, what the order was, was the code was, and what the result is for each approach.
  14. Now that we have a little information about the animation file format, we can try and make a strategy for attempting to parse and apply the animation to the file. The important aspect is to attempt to get the rotation values for each bone. Anything else, we can come back and attempt to address afterwards. Before we start the file, we're going to need the skeleton for the skinned mesh the animation is to be applied to. So in terms of order, we will read the animations after the mesh. When we create our UnmReader instance, we will need to set the skeleton, likely requiring it in the constructor function would probably be a good idea. For parsing, we accept the binary array buffer for an animation file as the argument. We start out by doing the normal check of looking at the magic number 'NUMO', and then checking the provided file length against the length of the arraybuffer provided to the function. After that we read the pointer to the header. So not much here. We can make a quick struct to keep track. struct unm_magic_t { uint32_t magic; unit32_t length; uint32_t header_ofs; uint32_t nop; } Then we read the header. The main things to look for in this struct are going to be the number of frames, the number of bone entries in the animation table, and the offset to the start of the table. Since the header is only one offset from the start of the file, it would probably be a good idea to loop through all of the animation files we have and try to see if there are any patterns in the header, like which values are constant and which values change depending on the animation, and which animations share certain values. In terms of struct, it looks like most of the values in the header are actually short. But unless we have more specific information about what some of these values are, I don't see any reason to try and make guesses too soon about how unknown values are used, so I will label them as unit32_t's, and then we can define more specific types as needed later. struct unm_header_t { unit32_t flags; uint32_t zero; float anim_length; uint2_t motion_count; uint32_t motion_ofs; float frames_per_second; uint32_t nop[4]; } From there we have the motion data table. For this one, we can generally assume the first dword is a flag, we don't know what the middle 6 dwords are for, and then the last three dwords are for the count, size and offset to the key frame values. struct unm_motion_t { uint32_t keyframe_type; uint16_t short_a; uint16_t short_b; uint32_t bone_id; float unknown_a; // zero? float unknown_b; // 40.0f? float unknown_c; // zero? float unknown_d; // 40.0f? uint32_t keyframe_count; uint32_t keyframe_size; uint32_t keyframe_ofs; } And then last we have our key frames. Which are kind of weird. We have two kind of keyframe values float (probably for position), and rotation. The rotation values are pretty self explanatory and use the same format as psobb for the rotation values. struct unm_rotframe_t { uint16_t frame_no; uint16_t rot_x; uint16_t rot_y; uint16_t rot_z; } For position, we would generally expect float values for x, y, and z. That's not really the problem. The weird part is the first float starts at 0.0f and counts up to 40.0f even though there should be 0x30 frames. So I'm not sure if this is some kind of time manage management thing, where the position defines the time scale, and then the rotation values are actually referring to a position index to get their time frame? We'll go ahead and label it time for now, and we'll see how well my sanity manages to hold up on this one, before crying in the corner. struct unm_posframe_t { float time; float pos_x; float pos_y; float pos_z; }
  15. With that we can go ahead and look back and the data we passed over. First stop is the magic number. We confirmed a size of 0x10. Looks like the value of 0x0c is 0x00 and doesn't seem to be used for anything. For the header we know that 0x15 is the number of bones and 0x0ba0 is the offset to the start of the animation table. We don't really have any hints to go on for the rest of the values unless we start comparing with more files. So I might need to write a loop that reads through all of the .unm files in the archive, and then reads and compares these values for differences. Edit: I went back and looked at 0x00002042 and 0x0000f041 with a float converter and it looks like those are values for 40.0f and 30.0f respectively. So that could be the number of position frames for 40, and likely the number of rotation frames for 30. But it doesn't exactly make sense why these values would be different. We can look back at the table, and some of the values look more recognizable. The first dword, which is 0x0701 for the first struct and 0x3812 for every struct afterwards seems to be some kind of identifier. We can skip ahead to see the third dword (underlined in red) is the bone number for each struct. So we see the first two structs are actually bone 0, probably one for position and the other for rotation, and then the next struct skips to bone 2. So I think that explains why the number of bones in the header doesn't match the number in the model. Probably because only bones with key frames are encoded into the animation file. It also means that instead of skipping over the root bone, we actually have two list of key frames to read for the root bone. Also labeled in the images is the count, size, and offset of each of the key framed value lists. So in the first root bone position, we have the count of 0x28 frames, which is probably a position key frame for every frame in the animation, and also gives us the total number of frames in the animation. Following that is the size of each key frame value, which is 0x10 for position and 0x08 for rotation. And then the last dword in the table struct is the offset to the key frame values themselves. Lastly are the middle 4 dwords, which look like they almost have some kind of pattern going 0x000, 0x2042, 0x0000, 0x2042 for most of the structs, but not all of them. We also see the second struct which is mostly 0x02000200 for most of the structs, but not all of them. It doesn't look like these values have any functionality from the context of the file around them. But if they didn't do anything, they probably wouldn't be there. But for now we can cautiously attempt to try to ignore these values, and potentially attempt to test these values by editing the memory in the emulator if needed, or see if the file can be parsed successfully without interpreting what these other unknown values mean.
×
×
  • Create New...