Space EngineersのDedicatedサーバをWindowsで構築する

内容について

ほとんど以下の公式ページに書いてあります。
http://www.spaceengineersgame.com/dedicated-servers.html

SpaceEngineersのサーバを立てる際に自分がやった内容を残しておきます。
とはいえこのゲームをやるEngineerならこの程度は問題とならないような気もしますが、手間を省く一助となれば幸いです。
ちなみに、Dedicatedサーバはゲームクライアントを起動せず、スタンドアローンのサーバアプリケーションのみを起動して使うものになります。

確認環境

この内容は全て以下の環境で確認しています。
特にSpaceEngineersはBeta版での確認となるため、仕様が変更となる可能性が大きくあります。

  • SpaceEngineers 01.168 Stable
  • Windows10 64bit

Dedicatedサーバのインストール

SteamクライアントでダウンロードするゲームアプリケーションにもDedicatedサーバが同梱されていますが、ここではSteamCMDでダウンロードする方法をとります。

SteamCMDの準備

以下からSteamCMDをダウンロードして、適当な場所に解凍。
https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip

ダウンロード/アップデートバッチの作成と実行

「steamcmd.exe」と同じディレクトリに以下の内容のバッチファイルを作成。
名前は任意なので、ここでは例として「SE_inst_updt.bat」とする。

※「C:\SE」の部分はDedicatedサーバのインストール先となるため。任意に変更する。
※app_updateで指定するIDはここを参照。

@echo off
start "" steamcmd.exe +login anonymous +force_install_dir "C:\SE" +app_update 298740 validate +quit

作成後、バッチを実行し、終わるまで待つ。

以上で、インストールは完了。

サーバの起動

サービスへの登録を行う場合、以下の作業はAdministrators権限のあるユーザで行うこと。

サーバ管理アプリケーションの起動

[(バッチで指定したインストールフォルダ)\DedicatedServer64\SpaceEngineersDedicated.exe]を起動。

インスタンス選択画面

インスタンス(サービス/OS起動時の自動起動)を設定するか否かで以下の手順が異なります。

インスタンスを作成しない場合

「Lcal/Console」を選択して「Continue to Server Configuration」を押す。

インスタンスを作成する場合

「Run as Admin」ボタンで管理者権限を与えて、「Add new instance」ボタンからインスタンス名を決めて作成。
その後、作成したインスタンスを選択して「Continue to Server Configuration」を押す。

サーバ設定画面

ここの設定値については、ドキュメントが見つからなくってよくわからない部分が多い。

とりあえず、新規にワールドを作るときは以下になる。
1. Nwe gameを選択
2. Scenarioを選択
3. Server portが標準のUDP27016意外であれば変更(当然ですが、NAT等でインターネットからアクセス可能であること)
4. Server nameを入力(サーバリスト上で表示される)
5. World nameを入力(サーバリスト上で表示される)
6. 適当にその他の設定値を入力。
7. Save & startでサーバを起動

これで、ゲーム上のサーバリストから見えて、サーバに入ることができる状態になっている。

以前に作成したワールドを再度起動する場合

Saved worldsと、起動したいワールド名を選択して起動。

インスタンスで自動起動されるワールド

最後に起動したワールドが自動起動で使用されているように見えるが、設定場所も正確な記述もわからなかった。

アップデート

SpaceEngineersは定期的にアップデートがあり、ほとんどの場合、新しいクライアントから古いサーバへはアクセスできなくなる。
そのため、ある程度定期的にサーバのアップデートが必要になると思われる。
サービスを停止してバッチを動かすだけのため、自動化もむすかしくはなさそう。

アップデート方法

サーバをサーバ設定の「Stop」ボタンで停止し、インストール時に使用したバッチファイルを実行する。

Modの設定

ワールド内で、ワークショップで公開されているModが使用できる。

ModのIDを調べる

ワークショップのページから適当に入れたいModを探しページを開く。
URLの後ろの方の[id=]の後の数字をメモする。

サーバ設定

サーバ設定のModタブに入力する。
複数の場合は改行して入力していく。

あとは起動すればModの内容が使用可能な状態となっており、クライアントが接続するとクライアント側で必要なModのデータがダウンロードされるようになる。
一度ダウンロードすれば2度目以降は差分のみとなるため、素早くサーバに入れる。

適用されないMod?

ブループリントとスクリプトはサーバで指定しても出てこないっぽい?
クライアントでサブスクライブしてれば使えるから?

グループメンバーのみが入れるように設定する

サーバは公式のリストに載せられてしまうため、そのままだと誰でも簡単に入ってこれる状態となる。
しかし、Steamで作成したグループのメンバーのみがサーバに入れるよう設定することができる。

グループを作成

Steamクライアントからであれば、以下で作成画面に行ける。

  1. (ユーザ名)->グループを選択。
  2. 「新しいグループを作成」をクリック。

