はじめに
今回はUnityでNTPサーバーから日時を取得して使用する方法について紹介します。
どんな場面で使うのか
ゲームにはログインボーナスや時間経過で回復するスタミナなど、現実の時間と連動した要素がたくさんあります。
それらのイベントで使用される時間がユーザー個人の端末に依存していた場合、端末の時間を進めることで不正に報酬を先取り出来てしまいます。
そのような不正を防止する方法の1つがサーバー時間を使用することです。
サーバーから時間を取得しているので、ユーザーが端末の時間を変更してもゲームには影響が出ないというわけです。
※悪意のある不正行為を完全に防げるわけではなく、あくまで改ざんされにくくするだけという点に注意
サーバーは何を使ってもいいのですが、今回は誰でも使えるNTPサーバーを使用して日時を取得してみました。
やり方
スクリプト
NTPサーバーから日時を取得するスクリプトです。
using System;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
public class NTPTime : MonoBehaviour
{
// サーバー
const string NTP_SERVER = "ntp.jst.mfeed.ad.jp";
// ポート番号
const int NTP_PORT = 123;
void Start()
{
// UTCからローカル日時に変換して出力
Debug.Log(GetNTPTime().ToLocalTime());
}
/// <summary>
/// NTPサーバーからサーバー時刻を取得
/// </summary>
public static DateTime GetNTPTime()
{
// NTPサーバーへの接続用UDP生成
IPEndPoint ipAny = new IPEndPoint(IPAddress.Any, 0);
UdpClient udp = new UdpClient(ipAny);
// リクエスト送信
byte[] sendData = new byte[48];
sendData[0] = 0xB;
udp.Send(sendData, sendData.Length, NTP_SERVER, NTP_PORT);
// 日時データ受信
byte[] receiveData = udp.Receive(ref ipAny);
// DateTime型に変換
return Deserialize(receiveData);
}
public static DateTime Deserialize(byte[] bytes)
{
if (bytes == null)
throw new ArgumentNullException(nameof(bytes));
if (bytes.Length < 5)
throw new ArgumentOutOfRangeException(nameof(bytes));
DateTime date = new DateTime(1900, 1, 1);
uint ticks = (uint)(bytes[42] << 8 | bytes[41] << 16 | bytes[40] << 24 | bytes[43] << 32);
return date.AddSeconds(ticks);
}
}
1つずつ見ていきます。
// サーバー
const string NTP_SERVER = "ntp.jst.mfeed.ad.jp";
// ポート番号
const int NTP_PORT = 123;
NTPサーバーのドメイン名とポート番号です。
今回は「ntp.jst.mfeed.ad.jp」を使用しました。
void Start()
{
// UTCからローカル日時に変換して出力
Debug.Log(GetNTPTime().ToLocalTime());
}
取得したサーバー時間をDebug.Log
で出力します。
そのままだとUTCになっているのでToLocalTime()
でローカルのタイムゾーンに変換しています。
/// <summary>
/// NTPサーバーからサーバー時刻を取得
/// </summary>
public static DateTime GetNTPTime()
{
// NTPサーバーへの接続用UDP生成
IPEndPoint ipAny = new IPEndPoint(IPAddress.Any, 0);
UdpClient udp = new UdpClient(ipAny);
// リクエスト送信
byte[] sendData = new byte[48];
sendData[0] = 0xB;
udp.Send(sendData, sendData.Length, NTP_SERVER, NTP_PORT);
// 日時データ受信
byte[] receiveData = udp.Receive(ref ipAny);
// DateTime型に変換
return Deserialize(receiveData);
}
UDPを使用してNTPサーバーに接続します。
送受信などのやり方はMicrosoftのリファレンスを参考にしました。
public static DateTime Deserialize(byte[] bytes)
{
if (bytes == null)
throw new ArgumentNullException(nameof(bytes));
if (bytes.Length < 5)
throw new ArgumentOutOfRangeException(nameof(bytes));
DateTime date = new DateTime(1900, 1, 1);
uint ticks = (uint)(bytes[42] << 8 | bytes[41] << 16 | bytes[40] << 24 | bytes[43] << 32);
return date.AddSeconds(ticks);
}
受信したデータをDateTime
型に変換します。
NTPで使用されるタイムスタンプは起点の1900年1月1日から何秒経過しているかを表す値になっていて、秒を表す32ビット部分と秒未満の時間を表す32ビット部分で構成されています。
そのため、1900年1月1日に設定したDateTime
に対し経過時間を加算することで現在の時間を取得できます。
動作確認
まずはそのまま実行してみると、PCと同じ日時が表示されました。
次に設定でPCの時間を変更してから実行してみると、変更後のPCの日時ではなく本来の日時が表示されました。
これでデバイスではなくサーバーから時間を取得していることが確認できました。
コメント