【2.7】原神私服简单搭建流程·Docker环境·Grasscutter

• May 16, 2022 • Read: 954 • 技术分享

「一」总体逻辑实现

1. Grasscutter简介
众所周知,对于剧情后期玩家来说,原神在版本更新的间隙会有很长时间的长草期。于是,为了创造乐趣,伟大的除草机便诞生了!(经典开头)

关于Grasscutter,是作者通过分析原客户端的请求和服务器返回值,模拟这些请求和返回基于Java写了一个模拟的拟态服务端(并不是原版泄露GM版),目前功能正在以较高的频率逐步增加中,仓库的提交频率也是呈现出一片欣欣向荣。

项目地址:https://github.com/Grasscutters/Grasscutter
官方Wiki文档:https://github.com/Grasscutters/Grasscutter/wiki

基本原理:

【客户端】-通过其他的代理相关软件,在本地抓包拦截原神客户端发向官方服务器的网络请求,并转而发送向自建的服务器(可以搭建在本地机器)

【服务端】-接收客户端发来的请求并模拟原版返回值,由于可以自定义数据,故而实现了自由度极高的私服功能。类似的东西还有很多,Grasscutter只是其中的一个。


「二」服务端搭建

本文综合各路先驱者教程及实操经验,介绍通过Docker容器技术较为简单地搭建原神私服。

Docker是一种如字面意思的“容器”技术,可以理解为更加小型的虚拟机(当然本质并不是)。使用她的目的是为了将一整套环境打包封装成镜像,无需重复配置环境,解决环境带来的种种问题,同时也方便服务端的迁移。

本流程适用基于Linux系统的服务器搭建,当然,如果想在本地搭建,可以选择直接安装环境所需软件;或通过Windows平台运行Docker,即使用Docker Desktop运行环境。不过,后者因为性能原因并不推荐,有兴趣可点此了解

2.1 所需环境
官方文档
如官方文档所示,需要MongoDB和JDK 17两个环境,我们使用两个Docker容器分别实现。

2.2 安装环境

·2.2.1· 首先,安装Docker(社区版)

yum install -y docker-ce  # 安装docker
systemctl start docker    # 启动docker服务

CentOS、Debian和Ubuntu的安装命令有些许差异,可自行搜索
也可以使用宝塔面板一键安装,更加便捷。

·2.2.2· 创建Docker network(可选)

使用docker network对Docker容器进行统一管理,MongoDB数据库不对外提供端口,各容器之间通过hostname进行内部通信,保护系统安全。

docker network create genshin  # 创建Docker network
docker network ls              # 查看已创建的网络

·2.2.3· 【第一个Docker容器“mongodb”】:MongoDB

搭建Docker-MongoDB环境,在同一个docker network里,在内部通过hostname进行通信。
退出容器内部的命令是exit

docker pull mongo:latest                # 拉取MongoDB基础镜像
mkdir -p /mydocker/mongodb/data         # 在物理机创建目录
# 创建容器,容器名mongodb;网络为创建的genshin;
# -v表示宿主机路径:容器路径;-h表示hostname;
# 最后指定镜像mongo
docker run -itd --name mongodb --network genshin -v /mydocker/mongodb/data:/data/db -h genshin_mongo mongo
docker update mongodb --restart=always  # 重启Docker时,自动启动容器

若没有创建Docker network,可以将第三句命令改为:

docker run -itd --name mongodb -v /mydocker/mongodb/data:/data/db mongo

·2.2.4· 【第二个Docker容器“grasscutter”】:JDK 17

搭建Docker-JDK17环境,同时这也是割草机服务端的运行容器:
①「创建容器」

