カテゴリー別アーカイブ: コード

SwiftでC言語互換のポインタ操作が面倒

というかそんなことをしなければ良いのかも知れませんが画素などを直接操作する場合には必要になってしまうこともないわけではないのでいろいろとやってみました。ちなみに、普通であればそんな操作をする場合であればC言語やObjective-Cにコードを持って行ってそこで処理した結果をSwiftに拾い上げた方が良いと思います。まあ、それは置いておいて、どんなコートが互換コードとなるのか見てみるとその性質が見えてくるのではないでしょうか。

 

SwiftでC言語互換のポインタを扱う

まずはこれから。この辺はまだSwiftの言語仕様などを解説している本やページを見れば書いてあると思いますがちょっとだけ解説。関数ポインタやクラスのポインタでは扱いが微妙に異なりますが通常こんなものが必要なのは生のデータを扱う時だと思いますのでそれを念頭に。

まずポインタの種類から。Swiftではポインタを扱うことができませんから何らかの方法でポインタを保持する必要があります。そのためにとある型が用意されています。それがUnsafePointer<T>、UnsafeMutablePointer<T>、AutoreleasingUnsafeMutablePointer<T>の三つになります。それぞれ、以下のような役割となります。

C言語/Objective-Cの型 Swiftの型
const T * UnsafePointer<T>
T * UnsafeMutablePointer<T>
T ** AutoreleasingUnsafeMutablePointer<T>

Tの部分は使用する型に置き換えて下さい。ちなみに、C言語/Objective-Cではvoidと書くvoid型ですが、SwiftではVoidとVが大文字となり、NULL(nil)ポインタはSwiftではすべてnilで扱われます。また、クラスオブジェクトを扱わないのであれば上の2つだけでデータ操作は何とかなると思います。AutoreleaseingUnsafeMutablePointerはどちらかというとObjective-Cのオブジェクトを保持しておくためにあるのでC言語レベルの処理では使わないはずです。

 

ポインタのキャスト

ポインタなのでキャストできないとどうしようもありません。が、Swift上では独立した型なのでそのままコピーは一切できません。C言語と同じようにキャスト構文を書く必要があります。たとえば、C言語上で

unsigned char *buf;
int *array;
・・・
array = (int *)buf

と書きたい場面があるとします。このとき、型キャストを書くと

var buf: UnsafeMutablePointer<UInt8>
var array: UnsafeMutablePointer<Int32>
・・・
array = UnsafeMutablePointer<Int32>(buf)

となります。このあたりはまだ理解しやすいのではないかと思います。微妙に見えるのは型キャストのたびに変換する型のオブジェクトを擬似的に作成するところでしょうか。ちなみに、unsigned charは8bitの符号無し整数と考えてUInt8に、intは32bit符号あり整数と考えてInt32と型を限定してつけているのにも注意です。変数のサイズも考えて書かないと互換とは言い切れなくなるので。

 

UnsafePointerやUnsafeMutablePointerのポインタを参照する

ポインタを参照する時の手順です。単純にポインタとしてアクセスする場合もありますし、ポインタを配列のように扱ってインデックスをつけて処理する場合もあります。この場合はどうするのかというと・・・。いろいろとパターンがあるのでいくつかのパターンで見ていきます。基本的にはUnsafePointer<T>やUnsafeMutablePointer<T>が持つプロパティであるmemoryを通します。が、このmemoryの使い勝手が微妙に面倒。特に構造体で各メンバ単体での取得ができなかったり、インデックス参照ができなかったり、と。

で、例ですが、一応構造体の例もあるので構造体を一つ宣言してそれについてのアクセスも見ていきます。意味が無い処理も多少ありますがサンプルと言うことで。

typedef struct clr32{ unsigned char b,g,r,a; } CLR32;

const CLR32 *p1; const int *p2; CLR32 *p3; int *p4; unsigned char gray;
・・・
// 以下p1,p2,p3,p4には有効なアドレスが入っているものとする(メモリ領域は十分にあるものとする)
// 読み込み+計算処理
gray = (unsinged char)((int)p1->r * 306 + (int)p1->g * 601 + (int)p1->b * 116) >> 10);

// 書き込み処理
p3->b = p3->g = p3->r = gray; p3->a = p1->a; *p4 = *p2;

// 読み込み+書き込み(インデックス、ポインタ演算)
p3[1] = *(p1 + 1); p4[1] = *(p2 + 1);

という処理があったとします。なんかグレースケール変換の処理が混じっていますがまあ気にせず。これをSwiftで再現すると・・・

internal struct CLR32{
    var b: UInt8 = 0
    var g: UInt8 = 0
    var r: UInt8 = 0
    var a: UInt8 = 0
}
var p1: UnsafePointer<CLR32>
var p2: UnsafePointer<Int32>
var p3: UnsafeMutablePointer<CLR32>
var p4: UnsafeMutablePointer<Int32>

・・・
// 以下p1,p2,p3,p4には有効なアドレスが入っているものとする(メモリ領域は十分にあるものとする)

// 読み込み+計算処理
let p1mem = p1.memory
let gray = UInt8((Int(p1mem.r) * 306 + Int(p1mem.g) * 601 + Int(p1mem.b) * 116) >> 10)
let clr = CLR32(b: gray, g: gray, r: gray, a: p1mem.a)

// 書き込み処理
p3.memory = clr
p4.memory = p2.memory

// 読み込み+書き込み(インデックス、ポインタ演算)
// 書き方はポインタ演算の書き方のみ可能
p3.advancedBy(1).memory = p1.advancedBy(1).memory
p4.advancedBy(1).memory = p2.advancedBy(1).memory

どうですか。面倒になったでしょう。memoryを通して扱うのですが、特に構造体などで注意ですが構造体のプロパティ(C言語などではメンバ)を通すと書き込み特性が失われてしまうので読み込む場合はそのまま参照してもどうと言うことはないのですが、構造体のデータを書き込むときは書き込む前にデータを集めて一気に書き込まないとうまくいきません。また、インデックス付きの場合はadvancedByでインデックス移動したポインタオブジェクトを新たに作成してその場所のmemoryを参照する、というやり方が必要になります。

 