グループを作ったら、サーバに入れるようにしたいユーザを招待する。

グループIDを調べる

グループ管理画面から「フレンドを招待」をクリックし、その後のページのURLの「invitegid=」の後の数字列がグループID。
(例)http://steamcommunity.com/profiles/???????????/friends/?invitegid=(グループID)

サーバ設定

サーバ設定の「Steam Group IDに調べたグループIDを入力して起動。

Ampacheを無理やりSJISのタグに対応させる

何をするのか

Ampacheは便利なのですが、そのままだと日本語のSJISのタグが付いたmp3などを突っ込むと文字化けしまくる。
WindowsはSJIS以外だとエクスプローラで文字化けするため、意識してないとSJISで管理しちゃうことが多い。

タグを別の文字コードに変換するのは面倒なため、Ampache側をいじることとする。

やり方

注意:かなり雑な方法なので、通常だと問題ない文字コードや特殊な文字などを処理したときに文字化けするかも。

確認環境

ubuntu 16.04 + nginx + php 7.0 + Ampache 3.8.3 (+ getID3 1.9.11)

「mb_detect_encoding」関数を使うので、php-mbstringパッケージをインストールして有効化

もう入っていれば飛ばして問題なし。

sudo apt-get install php7.0-mbstring
sudo systemctl restart php7.0-fpm.service

getID3にSJIS判定を追加

以下のファイルを編集する
– (ampacheインストールディレクトリ)/lib/vendor/james-heinrich/getid3/getid3/getid3.lib.php

この部分のすぐ後に

