UAC、アクセス制御対策

電車内での暇つぶしがてら書く日記。
今回はUACです。
UACとは、Windows Vista以降のWindows OSに取り入れられた機能です。
User Account Controlの略で、許可のないアプリケーションの権限を操作するなどして、OSに勝手な変更が加えられないようにするためのセキュリティ基盤技術です。
何かのソフトウェアをインストールしようとしたとき、画面が暗くなって、「次のアプリケーションを実行しますか?」と聞かれたことはないでしょうか。あれもUACの一部です。

UACの影響

ゲーム制作において足かせとなるUAC・アクセス制御の動作は、「Program Files」ディレクトリでファイルの書き込み・削除が出来ないというものです(正確に言うと、ユーザ権限プログラムには書き込み権限が無い)。
つまり、以下のプログラムをProgram Filesディレクトリ以下で普通に実行すると「失敗」と表示されるということです。

#include <stdio.h>
int main()
{
  FILE *fp = fopen("hoge.dat" ,"w");
  if(!fp)
    printf("失敗");
  else
    fclose(fp);
  return 0;
}

はてさて、これではセーブデータや設定ファイルを書き込むことができません。どうすればよいのでしょうか。

Tips:UACいらない子

Windows XPまでできてたことができなくなるなんて!
こう憤慨してる方もいるでしょう。
――――詳細はどうでもいいんだよ!という方は読み飛ばしてください――――
正しく言うと、Windows XPまででもできませんでした。
管理者権限が無いプログラムは、Program Filesなどのディレクトリでファイルを書き込んだりできませんでした。Guestアカウントだと動かないプログラムがあったりした覚えはないでしょうか。
しかし、ログインしているユーザが管理者権限を持っていた場合、実行するプログラムは基本的に管理者権限で実行されてしまいます。
そして、大抵のユーザは管理者権限を持ったアカウントでログインしていたので、プログラムを管理者権限で動かすのが当たり前だったという訳です。
それではまずいということになったので、ユーザが管理者権限を持っていても、プログラムをProgram Filesなどで動かす場合、許可されていないプログラムは管理者権限からユーザ権限に落とされる、というUACが組み込まれました。
――――読み飛ばしここまで――――


「なんだかよくわからんけど結局面倒になっただけじゃないか!」こう思う方もいるでしょう。
しかし、考えてもみてください。どこの誰が作ったか分からないようなアプリケーションが、Program FilesやWindowsのシステムディレクトリのファイルを、好き勝手に書き換えたり削除したりできるということを。
誰にでも容易にOSの破壊ができてします。
こういった点を考えると、UACの導入はセキュリティの観点から必要なものなのです。
と言うわけで、UACが邪魔だからと言ってあまり邪険にしないようにしましょう。

UACがある環境でのデータ保存

UACを避けてコンフィグやデータファイルを保存する方法をいくつか挙げていきます。

デフォルトインストールディレクトリをProgram Files以下にしない

最も楽な方法です。書き込み権限があるディレクトリにインストールしてもらえばOKです。
C:\Games\hogehogeなどにインストールしてもらえば、書き込み権限があるディレクトリなのでとりわけ困ったことにはなりません。
ただし、マルチユーザ(1つのPC上に、複数のアカウントがあること)の観点から言うと、どのユーザがゲームを実行しても全てのユーザの間で設定やセーブデータが共有されるというのは好ましくありません。
そこで、この方法を採る場合、C:\Users\user_name\hogehogeなどにインストールするのが好ましいと思われます。

実行時に管理者権限を要求する

管理者権限をプログラムが得れば、Program Files以下などでも書き込みができるようになります。
しかし、ゲームの実行毎に「このプログラムを管理者権限で実行しますか?」とUACに質問されるのはウザ過ぎることこの上ないですし、なによりセキュリティの観点からよろしくないです。
非推奨な方法です。

%AppData%以下に保存する

Program Filesなどにインストールされたプログラムのために、%AppData%という環境変数が示す特殊なディレクトリをWindowsが準備しています。
Windows VistaWindows7だと、
 C:\Users\user_name\AppData\Roaming
などが該当します。
このディレクトリ名を取得するのは、WinAPIのSHGetSpecialFolderPathという関数などを用いると可能です。
コード例は以下のとおりです。

void foo()
{
  TCHAR dir[MAX_PATH];
  if( SHGetSpecialFolderPath( NULL , dir , CSIDL_APPDATA , TRUE ) )
  {
    // dirに%AppData%が示すディレクトリ名が入ってる
  }
  else
  {
    // ディレクトリ名取得失敗
  }
}

このコードのdirを用いれば、Program Filesにプログラムがあっても、書き込みが可能な%AppData%以下にデータを書き込むことが出来ます。

  • 星くず彼方に さんから頂いたコメントから追記

Windows Vista以降、ユーザごとに「保存されたゲーム」というディレクトリも準備されます。
OS付属のマインスイーパーなどはここにセーブデータを入れてます。
%AppData%のディレクトリ名を取得するのと同様に、SHGetKnownFolderPathという関数で、FOLDERID_SavedGamesを指定すれば取得できます。
ただし、SHGetKnownFolderPathはWindows Vista以降のWinAPIにしか組み込まれていないので、OSのバージョン判定や、DLLを動的リンクしてSHGetKnownFolderPathを遅延ロードしたりする必要があります。


とりあえず3つのやり方を示しました。
Program Files以外をデフォルトインストールディレクトリにする、%AppData%以下を使う、などが妥当でしょう。
セーブデータの保存ができない、という現象がおきると、ユーザから一気にゲームをする気を奪ってしまいます。
そこまで力を入れる場所ではないですが、ここらへんは気をつけておきましょう。