UnsafePointerやUnsafeMutablePointerのポインタ位置を動かす

C言語などではインクリメントなどで次の要素を簡単に指せるのですが、Swift上で「再現する」となると意外と面倒です。一つにはインデックスを別変数にして参照する、という方法があり、これは上に紹介したやり方が使えますね。では本当に再現するとどんなコードになるのか、というと。まずC言語上で

int *p;

//ポインタのインクリメント
p++;

//ポインタを二つ動かす
p += 2

となるコードですが、(ここまでの説明を見た人なら何となく分かると思いますが)これをSwiftで書くと

var p: UnsafeMutablePointer<Int32>

//ポインタのインクリメント
p = p.advancedBy(1)

//ポインタを二つ動かす
p = p.advancedBy(2)

とadvancedByを用いた形式に早変わりです。移動量はオブジェクトの分だけ動くのでsizeof等の考え方は必要ないのがありがたいですか。関数の戻り値を代入しているように見えますが、一枚後ろではオブジェクトを生成してそれを保持するようにするコードなのでポインタを動かすたびにオブジェクトを生成しているように見えて嫌な感じもする人もいるかも。

 

UnsafeMutablePointerでのメモリの確保および解放

もちろん「C言語互換のポインタ」と言っているだけあってメモリの確保および解放は普通にできます。UnsafePointerはconstポインタなのでメモリの確保や解放とは無縁でしょうから無視して。C言語でメモリを確保する場合はだいたい

int *array;

//メモリの確保
array = (int *)calloc(100,sizeof(int)); //もしくはmalloc(100 * sizeof(int))
・・・
//メモリの解放
free(array);

と書きますが、これをSwiftに持って行くと

var array: UnsafeMutablePointer<Int32>

//メモリの確保
array = UnsafeMutablePointer<Int32>.alloc(100)
・・・
//メモリの解放
array.dealloc(100);

となります。メモリの解放時に解放するデータサイズを指定する必要があるのでメモリを確保した本体は同じポインタを保持した方がわかりやすいと思います。

で、私がとあることを忘れてテスト段階でアプリを落としまくった要注意点が一つ。

UnsafeMutablePointerはC言語のポインタをSwiftで扱うためのものなので(UnsafeMutablePointer自身はARCの管理下にあるが)UnsafeMutablePointer内部のポインタにARCなんて便利な構造はなく、メモリ管理はプログラマが行わなければならない。つまり確保したメモリはUnsafeMutablePointerがオブジェクトとして解放されるタイミングになってもdeallocは呼ばれないので自分で呼び出さないとメモリリークを起こすことに注意しよう

はじめはUnsafeMutablePointerのスコープが切れた段階で自動解放されるものだと思っていたので対応するコードを書かなかったのですが・・・。書かないとだめですので解放のポイントを間違えないようにしましょう。

 

無理矢理書かない方がいいのかも

と思わないでもない今日この頃。無理に書くくらいならC言語で書いたコードを連結した方がはるかにわかりやすいですね。メモリの確保と解放だけをSwift上で行うことになるはずなのでその場合はメモリの管理を忘れずに。

なお、本来ならSwiftのような言語では変数宣言は実際にその変数を使うまで遅らせて宣言と同時に初期化するのが正しい作法です。が、今回はC言語との対比を見るためにそのような書き方をせずに書いていますのであしからず。

CPUIDの処理もいろいろと変わったのね

まあ、知ってはいましたが。

VisualStudioのバージョンを2010に変更したことでCPUIDを参照するための組み込み命令も拡張されましたのでそれの対応処理をいろいろと書いていました。そのための資料も一新したのでちょっと書いてみたいと思います。

 

まずは基礎。CPUIDとは

で、何も知らない人のためにCPUIDの説明から。CPUIDとはx86系のCPUが持つ命令の一つで、CPUをある状態にして実行することで対象のCPUが持つ様々な情報を得ることができる命令です。特に昔(10年ほど前)だとADVシステムを作成するときにCPUの拡張命令であるMMXやSSE2を持っているかどうかという情報がゲームの実行速度(特に描画関係)に大きな影響を与えるために拡張命令が使用できるかどうかを調べて命令を切り替える、という作業が必要であったり、サポートのために実行しているPCの実行環境を出すために必要な命令として使用していたものです。

x86と書いているのでIntelのCPUでもAMDのCPUでも実装していますし、x86-64環境でも動作する命令です。もちろんこれはCPU上で直接実行する命令ですので組み込み命令を記述できるプログラミング言語でしか直接の実行はできません。CPUIDの情報を受け取るような別の処理を持っているプログラム言語もあるかもしれませんがそれはまあそれということで。

今日ではほとんど話題にもならなくなってしまった命令でもあります。そこまで拡張命令にこだわるとすればCPUを使った科学技術計算や動画のエンコードなどCPUの処理を限界まで引き出すときに対象の命令が有効かどうかを調べたりCPU-Zなどで自分のCPUを調べるときにCPU-Z上で実行される、という形でしか見ることは無いと思います。

ちなみにこの命令は実行しているCPUがIntel製かAMD製か、などを直接判別するために使われる命令でもあります。そのため、CPUIDの結果はCPUの種類ごとにかなり異なったものとなる場合分けがとんでもなく必要な命令だったりします。CPUID命令実行時にどのような処理結果となるかを直接知りたい人は各社が公開している資料を見てみるといいと思います。Intelの場合は「Intel 64 and IA-32 Architectures Software Developer’s Manual 2A」に、AMDの場合は「AMD64 Architecture Programmer’s Manual Volume 3 General-Purpose and System Instructions」に記述があります。

 

命令のサポートフラグがいろんな場所に・・・

本当にいろんなところにフラグがあるんです・・・。CPUが実行可能な命令を取得するためにCPUIDを実行するわけですが、この実行手順が異様なほど面倒。今だとAVX2が実行可能であるかを調べる手順として考えられますが、どんな感じか見てみましょう。ここ数年で発売されているx86命令をサポートするCPUでは実装がほぼ当たり前となっているようなx86-64の動作フラグなどもこんな方法でチェックされます。なお、この手順では「CPUID命令は実行可能である」という条件を入れてあります。昔はこの条件がないことがありテストコードも書いていたものですが、今更そんなものもいらないでしょう。というわけで以下が手順。基本的にCPUIDで入出力するのは32bit値なのでそれに従います。

