ページ 1 / 1
fwrite_セーブデータ
Posted: 2017年7月15日(土) 20:33
by にこよん
こんばんは
現在作成しているゲームで以下のようなセーブ機能を作ったのですが(一部省略)
コード:
void save_file() { //ステージをセーブする
int data[10]; //セーブデータ構造体
DATEDATA ddata; //日付と時間の格納用変数定義
GetDateTime(&ddata); //現在時刻を取得
data[0] = ddata.Year;
data[1] = ddata.Mon;
data[2] = ddata.Day;
data[3] = ddata.Hour;
data[4] = ddata.Min;
data[5] = map_edit.pass;
data[6] = map_edit.cx;
data[7] = map_edit.cy;
data[8] = (map_edit.cx + map_edit.cy + map_edit.pass + ddata.Min) / 2;
data[9] = map_edit.back;
MakeFolder("EditStage");
FILE *fp;
errno_t err;
err = fopen_s(&fp, map_edit.f_path, "wb");
fwrite(data, sizeof(int), 10, fp);
fwrite(enemy_edit.flag, sizeof(int), ENEMY_MAX, fp);
fwrite(enemy_edit.kind, sizeof(int), ENEMY_MAX, fp);
fclose(fp);
}
データ改造&解析対策(暗号化)と、ファイル容量を減らす(圧縮)ことを目的として
軽く圧縮をかけてから保存したいと考えています。
どのようにすればいいのか教えてください。
私が現在考えているのはfweite関数のファイルに出力するのではなく
バイナリになった文字列として返してくれる関数を使用して(あるのかどうかわかりませんが)
char型などに一度格納し同じ文字列を短縮してからファイルに書き出すというものです。
ただググっても上の様な関数は出てこなかったのでどうすればいいのかもわからずここで質問させてもらいました。
圧縮の方法は以下の様な方法を考えています
DXライブラリ管理人 さんが書きました:
RRRRRRABDDDDDGG
という文字列があったとします。
これに『ランレングス圧縮』という圧縮をかけると次のようなデータになります。
RR6ABDD5GG2
ここでは
同じ文字が2回以上続いたらその部分を
[2回以上続いた文字][2回以上続いた文字][続いた数]
という情報に置き換える
...
↑
ttp://dxlib.o.oo7.jp/lecture/Press/press.html
Re: fwrite_セーブデータ
Posted: 2017年7月15日(土) 20:44
by みけCAT
にこよん さんが書きました:バイナリになった文字列として返してくれる関数を使用して(あるのかどうかわかりませんが)
char型などに一度格納し同じ文字列を短縮してからファイルに書き出すというものです。
ただググっても上の様な関数は出てこなかったのでどうすればいいのかもわからずここで質問させてもらいました。
例として、memcpy関数を用いてchar型の配列に格納し、処理する方法が考えられます。
「文字列」(NUL終端されたバイト列)にはならないので、自分で長さを管理する必要があり、strcpyなどの文字列を操作する関数は使えないことに注意してください。
コード:
int data[10];
char work[sizeof(int) * 10];
/* dataに値を格納する */
memcpy(work, data, sizeof(data));
/* workの中身を圧縮する */
/* 圧縮したデータをファイルに書き込む */
オフトピック
40バイト程度の小さいデータをわざわざ圧縮しても、現在一般的なディスクでは512バイト以上のブロック単位でファイルを格納するので、意味がほとんど感じられません。
複数のファイルをまとめたアーカイブなら効果は無くはないかもしれませんが、誤差でしょう。
したがって、圧縮よりも、xor、DES、AESなどの暗号化を考える方がいいと思います。
【追記】
見落としていました。長さがわからないですが、enemy_editの情報も格納するのですね。
Re: fwrite_セーブデータ
Posted: 2017年7月15日(土) 20:49
by みけCAT
複数の配列の場合は、例えば以下のようにするといいでしょう。
コード:
/* 入力の配列の宣言は省略 */
char work[sizeof(int) * (10 + ENEMY_MAX + ENEMY_MAX)];
memcpy(work, data, sizeof(int) * 10);
memcpy(work + sizeof(int) * 10, enemy_edit.flag, sizeof(int) * ENEMY_MAX);
memcpy(work + sizeof(int) * (10 + ENEMY_MAX), enemy_edit.kind, sizeof(int) * ENEMY_MAX);
Re: みけCAT 様
Posted: 2017年7月15日(土) 21:02
by にこよん
速い返信ありがとうございます。
>40バイト程度の小さいデータをわざわざ圧縮しても、現在一般的なディスクでは512バイト以上のブロック単位でファイルを格納するので、意味がほとんど感じられません。
最初に書いたコードは一部省略しているのですが実際は500KB程度のファイルになります。
容量的には意味はないというのは初めて知りました。
ただ、zip圧縮するとこのファイルが3KBになるので暗号化のついでに自分で圧縮したいと考えた次第です。(多分 0 ばっかり保存してるから)
それと、暗号化もできれば自分でしたかったのです。
文字を入れ替えるにしても、自分でもよくわかってない暗号化っていうのが嫌で...(すみません)
C言語初心者でここからコードにするのが難しいのでもう少し具体的に教えていただけないでしょうか?
memcpyで複数の構造体をwork[]内に入れる方法が分かりません。
もしworkが圧縮しできたとして、workをそのままfwriteでバイナリモードで書き込むのでしょうか?
質問ばかりですみません。
Re: fwrite_セーブデータ
Posted: 2017年7月15日(土) 21:18
by Dixq (管理人)
データの改竄を発見できる仕組みを
http://dixq.net/g/
の3.12章から解説していますけどご覧になりましたか?
多くの場合、ハッシュ値を付けたら暗号化は不要です。
但しバイナリコードを読んでハッシュ値チェック部分をスキップしてしまうようなチーターまで対策したいなら
ハッシュ値対策に暗号化まで組み合わせると安全性は上がるかと思います。
まずはMD5等のハッシュ値を付ければよいかと思います。
ちなみに龍神録1も2もMD5によるハッシュ値が付けてあるのでセーブデータを1bitでも変更して保存すると
「このデータは改竄されています!」のような表示がでるようになっています。
Re: Dixq (管理人)様
Posted: 2017年7月15日(土) 22:03
by にこよん
ゲームプログラミングの館や竜神録の館でいつもお世話になってます。
上2つのサイトはほぼすべて読まさせていただきました。
自分で作成したハッシュ値は下の部分で使用しているのですが
data[8] = (map_edit.cx + map_edit.cy + map_edit.pass + ddata.Min) / 2;
MD5は昔よくわからなくてあきらめていたのでまた調べてみようと思います。
ただ、暗号化のついでに圧縮もしてみたいので最初の目的は変えないでおこうと思います。
Re: みけCAT 様
Posted: 2017年7月15日(土) 22:18
by にこよん
複数の配列の場合の方法はなんとなくわかったのですが
コード:
fwrite(enemy_edit.flag, sizeof(int), ENEMY_MAX, fp);
の様なものが30個ぐらいあるのですがデータの量が変わったときなどに大変そうなので、もう少し効率のいい方法はないでしょうか?
例えば fwrite 形式でとりあえず .temp ファイルとして書き出してそれを再度読み込むような...
ただこれだと tempファイルが消せない場合があったりいろいろ問題がありそうなのであまりいいとは思わないのですが。
Re: fwrite_セーブデータ
Posted: 2017年7月16日(日) 09:58
by Dixq (管理人)
セーブデータの先頭には王道のこの3つを入れるといいでしょう。
識別子:char8 バイト
バージョン番号:short int 2バイト
本体のデータサイズ:int 4バイト
識別子は何でもいいです。「nikoyon」という文字列でもいいです。このファイルがにこよんさんが作ったセーブデータだという宣言が書いてあるようにします。
バージョン番号がないとゲームをリリースした後にセーブデータの仕様変更があったとき対応できなくなります。
続くデータサイズがいくらあるのかをここに記載します。
> 暗号化のついでに圧縮もしてみたいので
暗号化は暗号化でちゃんとし、圧縮は圧縮でちゃんとしましょう。
中途半端に~~がてらというのは良くないです。
圧縮については自分で計算しなくてもライブラリを使うべきでしょう。
世界でもっとも実績のあるzip圧縮がよいでしょう。
http://www.zlib.net/
> の様なものが30個ぐらいあるのですが
一度に全て入出力したいなら、一つの構造体やクラスにつめてやればどうですか?
Re: みけCAT 様
Posted: 2017年7月16日(日) 10:53
by みけCAT
にこよん さんが書きました:データの量が変わったときなどに大変そうなので、もう少し効率のいい方法はないでしょうか?
C++であれば、std::vectorを使うのがよさそうだと思います。
コード:
#include <vector>
#include <cstdio>
int main(void) {
int array1[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int array2[5] = {123456, 789012, 345678, 901234, 567890};
std::vector<char> data;
// 各配列のデータをバイト単位でdataの最後に追加
data.insert(data.end(), (char*)array1, (char*)(array1 + 10));
data.insert(data.end(), (char*)array2, (char*)(array2 + 5));
// テスト用に出力
int count = 0;
for (std::vector<char>::iterator it = data.begin(); it != data.end(); it++) {
printf("%02X%c", (unsigned char)*it, ++count % 16 == 0 ? '\n' : ' ');
}
if (count % 16 != 0) putchar('\n');
return 0;
}
Re: fwrite_セーブデータ
Posted: 2017年7月16日(日) 12:12
by littlestream
暗号化としては、xorが一番シンプルでかつ簡単だと思います。
圧縮としては、ランレングス圧縮が一番シンプルでかつ簡単だと思います。
詳しく圧縮について知りたいのであれば、アマゾンのギフトカードをコンビニ等で購入したうえで
Windows版のキンドルというソフトウェアをインストールしたら
Cマガジンの1998年10月の記事を108円で購入すると、
特集記事の第3章に圧縮処理の技法が載っているので
圧縮処理のヒントになると思いますが、大分古いので、ネット上の圧縮アルゴリズムも調べてみる事も
おすすめします。
サンプルコードを載せておきます。
コード:
#include<stdio.h>//暗号化プログラム
int main(int argc,char *argv[])
{
FILE *fp,*fpw;
char data;
if(argc>1)//コマンドラインの引数が1より大きければ
{
fp=fopen(argv[1],"rb");//ファイルを読み込みでオープンする
if(fp==NULL)
{
printf("ファイル%sがオープン出来ませんでした。\n",argv[1]);
return -1;
}
fpw=fopen("xorcrypt.bin","wb");//xorcrypt.binファイルに書き込む(固定だが工夫の余地あり)
if(fpw==NULL)
{
printf("ファイルxorcrypt.binがオープン出来ませんでした。\n");
return -1;
}
while(fread(&data,sizeof(data),1,fp)!=NULL)//EOF(NULL)になるまで読み込む
{
data^=0xFF;//暗号化のキー255(これは255以外でなんでもいいが、
//復号化と暗号化が同じキーじゃないと齟齬が出来てしまう、
//また関数と逆関数なども良いアイデアだと思う)
fwrite(&data,sizeof(data),1,fpw);
}
fclose(fpw); //ファイルクローズ
fclose(fp);//ファイルをクローズする
}
}
Re: Dixq (管理人)
Posted: 2017年7月17日(月) 02:21
by にこよん
ご回答ありがとうございます。
>識別子:char8 バイト
>バージョン番号:short int 2バイト
>本体のデータサイズ:int 4バイト
このような暗黙のルール?は初めて知りました。
今後データを保存する時には書き出すことにします。
データサイズは上の3つも含めてsizeofの返り値をそのまま書き出すので大丈夫でしょうか?
>暗号化は暗号化でちゃんとし、圧縮は圧縮でちゃんとしましょう。
分かりました。
ziplibについてはなんとなくググったときに見つけたのですが使い方がよくわかりませんでした。
>一度に全て入出力したいなら、一つの構造体やクラスにつめてやればどうですか?
沢山のソースファイルの static 変数を保存する予定なので悩んでました。
クラスについてはよくわかったいないので調べてみます。
Re: みけCAT 様
Posted: 2017年7月17日(月) 02:25
by にこよん
>C++であれば、std::vectorを使うのがよさそうだと思います。
サンプルコードありがとうございます。
確かにC++のコンパイラなのですがC言語以上のことは全く分かりません。
サンプルコードも何が起こっているのかわからないので応用するのは難しそうです。
Re: fwrite_セーブデータ
Posted: 2017年7月17日(月) 02:41
by にこよん
サンプルコードありがとうございます。
^=すら理解できずに調べたら「ビット単位の排他OR代入演算子」と出てきて
さらに?が増える始末。
これはしばらく調べたらなんとなくわかりました。
比較的簡単そうなので、
とりあえず暗号化はこれとハッシュ値を組み合わせた数にでもします。
Re: fwrite_セーブデータ
Posted: 2017年7月17日(月) 11:49
by Dixq (管理人)
> データサイズは上の3つも含めてsizeofの返り値をそのまま書き出すので大丈夫でしょうか?
一つのクラスや構造体に出来ないようなので、sizeofは使えないのではないでしょうか?
また、一般的に本体のサイズにヘッダサイズは含めません。
> 沢山のソースファイルの static 変数を保存する予定なので悩んでました。
仮にバラバラの場所にある変数だとしても構造体に入れてから書き出すことは出来ますよね?
> ziplibについてはなんとなくググったときに見つけたのですが使い方がよくわかりませんでした。
なんとなくぐぐった時にも、今改めてググった時にも分からないのでしょうか?
参考になるサイトやサンプルコードは沢山出てきますよ。
https://www.google.co.jp/?gws_rd=ssl#q=zlib+C
Re: Dixq (管理人) 様
Posted: 2017年7月17日(月) 19:18
by にこよん
いま改めてググっていろいろ調べていたのですが
zlibをダウンロードして中の説明書通りにzlibvc.slnをビジュアルスタジオ2015で開き、
ビルドしたところで行き詰りました。
6つほどファイルができたのですが
そのうち
zlibstat.libやzlibwapi.dll
と、元から入っていた
zlib.h
を何とかすればいいぐらいまでしか調べても分かりませんでした。
zlib.hをインクルードしてサンプルコードをコピペしたところ
deflateInit(&zip, Z_DEFAULT_COMPRESSION);
が赤波になってしまい、ビルドすると179こエラーを吐かれます。
このあたりからどのようにすればいいのかご教授下さい。
Re: fwrite_セーブデータ
Posted: 2017年7月19日(水) 18:38
by littlestream
http://free.pjc.co.jp/Zlib/indexold.html
DLLバージョンのzlibの解凍・圧縮のソースを探していたのですが、
なかなか見つからず結局上のページに辿り着きました。
コマンドライン上で動くようですが、デバッグしやすいように工夫されているとの事なので
使ってみてはどうでしょうか?
Re: fwrite_セーブデータ
Posted: 2017年7月20日(木) 14:40
by にこよん
>コマンドライン上で動くようですが、デバッグしやすいように工夫されているとの事なので
>使ってみてはどうでしょうか?
すみませんやはりよくわかりませんでした。
返信後もいろいろ調べてみたのですが、よくわからずzlibは今回はあきらめます。
最初の方で教えていただいた方法やファイルheaderを使って、何とかしようと思います。
分からなかったら又質問するかもしてませんが、
スレッドが長引くのはあまりよくないと思いますので教えていただいたことを元にここからは自分で頑張っていこうと思います。
このスレッドで教えてくださった皆様ありがとうございました。