docker pull python                                # 拉取Python基础镜像
# 创建容器,容器名grasscutter;网络为创建的genshin;
# -p表示端口映射,这里将默认的443端口改为444或其他,避免占用;
# -h表示hostname;最后指定镜像python
docker run -itd --name grasscutter --network genshin -p 22102:22102/udp -p 444:444/tcp -h genshin_grasscutter python
iptables -I INPUT -p udp --dport 22102 -j ACCEPT  # 放行22102端口的udp协议
iptables -I INPUT -p tcp --dport 444 -j ACCEPT    # 放行444端口的tcp协议
docker update grasscutter --restart=always        # 设置开机自启
docker exec -it grasscutter /bin/bash             # 进入容器内部
cat /etc/issue                                    # 查看系统版本(Debian GNU/Linux 11)

若没有创建Docker network,可以将第二句命令改为:

docker run -itd --name grasscutter -p 22102:22102/udp -p 444:444/tcp python

也可以使用数据卷方式共享目录

如果你的空间余量吃紧,也可以使用Docker数据卷的方式来将物理机上的目录“共享”到容器
如命令后使用参数:-v /mydocker/Grasscutter:/Grasscutter

②「在容器内安装常用软件包」

apt-get update                      # 更新apt-get
apt-get install sudo -y             # 安装sudo
apt-get install wget -y             # 安装wget
apt-get install curl -y             # 安装curl
apt-get install vim -y              # 安装vim

③「在容器内安装jdk-17」

sudo apt install openjdk-17-jdk -y  # 安装jdk17
sudo apt install openjdk-17-jre -y  # 安装jre17
java -version                       # 查看版本
javac -version

此处创建容器时映射的22102和444端口,是为了后续搭建Grasscutter私服保留的。
22102(UDP)是游戏服务器(GameServer)端口,用来处理一些传送、伤害数值计算等信息,如果是在服务器部署的话记得在安全组放开这个端口的UDP协议,防止出现4206错误无法进入游戏。
444(TCP)是用来处理登录、游戏更新的端口(DispatchServer),默认的443大概率被占用,可以更换成自己喜欢的,本文使用444。


「三」拷贝服务端资源

3.1 下载仓库文件

下载割草机Grasscutter仓库文件(Code -> Download ZIP):https://github.com/Grasscutters/Grasscutter
这里建议将左上角stable分支改为development分支再下载,此分支更新频率高,包含更多大世界新特性

2022/5/19 Update

更新

近日development分支结构做了较大改动,详情可查看这个commit:
https://github.com/Grasscutters/Grasscutter/commit/ead0df336e811bfb0401c4ed9af4308ba144180a

以及这个pr:
https://github.com/Grasscutters/Grasscutter/pull/927