public static function iconv_fallback($in_charset, $out_charset, $string) {

       if ($in_charset == $out_charset) {
                return $string;
       }

以下を追加する

       if( mb_detect_encoding($string,"SJIS-win",true) ){
                return mb_convert_encoding($string,$out_charset,"SJIS-win");
       }

対応方法についてのメモ

何か問題が起こったときに確認するためのメモ。

getID3について

以下で公開されてる。
https://github.com/JamesHeinrich/getID3

Ampacheではこれを使ってmp3などのタグを処理してるのですが、getID3は本記事時点で最新のv1.9.13でもSJISに対応していない。
とはいえプルリクエストしてどうにかなるものではなさそう。

文字コードに何を使っているのか文字列から判定する必要があるのですが、その対応がとても難しいです。
少なくともPHPの関数だけでは世界中の文字コードを間違いなく判定することはできないと思われる。
なので、対応する文字列を無難な中から取捨選択するしかなさそう。

そもそもSJISなんて使ってるMicrosoftが諸悪の根源に違いない。

getID3に追記した内容について

文字列の判定と変換を、通常では以下の順番でやってる。

  1. [変換前文字コード=変換後文字コード]の場合
  2. …もろもろそれ以外の場合

それをこうする

  1. [変換前文字コード=変換後文字コード]の場合
  2. [変換前文字コード=SJIS]の場合 <= 追加
  3. …もろもろそれ以外の場合

waifu2x-caffeとffmpegで動画を拡大するPowerShellスクリプト

どういうものか

waifu2xという、機械学習の結果を利用して、画像をきれいに拡大したり、圧縮時のノイズを除去したりするソフトウェアが公開されています。
そのWindows向けビルドがwaifu2x-caffeで、動画変換ソフトのFFmpegと組み合わせることで動画をきれいに拡大することができるため、PowerShellスクリプトで自動的に処理を流せるようにしました。

※waifu2xの処理は動画向けではないため、元々大きいサイズの動画があれば、もちろんそちらのほうが良いです。

各コマンドは「RICOH THETA Sの動画機能を4k相当に拡張する(waifu 2x で) – izm_11’s blog」を参考にさせていただきました。

対象フォルダに入れた動画を以下の流れで変換します。

  1. ffprobe(FFmpegに付属)で対象の動画情報を取得
  2. ffmpegで音声を分離
  3. ffmpegで動画を1コマずつ画像に分離
  4. waifu2x-caffeによる画像の拡大とノイズ除去
  5. ffmpegにより拡大済み画像と音声を結合してmp4(H264)の動画に変換
  6. 次の動画があれば1から処理

作った理由

古いMVをwaifu2x-caffeとffmpegで4Kにして見るのが最近の個人的トレンドなのですが、とてもとてもとても処理に時間がかかる。
それなりに高くて新しいグラフィックボード(GTX1080)で3分の動画を4Kに変換すると2時間20分かかりました。
で、PCに張り付いて手動でコマンドを打つのは大変なので、自動化することにしました。

ホントはスクリプト作り込むなら慣れてるLinuxでやりたかったけど、それなりのグラボを積んだPCはWindows10で、理由があり別OSを入れられない状況。
プラスして、Windows 10のデフォルトシェルがPowerShellになるという話を見たばかりなのでPowerShellで作りました。

必要ソフトウェアと動作確認バージョン

スクリプト

本スクリプトで損害が発生しても当方は責任を負いません。(おやくそく)

###
# 設定

$movieWidth = 3840 # 変換後の横サイズ
$movieHeight = 2160 # 変換後の縦サイズ

$ffmpegPath = 'C:\ffmpeg\bin\ffmpeg.exe' # ffmpeg.exeのパス
$ffprobePath = 'C:\ffmpeg\bin\ffprobe.exe' # ffprobe.exeのパス
$waifu2xCaffeCui = 'C:\waifu2x-caffe\waifu2x-caffe-cui.exe' # waifu2x-caffe-cui.exeのパス

$inputFolder = 'D:\INPUT' # 入力ディレクトリ
$outputFolder = 'D:\OUTPUT' # 出力ディレクトリ
$fileType = ('*.mp4','*.avi') # 処理対象の拡張子
$tmpFolder = 'D:\tmp' # 作業用フォルダ(一時的に画像等を展開するため、それなりの容量が必要)

# Waifu2xCaffeのオプション
# オプションについては公開元をご参照ください
# https://github.com/lltcggie/waifu2x-caffe
$noiseScaleMode = 'noise_scale'
$noiseLevel = 1
$processType = 'gpu'
$cropSize = 512
$modelDir = 'models/upconv_7_anime_style_art_rgb'

##
# 以下処理

$tmpSourceImageFolder = $tmpFolder + '\movie2x\img_s'
$tmpSourceImageFiles = $tmpSourceImageFolder + '\img_%07d.jpg'
$tmpConvertImageFolder = $tmpFolder + '\movie2x\img_c'
$tmpConvertImageFiles = $tmpConvertImageFolder + '\img_%07d.png'
$tmpSourceWaveFile = $tmpFolder + '\movie2x\base.wav'

function RunProcess($exePathe, $argObj){
  $pinfo = New-Object System.Diagnostics.ProcessStartInfo
  $pinfo.RedirectStandardOutput = $true
  $pinfo.UseShellExecute = $false
  $p = New-Object System.Diagnostics.Process

  $pinfo.FileName = $exePathe
  $pinfo.Arguments = $argObj
  $p.StartInfo = $pinfo
  $p.Start() | Out-Null

  Write-Output $p
}

Remove-Item $tmpSourceImageFolder -recurse 2> $null
Remove-Item $tmpConvertImageFolder -recurse 2> $null
Remove-Item $tmpSourceWaveFile 2> $null

Get-ChildItem -Path $inputFolder -Recurse -Include $fileType | ForEach-Object {
  $date = Get-Date -Format "yyyy/MM/dd HH:mm"
  Write-Host "$date ファイル : $_ の処理を開始しました。" 

  ###
  # 動画の情報をffprobeで取得
  $expArg = '"' + $_.Fullname + '" -show_entries format -show_streams -print_format json'
  $process = RunProcess $ffprobePath $expArg

  $stdout = $process.StandardOutput.ReadToEnd()
  $json = $stdout | ConvertFrom-Json

  $fpsStrings = $json.streams[0].avg_frame_rate -split '/'
  $fps = $fpsStrings[0] / $fpsStrings[1]
  $sourceMovieWidth = $json.streams[0].width
  $sourceMovieHeight = $json.streams[0].height

  ##
  # 作業ディレクトリの作成
  New-Item $tmpSourceImageFolder -type directory -Force | Out-Null
  New-Item $tmpConvertImageFolder -type directory -Force | Out-Null

  ##
  #ffmpegにより音声/画像の分離
  $expArg = ' -i "' + $_.Fullname + '" -vn "' + $tmpSourceWaveFile

  $process = RunProcess $ffmpegPath $expArg
  $process.WaitForExit()

  $expArg = ' -i "' + $_.Fullname + '" -f image2 -vcodec mjpeg -qscale 1 -qmin 1 -qmax 1 "' + $tmpSourceImageFiles

  $process = RunProcess $ffmpegPath $expArg
  $process.WaitForExit()

  ##
  #widthとheightのどちらに合わせるべきか判定
  if($movieWidth / $sourceMovieWidth -lt $movieHeight / $sourceMovieHeight){
    $scaleString = ' --scale_width ' + $movieWidth
  }else{
    $scaleString = ' --scale_height ' + $movieHeight
  }

  ##
  # waifu2x-caffeによる画像の拡大とノイズ除去
  $expArg = ' -i ' + $tmpSourceImageFolder + ' -o ' + $tmpConvertImageFolder + ' --model_dir ' + $modelDir + ' -m ' + $noiseScaleMode + $scaleString +' --noise_level ' + $noiseLevel + ' -p ' + $processType + ' -c ' + $cropSize

  $process = RunProcess $waifu2xCaffeCui $expArg
  $process.WaitForExit()

  ##
  # ffmpegにより画像と音声を結合してmp4(H264)の動画に変換
  $expArg = ' -r ' + $fps + ' -i "' + $tmpConvertImageFiles + '" -i "' + $tmpSourceWaveFile + '" -f mp4 -vcodec libx264 -bufsize 20000k -maxrate 25000k -s ' + $movieWidth + 'x' + $movieHeight + ' -aspect ' + $sourceMovieWidth + ':' + $sourceMovieHeight + ' -pix_fmt yuv420p "' + $outputFolder + '\' + $_.Name + '"'

  $process = RunProcess $ffmpegPath $expArg
  $process.WaitForExit()

  Remove-Item $tmpSourceImageFolder -recurse 2> $null
  Remove-Item $tmpConvertImageFolder -recurse 2> $null
  Remove-Item $tmpSourceWaveFile 2> $null

  $date = Get-Date -Format "yyyy/MM/dd HH:mm"
  Write-Host "$date ファイル : $_ の処理が完了しました。" 
}

Ubuntu16.04にnginx-rtmp-moduleを入れてRTMP配信をする

最終的にストリーミングキーにパスワードを入れる簡易認証について書くのですが、全て一つの投稿にすると長くなるのでまずは前段の環境構築から。
[追記] RTMPSできないと中間者攻撃対策できなくて、nginx-rtmp-moduleで対応していないということもあり一旦保留。

RTMPについて


ストリーミングでデータをやり取りするためのプロトコルで、ここでは映像をライブストリーミング(生放送)するために使用する。
通常利用するポートはTCP 1935番。

NginxでRTMPを扱うためのモジュールが公開されており、ソースに追加してMakeすることで利用可能。

確認環境


  • ubuntu 16.04

インストール手順


必要パッケージのインストール

makeするために必要なものを一通り準備。

sudo aptitude install build-essential libpcre3 libpcre3-dev libssl-dev unzip

Nginxのソースとrtmpモジュールをダウンロードしてmake/install

mkdir -p ~/work/nginx
cd ~/work/nginx

wget http://nginx.org/download/nginx-1.11.7.tar.gz
wget https://github.com/arut/nginx-rtmp-module/archive/master.zip

tar -zxvf nginx-1.11.7.tar.gz
unzip master.zip

cd nginx-1.11.7/

./configure --user=www-data --group=www-data --with-http_ssl_module --with-http_realip_module --add-module=../nginx-rtmp-module-master

make
sudo make install

サーバ起動時に自動起動するためSystemdにサービスとして登録

sudo vi /lib/systemd/system/nginx.service

/lib/systemd/system/nginx.service

[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Nginxの設定

RTMPと、テスト用のプレーヤーページを用意。

sudo vi /usr/local/nginx/conf/nginx.conf

/usr/local/nginx/conf/nginx.conf

worker_processes  1;

user  www-data www-data;
pid        /run/nginx.pid;

events {
    worker_connections  1024;
}

rtmp {
    server {
        listen 1935;

        application app {
            live on;
            record off;
        }
    }
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    server {
        listen 80;
        server_name  localhost;
        #デフォルトのルートディレクトリは /usr/local/nginx/html
        #変えるのめんどいのでそのまま使う
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
        index index.html index.htm;
    }
}

テスト用のプレーヤーページを作成

テストなのでCDNのvideo.jsを利用させていただき簡易的に作る。

sudo vi /usr/local/nginx/html/live.html

/usr/local/nginx/html/live.html

<!DOCTYPE html>
<html lang="en" class="">
<head>
    <title>Test Live</title>
    <link href="//vjs.zencdn.net/5.11/video-js.min.css" rel="stylesheet">
    <script src="//vjs.zencdn.net/5.11/video.min.js"></script>
</head>
<body>
    <video id="player" class="video-js vjs-default-skin" height="450" width="800" controls autoplay preload="auto">
        <source src="rtmp://(サーバのIP or ドメイン)/app/StreamKey" type='rtmp/mp4' />
    </video>
    <script>
        var player = videojs('#player');
    </script>
</body>
</html>

一応、所有者を変更しておく。

sudo chown www-data:www-data /usr/local/nginx/html/live.html

Nginxの起動と自動起動設定

sudo systemctl start nginx.service
sudo systemctl enable nginx.service

OBSで配信する際のURL指定


OBSClassicを使う場合は以下の値をいい感じに設定。

設定 -> 放送設定
FMS URL : rtmp://192.168.30.16/app
プレイパス/ストリームキー : StreamKey

使用方法は別途調べてください。

視聴


HTML5が見れるWEBブラウザで以下を開く。

http://(サーバのIP or ドメイン)/live.html

もちろん、配信していないと何も再生されないので注意。