なお、もしインラインアセンブラなどで命令を記述する場合、使うレジスタはeax,ebx,ecx,edxの4つです。VisualStudioの組み込み命令の場合、第一引数に32bit変数x4の配列(命令が終了したとき、配列にはそれぞれeax,ebx,ecx,edxの値が返る)、第二引数にeaxに与える命令の初期値、__cpuidexの場合は第三引数にecxに与える命令の初期値、となります。

  1. eax=0としてcpuidを実行。eaxに基本情報として取得できるcpuidの最大値、ebx,edx,ecxの順番にCPUの制作元文字列(ASCII)が返る。
  2. 基本情報に1以上が含まれない場合はこれで終了。
  3. eax=0x80000000としてcpuidを実行。eaxに拡張情報として取得できるcpuidの最大値が返る。なお、eaxに(符号なしとして)0x80000000を超える値が返らないときは拡張情報は無し。
  4. eax=1としてcpuidを実行。eaxにCPUのバージョン情報、edxに拡張命令フラグその1、ecxに拡張命令フラグその4が返る。
  5. 拡張情報がない場合はこれで終了
  6. eax=0x80000001としてcpuidを実行。edxに拡張命令フラグその2、ecxに拡張命令フラグその3が返る
  7. eax=0x80000002~0x80000004まではCPUの名前情報が返る。
  8. 基本情報に7以上が含まれない場合はこれで終了。
  9. eax=7、ecx=0としてcpuidを実行。eaxにeax=7としたときにecxに与えることができる最大値、ebxに拡張命令フラグその5が返る。

とまあ、こんな感じになります。ちょっとおもしろいのが拡張命令フラグの順番ですよね。なぜこうなってしまったか?はフラグが何を指しているかを見ればわかります。

フラグ種類 拡張命令フラグとしてよく確認される機能
 拡張命令フラグその1  TSC(TimeStampCounter),MMX,SSE,SSE2,SMT(HyperThreading)
 拡張命令フラグその2   3DNow!,3DNow! Extensions, AMD64(IntelのCPUではIntel64)
 拡張命令フラグその3  SSE4A,FMA4
 拡張命令フラグその4  SSE3,SSSE3,SSE4.1,SSE4.2,AESNI,AVX
 拡張命令フラグその5  AVX2

見てわかるとおり、その2とその3はAMDが先行して取り入れた機能に関するフラグになっています。そのため、Intel側の資料を見るとフラグの状態が微妙になってしまっています。このあたりはCPUの開発競争によるものなのでこういうこともある、というわけですね。フラグの順序をこのようにしたわけもこれです。

ちなみに、今後増えるとしたら拡張命令フラグその5と同じような取得の方法の場所に増えていくのだと思われます。そのためにわざわざ枝番なんてものを使って取得するようになっているのですからね。

 

ちなみにコードにすると

こんな感じになります。

#include <intrin.h>

#define CPUCMDSET_TSC		0x00000001
#define CPUCMDSET_MMX		0x00000002
#define CPUCMDSET_SSE		0x00000004
#define CPUCMDSET_SSE2		0x00000008
#define CPUCMDSET_3DNOW		0x00000010
#define CPUCMDSET_3DNOWEXT	0x00000020
#define CPUCMDSET_SMT		0x00000040
#define CPUCMDSET_64BIT		0x00000080
#define CPUCMDSET_SSE3		0x00000100
#define CPUCMDSET_SSSE3		0x00000200
#define CPUCMDSET_SSE4A		0x00001000
#define CPUCMDSET_FMA4		0x00002000
#define CPUCMDSET_SSE41		0x00004000
#define CPUCMDSET_SSE42		0x00008000
#define CPUCMDSET_AESNI		0x00010000
#define CPUCMDSET_AVX		0x00040000
#define CPUCMDSET_AVX2		0x00080000

unsigned int cpuidcheck(char vendor[16],char name[64])
{
	int cpuinfo[4]; int *ptr; unsigned int flag; int basicmax,extendmax,structuredmax; int i;

	//初期化
	flag = 0x00000000; basicmax = extendmax = structuredmax = 0;

	//基本命令領域長およびCPUVendorの取得
	__cpuid(cpuinfo,0x00000000);
	basicmax = cpuinfo[0]; ptr = (int *)(&vendor[0]);
	*ptr++ = cpuinfo[1]; *ptr++ = cpuinfo[3]; *ptr++ = cpuinfo[2]; *ptr = 0;
	
	//拡張情報領域長の取得
	__cpuid(cpuinfo,0x80000000);
	extendmax = cpuinfo[0];

	//拡張命令フラグその1
	__cpuid(cpuinfo,0x00000001);
	if(cpuinfo[3] & (1 << 4)) flag |= CPUCMDSET_TSC;
	if(cpuinfo[3] & (1 << 23)) flag |= CPUCMDSET_MMX;
	if(cpuinfo[3] & (1 << 25)) flag |= CPUCMDSET_SSE;
	if(cpuinfo[3] & (1 << 26)) flag |= CPUCMDSET_SSE2;
	if(cpuinfo[3] & (1 << 28)) flag |= CPUCMDSET_SMT;

	//拡張命令フラグその4
	if(cpuinfo[2] & (1 << 0)) flag |= CPUCMDSET_SSE3;
	if(cpuinfo[2] & (1 << 9)) flag |= CPUCMDSET_SSSE3;
	if(cpuinfo[2] & (1 << 19)) flag |= CPUCMDSET_SSE41;
	if(cpuinfo[2] & (1 << 20)) flag |= CPUCMDSET_SSE42;
	if(cpuinfo[2] & (1 << 25)) flag |= CPUCMDSET_AESNI;
	if(cpuinfo[2] & (1 << 28)) flag |= CPUCMDSET_AVX;

	//拡張領域がない場合は以降を行わない
	if((unsigned int)extendmax < (unsigned int)0x800000000) return flag;

	//拡張命令フラグその2
	__cpuid(cpuinfo,0x80000001);
	if(cpuinfo[3] & (1 << 31)) flag |= CPUCMDSET_3DNOW;
	if(cpuinfo[3] & (1 << 30)) flag |= CPUCMDSET_3DNOWEXT;
	if(cpuinfo[3] & (1 << 29)) flag |= CPUCMDSET_64BIT;

	//拡張命令フラグその3
	if(cpuinfo[2] & (1 << 6)) flag |= CPUCMDSET_SSE4A;
	if(cpuinfo[2] & (1 << 16)) flag |= CPUCMDSET_FMA4;
	
	//CPUNameの取得
	ptr = (int *)(&name[0]);
	__cpuid(cpuinfo,0x80000002); for(i = 0;i < 4;i++){ *ptr++ = cpuinfo[i]; }
	__cpuid(cpuinfo,0x80000003); for(i = 0;i < 4;i++){ *ptr++ = cpuinfo[i]; }
	__cpuid(cpuinfo,0x80000004); for(i = 0;i < 4;i++){ *ptr++ = cpuinfo[i]; }

	//基本領域がない場合は以降を行わない
	if(basicmax < 7) return flag;

	//拡張命令フラグその5
	__cpuidex(cpuinfo,7,0);
	structuredmax = cpuinfo[0];
	if(cpuinfo[1] & (1 << 5)) flag |= CPUCMDSET_AVX2;

	return flag;
}

