KLFileによるファイル入出力と整合ハッシュ/簡易暗号化について

KLib

KLibについては、まずこちらの記事を参照ください。


fopenが縛られるファイル管理

iOSのファイルを読み込む際、
バンドル(アプリに予め入れておくリソース)に含まれるファイルは
fopenで開くことができます。

しかし、よくファイル保存先に使用される
ドキュメントフォルダやテンポラリフォルダなどへの
ファイルアクセスはiOSAPIObjective-C)経由でしか正しく処理されません。
これらの場所へfopenでアクセスすると正しく動作しません。

そこで、今回はKLibに用意された
ドキュメントフォルダ内のファイルを処理する関数を
いくつかピックアップしたいと思います。


空のファイルをドキュメントフォルダに作成する

KLFile_MakeDocuments("test.txt");

ドキュメントフォルダにtest.txtという空ファイルが作成されます。
既存のファイルの場合は空ファイルで上書きます。


ドキュメントフォルダにあるファイルを読み込む

size_t filesize = 0;
bin8* pBin = KLFile_ReadDocuments("test.txt", &filesize);

test.txtを読み込み、バイナリ配列(char*と同じ)を返します。

読み込んだファイルサイズも知りたい場合は、
第二パラメータにsize_t型のポインタを渡すと
そこに読み込んだファイルサイズが入ってきます。
ファイルサイズを知るのが不要ならNULLを指定してください。


ドキュメントフォルダにあるファイルを読み込む+ファイル更新日を知る

char str[32];
bin8 pBin = KLFile_ReadDocumentsModDate( "test.txt", &filesize, str );

基本的には、KLFile_ReadDocumentsと同じですが
第三パラメータにchar配列のポインタを渡すことで、
ファイルの作成日or修正日の日付を入れて返してもらうことができます。
不要ならばNULLで構いません。

ファイル更新日の文字列は
"2012-01-23 04:56:00 +0000"のような形で返されます。


ドキュメントフォルダにある整合ハッシュ付きファイルを読み込む

// RPGのセーブデータっぽい例
typedef struct{
    u16 lv, hp, mp;
    u16 attack, guard, speed, magic; 
    u32 equip;
}SaveData;

SaveData save;
size_t filesize = 0;

if( KLFile_ReadDocumentsWithHash("rpgsave.bin", &filesize, &save) )
{
    // 整合できてファイルも読み込めた時の処理
}
else
{

     // 不整合やファイル読み込みできなかった時
     KLAlert_Open( "", "おきのどくですが、ぼうけんのしょは消えてしまいました。", "OK",NULL );
     // ついでにアラート出しの説明
     // KLAlert_Open( タイトル文字列, 文言, ボタン文字列, ボタン押しコールバック先 )

}

整合ハッシュについて後述しますが、
あらかじめWrite時もWithHashで書き出したファイルである必要があります。

WithHashの時は戻り値でバイナリがかえるのではなく、
あらかじめ決まった型の構造体などを直接渡し、そこに流しこむ形式を取っています。
ハッシュは除去された状態でかえるので、
コール側でハッシュの後始末を気にする必要はありません。

ハッシュ不整合、ファイル読込できないなど処理が正常に終了しない場合、
FALSEがかえるので、if文で分岐を行えます。


ドキュメントフォルダにあるファイルの削除

KLFile_DeleteDocuments("test.txt");

test.txtをドキュメントフォルダから削除します。
なければ処理されません。


指定パスをCFURLRef形式に変換

KLFile_CharToURLRef("YourPath");

Objective-CなどAppleAPIにはcharではなく
CFURLRefを渡せというものが存在します。
charで指定したいのに!という時はこれで変換したものを渡してください。


ドキュメントフォルダにファイルを書き込む

KLFile_WriteDocuments( "test.txt", pYourWriteValue, writeByteCount, adjustSize );

