【Unity】NTPサーバーから日時を取得する

【Unity】NTPサーバーから日時を取得する Unity
記事内に広告が含まれています。
スポンサーリンク

この記事でのバージョン
Unity 2022.3.30f1

はじめに

今回は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の日時ではなく本来の日時が表示されました。
これでデバイスではなくサーバーから時間を取得していることが確認できました。

参考

コメント

タイトルとURLをコピーしました