某些使用寫字板打開的.x文件具有可讀材料。您可以查看頂點等等,甚至可以對其進行編輯。但有些只是不可讀的符號。我的問題是:有沒有辦法能夠讀取信息?如何閱讀/查看.x文件
我想要做的是更改紋理路徑名稱。 .x文件告訴directx從文件加載紋理的名稱。會發生什麼是我下載了一個.x文件,並且紋理未被加載,因爲紋理的路徑名是創建該文件的路徑名。很顯然,我需要將它改變到現在我擁有紋理的地方。或者我將不得不創建與我使用的每個.x文件的路徑名相匹配的文件夾,這只是愚蠢的。
無論如何,任何幫助將不勝感激。
某些使用寫字板打開的.x文件具有可讀材料。您可以查看頂點等等,甚至可以對其進行編輯。但有些只是不可讀的符號。我的問題是:有沒有辦法能夠讀取信息?如何閱讀/查看.x文件
我想要做的是更改紋理路徑名稱。 .x文件告訴directx從文件加載紋理的名稱。會發生什麼是我下載了一個.x文件,並且紋理未被加載,因爲紋理的路徑名是創建該文件的路徑名。很顯然,我需要將它改變到現在我擁有紋理的地方。或者我將不得不創建與我使用的每個.x文件的路徑名相匹配的文件夾,這只是愚蠢的。
無論如何,任何幫助將不勝感激。
下面的代碼應該工作,這裏是完整的文件和頭文件以及其他實用程序文件用C來解析X檔案++:https://github.com/assimp/assimp/tree/master/code和https://github.com/assimp/assimp/blob/master/code/XFileParser.cpp
#ifndef ASSIMP_BUILD_NO_X_IMPORTER
#include "XFileParser.h"
#include "XFileHelper.h"
#include "fast_atof.h"
#include "Exceptional.h"
#include "TinyFormatter.h"
#include "ByteSwapper.h"
#include "StringUtils.h"
#include <assimp/DefaultLogger.hpp>
using namespace Assimp;
using namespace Assimp::XFile;
using namespace Assimp::Formatter;
#ifndef ASSIMP_BUILD_NO_COMPRESSED_X
# ifdef ASSIMP_BUILD_NO_OWN_ZLIB
# include <zlib.h>
# else
# include "../contrib/zlib/zlib.h"
# endif
// Magic identifier for MSZIP compressed data
#define MSZIP_MAGIC 0x4B43
#define MSZIP_BLOCK 32786
// ------------------------------------------------------------------------------------------------
// Dummy memory wrappers for use with zlib
static void* dummy_alloc (void* /*opaque*/, unsigned int items, unsigned int size) {
return ::operator new(items*size);
}
static void dummy_free (void* /*opaque*/, void* address) {
return ::operator delete(address);
}
#endif // !! ASSIMP_BUILD_NO_COMPRESSED_X
// ------------------------------------------------------------------------------------------------
// Constructor. Creates a data structure out of the XFile given in the memory block.
XFileParser::XFileParser(const std::vector<char>& pBuffer)
{
mMajorVersion = mMinorVersion = 0;
mIsBinaryFormat = false;
mBinaryNumCount = 0;
P = End = NULL;
mLineNumber = 0;
mScene = NULL;
// vector to store uncompressed file for INFLATE'd X files
std::vector<char> uncompressed;
// set up memory pointers
P = &pBuffer.front();
End = P + pBuffer.size() - 1;
// check header
if(strncmp(P, "xof ", 4) != 0)
throw DeadlyImportError("Header mismatch, file is not an XFile.");
// read version. It comes in a four byte format such as "0302"
mMajorVersion = (unsigned int)(P[4] - 48) * 10 + (unsigned int)(P[5] - 48);
mMinorVersion = (unsigned int)(P[6] - 48) * 10 + (unsigned int)(P[7] - 48);
bool compressed = false;
// txt - pure ASCII text format
if(strncmp(P + 8, "txt ", 4) == 0)
mIsBinaryFormat = false;
// bin - Binary format
else if(strncmp(P + 8, "bin ", 4) == 0)
mIsBinaryFormat = true;
// tzip - Inflate compressed text format
else if(strncmp(P + 8, "tzip", 4) == 0)
{
mIsBinaryFormat = false;
compressed = true;
}
// bzip - Inflate compressed binary format
else if(strncmp(P + 8, "bzip", 4) == 0)
{
mIsBinaryFormat = true;
compressed = true;
}
else ThrowException(format() << "Unsupported xfile format '" <<
P[8] << P[9] << P[10] << P[11] << "'");
// float size
mBinaryFloatSize = (unsigned int)(P[12] - 48) * 1000
+ (unsigned int)(P[13] - 48) * 100
+ (unsigned int)(P[14] - 48) * 10
+ (unsigned int)(P[15] - 48);
if(mBinaryFloatSize != 32 && mBinaryFloatSize != 64)
ThrowException(format() << "Unknown float size " << mBinaryFloatSize << " specified in xfile header.");
// The x format specifies size in bits, but we work in bytes
mBinaryFloatSize /= 8;
P += 16;
// If this is a compressed X file, apply the inflate algorithm to it
if (compressed)
{
#ifdef ASSIMP_BUILD_NO_COMPRESSED_X
throw DeadlyImportError("Assimp was built without compressed X support");
#else
/* ///////////////////////////////////////////////////////////////////////
* COMPRESSED X FILE FORMAT
* ///////////////////////////////////////////////////////////////////////
* [xhead]
* 2 major
* 2 minor
* 4 type // bzip,tzip
* [mszip_master_head]
* 4 unkn // checksum?
* 2 unkn // flags? (seems to be constant)
* [mszip_head]
* 2 ofs // offset to next section
* 2 magic // 'CK'
* ... ofs bytes of data
* ... next mszip_head
*
* http://www.kdedevelopers.org/node/3181 has been very helpful.
* ///////////////////////////////////////////////////////////////////////
*/
// build a zlib stream
z_stream stream;
stream.opaque = NULL;
stream.zalloc = &dummy_alloc;
stream.zfree = &dummy_free;
stream.data_type = (mIsBinaryFormat ? Z_BINARY : Z_ASCII);
// initialize the inflation algorithm
::inflateInit2(&stream, -MAX_WBITS);
// skip unknown data (checksum, flags?)
P += 6;
// First find out how much storage we'll need. Count sections.
const char* P1 = P;
unsigned int est_out = 0;
while (P1 + 3 < End)
{
// read next offset
uint16_t ofs = *((uint16_t*)P1);
AI_SWAP2(ofs); P1 += 2;
if (ofs >= MSZIP_BLOCK)
throw DeadlyImportError("X: Invalid offset to next MSZIP compressed block");
// check magic word
uint16_t magic = *((uint16_t*)P1);
AI_SWAP2(magic); P1 += 2;
if (magic != MSZIP_MAGIC)
throw DeadlyImportError("X: Unsupported compressed format, expected MSZIP header");
// and advance to the next offset
P1 += ofs;
est_out += MSZIP_BLOCK; // one decompressed block is 32786 in size
}
// Allocate storage and terminating zero and do the actual uncompressing
uncompressed.resize(est_out + 1);
char* out = &uncompressed.front();
while (P + 3 < End)
{
uint16_t ofs = *((uint16_t*)P);
AI_SWAP2(ofs);
P += 4;
if (P + ofs > End + 2) {
throw DeadlyImportError("X: Unexpected EOF in compressed chunk");
}
// push data to the stream
stream.next_in = (Bytef*)P;
stream.avail_in = ofs;
stream.next_out = (Bytef*)out;
stream.avail_out = MSZIP_BLOCK;
// and decompress the data ....
int ret = ::inflate(&stream, Z_SYNC_FLUSH);
if (ret != Z_OK && ret != Z_STREAM_END)
throw DeadlyImportError("X: Failed to decompress MSZIP-compressed data");
::inflateReset(&stream);
::inflateSetDictionary(&stream, (const Bytef*)out , MSZIP_BLOCK - stream.avail_out);
// and advance to the next offset
out += MSZIP_BLOCK - stream.avail_out;
P += ofs;
}
// terminate zlib
::inflateEnd(&stream);
// ok, update pointers to point to the uncompressed file data
P = &uncompressed[0];
End = out;
// FIXME: we don't need the compressed data anymore, could release
// it already for better memory usage. Consider breaking const-co.
DefaultLogger::get()->info("Successfully decompressed MSZIP-compressed file");
#endif // !! ASSIMP_BUILD_NO_COMPRESSED_X
}
else
{
// start reading here
ReadUntilEndOfLine();
}
mScene = new Scene;
ParseFile();
// filter the imported hierarchy for some degenerated cases
if(mScene->mRootNode) {
FilterHierarchy(mScene->mRootNode);
}
}
// ------------------------------------------------------------------------------------------------
// Destructor. Destroys all imported data along with it
XFileParser::~XFileParser()
{
// kill everything we created
delete mScene;
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseFile()
{
bool running = true;
while(running)
{
// read name of next object
std::string objectName = GetNextToken();
if (objectName.length() == 0)
break;
// parse specific object
if(objectName == "template")
ParseDataObjectTemplate();
else
if(objectName == "Frame")
ParseDataObjectFrame(NULL);
else
if(objectName == "Mesh")
{
// some meshes have no frames at all
Mesh* mesh = new Mesh;
ParseDataObjectMesh(mesh);
mScene->mGlobalMeshes.push_back(mesh);
} else
if(objectName == "AnimTicksPerSecond")
ParseDataObjectAnimTicksPerSecond();
else
if(objectName == "AnimationSet")
ParseDataObjectAnimationSet();
else
if(objectName == "Material")
{
// Material outside of a mesh or node
Material material;
ParseDataObjectMaterial(&material);
mScene->mGlobalMaterials.push_back(material);
} else
if(objectName == "}")
{
// whatever?
DefaultLogger::get()->warn("} found in dataObject");
} else
{
// unknown format
DefaultLogger::get()->warn("Unknown data object in animation of .x file");
ParseUnknownDataObject();
}
}
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectTemplate()
{
// parse a template data object. Currently not stored.
std::string name;
readHeadOfDataObject(&name);
// read GUID
std::string guid = GetNextToken();
// read and ignore data members
bool running = true;
while (running)
{
std::string s = GetNextToken();
if(s == "}")
break;
if(s.length() == 0)
ThrowException("Unexpected end of file reached while parsing template definition");
}
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectFrame(Node* pParent)
{
// A coordinate frame, or "frame of reference." The Frame template
// is open and can contain any object. The Direct3D extensions (D3DX)
// mesh-loading functions recognize Mesh, FrameTransformMatrix, and
// Frame template instances as child objects when loading a Frame
// instance.
std::string name;
readHeadOfDataObject(&name);
// create a named node and place it at its parent, if given
Node* node = new Node(pParent);
node->mName = name;
if(pParent)
{
pParent->mChildren.push_back(node);
} else
{
// there might be multiple root nodes
if(mScene->mRootNode != NULL)
{
// place a dummy root if not there
if(mScene->mRootNode->mName != "$dummy_root")
{
Node* exroot = mScene->mRootNode;
mScene->mRootNode = new Node(NULL);
mScene->mRootNode->mName = "$dummy_root";
mScene->mRootNode->mChildren.push_back(exroot);
exroot->mParent = mScene->mRootNode;
}
// put the new node as its child instead
mScene->mRootNode->mChildren.push_back(node);
node->mParent = mScene->mRootNode;
} else
{
// it's the first node imported. place it as root
mScene->mRootNode = node;
}
}
// Now inside a frame.
// read tokens until closing brace is reached.
bool running = true;
while (running)
{
std::string objectName = GetNextToken();
if (objectName.size() == 0)
ThrowException("Unexpected end of file reached while parsing frame");
if(objectName == "}")
break; // frame finished
else
if(objectName == "Frame")
ParseDataObjectFrame(node); // child frame
else
if(objectName == "FrameTransformMatrix")
ParseDataObjectTransformationMatrix(node->mTrafoMatrix);
else
if(objectName == "Mesh")
{
Mesh* mesh = new Mesh(name);
node->mMeshes.push_back(mesh);
ParseDataObjectMesh(mesh);
} else
{
DefaultLogger::get()->warn("Unknown data object in frame in x file");
ParseUnknownDataObject();
}
}
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectTransformationMatrix(aiMatrix4x4& pMatrix)
{
// read header, we're not interested if it has a name
readHeadOfDataObject();
// read its components
pMatrix.a1 = ReadFloat(); pMatrix.b1 = ReadFloat();
pMatrix.c1 = ReadFloat(); pMatrix.d1 = ReadFloat();
pMatrix.a2 = ReadFloat(); pMatrix.b2 = ReadFloat();
pMatrix.c2 = ReadFloat(); pMatrix.d2 = ReadFloat();
pMatrix.a3 = ReadFloat(); pMatrix.b3 = ReadFloat();
pMatrix.c3 = ReadFloat(); pMatrix.d3 = ReadFloat();
pMatrix.a4 = ReadFloat(); pMatrix.b4 = ReadFloat();
pMatrix.c4 = ReadFloat(); pMatrix.d4 = ReadFloat();
// trailing symbols
CheckForSemicolon();
CheckForClosingBrace();
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMesh(Mesh* pMesh)
{
std::string name;
readHeadOfDataObject(&name);
// read vertex count
unsigned int numVertices = ReadInt();
pMesh->mPositions.resize(numVertices);
// read vertices
for(unsigned int a = 0; a < numVertices; a++)
pMesh->mPositions[a] = ReadVector3();
// read position faces
unsigned int numPosFaces = ReadInt();
pMesh->mPosFaces.resize(numPosFaces);
for(unsigned int a = 0; a < numPosFaces; a++)
{
// read indices
unsigned int numIndices = ReadInt();
Face& face = pMesh->mPosFaces[a];
for (unsigned int b = 0; b < numIndices; b++) {
face.mIndices.push_back(ReadInt());
}
TestForSeparator();
}
// here, other data objects may follow
bool running = true;
while (running)
{
std::string objectName = GetNextToken();
if(objectName.size() == 0)
ThrowException("Unexpected end of file while parsing mesh structure");
else
if(objectName == "}")
break; // mesh finished
else
if(objectName == "MeshNormals")
ParseDataObjectMeshNormals(pMesh);
else
if(objectName == "MeshTextureCoords")
ParseDataObjectMeshTextureCoords(pMesh);
else
if(objectName == "MeshVertexColors")
ParseDataObjectMeshVertexColors(pMesh);
else
if(objectName == "MeshMaterialList")
ParseDataObjectMeshMaterialList(pMesh);
else
if(objectName == "VertexDuplicationIndices")
ParseUnknownDataObject(); // we'll ignore vertex duplication indices
else
if(objectName == "XSkinMeshHeader")
ParseDataObjectSkinMeshHeader(pMesh);
else
if(objectName == "SkinWeights")
ParseDataObjectSkinWeights(pMesh);
else
{
DefaultLogger::get()->warn("Unknown data object in mesh in x file");
ParseUnknownDataObject();
}
}
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectSkinWeights(Mesh *pMesh)
{
readHeadOfDataObject();
std::string transformNodeName;
GetNextTokenAsString(transformNodeName);
pMesh->mBones.push_back(Bone());
Bone& bone = pMesh->mBones.back();
bone.mName = transformNodeName;
// read vertex weights
unsigned int numWeights = ReadInt();
bone.mWeights.reserve(numWeights);
for(unsigned int a = 0; a < numWeights; a++)
{
BoneWeight weight;
weight.mVertex = ReadInt();
bone.mWeights.push_back(weight);
}
// read vertex weights
for(unsigned int a = 0; a < numWeights; a++)
bone.mWeights[a].mWeight = ReadFloat();
// read matrix offset
bone.mOffsetMatrix.a1 = ReadFloat(); bone.mOffsetMatrix.b1 = ReadFloat();
bone.mOffsetMatrix.c1 = ReadFloat(); bone.mOffsetMatrix.d1 = ReadFloat();
bone.mOffsetMatrix.a2 = ReadFloat(); bone.mOffsetMatrix.b2 = ReadFloat();
bone.mOffsetMatrix.c2 = ReadFloat(); bone.mOffsetMatrix.d2 = ReadFloat();
bone.mOffsetMatrix.a3 = ReadFloat(); bone.mOffsetMatrix.b3 = ReadFloat();
bone.mOffsetMatrix.c3 = ReadFloat(); bone.mOffsetMatrix.d3 = ReadFloat();
bone.mOffsetMatrix.a4 = ReadFloat(); bone.mOffsetMatrix.b4 = ReadFloat();
bone.mOffsetMatrix.c4 = ReadFloat(); bone.mOffsetMatrix.d4 = ReadFloat();
CheckForSemicolon();
CheckForClosingBrace();
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectSkinMeshHeader(Mesh* /*pMesh*/)
{
readHeadOfDataObject();
/*unsigned int maxSkinWeightsPerVertex =*/ ReadInt();
/*unsigned int maxSkinWeightsPerFace =*/ ReadInt();
/*unsigned int numBonesInMesh = */ReadInt();
CheckForClosingBrace();
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMeshNormals(Mesh* pMesh)
{
readHeadOfDataObject();
// read count
unsigned int numNormals = ReadInt();
pMesh->mNormals.resize(numNormals);
// read normal vectors
for(unsigned int a = 0; a < numNormals; a++)
pMesh->mNormals[a] = ReadVector3();
// read normal indices
unsigned int numFaces = ReadInt();
if(numFaces != pMesh->mPosFaces.size())
ThrowException("Normal face count does not match vertex face count.");
for(unsigned int a = 0; a < numFaces; a++)
{
unsigned int numIndices = ReadInt();
pMesh->mNormFaces.push_back(Face());
Face& face = pMesh->mNormFaces.back();
for(unsigned int b = 0; b < numIndices; b++)
face.mIndices.push_back(ReadInt());
TestForSeparator();
}
CheckForClosingBrace();
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMeshTextureCoords(Mesh* pMesh)
{
readHeadOfDataObject();
if(pMesh->mNumTextures + 1 > AI_MAX_NUMBER_OF_TEXTURECOORDS)
ThrowException("Too many sets of texture coordinates");
std::vector<aiVector2D>& coords = pMesh->mTexCoords[pMesh->mNumTextures++];
unsigned int numCoords = ReadInt();
if(numCoords != pMesh->mPositions.size())
ThrowException("Texture coord count does not match vertex count");
coords.resize(numCoords);
for(unsigned int a = 0; a < numCoords; a++)
coords[a] = ReadVector2();
CheckForClosingBrace();
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMeshVertexColors(Mesh* pMesh)
{
readHeadOfDataObject();
if(pMesh->mNumColorSets + 1 > AI_MAX_NUMBER_OF_COLOR_SETS)
ThrowException("Too many colorsets");
std::vector<aiColor4D>& colors = pMesh->mColors[pMesh->mNumColorSets++];
unsigned int numColors = ReadInt();
if(numColors != pMesh->mPositions.size())
ThrowException("Vertex color count does not match vertex count");
colors.resize(numColors, aiColor4D(0, 0, 0, 1));
for(unsigned int a = 0; a < numColors; a++)
{
unsigned int index = ReadInt();
if(index >= pMesh->mPositions.size())
ThrowException("Vertex color index out of bounds");
colors[index] = ReadRGBA();
// HACK: (thom) Maxon Cinema XPort plugin puts a third separator here, kwxPort puts a comma.
// Ignore gracefully.
if(!mIsBinaryFormat)
{
FindNextNoneWhiteSpace();
if(*P == ';' || *P == ',')
P++;
}
}
CheckForClosingBrace();
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMeshMaterialList(Mesh* pMesh)
{
readHeadOfDataObject();
// read material count
/*unsigned int numMaterials =*/ ReadInt();
// read non triangulated face material index count
unsigned int numMatIndices = ReadInt();
// some models have a material index count of 1... to be able to read them we
// replicate this single material index on every face
if(numMatIndices != pMesh->mPosFaces.size() && numMatIndices != 1)
ThrowException("Per-Face material index count does not match face count.");
// read per-face material indices
for(unsigned int a = 0; a < numMatIndices; a++)
pMesh->mFaceMaterials.push_back(ReadInt());
// in version 03.02, the face indices end with two semicolons.
// commented out version check, as version 03.03 exported from blender also has 2 semicolons
if(!mIsBinaryFormat) // && MajorVersion == 3 && MinorVersion <= 2)
{
if(P < End && *P == ';')
++P;
}
// if there was only a single material index, replicate it on all faces
while(pMesh->mFaceMaterials.size() < pMesh->mPosFaces.size())
pMesh->mFaceMaterials.push_back(pMesh->mFaceMaterials.front());
// read following data objects
bool running = true;
while (running)
{
std::string objectName = GetNextToken();
if(objectName.size() == 0)
ThrowException("Unexpected end of file while parsing mesh material list.");
else
if(objectName == "}")
break; // material list finished
else
if(objectName == "{")
{
// template materials
std::string matName = GetNextToken();
Material material;
material.mIsReference = true;
material.mName = matName;
pMesh->mMaterials.push_back(material);
CheckForClosingBrace(); // skip }
} else
if(objectName == "Material")
{
pMesh->mMaterials.push_back(Material());
ParseDataObjectMaterial(&pMesh->mMaterials.back());
} else
if(objectName == ";")
{
// ignore
} else
{
DefaultLogger::get()->warn("Unknown data object in material list in x file");
ParseUnknownDataObject();
}
}
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMaterial(Material* pMaterial)
{
std::string matName;
readHeadOfDataObject(&matName);
if(matName.empty())
matName = std::string("material") + to_string(mLineNumber);
pMaterial->mName = matName;
pMaterial->mIsReference = false;
// read material values
pMaterial->mDiffuse = ReadRGBA();
pMaterial->mSpecularExponent = ReadFloat();
pMaterial->mSpecular = ReadRGB();
pMaterial->mEmissive = ReadRGB();
// read other data objects
bool running = true;
while (running)
{
std::string objectName = GetNextToken();
if(objectName.size() == 0)
ThrowException("Unexpected end of file while parsing mesh material");
else
if(objectName == "}")
break; // material finished
else
if(objectName == "TextureFilename" || objectName == "TextureFileName")
{
// some exporters write "TextureFileName" instead.
std::string texname;
ParseDataObjectTextureFilename(texname);
pMaterial->mTextures.push_back(TexEntry(texname));
} else
if(objectName == "NormalmapFilename" || objectName == "NormalmapFileName")
{
// one exporter writes out the normal map in a separate filename tag
std::string texname;
ParseDataObjectTextureFilename(texname);
pMaterial->mTextures.push_back(TexEntry(texname, true));
} else
{
DefaultLogger::get()->warn("Unknown data object in material in x file");
ParseUnknownDataObject();
}
}
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectAnimTicksPerSecond()
{
readHeadOfDataObject();
mScene->mAnimTicksPerSecond = ReadInt();
CheckForClosingBrace();
}
// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectAnimationSet()
{
std::string animName;
readHeadOfDataObject(&animName);
Animation* anim = new Animation;
mScene->mAnims.push_back(anim);
anim->mName = animName;
bool running = true;
while (running)
{
std::string objectName = GetNextToken();
if(objectName.length() == 0)
ThrowException("Unexpected end of file while parsing animation set.");
else
if(objectName == "}")
break; // animation set finished
else
if(objectName == "Animation")
ParseDataObjectAnimation(anim);
else
{
DefaultLogger::get()->warn("Unknown data object in animation set in x file");
ParseUnknownDataObject();
}
}
}
[...]
謝謝你的答案,但我沒有找到加載.x文件的方式。我已經找到了一些東西。這看起來很有趣,我會檢查出來。我希望能夠用我的眼睛來閱讀它。有些是人類可讀的,但有些不是,我正在尋找一種閱讀不可讀的方式。 – CoconutDug
嘿,明白了。您仍然可以使用該庫導入x文件,完全讀取它並將其導出爲JSON等人類可讀格式。 – Isma
你可以嘗試使用十六進制編輯器裏面看 - 這裏的一個在線的不需要任何安裝https://hexed.it –