第一パラメータ…書き込むファイル名
第二パラメータ…書き込む変数のポインタ
第三パラメータ…何バイト書き込むかのサイズ
第四パラメータ…通常は0でOK
        非0でそのサイズまでファイルを切り詰めたり、
        ファイルサイズが足りなければ0埋めでそのサイズまで拡張する


ドキュメントフォルダにファイルを追記で書き込む

KLFile_WriteAddDocuments( "test.txt", pYourWriteValue, writeByteCount );

第一パラメータ…書き込むファイル名
第二パラメータ…書き込む変数のポインタ
第三パラメータ…何バイト書き込むかのサイズ

ファイルが存在すれば、その末尾に追記します。
存在しなければ作成して書き込みます。


ドキュメントフォルダに整合ハッシュ付きファイルを書き込む

KLFile_WriteDocumentsWithHash( "test.txt", pYourWriteValue, writeByteCount );

第一パラメータ…書き込むファイル名
第二パラメータ…書き込む変数のポインタ
第三パラメータ…何バイト書き込むかのサイズ

整合ハッシュについては後述しますが、
ファイルの改竄を検知するため、
簡易的なファイル整合ハッシュを頭に付与して
ファイルを保存します。

ハッシュ付きで保存したファイルはRead時もWithHashで読み込まないと
サイズが合わずにおかしくなるので注意してください。


バイナリを簡易暗号化/復元する

int test[] = { 1, 10, 100, 1000 };

// 暗号化前の数値を出力してみる
KLLog("Test:%u %u %u %u\n", test[0],test[1],test[2],test[3],);

// 簡易暗号化する( 後ろ4つは暗号化に使う適当な乱数シード )
KLMath_Encrypt( test, sizeof(int)*4, 1234, 5678, 9101, 11213 );

// 処理されたか出力してみる
KLLog("Test:%u %u %u %u\n", test[0],test[1],test[2],test[3],);

// 復元する( 後ろ4つは暗号化に使ったのと同じ乱数シード )
KLMath_Decrypt( test, sizeof(int)*4, 1234, 5678, 9101, 11213 );

// 復元されたか出力してみる
KLLog("Test:%u %u %u %u\n", test[0],test[1],test[2],test[3],);

WriteWithHashする前のバイナリをこれで簡易暗号化すると
セーブデータの解析がプレーンファイルよりは難しくできるでしょう。
使用する際は、必ず後述の注意を御覧ください。

なお、アプリ提出時の暗号化技術の使用云々については
この程度の処理は暗号化と言えるレベルでもないのでNOで問題無いはずです。
が、問題ありそうでしたらご一報頂けると助かります。


整合ハッシュについて

ファイルのバイナリから一定の法則で計算した4byteの数値で、
保存時に付与することで読み込み時に書き込んだ時と同じファイルかを整合できます。
法則を割り出されてファイルハッシュごと変えられたら元も子もないのですが、
プレーンファイルをまま保存するよりはマシ程度で使って頂けるかと思います。

セーブファイルに限らず、
ハッシュは変数のアドレスと何バイト計算するかを指定すれば
いつでも取得する事ができます。

#include "KLMath.h"

int test[10] = { 0,1,2,3,4,5,6,7,8,9 };
u32 hash = KLMath_GetHash32( test, sizeof(int)*10 );

使用上の注意

ハッシュやWithHashの関数、簡易暗号化/復元を使う場合、
アプリ毎にハッシュキー、またはランダムシードを変更してください。
これは、ハッシュ付与や暗号/復元時に内部でキーを元に乱数を作成しているためです。
キーは「KLMath.c」にKLMATH_HASHKEY_X〜Wの4つが定義されています。
乱数シードも変えたい場合は、「KLConfig.h」に4つ定義されていますので
そちらを変更してください。
(キーもConfigにまとめないとややこしいですねこれ…。)