最後の__cpuidexの組み込み命令がVS2005にはなかったので実装できなかったのですが、今ならばできるわけですね。

 

でも今だとCPUIDを使ってそこまで確認することの意味って薄いよね

組み替えを行っていて思ったことだったりします。一応組んではいますが、ADVゲームレベルでこれだけ調べてもあまり意味はありませんし、SSE2が使えれば十分、という速度まで処理を引き上げないとどうかと思いますし。

 

scryptにまつわる各アルゴリズムを実装してみる

どうもscryptでは日本語でアルゴリズムを紹介している数少ないサイトとなっているなぁ、と眺めていたのですが、それならば、と汎用コードを紹介してみようかと思います。解説については前編および後編を参考に。中編は外部アルゴリズムの説明なので飛ばしてもいいでしょう。

なお、何度も書いておきますが、これは各アルゴリズムをC言語で組んだもので最適化もなにも考えられた状態ではありません。そのため速度はかなり遅いのと外部にあるアルゴリズムはコード内ではプロトタイプ宣言のみを示した状態にします。使いたい場合はその意味に合うように対象のコードを組んでください。

また、C言語で書かれた各アルゴリズム内のコメントはbuffer alloc/release(内部バッファの確保および解放)とStep n(論文(?)上のアルゴリズムステップ番号)を示しています。本体を読んだ上でコードを見ている場合は参考にしてください。

 

まずは型や外部処理のプロトタイプ宣言

#include <stddef.h>
#include <memory.h>
#include <malloc.h>

//型の置き換え処理
typedef unsigned char byte_t;
typedef unsigned int uint32_t;

//メモリ簡易確保
#ifdef _WIN32
#define allocstack(n)		_alloca(n)
#define freestack(p)		_freea(p)
#else
#define allocstack(n)		malloc(n)
#define freestack(p)		free(p)
#endif //_WIN32

//内部で使用するハッシュ処理の定義
//なお、各処理においてdestがsrc、key、textと同一であったとしても正しい結果が返せることを前提とする
typedef void (*LPFNHASHFUNC)(void *dest,const void *src,void *hashctx);
typedef void (*LPFNHASHRAND)(void *dest,const void *key,size_t keylen,const void *text,size_t textlen,void *hashctx);
typedef size_t (*LPFNHASHLENGTH)(void *hashctx);
typedef uint32_t (*LPFNINTERGERIFY)(const void *dat,void *hashctx);

//リトルエンディアン処理
uint32_t SetLittleEndian(uint32_t n);

//Salsa20の宣言
void *NewHashSalsa20(int rounds);
void DeleteHashSalsa20(void *hashctx);
void GetHashSalsa20(void *dest,const void *src,void *hashctx);
size_t GetLengthHashSalsa20(void *hashctx);

//HMAC-SHA256の宣言
void *NewHashHMACSHA256(void);
void DeleteHashHMACSHA256(void *hashctx);
void GetHashHMACSHA256(void *dest,const void *key,size_t keylen,const void *text,size_t textlen,void *hashctx);
size_t GetLengthHashHMACSHA256(void *hashctx);

//PBKDF2の宣言
bool PBKDF2(void *dkdata,size_t dklen,LPFNHASHRAND hashrand,void *hashctx,size_t hashlen,const void *password,size_t pwlen,const void *salt,size_t saltlen,size_t itercount);

//バッファのXOR処理
void XORBuffer(void *dest,const void *src,size_t len);

上記に存在する関数プロトタイプはすべて外部処理となります。コードを実行してみたい場合はこれらを名前の意味に合うように組んでください。ハッシュ系はNew=>Get(+GetLength)=>Deleteの流れで良くある状態になっているはずです。一応リトルエンディアン処理も入っていますのでもしビックエンディアンのCPUで実行する場合は気をつけてください。

 

各アルゴリズムが使用するパラメータを保存する構造体を宣言

//使用するパラメータ
//ROMixで使用するパラメータ
typedef struct _ROMixParam{
	LPFNHASHFUNC hashfunc; //内部で使用するハッシュ処理
	LPFNINTERGERIFY intergerify; //内部で使用するハッシュ整数化処理
	void *hashctx; //ハッシュコンテキスト
	size_t hashlen; //ハッシュ長
	uint32_t metric; //メートリック(ブロック拡張パラメータ)
} ROMIXPARAM;

//BlockMixで使用するパラメータ
typedef struct _BlockMixParam{
	LPFNHASHFUNC hashfunc; //内部で使用するハッシュ処理
	void *hashctx; //ハッシュコンテキスト
	size_t hashlen; //ハッシュ長
	uint32_t blocksize; //処理ブロック数
} BLOCKMIXPARAM;

