From what I've seen it goes something like this: Let x = 7th section offset in ARD file's header. Goto offset x. Let y = 2nd offset from section beginning at x. Goto beginning of ARD file. So x+y would give you an offset to a table of strings. This seems to be the pattern in several files, but I might be wrong.
Awesome, now the real translating side of things can happen :). Found a bug in your ARDread program, though. As a test, I used dh01.ard (which contains text from the first cut scene in the game, including "So much to do," and "so little time..." in the English version). It extracts 3 text files containing 4, 0, and 99 strings each. For the second one (0 strings), it gives a red error instructing me to submit the name ;) This was the output when using the Final Mix version ARD, but the same error occurs with the US version at different offsets. It didn't extract the area of the file that contained "So much to do," in the US version (which is in the section beginning at 0x3ED80 in dh01.ard, the 7th offset in the initial header). I belive this bad entry resulted in the <1KB file named "dh01.ard-1.txt" How are you determining if an ARD section contains strings? I noticed when I try to extract strings from another ARD (di01.ard, containing first Destiny Islands cut scene), the cut scene strings are again found in the 7th section of the ard (err, pointed to by the 7th offset in the begging of the file), but this time your tool successfully extracts the strings to di01.ard-1.txt. I think the cutscene text from dh01.ard is supposed to be extracted to dh01.ard-1.txt, but your error catching routine discards it as bad data. Oddly, the part that contains the strings in dh01 does in fact end with the "Obtained Postcard." line.
Yes, in the OP. Gummy HP code in under "HP MODS" and all the rest you'd want are in the attachment
Great work, as always! If I wasn't so bogged down with school and work, I'd have time to look at how text is stored in other files (like the ARD files and evm). I think I said something about this in an earlier post, but ARD files seem to be a collection of assets for various levels/rooms in the game. Most of them begin with 3D meshes for interactive (non-static) objects, like doors/crates/etc. There're strings near the end too that are used in the level, seems to include captions for NPC speech bubbles as well as cut scene subtitles.
Ahh, I knew it :/. Well, good things take time
I've always heard that SM Coder used AR2 encrypted codes, but maybe I'm wrong. I converted the codes to AR2 for you using Omniconvert 1.1.1, see if these do the trick: kingdom hearts 2 final mix. (M) master code : CB6+/Gs3+/Xp4+ BCA70B09 14265F05 409BA208 1456E7A5 Inf MP In Battle (Pyriels Code Ported) 1C9B8228 90D3E5CD 1C9B822C 90D5E621 1C9B8230 1433D7A0 1C9B8234 0C58698D 1C9B8238 B0D4E625
Try this one: http://kh-vids.net/threads/kingdom-hearts-ii-final-mix-codes.117695/page-66#post-4063896
Here's a code that will switch the X and O buttons, made with PS2 Controller Remapper. I didn't have a chance to test it, but it should work fine: Spoiler Code: 202f5e7c 0803f408 200fd020 00a0782d 200fd024 24020334 200fd028 080bd7a1 202f5f44 0803f40b 200fd02c 3c08000f 200fd030 3508d000 200fd034 8de90000 200fd038 ad090000 200fd03c 8de90004 200fd040 ad090004 200fd044 8de90008 200fd048 ad090008 200fd04c 8de9000c 200fd050 ad09000c 200fd054 8de90010 200fd058 ad090010 200fd05c 81e9000c 200fd060 a109000b 200fd064 2d2a0080 200fd068 85090000 200fd06c 11400002 200fd070 3129dfff 200fd074 35292000 200fd078 a5090000 200fd07c 81e9000b 200fd080 a109000c 200fd084 2d2a0080 200fd088 85090000 200fd08c 11400002 200fd090 3129bfff 200fd094 35294000 200fd098 a5090000 200fd09c 8d090000 200fd0a0 ade90000 200fd0a4 8d090004 200fd0a8 ade90004 200fd0ac 8d090008 200fd0b0 ade90008 200fd0b4 8d09000c 200fd0b8 ade9000c 200fd0bc 8d090010 200fd0c0 ade90010 200fd0c4 03e00008 200fd0c8 00000000
202f5e7c 0803f408 200fd020 00a0782d 200fd024 24020334 200fd028 080bd7a1 202f5f44 0803f40b 200fd02c 3c08000f 200fd030 3508d000 200fd034 8de90000 200fd038 ad090000 200fd03c 8de90004 200fd040 ad090004 200fd044 8de90008 200fd048 ad090008 200fd04c 8de9000c 200fd050 ad09000c 200fd054 8de90010 200fd058 ad090010 200fd05c 81e9000c 200fd060 a109000b 200fd064 2d2a0080 200fd068 85090000 200fd06c 11400002 200fd070 3129dfff 200fd074 35292000 200fd078 a5090000 200fd07c 81e9000b 200fd080 a109000c 200fd084 2d2a0080 200fd088 85090000 200fd08c 11400002 200fd090 3129bfff 200fd094 35294000 200fd098 a5090000 200fd09c 8d090000 200fd0a0 ade90000 200fd0a4 8d090004 200fd0a8 ade90004 200fd0ac 8d090008 200fd0b0 ade90008 200fd0b4 8d09000c 200fd0b8 ade9000c 200fd0bc 8d090010 200fd0c0 ade90010 200fd0c4 03e00008 200fd0c8 00000000
Using the recently released "PS2 Controller Remapper", I was able to generate a code to swap the X and O buttons, so the game plays more like the NTSC/PAL versions Spoiler Code: 20268cec 0803f408 200fd020 00a0782d 200fd024 24020330 200fd028 0809a33d 20268db4 0803f40b 200fd02c 3c08000f 200fd030 3508d000 200fd034 8de90000 200fd038 ad090000 200fd03c 8de90004 200fd040 ad090004 200fd044 8de90008 200fd048 ad090008 200fd04c 8de9000c 200fd050 ad09000c 200fd054 8de90010 200fd058 ad090010 200fd05c 81e9000c 200fd060 a109000b 200fd064 2d2a0080 200fd068 85090000 200fd06c 11400002 200fd070 3129dfff 200fd074 35292000 200fd078 a5090000 200fd07c 81e9000b 200fd080 a109000c 200fd084 2d2a0080 200fd088 85090000 200fd08c 11400002 200fd090 3129bfff 200fd094 35294000 200fd098 a5090000 200fd09c 8d090000 200fd0a0 ade90000 200fd0a4 8d090004 200fd0a8 ade90004 200fd0ac 8d090008 200fd0b0 ade90008 200fd0b4 8d09000c 200fd0b8 ade9000c 200fd0bc 8d090010 200fd0c0 ade90010 200fd0c4 03e00008 200fd0c8 00000000
20268cec 0803f408 200fd020 00a0782d 200fd024 24020330 200fd028 0809a33d 20268db4 0803f40b 200fd02c 3c08000f 200fd030 3508d000 200fd034 8de90000 200fd038 ad090000 200fd03c 8de90004 200fd040 ad090004 200fd044 8de90008 200fd048 ad090008 200fd04c 8de9000c 200fd050 ad09000c 200fd054 8de90010 200fd058 ad090010 200fd05c 81e9000c 200fd060 a109000b 200fd064 2d2a0080 200fd068 85090000 200fd06c 11400002 200fd070 3129dfff 200fd074 35292000 200fd078 a5090000 200fd07c 81e9000b 200fd080 a109000c 200fd084 2d2a0080 200fd088 85090000 200fd08c 11400002 200fd090 3129bfff 200fd094 35294000 200fd098 a5090000 200fd09c 8d090000 200fd0a0 ade90000 200fd0a4 8d090004 200fd0a8 ade90004 200fd0ac 8d090008 200fd0b0 ade90008 200fd0b4 8d09000c 200fd0b8 ade9000c 200fd0bc 8d090010 200fd0c0 ade90010 200fd0c4 03e00008 200fd0c8 00000000
For a look at how 3D models and textures are stored, take a look at the source for the Noesis KH plugin. The textures in particular aren't your typical TIM2 images; they use their own format. Here's a link to the plugin w/ the source included: http://oasis.xentax.com/files/plug/kingdom_hearts.zip. Below is the meat of the plugin Spoiler Code: //this is kind of a poor example for plugins, since the format's not totally known and the code is WIP. //but it does showcase some interesting usages. #include "stdafx.h" mathImpFn_t *g_mfn = NULL; noePluginFn_t *g_nfn = NULL; #pragma pack(push, 1) typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned long uint32; typedef unsigned __int64 uint64; typedef signed char int8; typedef signed short int16; typedef signed long int32; typedef signed __int64 int64; struct MDLS_MOBJ_HEADER { // 0x00 uint32 dataTag; // 'MOBJ' uint32 dataSize; uint32 imageInfoOffset; uint32 imageInfoSize; // 0x10 uint32 imageOffset; uint32 imageSize; uint32 clutOffset; uint32 clutSize; // 0x20 uint32 meshDataOffset; uint32 meshDataSize; uint32 unknownOffset0x28; uint32 unknownSize0x2C; // 0x30 uint32 unknown0x30; uint32 unknown0x34[3]; }; struct MDLS_IMAGE_INFO { uint16 dataSize; // image data size / 16 uint8 w_exp; // width exponent (2^w) uint8 h_exp; // height exponent (2^h) uint16 width; uint16 height; uint32 unknown0x0C[2]; }; struct MDLS_MODEL_HEADER { // 0x00 uint32 jointCount; uint32 jointDataOffset; uint32 unknownOffset0x08; uint32 meshCount; }; struct MDLS_JOINT_BLOCK { // 0x00 uint32 dataTag; uint32 jointIndex; uint32 tableIndex; uint32 unknown0x0C; // 0x10 uint32 unknown0x10[28]; }; struct MDLS_MESH_ENTRY { uint32 unknown0x00; // unk, object id, unk, unk uint32 unknown0x04; // texture index uint32 unknown0x08; // texture index uint32 dataOffset; }; struct MDLS_VERTEX { // 0x00 float normal[3]; uint32 jointIndex; // 0x10 float position[3]; float jointWeight; // 0x20 float textureCoord[3]; uint32 unknown0x2C; // ? }; struct MDLS_VERTEX_BLOCK { // 0x00 uint32 dataTag; uint32 dataSize; uint32 vertexCount0; uint32 unknown0x0C; // 0x10 uint32 vertexCount1; uint32 unknown0x14; uint32 unknown0x18; uint32 unknown0x1C; // 0x20 //struct MDLS_VERTEX vertices[vertexCount0]; }; struct MDLS_MODEL_JOINT { float scale[3]; uint32 index; float rotation[3]; uint32 padding; float position[3]; int32 parent : 10; int32 sibling : 10; int32 child : 10; int32 unk1 : 2; }; struct MDLS_MATRIX_BLOCK { // 0x00 uint32 dataTag; uint32 dataSize; uint32 lineCount; uint32 unknown0x0C; // 0x10 uint32 matrixIndices[1]; }; #pragma pack(pop) //see if something is valid KH .mdls data bool Model_KH_Check(BYTE *fileBuffer, int bufferLen, noeRAPI_t *rapi) { if (bufferLen < (sizeof(MDLS_MOBJ_HEADER) + 8)) { return false; } // read mobj data offset uint32 mobjOffset = *((uint32*)(fileBuffer + 4)); if (bufferLen < (sizeof(MDLS_MOBJ_HEADER) + mobjOffset)) { return false; } uint8 *mobjData = (fileBuffer + mobjOffset); MDLS_MOBJ_HEADER *mobjHeader = (MDLS_MOBJ_HEADER *)(mobjData); if (mobjHeader->dataTag != 0x4A424F4D) { return false; } if ((bufferLen < (mobjHeader->dataSize + mobjOffset)) || (mobjHeader->imageInfoOffset > 0 && bufferLen < (mobjHeader->imageInfoOffset + mobjOffset)) || (mobjHeader->imageOffset > 0 && bufferLen < (mobjHeader->imageOffset + mobjOffset)) || (mobjHeader->clutOffset > 0 && bufferLen < (mobjHeader->clutOffset + mobjOffset)) || (mobjHeader->meshDataOffset > 0 && bufferLen < (mobjHeader->meshDataOffset + mobjOffset))) { return false; } return true; } //load texture bundle static void Model_KH_LoadTextures(CArrayList<noesisTex_t *> &textures, CArrayList<noesisMaterial_t *> &materials, BYTE *data, int dataSize, noeRAPI_t *rapi) { MDLS_MOBJ_HEADER *mobjHeader = (MDLS_MOBJ_HEADER *)(data); char matName[128] = {0}; noesisTex_t *nt = NULL; noesisMaterial_t *nmat = NULL; // calculate texture count int textureCount = mobjHeader->imageInfoSize >> 4; if (textureCount > 0) { // calculate image data offsets uint8 *imageInfo = (data + mobjHeader->imageInfoOffset); uint8 *imageData = (data + mobjHeader->imageOffset); uint8 *clutData = (data + mobjHeader->clutOffset); // read texture information int texNum = 0; for (texNum = 0; texNum < textureCount; ++texNum, imageInfo += sizeof(MDLS_IMAGE_INFO), clutData += 0x400) { MDLS_IMAGE_INFO &info = *((MDLS_IMAGE_INFO*)imageInfo); int imageSize = (info.dataSize << 4); uint8 *tempImage = (uint8 *)rapi->Noesis_UnpooledAlloc(info.width * info.height * 4); uint8 *dstPixel = tempImage; int pixelNum = 0; for (pixelNum = 0; pixelNum < imageSize; ++pixelNum, ++imageData, dstPixel += 4) { uint32 pixelIndex = *imageData; if ((pixelIndex & 31) >= 8) { if ((pixelIndex & 31) < 16) { pixelIndex += 8; // +8 - 15 to +16 - 23 } else if ((pixelIndex & 31) < 24) { pixelIndex -= 8; // +16 - 23 to +8 - 15 } } pixelIndex <<= 2; dstPixel[0] = clutData[pixelIndex + 0]; dstPixel[1] = clutData[pixelIndex + 1]; dstPixel[2] = clutData[pixelIndex + 2]; dstPixel[3] = (clutData[pixelIndex + 3] * 0xFF) >> 7; } // create texture sprintf_s(matName, 128, "texture%03d", texNum); nt = rapi->Noesis_TextureAlloc(matName, info.width, info.height, tempImage, NOESISTEX_RGBA32); nt->shouldFreeData = true; textures.Append(nt); sprintf_s(matName, 128, "material%03d", texNum); nmat = rapi->Noesis_GetMaterialList(1, true); nmat->name = rapi->Noesis_PooledString(matName); nmat->noDefaultBlend = false; nmat->noLighting = true; nmat->texIdx = texNum; materials.Append(nmat); } } } #define PI (3.1415926535897932384626433832795) #define DEG2RAD(a) ((a) * PI / 180.0) #define RAD2DEG(a) ((a) * 180.0 / PI) //convert the bones modelBone_t *Model_KH_CreateBones(BYTE *data, noeRAPI_t *rapi, int &numBones) { MDLS_MOBJ_HEADER *mobjHeader = (MDLS_MOBJ_HEADER *)(data); uint8 *modelData = (data + mobjHeader->meshDataOffset); MDLS_MODEL_HEADER *modelHeader = (MDLS_MODEL_HEADER*)modelData; numBones = 0; if (modelHeader->jointCount > 0) { uint8 *jointData = (modelData + modelHeader->jointDataOffset); // save joint count... numBones = modelHeader->jointCount; // ...and allocate joint data modelBone_t *bones = rapi->Noesis_AllocBones(numBones); modelBone_t *bone = bones; modelMatrix_t sclMatrix = g_identityMatrix; modelMatrix_t xMatrix = g_identityMatrix; modelMatrix_t yMatrix = g_identityMatrix; modelMatrix_t zMatrix = g_identityMatrix; modelMatrix_t rotMatrix = g_identityMatrix; MDLS_MODEL_JOINT *joint = (MDLS_MODEL_JOINT*)jointData; int jointNum = 0; for (jointNum = 0; jointNum < numBones; ++jointNum, ++joint, ++bone) { bone->index = jointNum; sprintf_s(bone->name, 30, "bone%03i", jointNum); // copy joint information sclMatrix.x1[0] = joint->scale[0]; sclMatrix.x2[1] = joint->scale[1]; sclMatrix.x3[2] = joint->scale[2]; rotMatrix = g_identityMatrix; g_mfn->Math_RotateMatrix(&rotMatrix, RAD2DEG(-joint->rotation[2]), 0, 0, 1); g_mfn->Math_RotateMatrix(&rotMatrix, RAD2DEG( joint->rotation[1]), 0, 1, 0); g_mfn->Math_RotateMatrix(&rotMatrix, RAD2DEG(-joint->rotation[0]), 1, 0, 0); g_mfn->Math_MatrixMultiply(&sclMatrix, &rotMatrix, &bone->mat); // g_mfn->Math_RotationMatrix( joint->rotation[0], 0, &xMatrix); // g_mfn->Math_RotationMatrix(-joint->rotation[1], 1, &yMatrix); // g_mfn->Math_RotationMatrix( joint->rotation[2], 2, &zMatrix); // g_mfn->Math_MatrixMultiply(&zMatrix, &yMatrix, &rotMatrix); // g_mfn->Math_MatrixMultiply(&rotMatrix, &xMatrix, &zMatrix); // g_mfn->Math_MatrixMultiply(&zMatrix, &sclMatrix, &bone->mat); g_mfn->Math_VecCopy(joint->position, bone->mat.o); bone->eData.parent = (joint->parent < 0) ? NULL : (bones + joint->parent); } // transform bones rapi->rpgMultiplyBones(bones, numBones); return bones; } return NULL; } //load a single model from a dat set static void Model_KH_LoadModel(BYTE* data, CArrayList<noesisTex_t *> &textures, CArrayList<noesisMaterial_t *> &materials, modelBone_t *bones, int numBones, noeRAPI_t *rapi) { MDLS_MOBJ_HEADER *mobjHeader = (MDLS_MOBJ_HEADER *)(data); uint8 *modelData = (data + mobjHeader->meshDataOffset); MDLS_MODEL_HEADER *modelHeader = (MDLS_MODEL_HEADER*)modelData; // process mesh information if (modelHeader->meshCount > 0) { uint8 *meshInfo = (modelData + 0x10); MDLS_MESH_ENTRY *meshEntries = (MDLS_MESH_ENTRY*)meshInfo; int meshCount = modelHeader->meshCount; char meshName[128] = {0}; int meshCnt = 0; int meshNum = 0; for (meshNum = 0; meshNum < meshCount; ++meshNum, ++meshEntries) { rapi->rpgSetMaterialIndex(meshEntries->unknown0x04); uint8 *meshStart = (meshInfo + meshEntries->dataOffset); uint32 vertexCount = 0; uint32 faceCount = 0; MDLS_MATRIX_BLOCK *matrixPalette = NULL; uint32 matrixIndices[16] = {0}; uint32 tableSize = 0; uint32 extraMax = 0; uint32 extraCount = 0; uint32 nextOffset = 0; do { uint32 dataTag = *((uint32*)meshStart); switch (dataTag) { case 0: { uint32 jointIndex = *((uint32*)(meshStart + 0x04)); uint32 tableIndex0 = *((uint32*)(meshStart + 0x08)); uint32 tableIndex1 = *((uint32*)(meshStart + 0x0C)); if (tableIndex1 == 0) { matrixIndices[tableIndex0] = jointIndex; } if (tableIndex1 > 0) { if (tableIndex1 == 1) tableSize = 0; matrixIndices[tableSize + 8] = jointIndex; ++tableSize; } nextOffset = 0x80; } break; case 1: { nextOffset = *((uint32*)(meshStart + 4)); MDLS_VERTEX_BLOCK *vertexBlock = (MDLS_VERTEX_BLOCK*)meshStart; if (vertexBlock->dataSize > 0 && vertexBlock->vertexCount0 > 0) { uint8 *vertexStart = (meshStart + 0x20); CArrayList<modelVert_t> vertices; CArrayList<modelVert_t> normals; CArrayList<modelTexCoord_t> texCoords; uint32 numVertices = vertexBlock->vertexCount0; uint32 vertexNum = 0; rapi->rpgBegin(RPGEO_TRIANGLE_STRIP); for (vertexNum = 0; vertexNum < numVertices; ++vertexNum) { uint32 jointInfo = *((uint32*)(vertexStart + 0x0C)); uint32 jointIndex = 0; uint32 positionCount = 1; uint8 *normalStart = vertexStart; vertexStart += 0x10; uint8 *positionStart = vertexStart; if (matrixPalette) { positionCount = jointInfo; vertexStart += 0x10 * jointInfo; } else { vertexStart += 0x10; } uint8 *uvStart = vertexStart; vertexStart += 0x10; CArrayList<DWORD> boneIndices; CArrayList<float> boneWeights; uint32 posNum = 0; float position[4] = {0}; float temp[4] = {0}; modelBone_t *bone = NULL; fourxMatrix_t jointMatrix = g_identityMatrix4x4; fourxMatrix_t vertexMatrix = g_identityMatrix4x4; fourxMatrix_t outputMatrix = g_identityMatrix4x4; for (posNum = 0; posNum < positionCount; ++posNum, ++extraCount) { if (matrixPalette) { jointIndex = matrixPalette->matrixIndices[extraCount]; if (jointIndex < 0x100) jointIndex = matrixIndices[jointIndex]; else { jointIndex = ((jointIndex & 0xFF) >> 3); matrixIndices[2] = matrixIndices[jointIndex + 8]; jointIndex = matrixIndices[2]; } } else { jointIndex = matrixIndices[jointInfo]; } bone = bones + jointIndex; memcpy(vertexMatrix.c4, (float*)positionStart, sizeof(vertexMatrix.c4)); g_mfn->Math_ModelMatToGL(&bone->mat, (float*)(&jointMatrix)); g_mfn->Math_MatrixMultiply4x4(&vertexMatrix, &jointMatrix, &outputMatrix); g_mfn->Math_VecAdd(outputMatrix.c4, position, position); boneIndices.Append(jointIndex); boneWeights.Append(vertexMatrix.c4[3]); positionStart += 0x10; } normals.Append(*((modelVert_t*)normalStart)); texCoords.Append(*((modelTexCoord_t*)uvStart)); vertices.Append(*((modelVert_t*)position)); if (vertexNum == 0 && vertexBlock->unknown0x0C != 0) { rapi->rpgVertBoneIndexUI(&boneIndices[0], boneIndices.Num()); rapi->rpgVertBoneWeightF(&boneWeights[0], boneWeights.Num()); rapi->rpgVertNormal3f((float*)normalStart); rapi->rpgVertUV2f((float*)uvStart, 0); rapi->rpgVertex3f((float*)position); } rapi->rpgVertBoneIndexUI(&boneIndices[0], boneIndices.Num()); rapi->rpgVertBoneWeightF(&boneWeights[0], boneWeights.Num()); rapi->rpgVertNormal3f((float*)normalStart); rapi->rpgVertUV2f((float*)uvStart, 0); rapi->rpgVertex3f((float*)position); } rapi->rpgEnd(); } } break; case 2: { matrixPalette = (MDLS_MATRIX_BLOCK*)meshStart; extraCount = 0; extraMax = matrixPalette->lineCount * 4; nextOffset = matrixPalette->dataSize; } break; case 0x8000: { nextOffset = *((uint32*)(meshStart + 4)); } break; default: { nextOffset = 0; } break; } meshStart += nextOffset; } while (nextOffset > 0); } } } //load it noesisModel_t *Model_KH_Load(BYTE *fileBuffer, int bufferLen, int &numMdl, noeRAPI_t *rapi) { numMdl = 0; if (bufferLen < (sizeof(MDLS_MOBJ_HEADER) + 8)) { return NULL; } // read mobj data offset uint32 mobjOffset = *((uint32*)(fileBuffer + 4)); if (bufferLen < (sizeof(MDLS_MOBJ_HEADER) + mobjOffset)) { return NULL; } uint8 *mobjData = (fileBuffer + mobjOffset); CArrayList<noesisTex_t *> textures; CArrayList<noesisMaterial_t *> materials; // attempt to load textures Model_KH_LoadTextures(textures, materials, mobjData, bufferLen, rapi); int numBones = 0; modelBone_t *bones = NULL; // process joint data bones = Model_KH_CreateBones(mobjData, rapi, numBones); void *pgctx = rapi->rpgCreateContext(); rapi->rpgSetEndian(false); rapi->rpgSetTriWinding(false); noesisMatData_t *md = rapi->Noesis_GetMatDataFromLists(materials, textures); rapi->rpgSetExData_Materials(md); rapi->rpgSetExData_Bones(bones, numBones); CArrayList<noesisModel_t *> models; Model_KH_LoadModel(mobjData, textures, materials, bones, numBones, rapi); #if 0 //create a procedural anim to move random bones around if (bones) { const int numMoveBones = 1 + rand()%(numBones-1); sharedPAnimParm_t *aparms = (sharedPAnimParm_t *)_alloca(sizeof(sharedPAnimParm_t)*numMoveBones); memset(aparms, 0, sizeof(aparms)); //it's a good idea to do this, in case future noesis versions add more meaningful fields. for (int i = 0; i < numMoveBones; i++) { aparms[i].angAmt = 25.0f; aparms[i].axis = 1; //rotate left and right aparms[i].boneIdx = rand()%numBones; //random bone aparms[i].timeScale = 0.1f; //acts as a framestep } noesisAnim_t *anim = rapi->rpgCreateProceduralAnim(bones, numBones, aparms, numMoveBones, 500); if (anim) { rapi->rpgSetExData_AnimsNum(anim, 1); } } #endif noesisModel_t *mdl = rapi->rpgConstructModel(); if (!mdl) { //nothing could be created return NULL; } models.Append(mdl); rapi->rpgDestroyContext(pgctx); materials.Clear(); if (models.Num() <= 0) { return NULL; } numMdl = models.Num(); noesisModel_t *mdlList = rapi->Noesis_ModelsFromList(models, numMdl); models.Clear(); return mdlList; } //called by Noesis to init the plugin NPLUGIN_API bool NPAPI_Init(mathImpFn_t *mathfn, noePluginFn_t *noepfn) { g_mfn = mathfn; g_nfn = noepfn; if (g_nfn->NPAPI_GetAPIVersion() < NOESIS_PLUGINAPI_VERSION) { //bad version of noesis for this plugin return false; } int fh = g_nfn->NPAPI_Register("Kingdom Hearts Model", ".mdls"); if (fh < 0) { return false; } //set the data handlers for this format g_nfn->NPAPI_SetTypeHandler_TypeCheck(fh, Model_KH_Check); g_nfn->NPAPI_SetTypeHandler_LoadModel(fh, Model_KH_Load); return true; } //called by Noesis before the plugin is freed NPLUGIN_API void NPAPI_Shutdown(void) { //nothing to do in this plugin } NPLUGIN_API int NPAPI_GetPluginVer(void) { return NOESIS_PLUGIN_VERSION; } NPLUGIN_API bool NPAPI_GetPluginInfo(noePluginInfo_t *infOut) { strcpy_s(infOut->pluginName, 64, "kingdom_hearts"); strcpy_s(infOut->pluginDesc, 512, "Kingdom Hearts model handler, by revel8n."); return true; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; }
//this is kind of a poor example for plugins, since the format's not totally known and the code is WIP. //but it does showcase some interesting usages. #include "stdafx.h" mathImpFn_t *g_mfn = NULL; noePluginFn_t *g_nfn = NULL; #pragma pack(push, 1) typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned long uint32; typedef unsigned __int64 uint64; typedef signed char int8; typedef signed short int16; typedef signed long int32; typedef signed __int64 int64; struct MDLS_MOBJ_HEADER { // 0x00 uint32 dataTag; // 'MOBJ' uint32 dataSize; uint32 imageInfoOffset; uint32 imageInfoSize; // 0x10 uint32 imageOffset; uint32 imageSize; uint32 clutOffset; uint32 clutSize; // 0x20 uint32 meshDataOffset; uint32 meshDataSize; uint32 unknownOffset0x28; uint32 unknownSize0x2C; // 0x30 uint32 unknown0x30; uint32 unknown0x34[3]; }; struct MDLS_IMAGE_INFO { uint16 dataSize; // image data size / 16 uint8 w_exp; // width exponent (2^w) uint8 h_exp; // height exponent (2^h) uint16 width; uint16 height; uint32 unknown0x0C[2]; }; struct MDLS_MODEL_HEADER { // 0x00 uint32 jointCount; uint32 jointDataOffset; uint32 unknownOffset0x08; uint32 meshCount; }; struct MDLS_JOINT_BLOCK { // 0x00 uint32 dataTag; uint32 jointIndex; uint32 tableIndex; uint32 unknown0x0C; // 0x10 uint32 unknown0x10[28]; }; struct MDLS_MESH_ENTRY { uint32 unknown0x00; // unk, object id, unk, unk uint32 unknown0x04; // texture index uint32 unknown0x08; // texture index uint32 dataOffset; }; struct MDLS_VERTEX { // 0x00 float normal[3]; uint32 jointIndex; // 0x10 float position[3]; float jointWeight; // 0x20 float textureCoord[3]; uint32 unknown0x2C; // ? }; struct MDLS_VERTEX_BLOCK { // 0x00 uint32 dataTag; uint32 dataSize; uint32 vertexCount0; uint32 unknown0x0C; // 0x10 uint32 vertexCount1; uint32 unknown0x14; uint32 unknown0x18; uint32 unknown0x1C; // 0x20 //struct MDLS_VERTEX vertices[vertexCount0]; }; struct MDLS_MODEL_JOINT { float scale[3]; uint32 index; float rotation[3]; uint32 padding; float position[3]; int32 parent : 10; int32 sibling : 10; int32 child : 10; int32 unk1 : 2; }; struct MDLS_MATRIX_BLOCK { // 0x00 uint32 dataTag; uint32 dataSize; uint32 lineCount; uint32 unknown0x0C; // 0x10 uint32 matrixIndices[1]; }; #pragma pack(pop) //see if something is valid KH .mdls data bool Model_KH_Check(BYTE *fileBuffer, int bufferLen, noeRAPI_t *rapi) { if (bufferLen < (sizeof(MDLS_MOBJ_HEADER) + 8)) { return false; } // read mobj data offset uint32 mobjOffset = *((uint32*)(fileBuffer + 4)); if (bufferLen < (sizeof(MDLS_MOBJ_HEADER) + mobjOffset)) { return false; } uint8 *mobjData = (fileBuffer + mobjOffset); MDLS_MOBJ_HEADER *mobjHeader = (MDLS_MOBJ_HEADER *)(mobjData); if (mobjHeader->dataTag != 0x4A424F4D) { return false; } if ((bufferLen < (mobjHeader->dataSize + mobjOffset)) || (mobjHeader->imageInfoOffset > 0 && bufferLen < (mobjHeader->imageInfoOffset + mobjOffset)) || (mobjHeader->imageOffset > 0 && bufferLen < (mobjHeader->imageOffset + mobjOffset)) || (mobjHeader->clutOffset > 0 && bufferLen < (mobjHeader->clutOffset + mobjOffset)) || (mobjHeader->meshDataOffset > 0 && bufferLen < (mobjHeader->meshDataOffset + mobjOffset))) { return false; } return true; } //load texture bundle static void Model_KH_LoadTextures(CArrayList<noesisTex_t *> &textures, CArrayList<noesisMaterial_t *> &materials, BYTE *data, int dataSize, noeRAPI_t *rapi) { MDLS_MOBJ_HEADER *mobjHeader = (MDLS_MOBJ_HEADER *)(data); char matName[128] = {0}; noesisTex_t *nt = NULL; noesisMaterial_t *nmat = NULL; // calculate texture count int textureCount = mobjHeader->imageInfoSize >> 4; if (textureCount > 0) { // calculate image data offsets uint8 *imageInfo = (data + mobjHeader->imageInfoOffset); uint8 *imageData = (data + mobjHeader->imageOffset); uint8 *clutData = (data + mobjHeader->clutOffset); // read texture information int texNum = 0; for (texNum = 0; texNum < textureCount; ++texNum, imageInfo += sizeof(MDLS_IMAGE_INFO), clutData += 0x400) { MDLS_IMAGE_INFO &info = *((MDLS_IMAGE_INFO*)imageInfo); int imageSize = (info.dataSize << 4); uint8 *tempImage = (uint8 *)rapi->Noesis_UnpooledAlloc(info.width * info.height * 4); uint8 *dstPixel = tempImage; int pixelNum = 0; for (pixelNum = 0; pixelNum < imageSize; ++pixelNum, ++imageData, dstPixel += 4) { uint32 pixelIndex = *imageData; if ((pixelIndex & 31) >= 8) { if ((pixelIndex & 31) < 16) { pixelIndex += 8; // +8 - 15 to +16 - 23 } else if ((pixelIndex & 31) < 24) { pixelIndex -= 8; // +16 - 23 to +8 - 15 } } pixelIndex <<= 2; dstPixel[0] = clutData[pixelIndex + 0]; dstPixel[1] = clutData[pixelIndex + 1]; dstPixel[2] = clutData[pixelIndex + 2]; dstPixel[3] = (clutData[pixelIndex + 3] * 0xFF) >> 7; } // create texture sprintf_s(matName, 128, "texture%03d", texNum); nt = rapi->Noesis_TextureAlloc(matName, info.width, info.height, tempImage, NOESISTEX_RGBA32); nt->shouldFreeData = true; textures.Append(nt); sprintf_s(matName, 128, "material%03d", texNum); nmat = rapi->Noesis_GetMaterialList(1, true); nmat->name = rapi->Noesis_PooledString(matName); nmat->noDefaultBlend = false; nmat->noLighting = true; nmat->texIdx = texNum; materials.Append(nmat); } } } #define PI (3.1415926535897932384626433832795) #define DEG2RAD(a) ((a) * PI / 180.0) #define RAD2DEG(a) ((a) * 180.0 / PI) //convert the bones modelBone_t *Model_KH_CreateBones(BYTE *data, noeRAPI_t *rapi, int &numBones) { MDLS_MOBJ_HEADER *mobjHeader = (MDLS_MOBJ_HEADER *)(data); uint8 *modelData = (data + mobjHeader->meshDataOffset); MDLS_MODEL_HEADER *modelHeader = (MDLS_MODEL_HEADER*)modelData; numBones = 0; if (modelHeader->jointCount > 0) { uint8 *jointData = (modelData + modelHeader->jointDataOffset); // save joint count... numBones = modelHeader->jointCount; // ...and allocate joint data modelBone_t *bones = rapi->Noesis_AllocBones(numBones); modelBone_t *bone = bones; modelMatrix_t sclMatrix = g_identityMatrix; modelMatrix_t xMatrix = g_identityMatrix; modelMatrix_t yMatrix = g_identityMatrix; modelMatrix_t zMatrix = g_identityMatrix; modelMatrix_t rotMatrix = g_identityMatrix; MDLS_MODEL_JOINT *joint = (MDLS_MODEL_JOINT*)jointData; int jointNum = 0; for (jointNum = 0; jointNum < numBones; ++jointNum, ++joint, ++bone) { bone->index = jointNum; sprintf_s(bone->name, 30, "bone%03i", jointNum); // copy joint information sclMatrix.x1[0] = joint->scale[0]; sclMatrix.x2[1] = joint->scale[1]; sclMatrix.x3[2] = joint->scale[2]; rotMatrix = g_identityMatrix; g_mfn->Math_RotateMatrix(&rotMatrix, RAD2DEG(-joint->rotation[2]), 0, 0, 1); g_mfn->Math_RotateMatrix(&rotMatrix, RAD2DEG( joint->rotation[1]), 0, 1, 0); g_mfn->Math_RotateMatrix(&rotMatrix, RAD2DEG(-joint->rotation[0]), 1, 0, 0); g_mfn->Math_MatrixMultiply(&sclMatrix, &rotMatrix, &bone->mat); // g_mfn->Math_RotationMatrix( joint->rotation[0], 0, &xMatrix); // g_mfn->Math_RotationMatrix(-joint->rotation[1], 1, &yMatrix); // g_mfn->Math_RotationMatrix( joint->rotation[2], 2, &zMatrix); // g_mfn->Math_MatrixMultiply(&zMatrix, &yMatrix, &rotMatrix); // g_mfn->Math_MatrixMultiply(&rotMatrix, &xMatrix, &zMatrix); // g_mfn->Math_MatrixMultiply(&zMatrix, &sclMatrix, &bone->mat); g_mfn->Math_VecCopy(joint->position, bone->mat.o); bone->eData.parent = (joint->parent < 0) ? NULL : (bones + joint->parent); } // transform bones rapi->rpgMultiplyBones(bones, numBones); return bones; } return NULL; } //load a single model from a dat set static void Model_KH_LoadModel(BYTE* data, CArrayList<noesisTex_t *> &textures, CArrayList<noesisMaterial_t *> &materials, modelBone_t *bones, int numBones, noeRAPI_t *rapi) { MDLS_MOBJ_HEADER *mobjHeader = (MDLS_MOBJ_HEADER *)(data); uint8 *modelData = (data + mobjHeader->meshDataOffset); MDLS_MODEL_HEADER *modelHeader = (MDLS_MODEL_HEADER*)modelData; // process mesh information if (modelHeader->meshCount > 0) { uint8 *meshInfo = (modelData + 0x10); MDLS_MESH_ENTRY *meshEntries = (MDLS_MESH_ENTRY*)meshInfo; int meshCount = modelHeader->meshCount; char meshName[128] = {0}; int meshCnt = 0; int meshNum = 0; for (meshNum = 0; meshNum < meshCount; ++meshNum, ++meshEntries) { rapi->rpgSetMaterialIndex(meshEntries->unknown0x04); uint8 *meshStart = (meshInfo + meshEntries->dataOffset); uint32 vertexCount = 0; uint32 faceCount = 0; MDLS_MATRIX_BLOCK *matrixPalette = NULL; uint32 matrixIndices[16] = {0}; uint32 tableSize = 0; uint32 extraMax = 0; uint32 extraCount = 0; uint32 nextOffset = 0; do { uint32 dataTag = *((uint32*)meshStart); switch (dataTag) { case 0: { uint32 jointIndex = *((uint32*)(meshStart + 0x04)); uint32 tableIndex0 = *((uint32*)(meshStart + 0x08)); uint32 tableIndex1 = *((uint32*)(meshStart + 0x0C)); if (tableIndex1 == 0) { matrixIndices[tableIndex0] = jointIndex; } if (tableIndex1 > 0) { if (tableIndex1 == 1) tableSize = 0; matrixIndices[tableSize + 8] = jointIndex; ++tableSize; } nextOffset = 0x80; } break; case 1: { nextOffset = *((uint32*)(meshStart + 4)); MDLS_VERTEX_BLOCK *vertexBlock = (MDLS_VERTEX_BLOCK*)meshStart; if (vertexBlock->dataSize > 0 && vertexBlock->vertexCount0 > 0) { uint8 *vertexStart = (meshStart + 0x20); CArrayList<modelVert_t> vertices; CArrayList<modelVert_t> normals; CArrayList<modelTexCoord_t> texCoords; uint32 numVertices = vertexBlock->vertexCount0; uint32 vertexNum = 0; rapi->rpgBegin(RPGEO_TRIANGLE_STRIP); for (vertexNum = 0; vertexNum < numVertices; ++vertexNum) { uint32 jointInfo = *((uint32*)(vertexStart + 0x0C)); uint32 jointIndex = 0; uint32 positionCount = 1; uint8 *normalStart = vertexStart; vertexStart += 0x10; uint8 *positionStart = vertexStart; if (matrixPalette) { positionCount = jointInfo; vertexStart += 0x10 * jointInfo; } else { vertexStart += 0x10; } uint8 *uvStart = vertexStart; vertexStart += 0x10; CArrayList<DWORD> boneIndices; CArrayList<float> boneWeights; uint32 posNum = 0; float position[4] = {0}; float temp[4] = {0}; modelBone_t *bone = NULL; fourxMatrix_t jointMatrix = g_identityMatrix4x4; fourxMatrix_t vertexMatrix = g_identityMatrix4x4; fourxMatrix_t outputMatrix = g_identityMatrix4x4; for (posNum = 0; posNum < positionCount; ++posNum, ++extraCount) { if (matrixPalette) { jointIndex = matrixPalette->matrixIndices[extraCount]; if (jointIndex < 0x100) jointIndex = matrixIndices[jointIndex]; else { jointIndex = ((jointIndex & 0xFF) >> 3); matrixIndices[2] = matrixIndices[jointIndex + 8]; jointIndex = matrixIndices[2]; } } else { jointIndex = matrixIndices[jointInfo]; } bone = bones + jointIndex; memcpy(vertexMatrix.c4, (float*)positionStart, sizeof(vertexMatrix.c4)); g_mfn->Math_ModelMatToGL(&bone->mat, (float*)(&jointMatrix)); g_mfn->Math_MatrixMultiply4x4(&vertexMatrix, &jointMatrix, &outputMatrix); g_mfn->Math_VecAdd(outputMatrix.c4, position, position); boneIndices.Append(jointIndex); boneWeights.Append(vertexMatrix.c4[3]); positionStart += 0x10; } normals.Append(*((modelVert_t*)normalStart)); texCoords.Append(*((modelTexCoord_t*)uvStart)); vertices.Append(*((modelVert_t*)position)); if (vertexNum == 0 && vertexBlock->unknown0x0C != 0) { rapi->rpgVertBoneIndexUI(&boneIndices[0], boneIndices.Num()); rapi->rpgVertBoneWeightF(&boneWeights[0], boneWeights.Num()); rapi->rpgVertNormal3f((float*)normalStart); rapi->rpgVertUV2f((float*)uvStart, 0); rapi->rpgVertex3f((float*)position); } rapi->rpgVertBoneIndexUI(&boneIndices[0], boneIndices.Num()); rapi->rpgVertBoneWeightF(&boneWeights[0], boneWeights.Num()); rapi->rpgVertNormal3f((float*)normalStart); rapi->rpgVertUV2f((float*)uvStart, 0); rapi->rpgVertex3f((float*)position); } rapi->rpgEnd(); } } break; case 2: { matrixPalette = (MDLS_MATRIX_BLOCK*)meshStart; extraCount = 0; extraMax = matrixPalette->lineCount * 4; nextOffset = matrixPalette->dataSize; } break; case 0x8000: { nextOffset = *((uint32*)(meshStart + 4)); } break; default: { nextOffset = 0; } break; } meshStart += nextOffset; } while (nextOffset > 0); } } } //load it noesisModel_t *Model_KH_Load(BYTE *fileBuffer, int bufferLen, int &numMdl, noeRAPI_t *rapi) { numMdl = 0; if (bufferLen < (sizeof(MDLS_MOBJ_HEADER) + 8)) { return NULL; } // read mobj data offset uint32 mobjOffset = *((uint32*)(fileBuffer + 4)); if (bufferLen < (sizeof(MDLS_MOBJ_HEADER) + mobjOffset)) { return NULL; } uint8 *mobjData = (fileBuffer + mobjOffset); CArrayList<noesisTex_t *> textures; CArrayList<noesisMaterial_t *> materials; // attempt to load textures Model_KH_LoadTextures(textures, materials, mobjData, bufferLen, rapi); int numBones = 0; modelBone_t *bones = NULL; // process joint data bones = Model_KH_CreateBones(mobjData, rapi, numBones); void *pgctx = rapi->rpgCreateContext(); rapi->rpgSetEndian(false); rapi->rpgSetTriWinding(false); noesisMatData_t *md = rapi->Noesis_GetMatDataFromLists(materials, textures); rapi->rpgSetExData_Materials(md); rapi->rpgSetExData_Bones(bones, numBones); CArrayList<noesisModel_t *> models; Model_KH_LoadModel(mobjData, textures, materials, bones, numBones, rapi); #if 0 //create a procedural anim to move random bones around if (bones) { const int numMoveBones = 1 + rand()%(numBones-1); sharedPAnimParm_t *aparms = (sharedPAnimParm_t *)_alloca(sizeof(sharedPAnimParm_t)*numMoveBones); memset(aparms, 0, sizeof(aparms)); //it's a good idea to do this, in case future noesis versions add more meaningful fields. for (int i = 0; i < numMoveBones; i++) { aparms[i].angAmt = 25.0f; aparms[i].axis = 1; //rotate left and right aparms[i].boneIdx = rand()%numBones; //random bone aparms[i].timeScale = 0.1f; //acts as a framestep } noesisAnim_t *anim = rapi->rpgCreateProceduralAnim(bones, numBones, aparms, numMoveBones, 500); if (anim) { rapi->rpgSetExData_AnimsNum(anim, 1); } } #endif noesisModel_t *mdl = rapi->rpgConstructModel(); if (!mdl) { //nothing could be created return NULL; } models.Append(mdl); rapi->rpgDestroyContext(pgctx); materials.Clear(); if (models.Num() <= 0) { return NULL; } numMdl = models.Num(); noesisModel_t *mdlList = rapi->Noesis_ModelsFromList(models, numMdl); models.Clear(); return mdlList; } //called by Noesis to init the plugin NPLUGIN_API bool NPAPI_Init(mathImpFn_t *mathfn, noePluginFn_t *noepfn) { g_mfn = mathfn; g_nfn = noepfn; if (g_nfn->NPAPI_GetAPIVersion() < NOESIS_PLUGINAPI_VERSION) { //bad version of noesis for this plugin return false; } int fh = g_nfn->NPAPI_Register("Kingdom Hearts Model", ".mdls"); if (fh < 0) { return false; } //set the data handlers for this format g_nfn->NPAPI_SetTypeHandler_TypeCheck(fh, Model_KH_Check); g_nfn->NPAPI_SetTypeHandler_LoadModel(fh, Model_KH_Load); return true; } //called by Noesis before the plugin is freed NPLUGIN_API void NPAPI_Shutdown(void) { //nothing to do in this plugin } NPLUGIN_API int NPAPI_GetPluginVer(void) { return NOESIS_PLUGIN_VERSION; } NPLUGIN_API bool NPAPI_GetPluginInfo(noePluginInfo_t *infOut) { strcpy_s(infOut->pluginName, 64, "kingdom_hearts"); strcpy_s(infOut->pluginDesc, 512, "Kingdom Hearts model handler, by revel8n."); return true; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; }
There isn't a way to have any sort of "active" cheats, at least if you don't have a system with CFW. Save hacks are the only option, unfortunately, but that still brings room for some interesting codes. With some work, you could crank out codes to unlock weapons, have all items, all accessories, journal stuff, etc.
You beat me too it lol. I made a program to extract the tm2 files from title.img, but didn't realize there were more files like it (which were .bins I guess). As for modifying the TM2s, I had issues getting them to compress back to a smaller file size if I added more than some really simple, repetitious stuff. Nevertheless, Optix iMageStudio seems to be the best tool to modify them, as it's officially used by developers.
Pretty much all findings/research related to the patch is being discussed publicly in this thread, so unless something is posted here, there's probably no new news to report.
Here's an updated version of the "Fly Anywhere" code that SHOULD work anywhere: Fly Anywhere (R2 On, L2 Off) E003FDFF 004DD49C 602DCAE0 00000008 00000001 00000070 E003FEFF 004DD49C 602DCAE0 00000000 00000001 00000070 This will work with any cheat device that supports the "6" code type, such as CodeBreaker. Others might support it, but I haven't tested it with anything except CB.
ARD files seem to contain 3D models (same format as MDLS models which can be viewed with Noesis), text, and maybe some other assets. At 0x100, there is a byte that represents the amount of models contained in the file. Immediately following the byte are the offsets for each model.The offsets use 0x100 as a starting point, so if you see "80 06 02" (which converts from little-endian to 0x020680), the real offset (starting from the beginning of the file) is 0x020780. I'm not sure what the stuff is at the top of ARD files, but they seem to be in a similar format of a length byte followed by offsets. I was able to manually copy the bytes of the first model in di01.ard and found that it was the cloth from the tree house in Destiny Island!
Alright, looks like I was trying this with other versions of KH before and never actually used Final Mix lol. It does work with Final Mix, and "1D 17 00 00 01 00 00 00 DD A6 03 00" can be found at 0x0049c000. Compatibility with other versions of the game can wait I guess, since that's beyond the scope and purpose of this project. The next step would be to make a tool to extract (or at least find the location of) the text portion of EVM files so we can start editing them. A tool would also be needed to replace the text in these files (and any other file that contains text) with text from the US release. This shouldn't be super hard, since the assets from Final Mix seem more similar to that of the US release than the original JP release. Edit: Just tried out the file replacer to replace a file from Final Mix with with it's US counterpart. I copied the file into the export directory and ran IMGreplace.exe. When I type "dh01.ard", which is the file I intended to replace, it says this: COMPRESSOR: Compressed from 347392 to 209309 (0.6025153141120118%)! New filesize is more then original, even with buffer! Can't replace this file! Oddly, it's a smaller file to begin with before compression. If the compressed file is larger than the original, can't we just move all the files in the image that come after this one by how ever many sectors larger the file will be after compression? Then you would increase the sector offsets in KINGDOM.IDX by the amount of additional sectors we needed.
Thanks for clarifying that. Looks like the extractor doesn't work, however. When I try the new extractor, it says "IMGFile: found IMG (BOOT2) ~ Offset: 569344" and just sits there for about a minute. After about a minute, it crashes with this error: "Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Boolean' to type 'System.Byte[]'. at JScript 0.main(Object this, VsaEngine vsa Engine) at JScript 0.Global Code() at JScript Main.Main(String[] )"
crazycatz00, have you taken a look at the memory management in your extractor? I saw the process jump over 2GB at one point! Not trying to be critical or anything, just putting it out there as a bug report I guess. I don't know much about C#, but is it true that you can't manually control memory allocation and deallocation? In C you could just malloc() and free(), but I guess you can't do that in C# since it has automatic garbage collection.
Hmm, the imgextract.log file shouldn't be empty. Try deleting it and running the extractor again. If that still doesn't work, here's my .log from FM: http://rghost.net/private/45345590/f75a07420a993c8aec656f13718a8939
When you run IMGextract, it should generate a file called imgextract.log in the same folder as the EXE. If you have already run the extractor from a different folder, copy the imgextract.log file to the folder where you have imgreplace.exe