アプリを一度リリースしたら、キーやシードは変更しない事をオススメします。
途中で変更してしまうとハッシュ計算の法則も変わり、
同じファイルでも違うハッシュになるためです。
そのため、リリース後にシードを変更してアップデートを行うと
アップデート前後でセーブデータの整合が取れなくなる事が予想されます。

iPad/iPhone両対応のアプリを作るには

Klib動作を確認できるiPadを保持していないので、確信が持てなかったのですが、

iPadシミュレータで確認した所、ユニバーサルでも動いたので

(たぶん)KLibでiPadアプリ開発もいけると思われます。

KLib

KLibについては、まずこちらの記事を参照ください。


ユニバーサルアプリで作成する方法

プロジェクトの設定からTARGETSのプロジェクトを選択

iOS Application Targetのくくりにある「Devices」を「iPhone」から「Universal」にすることで

iPhoneで起動時はiPhoneの解像度、iPadで起動時はiPadの解像度という風に

1バイナリでiPhoneiPadそれぞれのアプリを作成する事ができます。

klib.device.w と h の値も、それぞれの起動端末に合わせた値で返ってくるようになります。

iPadiPhoneでは画面サイズや性能が異なるので、処理も分岐させる必要がある箇所がでてくるはずです。

その際は、「klib.device.iPad」のフラグで起動端末を判別できますので、

それを元に処理を分岐してください。

klib.device.iPhoneで判別してしまうと、elseの対象がiPadとiPodTouchになってしまうので注意が必要です。

if( klib.device.iPad )
{
   // iPadだけで行いたいコード
}
else
{
   // iPhoneとiPodTouchで処理するコード
}

OpenGLES2のラッパー機能

KLib

KLibについては、まずこちらの記事を参照ください。


OpenGLES2のラッパー機能

KLibにはgl〜〜〜の関数を状態変更があった場合にだけ機能するラッパー関数が用意されています。

例えば、デプステストを有効にする場合、

glEnable(GL_DEPTH_TEST);

と記述しますが、接頭句 gl を gls とし

glsEnable(GL_DEPTH_TEST);

とする事で現在GL_DEPTH_TESTが無効になっている時のみ有効になります。

強制的に変更したい場合は、最後にfを付け

glEnablef(GL_DEPTH_TEST);

とする事で強制的に有効にする事もできます。

大体のgl関数はglsとする事でこのような処理が行えます。

OpenALを使用したサウンド再生

KLib

KLibについては、まずこちらの記事を参照ください。


KLib経由のサウンド再生

KLibでサウンド再生は、OpenALを使用して再生します。

必要な処理は、

  • 変数の用意
  • 解放処理の仕込み
  • 再生

です。


変数の用意

KLibがOpenALを使用する際に使う変数はKLAL型です。

使用するサウンド毎にKLAL型の変数を用意します。

#include "KLAL.h"
KLAL sound;

鳴らしたいサウンド数に応じて複数定義してください。


サウンドファイルの読み込み

次にサウンドファイルを読み込みます。

KLAL_Load( &sound, "YourSoundFileName.wav" );

対応ファイルはwav/mp3(内部変換可能なm4a形式)です。
また、OpenALで読み込めるファイル仕様にも基づきます。


解放処理の仕込み

全シーンで使いまわすならアプリ終了時、

読み込んだシーンだけで使うならそのシーンのアンロード処理に

サウンドのアンロード処理を加えてください。

KLAL_Unload( &sound );

再生

サウンド再生をしたい場所で、

float starttime = 0.0f;
KLAL_Play( &sound, starttime, isLoop );

をコールします。

starttimeにはサウンドを開始したいオフセットをfloat秒で、

isLoopには、サウンドをループさせたい場合にTRUEを設定します。



基本的なサウンドの鳴らし方は以上です。

KLDeviceで端末情報にアクセスする

KLib

KLibについては、まずこちらの記事を参照ください。


端末情報にアクセスする

KLib.hをインクルードすることで、

klibオブジェクトを参照する事ができます。