//MFCryptで使用するパラメータ
typedef struct _MFCryptParam{
	LPFNHASHFUNC hashfunc; //内部で使用するハッシュ処理
	LPFNHASHRAND hashrand; //内部で使用する疑似乱数処理
	void *hashfuncctx; //ハッシュコンテキスト
	void *hashrandctx; //疑似乱数コンテキスト
	size_t hashfunclen; //ハッシュ長
	size_t hashrandlen; //疑似乱数長
	uint32_t parallelparam; //並列パラメータ
} MFCRYPTPARAM;

//MFCryptに渡すデータ
typedef struct _MFCryptData{
	void *dkdata; //生成されるデータ列
	void *passphrase; //パスフレーズ
	void *saltstring; //ソルト文字列
	size_t dklen; //生成されるデータ長
	size_t passlen; //パスフレーズ長
	size_t saltlen; //ソルト文字列長
} MFCRYPTDATA;

この辺はアルゴリズムの文章と見比べて意味を把握してください。scrypt本体を実装する段階でどうなるのかは紹介しますので。

各アルゴリズムを単体で見るとそれぞれでハッシュ処理を使用しているのが分かると思います。これが汎用実装する場合に訳が分からなくなる原因だと思います。scryptのみを実装する場合はパラメータが特定されている状態なので特殊化すればよい(処理を埋め込めばよい)のでわかりやすいですが、汎用だとこんな感じになります。

 

BlockMixアルゴリズムの実装

//Intergerify
uint32_t BlockMixIntergerify(const void *dat,void *hashctx)
{
	BLOCKMIXPARAM *param = (BLOCKMIXPARAM *)hashctx;
	return SetLittleEndian(*(uint32_t *)((byte_t *)dat + param->hashlen * (param->blocksize * 2 - 1)));
}

//BlockMix
void BlockMix(void *dest,const void *src,void *hashctx)
{
	void *dat,*tmp; byte_t *buf; size_t i,round,hashlen,buflen;

	BLOCKMIXPARAM *param = (BLOCKMIXPARAM *)hashctx;
	round = param->blocksize * 2; hashlen = param->hashlen; buflen = hashlen * round;

	//buffer allocation
	buf = (byte_t *)allocstack(hashlen + buflen);
	dat = buf; tmp = buf + hashlen;
	
	//Step1
	memcpy(dat,(byte_t *)src + buflen - hashlen,hashlen);

	//Step2
	for(i = 0;i < round;i++){
		//Step 3
		XORBuffer(dat,(byte_t *)src + hashlen * i,hashlen);
		param->hashfunc(dat,dat,param->hashctx);

		//Step4(+Sort(for Step6))
		memcpy((byte_t *)tmp + hashlen * ((i >> 1) + (i & 0x01) * (round >> 1)),dat,hashlen);

	//Step 5
	}

	//Step6(Copy sorted buffer)
	memcpy(dest,tmp,buflen);

	//buffer release
	freestack(buf);
}

最終の並び替えが非常に面倒なので各ブロックのハッシュ計算時に並び替えを行って最終は単にバッファをコピーするだけにしてあります。BlockMixのループ数は2*blocksizeとなります。

 

ROMixアルゴリズムの実装

//ROMix
void ROMix(void *dest,const void *src,void *hashctx)
{
	void **vec,*dat; byte_t *buf,*ptr; size_t i,hashlen,memsize; uint32_t index,metric;

	ROMIXPARAM *param = (ROMIXPARAM *)hashctx;

	hashlen = param->hashlen; metric = param->metric;
	memsize = sizeof(void *) * metric + hashlen * (metric + 1);
	
	//buffer allocation
	ptr = buf = (byte_t *)malloc(memsize);
	vec = (void **)ptr; ptr += sizeof(void *) * metric;
	dat = ptr; ptr += hashlen;
	
	//Step 1
	memcpy(dat,src,hashlen);

	//Step2
	for(i = 0;i < metric;i++){
		//Step3
		vec[i] = ptr; ptr += hashlen;
		memcpy(vec[i],dat,hashlen);

		//Step4
		param->hashfunc(dat,dat,param->hashctx);

	//Step5
	}

	//Step6
	for(i = 0;i < metric;i++){
		//Step7
		index = param->intergerify(dat,param->hashctx) & (metric - 1);

		//Step8
		XORBuffer(dat,vec[index],hashlen);
		param->hashfunc(dat,dat,param->hashctx);

	//Step9
	}

	//Step10
	memcpy(dest,dat,hashlen);
	
	//buffer release
	free(buf);
}

ROMixはバッファ拡張パラメータN(プログラム内ではmetricと表記)のためにかなりメモリを使用するルーチンです。そのためスタックからメモリを確保すると不足することがあるためmalloc関数によるメモリ確保を使用しています。(たとえばscryptでN=1024、blocksize=1とすると1024×64×2×1=128kBとなる)scryptを専用に実装する場合はこのメモリ確保は外からやるようにしないと速度に影響します。

またアルゴリズムをそのまま実装しているのでROMix内で呼び出されるハッシュ処理の回数は2×Nとなります。N=1024だと2048回も呼び出されることになります。scryptだとこのハッシュ処理はBlockMixアルゴリズム(下にはさらにSalsa20/8がある)のでメモリだけではなくCPU負荷としてもかなり重い処理ですね。scryptを専用に実装する場合はROMixをどうやって高速になるように実装するかが重要となります。

 

SMixアルゴリズムの実装は・・・

汎用実装している場合はBLOCKMIXPARAM構造体に意味に合うようにパラメータを設定してROMixを呼び出すことと同じなので説明はscryptの実装に譲ります。

 

MFcryptアルゴリズムの実装

//MFcrypt
bool MFcrypt(MFCRYPTDATA *data,MFCRYPTPARAM *param)
{
	byte_t *buf,*dat; size_t i,blocklen,hashrandlen; bool ret = false;

	do{
		//buffer allocation
		hashrandlen = param->hashrandlen; blocklen = param->hashfunclen * param->parallelparam; buf = (byte_t *)allocstack(blocklen);

		//Step1
		if(!PBKDF2(buf,blocklen,param->hashrand,param->hashrandctx,hashrandlen,data->passphrase,data->passlen,data->saltstring,data->saltlen,1)) break;

		//Step2
		for(i = 0,dat = buf;i < param->parallelparam;i++,dat += param->hashfunclen){
			//Step3
			param->hashfunc(dat,dat,param->hashfuncctx);

		//Step4
		}

		//Step5
		if(!PBKDF2(data->dkdata,data->dklen,param->hashrand,param->hashrandctx,hashrandlen,data->passphrase,data->passlen,buf,blocklen,1)) break;

		ret = true;
	} while(0);

	//buffer release
	freestack(buf);
	return ret;
}

