PMX他環境でエンコードされたファイルのデコードについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Saigo
記事: 15
登録日時: 12年前

PMX他環境でエンコードされたファイルのデコードについて

#1

投稿記事 by Saigo » 11年前

朝早くの投稿になります。
PMX(MMDモデルのファイル形式)のロードを行うプログラムをC++で作っている時に、どうしても解決できない箇所があったので質問させていただきました。

解決できない箇所とは、モデル情報の部分で、そのデータ変換に.NET Frameworkを使用しているらしい(仕様より)のですが、私の開発環境がubuntuのため.NETが使えず、更に、PMXはバイナリなのでどうすることも出来ずに詰まっている状況です。(Wineは使いたくありません)
(同様に、テクスチャパス、材質名、ボーン等、他のTextBufを使っている箇所も出来そうにありません。)


どうにか.NETを使わずにロードする方法はありませんか?
どんなに些細な情報でも結構です。なにか知っていることがあれば、ご教授願いたいです。
また、PMXを扱うにあたって気を付けなければならないことなどあればお願いします。



開発環境
Ubuntu13.10
g++ 4.8.1

以下PMXの仕様になります。



[C#/HLSL方式+] : バイトサイズ | [C方式]

byte : 1 - 符号なし | unsigned char
sbyte : 1 - 符号あり | char

ushort : 2 - 符号なし | unsigned short
short : 2 - 符号あり | short

uint : 4 - 符号なし | unsigned int
int : 4 - 符号あり | int (32bit固定)

float : 4 - 単精度実数 | float

float2 : 8 (4 * 2) | Vector2 - X,Y(or U,V) の順
float3 : 12 (4 * 3) | Vector3 - X,Y,Z(or R,G,B) の順
float4 : 16 (4 * 4) | Vector4 - X,Y,Z,W(or R,G,B,A) の順

TextBuf : 可変 | 4 + バッファ長

bitFlag : 1 | 1byte につき 8フラグ bit-0:OFF 1:ON

○TextBuf (テキストバッファ)
int : バイト長
byte * バイト長 : byte列 エンコード形式はPMXヘッダに記載

※バッファサイズは 4 + n [byte]

※エディタ内のエンコード処理は、

→バイト列
byte[] buf = Encoding.Unicode.GetBytes(str); // UTF16
byte[] buf = Encoding.UTF8.GetBytes(str); // UTF8

→文字列
string str = Encoding.Unicode.GetString(buf, 0, buf.Length); // UTF16
string str = Encoding.UTF8.GetString(buf, 0, buf.Length); // UTF8

これだけです。
変換はすべてライブラリ(.NET Framework)にまかせているので、内部で発生する問題などには対応できないかと思われます。
※サロゲートペアの取り扱いについてなど詳細不明

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: PMX他環境でエンコードされたファイルのデコードについて

#2

投稿記事 by みけCAT » 11年前

WindowsならShift_JISに変換した方が扱いやすく感じるかもしれないですが、
Ubuntu13.10ならわざわざ変換しなくても、UTF-8のまま文字列を扱えばいいと思います。
ただし、エンコードがUnicode(UTF-16)の場合もある(どっちかははヘッダでわかる)が、それは規則に従って自分でUTF-8に変換すればいいはずです。
ここが参考になります。UTF-8 - Wikipedia
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Saigo
記事: 15
登録日時: 12年前

Re: PMX他環境でエンコードされたファイルのデコードについて

#3

投稿記事 by Saigo » 11年前

みけCAT さんが書きました:WindowsならShift_JISに変換した方が扱いやすく感じるかもしれないですが、
Ubuntu13.10ならわざわざ変換しなくても、UTF-8のまま文字列を扱えばいいと思います。
ただし、エンコードがUnicode(UTF-16)の場合もある(どっちかははヘッダでわかる)が、それは規則に従って自分でUTF-8に変換すればいいはずです。
ここが参考になります。UTF-8 - Wikipedia
返信ありがとうございます
文字列が何byteかはどこでわかるのでしょうか?
unsigned char[]で適当に14個くらい取ってきたのですが、一文字且つ文字化けをしてしまっている状況です

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: PMX他環境でエンコードされたファイルのデコードについて

#4

投稿記事 by みけCAT » 11年前

Saigo さんが書きました:文字列が何byteかはどこでわかるのでしょうか?
unsigned char[]で適当に14個くらい取ってきたのですが、一文字且つ文字化けをしてしまっている状況です
自分で「int : バイト長」と書いているので、そこのデータでわかるはずです。
モデルを1個バイナリエディタで見てみましたが、リトルエンディアンのようですね。
C++なら、必要なサイズの配列をnew 型名[要素数]で確保すればいいと思います。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: PMX他環境でエンコードされたファイルのデコードについて

#5

投稿記事 by みけCAT » 11年前

とりあえず、モデル情報までの読み込みを実装してみました。

コード:

#include <cstdio>
#include <string>

unsigned char read_byte(FILE* fp) {
	int rb=getc(fp);
	if(rb==EOF)throw (const char*)"unexpected end of file";
	return rb;
}

unsigned short read_ushort(FILE* fp) {
	int high,low;
	low=read_byte(fp);
	high=read_byte(fp);
	return (high<<8)|low;
}

unsigned int read_uint(FILE* fp) {
	unsigned int high,low;
	low=read_ushort(fp);
	high=read_ushort(fp);
	return (high<<16)|low;
}

float read_float(FILE* fp) {
	/* TODO : エンディアンの相違に対応 */
	float buf=0.0f;
	if(fread(&buf,sizeof(float),1,fp)!=1)throw (const char*)"unexpected end of file";
	return buf;
}

void read_char_array(unsigned char* out,FILE* fp,int len) {
	for(int i=0;i<len;i++)out[i]=read_byte(fp);
}

std::string utf16_to_utf8_string(const unsigned char* str) {
	int i;
	unsigned int next_number;
	std::string ret="";
	for(i=0;(next_number=str[i]|(str[i+1]<<8))!=0;i+=2) {
		unsigned char buffer[8]={};
		if(next_number<=0x7f) {
			buffer[0]=next_number;
		} else if(next_number<=0x7ff) {
			buffer[0]=0xc0 | ((next_number>>6)&0x1f);
			buffer[1]=0x80 | (next_number&0x3f);
		} else { // 仕様よりnext_number<=0xffff
			buffer[0]=0xe0 | ((next_number>>12)&0x1f);
			buffer[1]=0x80 | ((next_number>>6)&0x3f);
			buffer[2]=0x80 | (next_number&0x3f);
		}
		ret+=std::string((const char*)buffer);
	}
	return ret;
}

std::string read_text_buf(FILE* fp,unsigned char encode) {
	unsigned int length;
	unsigned char* buffer;
	std::string res;
	length=read_uint(fp);
	buffer=new unsigned char[length+2];
	read_char_array(buffer,fp,length);
	buffer[length]=buffer[length+1]='\0';
	switch(encode) {
		case 0: // UTF16
			res=utf16_to_utf8_string(buffer);
			break;
		case 1: // UTF8
			res=std::string((const char*)buffer);
			break;
		default:
			delete[] buffer;
			throw (const char*)"unsupported encode";
	}
	delete[] buffer;
	return res;
}

struct pmx_header {
	float version;
	unsigned char encode;
	unsigned char add_uv;
	unsigned char node_index_size;
	unsigned char texture_index_size;
	unsigned char material_index_size;
	unsigned char bone_index_size;
	unsigned char moofu_index_size;
	unsigned char goutai_index_size;
};

struct pmx_model_info {
	std::string name;
	std::string name_english;
	std::string comment;
	std::string comment_english;
};

struct pmx_t {
	pmx_header header;
	pmx_model_info model_info;
};

bool float_is_equal(float a,float b) {
	static const float EPS=1e-5;
	return a-b<EPS && b-a<EPS;
}

pmx_header read_pmx_header(FILE* fp) {
	pmx_header pmh;
	unsigned char magic[4];
	unsigned char next_size;
	read_char_array(magic,fp,4);
	if(magic[0]!=0x50 || magic[1]!=0x4d || magic[2]!=0x58 || magic[3]!=0x20) {
		throw (const char*)"this is not a PMX file";
	}
	pmh.version=read_float(fp);
	if(!float_is_equal(pmh.version,2.0f) && !float_is_equal(pmh.version,2.1f)) {
		throw (const char*)"this version of PMX file is not supported";
	}
	next_size=read_byte(fp);
	if(next_size!=8) {
		throw (const char*)"invalid header size";
	}
	pmh.encode=read_byte(fp);
	pmh.add_uv=read_byte(fp);
	pmh.node_index_size=read_byte(fp);
	pmh.texture_index_size=read_byte(fp);
	pmh.material_index_size=read_byte(fp);
	pmh.bone_index_size=read_byte(fp);
	pmh.moofu_index_size=read_byte(fp);
	pmh.goutai_index_size=read_byte(fp);
	return pmh;
}

pmx_model_info read_pmx_model_info(FILE* fp,const pmx_header& header) {
	pmx_model_info pmi;
	pmi.name=read_text_buf(fp,header.encode);
	pmi.name_english=read_text_buf(fp,header.encode);
	pmi.comment=read_text_buf(fp,header.encode);
	pmi.comment_english=read_text_buf(fp,header.encode);
	return pmi;
}

pmx_t read_pmx(FILE* fp) {
	pmx_t pmx;
	pmx.header=read_pmx_header(fp);
	pmx.model_info=read_pmx_model_info(fp,pmx.header);
	return pmx;
}

int main(int argc,char* argv[]) {
	FILE* fp;
	pmx_t pmx;
	if(argc<2) {
		fprintf(stderr,"Usage: %s <model file>\n",argv[0]);
		return 1;
	}
	fp=fopen(argv[1],"rb");
	if(fp==NULL) {
		fprintf(stderr,"failed to open \"%s\"\n",argv[1]);
		return 1;
	}
	try {
		// PMXファイルを読み込む
		pmx=read_pmx(fp);
		// 読み込んだ情報を出力する
		printf("version       = %f\n",pmx.header.version);
		printf("name          = %s\n",pmx.model_info.name.c_str());
		printf("name (eng)    = %s\n",pmx.model_info.name_english.c_str());
		printf("---------- comment ----------\n%s\n",pmx.model_info.comment.c_str());
		printf("---------- comment (eng) ----------\n%s\n",pmx.model_info.comment_english.c_str());
	} catch(const char* e) {
		fprintf(stderr,"ERROR: %s\n",e);
	}
	fclose(fp);
	return 0;
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Saigo
記事: 15
登録日時: 12年前

Re: PMX他環境でエンコードされたファイルのデコードについて

#6

投稿記事 by Saigo » 11年前

みけCAT さんが書きました:とりあえず、モデル情報までの読み込みを実装してみました。
反応おくれてすいません(部活をやっているので...)
コードまで書いていただきありがとうございます。
少し読ませて頂きます。

Saigo
記事: 15
登録日時: 12年前

Re: PMX他環境でエンコードされたファイルのデコードについて

#7

投稿記事 by Saigo » 11年前

みけCAT さんが書きました:とりあえず、モデル情報までの読み込みを実装してみました。
なんとなくですが理解出来ました。
自宅に帰って実装してみたいと思います。
本当にありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る