その中でも今回は「klib.device」でアクセスできる項目について説明します。

画面サイズを返す

端末の解像度(ピクセル数)を得るには

klib.device.w // 幅
klib.device.h // 高さ

を使用します。

960や640などの整数を返します。

画面の回転に対応させた場合、回転時に自動で再設定されます。


端末の向きを判別する

現在の端末向きがポートレイトか、ランドスケープかを判断したい場合、

KLDevice_IsPortrait()

または、

KLDevice_IsLandscape()

で判断できます。


端末の種類とiOSバージョンを取得する

起動している端末のタイプは

klib.device.type

で取得できます。

値は、KL_IPHONE4やKL_IPAD2GなどのKLDeviceTypeで返されます。

iOSのバージョンは

klib.device.iosVer

で取得します。

6.0や5.1などfloatで返されます。


iPhoneiPadかをフラグで判別する

iPhoneであれば

klib.device.iPhone

が真

iPadであれば

klib.device.iPad

が真

iPodTouchであれば

klib.device.iPodTouch

が真を返します。

iPhone4や5など端末のシリーズ番号は

klib.device.series

で取得できます。

また、シミュレータ上で起動しているかは

klib.device.simulator

で判別できます。


言語

現在端末に設定されている言語は、

klib.device.language

で取得できます。

例えば、英語なら"en"

日本語なら"ja"を返します。

英語と日本語に限り、判別する関数が用意されています。

KLDevice_IsLangEnglish() // 設定言語が英語で真
KLDevice_IsLangJapanese() // 設定言語が日本語で真

UIID

そのプロジェクトでビルドしたアプリの初回起動時、

KLibは初期化の際にUIIDを作成し、キーチェーンに保存します。

2回目以降からは保存したキーチェーンから読み込んだUIIDをセットします。

UIIDには

klib.device.uiid

でUIIDのchar文字列にアクセスできます。

マップチップを使ったテクスチャアトラスのKLSpriteサンプルをコミットしました

KLib

KLibについては、まずこちらの記事を参照ください。


サンプルに例を追加

KLibサンプルプロジェクトにテクスチャアトラスを使用して、
1枚のテクスチャからマップチップを切り貼りして描画するサンプルを追加しました。

一応iPhone4でもFPS60が出る事を確認済みです。(ギリギリかも?)


KLSpriteについて

テクスチャをロードするには、

KLSprite_Load( &sprite, "yourimage.png" );

のようにプロジェクトに追加したファイル名を指定します。

対応フォーマットは

に対応しています。

テクスチャを読み込んだスプライトは、
不要になったら必ずアンロードしてください。

アンロードは2種類あり、

KLSprite_Unload(&sprite);

KLSprite_UnloadWithTexture(&sprite);

です。

スプライトの使用しているテクスチャも破棄する場合は後者を使用してください。
スプライトを複製などして使いまわす時は前者を使用してください。

座標や大きさの設定

KLSprite_SetXY( &sprite, x, y );

または、

sprite.x = x;
sprite.y = y;

で座標を

KLSprite_SetWH( &sprite, width, height );

または、

sprite.w = width;
sprite.h = height;

で大きさを変更する事ができます。

回転は

KLSprite_SetRot(&sprite, rot);

または、

sprite.rz = rot;

で指定可能です。
一周360度ではなく、65536度です。
角度はu16型と同様のwdegという型で定義されているので注意してください。



表示座標や回転の基準点はデフォルトでスプライト左上の0,0になっていますが、
この座標点を変更したい場合は、

KLSprite_SetCenter(&sprite, sprite.w/2, sprite.h/2);

のようにSetCenterを使って基準点を設定可能です。
上記の場合はスプライトの中央が基準点になって回転軸も中心になります。

現在のサイズから中心をセットするならば、
上記の方法でも設定できますが、

KLSprite_SetCenterSimple(&sprite);