実装の鍵はPBKDF2をどう実装しているか、という部分です。今回は汎用実装なのでPBKDF2を普通に呼び出しているだけになります。scryptを専用で実装する時にはPBKDF2の呼び出しは「常にIterationCountを1で呼び出す」ためループを実装する必要が無いことに注意しましょう。今回一応PBKDF2にはパラメータ異常時にエラーを返すようにしてあるのでその処理が入っています。

 

scryptアルゴリズムの実装

//scrypt
bool scrypt(MFCRYPTDATA *data,uint32_t cpucost,uint32_t blocksize,uint32_t parallelparam)
{
	MFCRYPTPARAM mfcryptparam; ROMIXPARAM romixparam; BLOCKMIXPARAM blockmixparam; void *salsactx,*hmacsha256ctx; bool ret;

	//Setup hash algorithm
	salsactx = NewHashSalsa20(8);
	hmacsha256ctx = NewHashHMACSHA256();

	//Setup BlockMix
	blockmixparam.hashfunc = &GetHashSalsa20;
	blockmixparam.hashctx = salsactx;
	blockmixparam.hashlen = GetLengthHashSalsa20(salsactx);
	blockmixparam.blocksize = blocksize;

	//Setup ROMix
	romixparam.hashfunc = &BlockMix;
	romixparam.intergerify = &BlockMixIntergerify;
	romixparam.hashctx = &blockmixparam;
	romixparam.hashlen = blockmixparam.hashlen * blockmixparam.blocksize * 2;
	romixparam.metric = cpucost;

	//Setup MFCrypt
	mfcryptparam.hashfunc = &ROMix;
	mfcryptparam.hashrand = &GetHashHMACSHA256;
	mfcryptparam.hashfuncctx = &romixparam;
	mfcryptparam.hashrandctx = hmacsha256ctx;
	mfcryptparam.hashfunclen = romixparam.hashlen;
	mfcryptparam.hashrandlen = GetLengthHashHMACSHA256(hmacsha256ctx);
	mfcryptparam.parallelparam = parallelparam;

	//Call scrypt(MFcrypt)
	ret = MFcrypt(data,&mfcryptparam);

	//Release hash algorithm context
	DeleteHashHMACSHA256(hmacsha256ctx);
	DeleteHashSalsa20(salsactx);

	//Return
	return ret;
}

やっと最終です。各構造体へのセットアップや呼び出しを一気にやっています。ちなみにROMixをSMixとして呼び出す場合BLOCKMIXPARAMおよびROMIXPARAMをこの中で設定しているようなパラメータを設定してROMixを呼び出せばOKです。この実装を見れば各処理でハッシュ処理として何を使用しているか?などが分かると思います。汎用で組むとパラメータがかなり移動したりハッシュコンテキストが入れ子になったりする不思議なコードになるのですね・・・。

ちなみにエラー処理などは省いていますので動作検証用程度にしてください。ハッシュコンテキストが作成できない場合などいろいろとありそうなので・・・

 

アルゴリズムを「できるだけ正確に組む」とこんな感じ

となります。論文(?)として示されたアルゴリズムを組んでみる、というのは大学などだとよくあることなのだと思いますが私のような立場だとどうなのでしょうか。また実際に使用する場合にはLitecoinなどパラメータが固定の場合はこれを最適化した上で組めばいいのでこんなコードにはなりません。また私の内部実装だとC++を使っているのでもう少しすっきりしています。C++だとインターフェイスクラスなどを示す必要があるため今回は崩した形で、というわけでした。

 

Windows8.1でもバージョン情報を正しく取得する C#編

Windows8.1でもバージョン情報を正しく取得するには?のC#編です。このコードが正しかったようなのでC#でも組んでみました。テンプレートとoffsetofを使っている部分の必殺技がC#だとどう化けるかが今回のおもしろいところです。

ちなみに先に書いておくと、前回のコードで取得した場合はWindows8.1でも正しくバージョンを取得できます。逆にGetVersionExを使うとマニフェストでWindows8.1対応をうたっていない場合Windows8のバージョン情報を返すようです。Windows7とかさらに過去に戻るわけではなかったようです。

かなり大がかりなサンプルコード

using System;
using System.Runtime.InteropServices;

namespace win81ver
{
    class Program
    {
        //メイン処理
        static void Main(string[] args)
        {
            OSVersionInfoEx verinfo;
            bool b = IsWindows8OrGreater();
            GetWindowsVersionDetails(out verinfo);
        }

        //OSVERSIONINFOEXの定義
        unsafe public struct OSVersionInfoEx
        {
            public uint dwOSVersionInfoSize;
            public uint dwMajorVersion;
            public uint dwMinorVersion;
            public uint dwBuildNumber;
            public uint dwPlatformId;
            public fixed char szCSDVersion[128];
            public ushort wServicePackMajor;
            public ushort wServicePackMinor;
            public ushort wSuiteMask;
            public byte wProductType;
            public byte wReserved;
        }

        //VerifyVersionInfoの取得
        [DllImport("kernel32.dll", EntryPoint = "VerifyVersionInfoW")]
        unsafe static extern int VerifyVersionInfo(OSVersionInfoEx info, uint typemask, ulong conditionmask);

        //VerSetConditionMaskの取得
        [DllImport("kernel32.dll", EntryPoint = "VerSetConditionMask")]
        static extern ulong VerSetConditionMask(ulong mask, uint type, ulong cond);

        //VerifyVersionInfoなどで使用する定数群
        class VVIConst{
            public static readonly uint VER_EQUAL = 1;
            public static readonly uint VER_GREATER = 2;
            public static readonly uint VER_GREATER_EQUAL = 3;
            public static readonly uint VER_LESS = 4;
            public static readonly uint VER_LESS_EQUAL = 5;
            public static readonly uint VER_AND = 6;
            public static readonly uint VER_OR = 7;
            public static readonly uint VER_CONDITION_MASK = 7;
            public static readonly uint VER_NUM_BITS_PER_CONDITION_MASK = 3;