如若第一次搭建追求稳定,还是先使用stable稳定分支文件进行搭建(×
2020.6.8更新:现在直接用development分支就可以~

将文件放至服务器任意目录(如根目录/Grasscutter)解压,进行下一步处理

3.2 下载resources资源文件

从另一个仓库下载资源:https://github.com/Koko-boya/Grasscutter_Resources
将文件夹改名为resources,放入上个步骤的目录/Grasscutter/中。注意,原版割草机仓库中并不包含resources文件夹。

3.3 下载jar启动文件

截至2022.5.16,正式版本号为1.1.0,开发版为1.1.2-dev

【推荐】:从自动构建下载development版本:
https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip

①从releases下载:https://github.com/Melledy/Grasscutter/releases

②stable版本自动构建下载:
https://nightly.link/Grasscutters/Grasscutter/workflows/build/stable/Grasscutter.zip

③也可以自己尝试构建:https://github.com/Grasscutters/Grasscutter#Building

下载完成后放入根目录,可以删掉其他多余的文件。此时,必需的目录结构应为:

Grasscutter
 ├── src
 ├── proto
 ├── lib
 ├── keystore.p12
 ├── plugin-schema.json
 ├── grasscutter-1.1.2-dev.jar
 └── resources
     ├── BinOutput
     ├── ExcelBinOutput
     ├── Readable
     ├── Subtitle
     └── TextMap

运行前的目录结构:
运行前

3.4 复制到Docker容器

之后会经常使用到的两个命令:
①在容器与宿主机之间相互复制文件:
docker cp [路径] [容器名]:[路径]
(注:表示从前者复制到后者,可以互换顺序)

②进入指定容器:
docker exec -it [容器名] /bin/bash


3.5 第一次启动,waku waku~

docker cp /Grasscutter grasscutter:/Grasscutter  # 将文件夹复制到容器里
docker exec -it grasscutter /bin/bash            # 进入grasscutter容器
cd /Grasscutter                                  # 进入刚刚复制进去的文件夹
java -jar grasscutter-1.1.2-dev.jar              # 启动

第一次启动会失败,初次启动的目的是让jar包执行时自动生成一个config.json(如果有就不会重新生成),下面我们需要修改里面的配置:

vim config.json

配置含义说明:

·如果是本地运行的话里面的两个PublicIp不用动,如果是放在服务器上的话需要把两个PublicIp改为服务器的公网IP。

·上面的“bindPort”: 444是用来处理登录、游戏更新的端口(DispatchServer),默认的443端口服务器可能不太好搞,国内可能需要备案才能使用,或者被Nginx等服务占用了。而Windows也可能会被VMWare的Share共享服务或者Steam占用,所以可以改为别的端口。改好之后如果是服务器的话记得在安全组放行该端口的TCP协议。除了安全组外,还要检查一下防火墙有没有开放。

·下面的“bindPort”: 22102是游戏服务器(GameServer)的端口,用来处理一些传送、伤害数值计算等信息,如果是在服务器部署的话记得在安全组放开这个端口的UDP协议,防止出现4206错误无法进入游戏。除了安全组外,还要检查一下防火墙有没有开放。

·“welcomeMessage”: “Welcome to Grasscutter emu”,这个是进服务器之后Server发给你的欢迎语句,可以改成“欢迎来到XXX私服“之类的。

·“KeystorePassword”: “123456”,证书文件的密码,不需要修改。

·“autoCreate”: true,这个是控制自动创建账号的,改为true之后,如果登录私服的时候没有那个账号的话会自动创建一个并登录进去,然后分配默认的uid,从10001开始往后顺延。

·“singlePlayerTeam”: 5 “multiplayerTeam”: 10, 联机队伍中最多角色数,更改之后可以切换联机数量。

可能用到的命令:
①查看容器的内部IP:docker inspect 容器名 | grep IPAddress
②查询运行的Java程序:ps -ef|grep java
③停止运行的Java程序:kill [进程ID]

我的配置文件供参考(2.7版本后更新):

{
  "folderStructure": {
    "resources": "./resources/",
    "data": "./data/",
    "packets": "./packets/",
    "keys": "./keys/",
    "scripts": "./resources/scripts/",
    "plugins": "./plugins/"
  },
  "databaseInfo": {
    "server": {
      "connectionUri": "mongodb://【mongodb容器内部IP】:27017",
      "collection": "grasscutter"
    },
    "game": {
      "connectionUri": "mongodb://【mongodb容器内部IP】:27017",
      "collection": "grasscutter"
    }
  },
  "language": {
    "language": "zh",
    "fallback": "zh_CN"
  },
  "account": {
    "autoCreate": true,
    "defaultPermissions": []
  },
  "server": {
    "debugLevel": "NONE",
    "runMode": "HYBRID",
    "http": {
      "bindAddress": "0.0.0.0",
      "accessAddress": "【服务器IP】",
      "bindPort": 444,
      "accessPort": 0,
      "encryption": {
        "useEncryption": true,
        "useInRouting": true,
        "keystore": "./keystore.p12",
        "keystorePassword": "123456"
      },
      "policies": {
        "cors": {
          "enabled": false,
          "allowedOrigins": [
            "*"
          ]
        }
      },
      "files": {
        "indexFile": "./index.html",
        "errorFile": "./404.html"
      }
    },
    "game": {
      "bindAddress": "0.0.0.0",
      "accessAddress": "【服务器IP】",
      "bindPort": 22102,
      "accessPort": 0,
      "gameOptions": {
        "inventoryLimits": {
          "weapons": 2000,
          "relics": 2000,
          "materials": 2000,
          "furniture": 2000,
          "all": 30000
        },
        "avatarLimits": {
          "singlePlayerTeam": 5,
          "multiplayerTeam": 10
        },
        "worldEntityLimit": 1000,
        "watchGachaConfig": false,
        "enableShopItems": true,
        "staminaUsage": true,
        "rates": {
          "adventureExp": 1.0,
          "mora": 1.0,
          "leyLines": 1.0
        }
      },
      "joinOptions": {
        "welcomeEmotes": [
          2007,
          1002,
          4010
        ],
        "welcomeMessage": "欢迎来到<color=#66CCFF>青空的提瓦特</color>!",
        "welcomeMail": {
          "title": "Welcome to <color=#66CCFF>天空岛</color>服务器!",
          "content": "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n\u003ctype\u003d\"browser\" text\u003d\"Discord\" href\u003d\"https://discord.gg/T5vZU6UyeG\"/\u003e\n",
          "sender": "白毛天理",
          "items": [
            {
              "itemId": 13509,
              "itemCount": 1,
              "itemLevel": 1
            },
            {
              "itemId": 201,
              "itemCount": 99999,
              "itemLevel": 1
            },
            {
              "itemId": 11101,
              "itemCount": 1000,
              "itemLevel": 1
            },
            {
              "itemId": 223,
              "itemCount": 1000,
              "itemLevel": 1
            },
            {
              "itemId": 224,
              "itemCount": 1000,
              "itemLevel": 1
            }
          ]
        }
      },
      "serverAccount": {
        "avatarId": 10000007,
        "nameCardId": 210001,
        "adventureRank": 60,
        "worldLevel": 8,
        "nickName": "白毛天理",
        "signature": "天空岛之上,天理永存"
      }
    },
    "dispatch": {
      "regions": [],
      "defaultName": "<color=#66CCFF>天空岛</color>服务器"
    }
  },
  "version": 3
}

旧代码

{
  "DatabaseUrl": "mongodb://【mongodb容器内部IP】:27017",
  "DatabaseCollection": "grasscutter",
  "RESOURCE_FOLDER": "./resources/",
  "DATA_FOLDER": "./data/",
  "PACKETS_FOLDER": "./packets/",
  "DUMPS_FOLDER": "./dumps/",
  "KEY_FOLDER": "./keys/",
  "SCRIPTS_FOLDER": "./resources/Scripts/",
  "PLUGINS_FOLDER": "./plugins/",
  "DebugMode": "NONE",
  "RunMode": "HYBRID",
  "GameServer": {
    "Name": "【服务器名】",
    "Ip": "0.0.0.0",
    "PublicIp": "【服务器IP】",
    "Port": 22102,
    "PublicPort": 22102,
    "DispatchServerDatabaseUrl": "mongodb://【mongodb容器内部IP】:27017",
    "DispatchServerDatabaseCollection": "grasscutter",
    "InventoryLimitWeapon": 2000,
    "InventoryLimitRelic": 2000,
    "InventoryLimitMaterial": 2000,
    "InventoryLimitFurniture": 2000,
    "InventoryLimitAll": 30000,
    "MaxAvatarsInTeam": 5,
    "MaxAvatarsInTeamMultiplayer": 10,
    "MaxEntityLimit": 1000,
    "WatchGacha": false,
    "ServerNickname": "【服务器用户昵称】",
    "ServerAvatarId": 10000007,
    "ServerNameCardId": 210001,
    "ServerLevel": 60,
    "ServerWorldLevel": 8,
    "ServerSignature": "【服务器用户签名】",
    "WelcomeEmotes": [
      2007,
      1002,
      4010
    ],
    "WelcomeMotd": "Welcome to Grasscutter!",
    "WelcomeMailTitle": "Welcome to Grasscutter!",
    "WelcomeMailSender": "Grasscutter",
    "WelcomeMailContent": "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that AIR The Sustainer can help you! \r\n\r\nCheck out our:\r\n\u003ctype\u003d\"browser\" text\u003d\"Discord\" href\u003d\"https://discord.gg/T5vZU6UyeG\"/\u003e",
    "WelcomeMailItems": [
      {
        "itemId": 13509,
        "itemCount": 1,
        "itemLevel": 1
      },
      {
        "itemId": 201,
        "itemCount": 10000,
        "itemLevel": 1
      },
      {
        "itemID": 223,
        "itemCount": 1000,
        "itemLevel": 1
      },
      {
        "itemId": 224,
        "itemCount": 1000,
        "itemLevel": 1
      }
    ],
    "EnableOfficialShop": true,
    "Game": {
      "ADVENTURE_EXP_RATE": 10.0,
      "MORA_RATE": 10.0,
      "DOMAIN_DROP_RATE": 10.0
    }
  },
  "DispatchServer": {
    "Ip": "0.0.0.0",
    "PublicIp": "【服务器IP】",
    "Port": 444,
    "PublicPort": 444,
    "KeystorePath": "./keystore.p12",
    "KeystorePassword": "123456",
    "UseSSL": true,
    "FrontHTTPS": true,
    "CORS": false,
    "CORSAllowedOrigins": [
      "*"
    ],
    "AutomaticallyCreateAccounts": true,
    "defaultPermissions": [
      ""
    ],
    "GameServers": []
  },
  "LocaleLanguage": "zh-CN",
  "DefaultLanguage": "zh-CN",
  "OpenStamina": true
}

配置文件中的语言Language可以使用zh-CN或en-US~

以后更新的时候只用替换掉grasscutter.jar和resources文件夹就行,config.json也要重新生成并进行修改。

出现类似下方的提示,既为启动成功:
启动成功
启动成功-中文

此时如果想创建自定义UID账号,可输入account create [用户名] [想要的UID]

启动成功后直接关闭就行,不需要nohup指令,因为可能会造成性能和空间的消耗。
此时浏览器访问https://ip:port可以看到Welcome to Grasscutter的字样。


「四」准备畅玩,安装代理转发软件

至此,服务端的搭建已经完成!我们只需要将原神游戏发出的请求转发到自己的服务器上就OK了~

在这里推荐Windows端使用Fiddler作为流量转发工具,不需要再下载一遍游戏,启动 Fiddler 是私服,关掉 Fiddler 就是官服。

Fiddler官网下载:https://www.telerik.com/fiddler/fiddler-classic

配置说明:

第一步:选择左上角Tools-Options,在HTTPS里面选择Decrypt HTTPS traffic,三个勾可以都打上。
第一步
勾上

第二步:选择Connections,然后选择一个端口,建议避开8888端口防止占用,我这里用的是8181端口,点击OK。
第二步

第三步:在Fiddler右侧找到FiddlerScript,把原来的代码全部删掉,然后把下面的这一段代码放进去(把oS.host改成自己的即可),然后点击左上角的Save Script保存脚本使其生效。
第三步

/* Original script by NicknameGG, modified for Grasscutter by contributors. */
import System;
import System.Windows.Forms;
import Fiddler;
import System.Text.RegularExpressions;
 
class Handlers
{
    static function OnBeforeRequest(oS: Session) {
        if(oS.host.EndsWith(".yuanshen.com") || oS.host.EndsWith(".hoyoverse.com") || oS.host.EndsWith(".mihoyo.com")) {
            oS.host = "服务器IP:444"; // This can also be replaced with another IP address.(输入域名也可)
        }
        if(oS.uriContains("http://uspider.yuanshen.com:8888/log")){
            oS.oRequest.FailSession(200, "Blocked", "haha");
        }
    }
};

恭喜!至此,理论的搭建已经全部完成,打开游戏体验吧~
当登陆界面的logo变成hoyoverse,意味着代理已经成功运行了,输入事先创建的用户名,密码随便写~


「五」指令的下达

传统方式的命令执行,是在进入游戏后进入会话列表,添加会话用户找到Server用户并添加,就可以发送“/命令”的方式来执行指令。

当然,这种方法效率有些低,我们可以使用@jie65535制作的命令生成器
https://github.com/jie65535/GrasscutterCommandGenerator
可以方便地生成命令供粘贴使用。

进一步,这个工具还可以通过插件的方式远程执行指令,更加快捷:
服务端插件:https://github.com/jie65535/gc-opencommand-plugin

服务端插件使用方法:
·在Release下载 jar
·放入服务器目录中的/Grasscutter/plugins文件夹
·重启割草机服务端:
①查询运行的Java程序:ps -ef|grep java
②停止运行的Java程序:kill [进程ID]
③java -jar grasscutter-1.1.0.jar

·先进入游戏,再打开命令生成器,在“远程”页面中查询、发送验证码到你的UID,再将游戏中左下角消息中的验证码填入即可。接下来,使用一键执行命令,成为提瓦特的天理吧!

环境备份方法:

一、Docker镜像备份法
生成本地镜像:

docker save -o [导出的镜像名.tar] [本地镜像名]:[镜像标签]
如:docker save -o /home/grasscutter-image.tar grasscutter:latest

使用本地镜像:

docker load -i [本地tar包文件] 

二、手动备份法
复制Docker内指定目录到宿主机:

docker cp grasscutter:/Grasscutter /Grasscutter

输出MongoDB数据库备份:

mongodump -o /home/mongodb/

恢复所有数据到数据库:

mongorestore /home/mongodb/

年轻时诋毁原神,长大后理解OP,成熟后成为天理!


〔部分引用〕使用Grasscutter服务端搭建原神私服

Last Modified: June 15, 2022
Leave a Comment

12 Comments
  1. 虽然没玩过原神,但是看到用Java模拟服务端,我还是露出了欣慰的笑容(

    1. @天空Blond#(击掌)

  2. dao dao

    为啥不直接做成Dockerfile呢,或者直接做成docker镜像,每次手动进容器里面操作很麻烦

    1. @dao高情商:更新频繁且文件结构变化快,享受亲自逐行输指令的快乐
      低情商:忙碌中,几行代码的过程,暂时没什么必要,忙完了再加

  3. ... ...

    请问一下我在运行 java -jar grasscutter-1.1.0.jar 这个命令的时候报出:

    Exception in monitor thread while connecting to server 127.0.0.1:27017
    com.mongodb.MongoSocketOpenException: Exception opening socket

    Cluster description not yet available. Waiting for 30000 ms before timing out
    Exception in thread "main" com.mongodb.MongoTimeoutException: Timed out after 30000 ms while waiting to connect. Client view of cluster state is {type=UNKNOWN, servers=[{address=127.0.0.1:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]

    这个错误请问有没有办法解决

    1. @...从报错来看是连接被拒绝,可以尝试将配置文件(mongodb.conf)中的 bind_ip:127.0.0.1(localhost) 改为 0.0.0.0,或者将这一行注释掉,之后重新启动MongoDB试试看

    2. ... ...

      @青空感谢,这个错误解决了,但是后面运行游戏我看fiddler的请求是502,这是要配置什么吗

    3. @...你可以用浏览器访问下对应ip端口,看看有没有返回信息

    4. ... ...

      @青空有的,返回Welcome to Grasscutter这个信息

    5. @...2.7版本已更新~

  4. Lone Lone

    大佬太牛了,教程太细致了。按照教程我运行jar包的时候,报了一个错误。 [WARN] Failed to load language file: en.json
    。提示有个文件没有找到,后面是空指针的报错。但是我没有找到这个错误的原因,猜测应该是服务端的资源缺了什么东西,我拉的是稳定版的分支,但是比对最后的目录也没缺啥文件。不知道大佬可否分享一下你的服务端资源

    1. @Lone感谢评论~关于语言文件,在稳定版和开发版的路径都是 "src/main/resources/languages/",可以对比看一下config.json里拼写的正确与否,目前来看稳定版有en-US、zh-CN和zh-TW。从最近更新来看,关于语言文件其实还有一点问题,不过如果读取失败会自动切换到en-US,所以可以多次尝试下看看~