とする事で同様の処理が行われます。
状況に合わせて使い分けてください。


テクスチャ座標を指定するには、

KLSprite_SetUV(&sprite, x, y, rightBottomX, rightBottomY )

で割り当てできます。
サイズは現在のままでテクスチャだけセットされますので、
テクスチャ設定時に一緒にサイズも合わせたい場合は、
関数後ろにFitをつけて

KLSprite_SetUVFit(&sprite, x, y, rightBottomX, rightBottomY )

とする事で同時にサイズも合わせられます。



座標などの設定が完了し、そのフレームで描画する事が決まったら

KLSprite_Draw(&sprite);

として描画を行なってください。



色の指定も可能です。

KLSprite_SetRgba(&sprite, r, g, b, a)

とすると、テクスチャの不透明な部分の色をどれくらい有効にするかを設定します。
これは白テクスチャにどういう色を塗るかという認識に近いです。

例えば、真っ白なテクスチャはデフォルトでRGBAがそれぞれ255ですが、
真っ赤にしたい時は

KLSprite_SetRgba(&sprite, 255, 0, 0, a)

のように赤だけ255、緑、青を0とする事で行えます。(赤だけ有効になる)

テクスチャ全体に色を加算したい場合は、

KLSprite_SetRgbaAdd(&sprite, r, g, b, a)

としてください。
a値も加算すると透明なピクセルのアルファも加算されてしまうので注意してください。


既知の不具合

大きい木の上部などを見るとわかりますが、
前後のテクスチャを1ピクセル拾ってきてしまう問題があるみたいです‥
おそらく小数精度の問題と思われますが別途修正してコミットします。

KLTouchについて

KLib

KLibについては、まずこちらの記事を参照ください。



割り込みイベントではないタッチ判定

iOSプログラミングの際、タッチはイベントで発生するので

イベントを受け取って都度判定をするなんて事がありますが、

KLibでは、メインループで各フレームごとにタッチイベントがあったかで判別をします。

具体例

例えば、ある範囲がタッチされたかをチェックしたい場合、

if( KLTouch_IsStart( x, y, width, height ) ){
    // 範囲内をタッチした
}

とする事でx,yから幅width、高さheightの範囲内でタッチがあれば

真判定となります。

同様に

if( KLTouch_IsMove( x, y, width, height ) ){
    // 範囲内を移動した指がある
}

if( KLTouch_IsEnd( x, y, width, height ) ){
    // 範囲内に離した指がある
}

でスワイプや離した判定をする事が可能です。

KLTouch_Is○○は範囲内に該当した指があればその指IDを返しています。

該当しなければ0を返すのでif文で判定できます。

指情報の詳細を得る

ある指のタッチが始まった座標や移動方向を知りたい場合、

u32 fingerid = KLTouch_IsStart(x,y,width,height);

のようにしてまず指IDを保持します。

次に、そのIDを使用してKLFingerオブジェクトを取得します。

if( fingerid){
    KLFinger* pFinger = KLTouch_GetFinger(fingerid);
    KLLog("Touch startx:%d, Y:%d\n", pFinger->startx, pFinger->starty );
}

指情報オブジェクトから、

  • タッチ開始点(startx,starty)
  • 移動中の座標(movex.movey)
  • 離した際の座標(endx,endy)
  • 移動した方向と距離(power.dir, power.dist)

などが取得できます。

条件に当てはまる指IDをすべて取得

KLTouch_IsStartなどは、条件にひとつでも当てはまる指があれば

はじめに当てはまる指IDを返します。

しかし、条件に当てはまるすべての指IDが欲しい場合もあります。

そんな時には、

u32* pFingerList = KLTouch_IsStartAll(x,y,width,height);

のようにAllを末尾に加えた関数をコールしてください。

当てはまる指ID配列のポインタを返します。

当てはまるものがある場合、終端は0になっています。[1234,5678,9101,0]など

当てはまるものがなければNULLを返します。