            public static readonly uint VER_MINORVERSION = 0x00000001;
            public static readonly uint VER_MAJORVERSION = 0x00000002;
            public static readonly uint VER_BUILDNUMBER = 0x00000004;
            public static readonly uint VER_PLATFORMID = 0x00000008;
            public static readonly uint VER_SERVICEPACKMINOR = 0x00000010;
            public static readonly uint VER_SERVICEPACKMAJOR = 0x00000020;
            public static readonly uint VER_SUITENAME = 0x00000040;
            public static readonly uint VER_PRODUCT_TYPE = 0x00000080;

            public static readonly uint VER_NT_WORKSTATION = 0x00000001;
            public static readonly uint VER_NT_DOMAIN_CONTROLLER = 0x00000002;
            public static readonly uint VER_NT_SERVER = 0x00000003;
        }

        //IsWindows~の本体処理
        public static bool IsGreaterWindowsVersion(Int32 nMajor,Int32 nMinor,Int32 nServicePack,Int32 nProductType)
        {
            OSVersionInfoEx verinfo; uint typemask = 0x00; ulong conditionmask = 0x00;

            verinfo = new OSVersionInfoEx();
            verinfo.dwOSVersionInfoSize = (uint)Marshal.SizeOf(verinfo);
            if (nMajor >= 0) { verinfo.dwMajorVersion = (uint)nMajor; typemask |= VVIConst.VER_MAJORVERSION; conditionmask = VerSetConditionMask(conditionmask, VVIConst.VER_MAJORVERSION, VVIConst.VER_GREATER_EQUAL); }
            if (nMinor >= 0) { verinfo.dwMinorVersion = (uint)nMinor; typemask |= VVIConst.VER_MINORVERSION; conditionmask = VerSetConditionMask(conditionmask, VVIConst.VER_MINORVERSION, VVIConst.VER_GREATER_EQUAL); }
            if (nServicePack >= 0) { verinfo.wServicePackMajor = (ushort)nServicePack; typemask |= VVIConst.VER_SERVICEPACKMAJOR; conditionmask = VerSetConditionMask(conditionmask, VVIConst.VER_SERVICEPACKMAJOR, VVIConst.VER_GREATER_EQUAL); }
            if (nProductType >= 0) { verinfo.wProductType = (byte)nProductType; typemask |= VVIConst.VER_PRODUCT_TYPE; conditionmask = VerSetConditionMask(conditionmask, VVIConst.VER_PRODUCT_TYPE, VVIConst.VER_EQUAL); }
            return VerifyVersionInfo(verinfo, typemask, conditionmask) != 0;
        }

        //バージョンの比較処理
        public static bool IsWindowsXPOrGreater() { return IsGreaterWindowsVersion(5, 1, 0, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindowsXPSP1OrGreater() { return IsGreaterWindowsVersion(5, 1, 1, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindowsXPSP2OrGreater() { return IsGreaterWindowsVersion(5, 1, 2, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindowsXPSP3OrGreater() { return IsGreaterWindowsVersion(5, 1, 3, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindowsVistaOrGreater() { return IsGreaterWindowsVersion(6, 0, 0, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindowsVistaSP1OrGreater() { return IsGreaterWindowsVersion(6, 0, 1, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindowsVistaSP2OrGreater() { return IsGreaterWindowsVersion(6, 0, 2, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindows7OrGreater() { return IsGreaterWindowsVersion(6, 1, 0, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindows7SP1OrGreater() { return IsGreaterWindowsVersion(6, 1, 1, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindows8OrGreater() { return IsGreaterWindowsVersion(6, 2, 0, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindows8Point1OrGreater() { return IsGreaterWindowsVersion(6, 3, 0, (int)VVIConst.VER_NT_WORKSTATION); }
        public static bool IsWindowsServer() { return IsGreaterWindowsVersion(-1, -1, -1, (int)VVIConst.VER_NT_SERVER); }

        //GetVersionExを詳細にしたもの
        public static bool GetWindowsVersionDetails(out OSVersionInfoEx verinfo)
        {
            uint ui = uint.MaxValue; ushort us = ushort.MaxValue; byte uc = byte.MaxValue;

            verinfo = new OSVersionInfoEx();
            verinfo.dwOSVersionInfoSize = (uint)Marshal.SizeOf(verinfo);
            VerifyVersionInfoItem(ref verinfo, ref verinfo.dwMajorVersion, ref us, ref uc, VVIConst.VER_MAJORVERSION, 0, 16);
            VerifyVersionInfoItem(ref verinfo, ref verinfo.dwMinorVersion, ref us, ref uc, VVIConst.VER_MINORVERSION, 0, 16);
            VerifyVersionInfoItem(ref verinfo, ref verinfo.dwBuildNumber, ref us, ref uc, VVIConst.VER_BUILDNUMBER, 0, 100000);
            VerifyVersionInfoItem(ref verinfo, ref verinfo.dwPlatformId, ref us, ref uc, VVIConst.VER_PLATFORMID, 0, 8);
            VerifyVersionInfoItem(ref verinfo, ref ui, ref verinfo.wServicePackMajor, ref uc, VVIConst.VER_SERVICEPACKMAJOR, 0, 16);
            VerifyVersionInfoItem(ref verinfo, ref ui, ref verinfo.wServicePackMinor, ref uc, VVIConst.VER_SERVICEPACKMINOR, 0, 16);
            VerifyVersionInfoItem(ref verinfo, ref ui, ref verinfo.wSuiteMask, ref uc, VVIConst.VER_SUITENAME, 0, 0);
            VerifyVersionInfoItem(ref verinfo, ref ui, ref us, ref verinfo.wProductType, VVIConst.VER_PRODUCT_TYPE, 0, 16);

            return true;
        }

        //GetWindowsVersionDetailsのサポート処理
        private static void VerifyVersionInfoItem(ref OSVersionInfoEx verinfo,ref uint ui,ref ushort us,ref byte uc,uint type, uint min, uint max)
        {
            if (min != max)
            {
                ulong conditiongreater = VerSetConditionMask(0, type, VVIConst.VER_GREATER);
                ulong conditionequal = VerSetConditionMask(0, type, VVIConst.VER_EQUAL);

                while (min != max)
                {
                    uint mid = (max + min) / 2;
                    if (ui != uint.MaxValue) { ui = mid; } else if (us != ushort.MaxValue) { us = (ushort)mid; } else { uc = (byte)mid; }
                    if(VerifyVersionInfo(verinfo,type,conditionequal) != 0){ min = max = mid; }
                    else if(VerifyVersionInfo(verinfo,type,conditiongreater) != 0){ min = mid + 1; }
                    else{ max = mid; }
                }
            }
            else
            {
                ulong conditionand = VerSetConditionMask(0, type, VVIConst.VER_AND); uint mask = 0; uint bits;
                if(ui != uint.MaxValue){ bits = 32; } else if(us != ushort.MaxValue){ bits = 16; } else{ bits = 8; }

                for(uint i = 0;i < bits;i++){
                    uint value = (uint)(1 << (byte)i);
                    if(ui != uint.MaxValue){ ui = value; } else if(us != ushort.MaxValue){ us = (ushort)value; } else{ uc = (byte)value; }
                    if(VerifyVersionInfo(verinfo,type,conditionand) != 0){ mask |= value; }
                }
                if (ui != uint.MaxValue) { ui = mask; } else if (us != ushort.MaxValue) { us = (ushort)mask; } else { uc = (byte)mask; }
            }
        }
    }
}

呼び出し方はMainを参照のこと

結局offsetofによる間接的な構造体参照を参照渡しによる要素指定によって切り返した形になっています。よくもまあこんな実装を思いつくことやら。また相も変わらずunsafeが出てくるのは仕方がないですか

一応C言語版VersionHelpers.hの呼び出しをまねて実装しただけなので実はC#だと普通にバージョンが取ってこれる、とかだと非常に悲しいです。そうだとしてもどうやって移植するのか?の例にはなるでしょうが。

スクリーンセーバーを起動させない方法 汎用編

以前にWindows Vista以上でプログラムからスクリーンセーバーを起動させない方法という記事を書いたのですが、実はもっと汎用的なやり方があったので紹介しておきます。

なお、この処理ですが次のWindowModePatchにオプション設定として実装する予定です。

 

SystemParamtersInfoなんて便利な処理があったのね・・・

ちょっと気になったのでMSDNで「スクリーンセーバー」をキーワードに検索をかけて見つかった記事を調べていく、という方法で使えそうな処理を探してみたわけです。

で、見つかったのがこの関数。元々は設定ツールを作成したときに呼び出すために用意された関数なのですが、一時的なものにも使おうと思えば使えるのでこれを使った方が早いことが判明しました。いくつか自分が持っているゲームを見てみたのですが、SystemParametersInfoを呼び出しているものがあったので「なるほど!」と思ってしまいました。こんなに簡単なら無理にMOUSEMOVEを発行するように仕向けなくてもよかったわけですね。

 

使い方を見てみる

とっても簡単です。はい。

//保存する状態
BOOL g_bIsLowPowerActive = FALSE;
BOOL g_bIsPowerOffActive = FALSE;
BOOL g_bIsScreenSaverActive = FALSE;

//モニタを強制的にアクティブにする
BOOL SetForceActiveMonitor(void)
{
	//モニタの管理状態を取得
	SystemParametersInfo(SPI_GETLOWPOWERACTIVE,0,(UINT *)g_bIsLowPowerActive,0);
	SystemParametersInfo(SPI_GETPOWEROFFACTIVE,0,(UINT *)g_bIsPowerOffActive,0);
	SystemParametersInfo(SPI_GETSCREENSAVEACTIVE,0,(UINT *)g_bIsScreenSaverActive,0);

	//モニタの管理状態を設定
	SystemParametersInfo(SPI_SETLOWPOWERACTIVE,FALSE,NULL,SPIF_SENDCHANGE);
	SystemParametersInfo(SPI_SETPOWEROFFACTIVE,FALSE,NULL,SPIF_SENDCHANGE);
	SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,FALSE,NULL,SPIF_SENDCHANGE);

	return TRUE;
}

//モニタアクティブ状態を無効にする
BOOL ResetForceActiveMonitor(void)
{
	//管理状態を復帰させる
	SystemParametersInfo(SPI_SETLOWPOWERACTIVE,g_bIsLowPowerActive,NULL,SPIF_SENDCHANGE);
	SystemParametersInfo(SPI_SETPOWEROFFACTIVE,g_bIsPowerOffActive,NULL,SPIF_SENDCHANGE);
	SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,g_bIsScreenSaverActive,NULL,SPIF_SENDCHANGE);

	return TRUE;
}

ほら、簡単だったでしょ?

必要なのは

  • スクリーンセーバーが有効になっているかどうか(SPI_GETSCREENSAVEACTIVE,SPI_SETSCREENSAVEACTIVE)
  • モニタが省電力モードになるかどうか(SPI_GETLOWPOWERACTIVE,SPI_SETLOWPOWERACTIVE)
  • モニタの電源を切る設定になっているかどうか(SPI_GETPOWEROFFACTIVE,SPI_SETPOWEROFFACTIVE)

の3つです。強制設定するときは現在の状態を変数に保存してからすべてOFFにする、復帰させるときは変数の状態をそのまま設定する、と言う動作です。

なお、最後のフラグがSPIF_SENDCHANGEであれば設定は一時的なものとなり、最悪は再起動で元に戻ります。SPIF_UPDATEINIFILEをつけるとシステムの設定を変えてしまうので要注意です。

 

これをWindowModePatchとADVゲームに実装するわけ

またバージョンアップの予感です。今回のはちょっと補助的な機能のつもりです。コントローラだけで操作するゲームなのにスクリーンセーバーを無効化しないために勝手に入る、という現象をこちら側から制御しようかな~と思って調べたものがこんな形になってフィードバックするわけですね。おもしろいです。まあしばらく確認が入りますのでそれまでお待ちください、と。