Git Product home page Git Product logo

authlib-injector's Introduction

  • English
  • 简体中文(Chinese Simplified)

authlib-injector

latest release ci status license agpl-3.0

通过运行时修改 authlib 实现游戏外登录,并为 Yggdrasil 服务端的实现提供规范。

关于该项目的详细介绍见 wiki

获取

您可以从这里获取最新的 authlib-injector。

构建

构建依赖:Gradle、JDK 17+(目标 Java 版本为 8)。

执行以下命令:

gradle

构建输出位于 build/libs 下。

部署

通过添加以下 JVM 参数来配置:

-javaagent:{authlib-injector.jar 的路径}={验证服务器 URL (API 地址)}

参数

-Dauthlibinjector.noLogFile
    不要将日志输出到文件.
    默认情况下, authlib-injector 会将日志输出到控制台以及当前目录下的 authlib-injector.log 文件.
    开启此选项后, 日志仅会输出到控制台.

    需要注意的是, authlib-injector 的日志是不会输出到 Minecraft 服务端/客户端的日志文件中的.

    每次启动时, 日志文件都会被清空. 如果有多个进程使用同一个日志文件, 则只有最早启动的会成功打开日志文件.

-Dauthlibinjector.mojangNamespace={default|enabled|disabled}
    设置是否启用 Mojang 命名空间 (@mojang 后缀).
    若验证服务器未设置 feature.no_mojang_namespace 选项, 则该功能默认启用.

    启用后, 则可以使用名为 <username>@mojang 的虚拟角色来调用对应正版角色的皮肤.
    例如,
     - /give @p minecraft:skull 1 3 {SkullOwner:"Notch@mojang"}
     - /npc skin Notch@mojang
    显示的将会是 Notch 的皮肤.

    注意, 虚拟角色和对应正版角色的 UUID 是不同的. 为了将虚拟角色和正版角色区别开,
    虚拟角色 UUID 中 time_hi_and_version 字段的最高位被置为 1 (见 RFC 4122 4.1.3 章节).
    例如:
      069a79f4-44e9-4726-a5be-fca90e38aaf5 Notch
      069a79f4-44e9-c726-a5be-fca90e38aaf5 Notch@mojang
    采用该方法的原因是, 在 RFC 4122 中 UUID 版本号只有 6 种可能的取值 (0~5), 版本号的最高位始终为 0.
    而实际上, Mojang 使用的是版本 4 (随机) UUID, 因此其对应的虚拟角色的 UUID 版本号为 12.

-Dauthlibinjector.mojangProxy={代理服务器 URL}
    设置访问 Mojang 验证服务时使用的代理, 目前仅支持 SOCKS 协议.
    URL 格式: socks://<host>:<port>

    这一代理仅作用于 Mojang 命名空间 功能, 其仅用于访问 Mojang 服务器.
    若要在访问自定义验证服务器时使用代理, 请参考 https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html .

-Dauthlibinjector.legacySkinPolyfill={default|enabled|disabled}
    是否启用旧式皮肤 API polyfill, 即 'GET /skins/MinecraftSkins/{username}.png'.
    若验证服务器未设置 feature.legacy_skin_api 选项, 则该功能默认启用.

-Dauthlibinjector.debug (等价于 -Dauthlibinjector.debug=verbose,authlib)
 或 -Dauthlibinjector.debug={调试选项; 逗号分隔}
    可用的调试选项:
     - verbose             详细日志
     - authlib             开启 Mojang authlib 的调试输出
     - dumpClass           转储修改过的类
     - printUntransformed  打印已分析但未修改的类; 隐含 verbose

-Dauthlibinjector.ignoredPackages={包列表; 逗号分隔}
    忽略指定的包, 其中的类将不会被分析或修改.

-Dauthlibinjector.disableHttpd
    禁用内建的 HTTP 服务器.
    以下依赖内建 HTTP 服务器的功能将不可用:
     - Mojang 命名空间
     - 旧式皮肤 API polyfill

-Dauthlibinjector.httpdPort={端口号}
    设置内置 HTTP 服务器使用的端口号, 默认为 0 (随机分配).

-Dauthlibinjector.noShowServerName
    不要在 Minecraft 主界面展示验证服务器名称.
    默认情况下, authlib-injector 通过更改 --versionType 参数来在 Minecraft 主界面显示验证服务器名称, 使用本选项可以禁用该功能.

-Dauthlibinjector.mojangAntiFeatures={default|enabled|disabled}
    设置是否开启 Minecraft 的部分 anti-feature.
    若验证服务器未设置 feature.enable_mojang_anti_features 选项, 则默认禁用.

  Minecraft 的 anti-feature 包括:
     - Minecraft 服务器屏蔽列表
     - 查询用户权限的接口, 涵盖以下项目:
       * 聊天权限 (禁用后默认允许)
       * 多人游戏权限 (禁用后默认允许)
       * 领域权限 (禁用后默认允许)
       * 遥测 (禁用后默认关闭)
       * 冒犯性内容过滤 (禁用后默认关闭)

-Dauthlibinjector.profileKey={default|enabled|disabled}
    是否启用消息签名密钥对功能, 这一功能在 22w17a 引入, 用于多人游戏中聊天消息的数字签名.
    启用此功能后, Minecraft 会向 /minecraftservices/player/certificates 发送 POST 请求, 以获取由验证服务器颁发的密钥对.
    此功能需要验证服务器支持, 若验证服务器未设置 feature.enable_profile_key 选项, 则该功能默认禁用.

-Dauthlibinjector.usernameCheck={default|enabled|disabled}
    是否启用玩家用户名检查, 若禁用, 则 authlib-injector 将关闭 Minecraft、BungeeCord 和 Paper 的用户名检查功能.
    若验证服务器未设置 feature.usernameCheck 选项, 则默认禁用.
    注意, 开启此功能将导致用户名包含非英文字符的玩家无法进入服务器.

捐助

BMCLAPI 为 authlib-injector 提供了下载镜像站。如果您想要支持 authlib-injector 的开发,您可以捐助 BMCLAPI

许可

本程序使用 GNU Affero General Public License v3.0 or later 许可,并附有以下例外:

AGPL 的例外情况:

作为特例,如果您的程序通过以下方式利用本作品,则相应的行为不会导致您的作品被 AGPL 协议涵盖。

  1. 您的程序通过打包的方式包含本作品未经修改的二进制形式,而没有静态或动态地链接到本作品;或
  2. 您的程序通过本作品提供的进程间通信接口(如 HTTP API)进行交互;或
  3. 您的程序将本作品作为 Java Agent 加载进 Java 虚拟机。

authlib-injector's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

authlib-injector's Issues

为什么会出现文件未找到异常

image

[authlib-injector] unable to configure remotely: java.io.FileNotFoundException: http://10.0.0.100:50001/yggdrasil-backend-0.0.1-SNAPSHOT/yggdrasil
java.io.UncheckedIOException: java.io.FileNotFoundException: no config is found
at org.to2mbn.authlibinjector.AuthlibInjector.loadConfig(AuthlibInjector.java:59)
at org.to2mbn.authlibinjector.AuthlibInjector.bootstrap(AuthlibInjector.java:39)
at org.to2mbn.authlibinjector.javaagent.AuthlibInjectorPremain.premain(AuthlibInjectorPremain.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)
at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(Unknown Source)
Caused by: java.io.FileNotFoundException: no config is found
at org.to2mbn.authlibinjector.AuthlibInjector.lookupConfig(AuthlibInjector.java:83)
at org.to2mbn.authlibinjector.AuthlibInjector.loadConfig(AuthlibInjector.java:55)
... 8 more

关于Bungeecord的连接出错

  1. 在按照Wiki指示配置服务端:
  • 在Bungeecord和服务端各加载API成功、
  • 二者Online模式都按要求设置完成、
  • 确认服务端可以直接连接
    的情况下,连接Bungeecord,出现:
    “Exception Connecting:Quiet Exception :Unexpected Packet received during serveur login processus
    1bcc017b227472616e736c617465223a”
  1. 检查配置发现Bungeecord存在
  • ip_fowarding
    一项默认开启,
  1. 禁用此项、重启、尝试连接后方可成功。
    ----这仅仅是巧合还是配置方面需要额外加以注意的地方?

服务器启动后直接提示http403错误 浏览器可以访问

这是日志

[authlib-injector.launch] [INFO] Version: 1.1.26-41a7a47 [authlib-injector.config] [INFO] API root: https://bss.fengzi.run/api/yggdrasil/ [authlib-injector.config] [SEVERE] Failed to fetch metadata: java.io.IOException: Server returned HTTP response code: 403 for URL: https://bss.fengzi.run/api/yggdrasil/

[proposal] 元数据中包含验证服务器网址

概述

该提案提供了一种验证服务器向启动器传递网址的方式。通过这一功能,启动器可以获得验证服务器首页地址、注册页面地址等信息。

格式

这些链接的地址包含在 meta 里的 links 属性中,其格式如下:

{
    "homepage": "首页 URL",
    "register": "注册页面 URL"
}

上面的每一个链接都是可选的,并且今后可能会有扩充。如果不需要其中某一项,验证服务器可以不包含。

下面给出一个完整的例子:

{
    "meta": {
        "links": {
            "homepage": "https://www.example.com/",
            "register": "https://www.example.com/register"
        },
        ...
    },
    ...
}

对启动器的建议

  • 如果验证服务器提供了注册页面地址,那么在用户登录时,启动器可以在一旁显示一个“注册”按钮。

java.lang.instrument ASSERTION FAILED! 疑似需要VPN

  • MC版本:1.14.4
  • 服务器Java版本:9-internal
  • 使用的认证服务器:LittleSkin
    online-mode=true的情况下,启动时报错:
[authlib-injector.launch] [INFO] Version: 1.1.26-41a7a47
[authlib-injector.config] [INFO] API root: https://littleskin.cn/api/yggdrasil/

***java.lang.instrument ASSERTION FAILED!***: "!errorOutStanding" with message transform method call failed at JPLISAgent.c line: 884
***java.lang.instrument ASSERTION FAILED!***: "!errorOutStanding" with message transform method call failed at JPLISAgent.c line: 884

此时尝试通过使用同样认证服务器的HMCL进入世界时显示:
image

疑似Java版本不对应所致。

Forge客户端在服务器内只能看见自己皮肤

在原版游戏内没有问题,只发生在装有forge的客户端
游戏内皮肤、TAB的头像只有自己的是正确的,其他玩家都是史蒂夫或者阿利克斯
其他需要显示玩家头像的mod均能显示正确的头像,小地图、墓碑等
游戏版本1.12.2
forge版本尝试过2705、2768、2807都有问题
authlib-injector版本1.1.24-fe7cb0b

可否在wiki“材质 URL 规范”部分公布示例图片的缓冲区内容

本人在使用Python实现该API时,计算得出示例图片的二进制值为:
\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff\x00\x00\xff\x00\xff\x00\xff\x00\x00\xff\x00\x00\x00\x00\xff\xff\x00\xff\xff\xff\xff\x00,即
图片宽度\x00\x00\x00\x02,高度\x00\x00\x00\x03,像素部分\xff\xff\x00\x00
\xff\x00\xff\x00 \xff\x00\x00\xff \x00\x00\x00\x00 \xff\xff\x00\xff \xff\xff\xff\x00共6个像素,
是示例图片从从左往右,从上至下先横后纵的扫描结果。
但计算出的sha256值与示例不符。
故欲与您计算哈希之前的缓冲区二进制值作比对。
自己初入GitHub及Python大坑,若本人对您wiki的理解有误,还请斧正。

Macos通过AI无法正常启动1.13

以下是error logs:

[authlib-injector] launched from premain
[authlib-injector] version: 1.1.18-daa6fb4
[authlib-injector] api root: https://i.timewk.cn/api/yggdrasil/
[authlib-injector] prefetched metadata detected
[authlib-injector] httpd is running on port 53185
[authlib-injector] transform [http://skins.minecraft.net/MinecraftSkins/%s.png] to [http://127.0.0.1:53185/MinecraftSkins/%s.png]
[authlib-injector] transform csv using deprecated-api-transform
Exception in thread "Client thread" java.lang.NoClassDefFoundError: org/lwjgl/glfw/GLFWErrorCallbackI
	at net.minecraft.client.main.Main.main(SourceFile:144)
Caused by: java.lang.ClassNotFoundException: org.lwjgl.glfw.GLFWErrorCallbackI
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 1 more
[15:04:29] [Client Shutdown Thread/ERROR] [net.minecraft.client.main.Main] Caught previously unhandled exception :

在PaperSpigot的最新构建版本(1.15.2-#164)上无法运行

java.net.SocketException: Connection reset
        at java.net.SocketInputStream.read(SocketInputStream.java:210)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
        at sun.security.ssl.InputRecord.read(InputRecord.java:503)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:167)
        at cn.mcres.karlatemp.yop.server.Connecting.run(Connecting.java:133)
        at cn.mcres.karlatemp.yop.server.Connecting.run(Connecting.java:60)
        at cn.mcres.karlatemp.yop.server.Connecting.run(Connecting.java:35)
        at cn.mcres.karlatemp.yop.YggdrasilOfficialProxy.request(YggdrasilOfficialProxy.java:125)
        at cn.mcres.karlatemp.yop.server.ContextHandler.channelRead(ContextHandler.java:129)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:326)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:300)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1422)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:931)
        at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792)
        at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:502)
        at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:407)
        at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1050)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at java.lang.Thread.run(Thread.java:748)
java.net.SocketException: Connection reset
        at java.net.SocketInputStream.read(SocketInputStream.java:210)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
        at sun.security.ssl.InputRecord.read(InputRecord.java:503)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:167)
        at cn.mcres.karlatemp.yop.server.Connecting.run(Connecting.java:133)
        at cn.mcres.karlatemp.yop.server.Connecting.run(Connecting.java:60)
        at cn.mcres.karlatemp.yop.server.Connecting.run(Connecting.java:35)
        at cn.mcres.karlatemp.yop.YggdrasilOfficialProxy.request(YggdrasilOfficialProxy.java:125)
        at cn.mcres.karlatemp.yop.server.ContextHandler.channelRead(ContextHandler.java:129)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:326)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:300)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1422)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:931)
        at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792)
        at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:502)
        at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:407)
        at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1050)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at java.lang.Thread.run(Thread.java:748)
[authlib-injector.config] [SEVERE] Failed to fetch metadata: java.net.SocketException: Unexpected end of file from server

Cannot inject urls in 1.7.10 mods server

Cannot inject urls in 1.7.10 mods server, it seems that the transform unit can only find strings in main jar instead of libraries. The mojang authlib is located at libraries folder.

Java11+ 上不兼容 MC 1.15.2 + Optifine

安装optifine后,authlib无法使用,必须改成离线启动。Optifine版本为G1_pre1
错误记录:

[15:39:22] [main/INFO]: Using primary tweak class name optifine.OptiFineTweaker
[15:39:22] [main/INFO]: Calling tweak class optifine.OptiFineTweaker
[15:39:23] [main/INFO]: Launching wrapped minecraft {net.minecraft.client.main.Main}
[15:39:23] [main/ERROR]: Unable to launch
java.lang.reflect.InvocationTargetException: null
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
	at net.minecraft.launchwrapper.Launch.launch(Launch.java:159) [launchwrapper-of-2.1.jar:2.1]
	at net.minecraft.launchwrapper.Launch.main(Launch.java:30) [launchwrapper-of-2.1.jar:2.1]
Caused by: java.lang.NoClassDefFoundError: moe/yushi/authlibinjector/transform/CallbackEntryPoint
	at net.minecraft.client.main.Main.main(SourceFile) ~[Main.class:?]
	... 6 more
Caused by: java.lang.ClassNotFoundException: moe.yushi.authlibinjector.transform.CallbackEntryPoint
	at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:185) ~[launchwrapper-of-2.1.jar:2.1]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:588) ~[?:?]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[?:?]
	at net.minecraft.client.main.Main.main(SourceFile) ~[Main.class:?]
	... 6 more
Caused by: java.lang.NullPointerException
	at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:176) ~[launchwrapper-of-2.1.jar:2.1]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:588) ~[?:?]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[?:?]
	at net.minecraft.client.main.Main.main(SourceFile) ~[Main.class:?]
	... 6 more

为什么返回的ID时NULL?

用1.14.2进入游戏时控制台消息为 com.mojang.authlib.GameProfile@7a42b14[ id=<null>,name=Lazy_Creeper,properties={},legacy=false ]
为什么API 返回的ID时NULL?

对令牌过期的描述与Mojang的Yggdrasil实现不符

对Mojang的Yggdrasil服务端发出以下请求,行为与文档描述的不一致:

actions:
- type: authenticate
  request:
    username: ******
    password: ******
    clientToken: a4e0147b7d254d17a01050390abe0e6d # -> $c1
  response:
    accessToken: 6b70c76057744e579c4777d90c69b46b # -> $a1
    clientToken: a4e0147b7d254d17a01050390abe0e6d # $c1

- type: authenticate
  request:
    username: ******
    password: ******
  response:
    accessToken: be9bbcc2b31a45b3b94e1948f6f031ad # -> $a2
    clientToken: cf37f0dc4ed9499cb2ba182e5c456c0d # -> $c2

- type: validate
  request:
    accessToken: 6b70c76057744e579c4777d90c69b46b # $a1
  response:
    error: ForbiddenOperationException

- type: validate
  request:
    accessToken: be9bbcc2b31a45b3b94e1948f6f031ad # $a2
  response: {} # 204

- type: refresh
  request:
    accessToken: 6b70c76057744e579c4777d90c69b46b # $a1
  response:
    accessToken: 713586718473440a85f66d93f10e425a # -> $a3
    clientToken: a4e0147b7d254d17a01050390abe0e6d # $c1

- type: validate
  request:
    accessToken: 6b70c76057744e579c4777d90c69b46b # $a1
  response:
    error: ForbiddenOperationException

- type: validate
  request:
    accessToken: 713586718473440a85f66d93f10e425a # $a3
  response: {} # 204

- type: validate
  request:
    accessToken: be9bbcc2b31a45b3b94e1948f6f031ad # $a2
  response:
    error: ForbiddenOperationException

- type: refresh
  request:
    accessToken: be9bbcc2b31a45b3b94e1948f6f031ad # $a2
  response:
    accessToken: d9031657c43546d993fc306b9b4a18f3 # -> $a4
    clientToken: cf37f0dc4ed9499cb2ba182e5c456c0d # -> $c2

帮助在面板服使用authlib-injector的辅助工具LaunchHelper

LaunchHelper

国内的面板服几乎都是Multicraft面板,它能使用自定义服务端(如果商家允许的话),但不能自定义启动参数,所以平时无法使用authlib-injector。

LaunchHelper的Jar可以实现不添加任何命令行参数载入Java agent并启动另一个可执行Jar,从而解决了在Multicraft面板服使用authlib-injector的难题。

仓库地址:Codex-in-somnio/LaunchHelper

这个轮子刚刚能转,欢迎大家前来吃🦀

[proposal] 地址协议补全

概述

此提案旨在简化 Yggdrasil 服务器地址的输入,其适用于启动器和 authlib-injector 自身。

当用户输入 Yggdrasil 服务器地址时,https:// 前缀可以被省略。如果无法使用 HTTPS 协议连接,则应直接提示出错,不得降级到不安全的 HTTP。

参见 启动器技术规范#地址协议补全


欢迎诸位就本提案提出意见。

部分细节与Mojang的Yggdrasil实现不符

1.未在文档中规定 Username ->UUID API

Resolved

2.UUID->Profile API中存在导致NPE的定义(edited by @yushijinhun: solved)

properties及signature项目在无特殊说明的情况下不需要包含。

实际上,若是 properties 被省略,则会在

com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService.fillGameProfile(YggdrasilMinecraftSessionService.java:187)

发生NPE,完整的日志如下

java.lang.NullPointerException
com.google.common.collect.AbstractMultimap.putAll(AbstractMultimap.java:95)
com.google.common.collect.LinkedHashMultimap.putAll(LinkedHashMultimap.java:86)
com.google.common.collect.ForwardingMultimap.putAll(ForwardingMultimap.java:110)
com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService.fillGameProfile(YggdrasilMinecraftSessionService.java:187)
com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService$1.load(YggdrasilMinecraftSessionService.java:60)
com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService$1.load(YggdrasilMinecraftSessionService.java:57)
com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3716)
com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2424)
com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2298)
com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2211)
com.google.common.cache.LocalCache.get(LocalCache.java:4154)
com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:4158)
com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:5147)
com.google.common.cache.LocalCache$LocalLoadingCache.getUnchecked(LocalCache.java:5153)
com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService.fillProfileProperties(YggdrasilMinecraftSessionService.java:170)
customskinloader.loader.MojangAPILoader.loadProfile(MojangAPILoader.java:46)
customskinloader.CustomSkinLoader.loadProfile0(CustomSkinLoader.java:105)
customskinloader.CustomSkinLoader.loadProfile(CustomSkinLoader.java:77)
customskinloader.fake.FakeSkinManager$1.run(FakeSkinManager.java:61)
java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
java.util.concurrent.FutureTask.run(Unknown Source)
java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
java.lang.Thread.run(Unknown Source)

望您能在百忙之中抽出时间进行修改,麻烦您了

[proposal] 启动器公告

概述

该提案为验证服务器向启动器推送公告制定了规范。

格式

公告包含在 meta 里的 notice 属性中,其格式如下:

{
    "content": "公告文本",
    "lastUpdate": "最后更新时间(可选)"
}
  • 公告文本 content 是一段纯文本。文本可以有多行,使用 LF 作为换行符。
  • 最后更新时间 lastUpdate 是一个符合 ISO 8601 格式的日期时间,如 2011-03-21T00:18:56Z

下面给出一个完整的例子:

{
    "meta": {
        "notice": {
            "content": "欢迎来到某服务器\n网站:https://example.com/\nQQ群:123456789",
            "lastUpdate": "2018-12-01T13:16:17Z"
        },
        ...
    },
    ...
}

启动器实现时的注意点

  • 建议将文本中的 URL 识别为超链接,以便用户直接点击访问。
  • 建议将公告显示在启动器首屏上,让用户一眼就能看到其内容。
  • 显示最后更新时间时,注意将其转换为当地时间。
  • 可以通过最后更新时间的变化,来提示用户公告内容有改动。

验证服务器实现时的注意点

  • 公告内容中,换行符使用 LF。
  • 如果公告中包含 URL,且 URL 较长,则可以考虑使用短链接。

欢迎诸位就本提案提出意见。

[proposal] API 地址指示(ALI)

概述

目前,用户配置 authlib-injector 时,需要输入完整的 Yggdrasil API 地址,这是十分麻烦的。尽管通过 DnD 方式添加 Yggdrasil 服务端可以避免输入 API 地址,但此功能的使用率并不高。

此提案规定了一个 HTTP 响应头字段 X-Authlib-Injector-API-Location,其被称为 API 地址指示(API Location Indication,简称 ALI)。ALI 的值可以是相对 URL(如 /api/yggdrasil/),也可以是绝对 URL(如 https://skin.example.com/api/yggdrasil/),它指向与当前页面相关联的 Yggdrasil API 地址。

对启动器的改动

当用户添加某个 URL 为 Yggdrasil 服务器时,启动器向此 URL 发送 GET 请求。不论 HTTP 状态码是多少,如果响应中包含 ALI,则启动器重定向到其所指的 URL。这样的重定向允许进行多次,但必须有次数上限。在这里规定这个上限为 5 次。

如果 ALI 指向其自身(比较时除去末尾/),则该 ALI 应当被忽略,当前 URL 即为 API 地址。

需要注意的是,ALI 仅用于确定 Yggdrasil API 地址,即仅在添加 Yggdrasil 服务器时被考虑。一旦 Yggdrasil 服务器被添加,其 API 地址就不再发生改变,无论服务器的后续响应中是否包含 ALI。此外,启动器应当显示真正的 API 地址,而不是重定向之前的。

对 authlib-injector 的改动

当 authlib-injector 运行在 MC 服务端上时,其支持 ALI,实现方式与启动器相同;当 authlib-injector 运行在 MC 客户端上时,其不支持 ALI(这里认为启动器已经处理过 ALI,传入的 API 地址即为真正的 API 地址)。

判断运行环境是客户端还是服务端的方法,见 #16

例子

如果 #16#17 及本提案可以被实现,那么就会有以下效果:

假定 https://example.com/ 是一个皮肤站,其 Yggdrasil API 地址为 https://skin.example.com/api/yggdrasil。页面 https://example.com/ 使用了 ALI。

  • 配置启动器时,仅需要输入 example.com
  • 配置服务端时,仅需要添加参数 -javaagent:authlib-injector.jar=example.com

欢迎诸位就本提案提出意见。

客户端启动报错

[authlib-injector.launch] [INFO] Version: 1.1.21-f242b97
[authlib-injector.config] [INFO] API root: https://skin.fkj233.cn/api/yggdrasil/
[authlib-injector.config] [SEVERE] Failed to fetch metadata: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Capes not visible on server

I'm testing authlib-injector with Blessing Skin Server on a server, and while I can see my skin, capes don't seem to show. Joining a singleplayer world I can see it just fine, though.

Is it just me, or?

Workaround for MC-52974 (1.7.10)

这个 issue 是从 #30 中独立出来的。

在 MC 1.7.10 中,MC-52974 这个 bug 是由以下两个问题造成的:

  1. Host 在查询自己的 GameProfile 时,没有查询数字签名,因此其向 Guest 所发送的 Textures payload 中也不可能包含数字签名。
  2. Host 根本没有向 Guest 发送 GameProfile 中的 properties。

第一个问题已在 #31 中解决。在和 @NekoCaffeine 讨论后,我找到了造成第二个问题的原因:

  1. net.minecraft.util.Session负责创建GameProfile对象,每次进服(包括本地游戏)时,它就创建一个。其创建的GameProfile不包含properties。
  2. 创建的GameProfile被放入C00PacketLoginStart中,不经过序列化直接被传递到本地服务器。
  3. 本地服务器取出C00PacketLoginStart中的GameProfile对象。因为进入本地游戏时不经过KEY阶段,直接跳到READY_TO_ACCEPT阶段,所以不会去调用hasJoined接口,所以本地服务器使用的GameProfile对象直接就是一开始Session所创建的对象。
  4. 因为GameProfile不包含properties,所以发送的S0CPacketSpawnPlayer中也不包含properties。
  5. SkinManager中存在逻辑:加载皮肤时,如果properties为空,并且加载的是自己的皮肤,就去查询皮肤。所以Host可以看到自己的皮肤,而Guest不能看到Host皮肤。

我所给出的解决方法:

  1. 修改Session,对其创建的GameProfile对象进行标记。
  2. 修改S0CPacketSpawnPlayer,当包被序列化时,如果其中的GameProfile是被标记过的,就查询properties并加入其中。

【偷懒计划】这样偷懒N下是否可行

1.不去做令牌的生命周期,取而代之的是在MySQL存储玩家的accesstoken和clinttoken。这样的话 /refresh 接口就可以万年不用调用了 哈哈哈哈
如果有新的token,直接覆盖掉原有的数据,保证数据库内玩家有且仅有一个accesstoken和clinttoken

2.不自己实现皮肤站的功能,想办法实现一个与Mojang正版账号绑定的功能,然后通过Mojang的api给玩家发放皮肤,自建ygg后端不进行存储皮肤文件

3.还有其他可以偷懒的可行办法吗

(打算用php搞,这块提供的方案是java的,想少造点轮子)

4.(从bs皮肤站作者那里看到的)

初次登录时,用账号密码拿到 AccessToken
POST /authserver/authenticate
之后的登录都是直接用这个 API 签发新的令牌
POST /authserver/refresh
加入服务器
POST /sessionserver/session/minecraft/join
验证是否加入了服务器
GET /sessionserver/session/minecraft/hasJoined
获取玩家完整 Profile
GET /api/profiles/minecraft/{uuid}

仅用这五个api,可以满足日用吗(有点好奇qwq)

添加:
还有一个...如果我把进服的joinId这么处理 (有可能没法用redis那类东西做)

客户端post过来joinid,入库joinid
服务端发送get请求来校验joinid
然后跟mysql库里的那个进行比对
比对完成后,数据库里的“最后joinid”不抹除,等待下一次请求时自动由第一步覆盖

这样弄的话...会不会有安全性问题

服务器无限重启

[authlib-injector.launch] [INFO] Version: 1.1.21-f242b97
[authlib-injector.config] [INFO] API root: https://skin.fkj233.cn/api/yggdrasil/
[authlib-injector.transform] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository] with [Yggdrasil API Transformer]
[authlib-injector.transform] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService] with [Yggdrasil API Transformer]
[authlib-injector.transform] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService] with [Yggdrasil Public Key Transformer]
[authlib-injector.transform] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication] with [Yggdrasil API Transformer]

第一次启动大概率服Yggdrasil服务器收不到任何请求

你好,我现在使用authlib-injector开发了Makku启动器,目前遇到以下情况:
1 首次启动大概率提示验证服务正在停机维护,Yggdrasil服务器日志没提示收到请求
2 玩家因网络异常掉线后无法重连,会提示各种千奇百怪的错误,服务器控制台则是Netty的报错提示
如果需要详细日志,我可稍后上传,望解答。

[bug] authlib-injector 在 20w15a 中无效

Env

HMCL 3.3.160, JRE 1.8.0_162

Description

经测试,在 20w14a 中依然可用。另经小范围调查,确认其他人也有此类情况。

按照日志文件来看,Mojang 似乎改变了 Yggdrasil API 的一些细节

Log

[12:34:21] [Render thread/INFO]: Setting user: Xiao_Jin
[12:34:30] [Render thread/INFO]: Backend library: LWJGL version 3.2.2 build 10
[12:34:40] [Render thread/INFO]: Narrator library for x64 successfully loaded
[12:34:41] [Render thread/INFO]: Reloading ResourceManager: Default
[12:34:52] [Realms Notification Availability checker #1/INFO]: Could not authorize you against Realms server: Invalid session id
[12:35:22] [Render thread/INFO]: OpenAL initialized.
[12:35:22] [Render thread/INFO]: Sound engine started
[12:35:23] [Render thread/INFO]: Created: 1024x1024x2 minecraft:textures/atlas/blocks.png-atlas
[12:35:23] [Render thread/INFO]: Created: 256x128x2 minecraft:textures/atlas/signs.png-atlas
[12:35:23] [Render thread/INFO]: Created: 1024x512x2 minecraft:textures/atlas/banner_patterns.png-atlas
[12:35:23] [Render thread/INFO]: Created: 1024x512x2 minecraft:textures/atlas/shield_patterns.png-atlas
[12:35:23] [Render thread/INFO]: Created: 256x256x2 minecraft:textures/atlas/chest.png-atlas
[12:35:23] [Render thread/INFO]: Created: 512x256x2 minecraft:textures/atlas/beds.png-atlas
[12:35:23] [Render thread/INFO]: Created: 512x256x2 minecraft:textures/atlas/shulker_boxes.png-atlas
[12:35:24] [Render thread/INFO]: Created: 256x256x0 minecraft:textures/atlas/particles.png-atlas
[12:35:24] [Render thread/INFO]: Created: 256x256x0 minecraft:textures/atlas/paintings.png-atlas
[12:35:24] [Render thread/INFO]: Created: 256x128x0 minecraft:textures/atlas/mob_effects.png-atlas
[12:36:02] [Render thread/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', name='PROD'
[12:36:02] [Render thread/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]
[12:36:02] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[12:36:02] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]
[12:36:02] [Render thread/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]
[12:36:02] [Render thread/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[12:36:02] [Server thread/INFO]: Starting integrated minecraft server version 20w15a
[12:36:02] [Server thread/INFO]: Generating keypair
[12:36:03] [Server thread/INFO]: Found new data pack vanilla, loading it automatically
[12:36:03] [Server thread/INFO]: Reloading ResourceManager: Default
[12:36:04] [Server thread/INFO]: Loaded 6 recipes
[12:36:04] [Server thread/INFO]: Loaded 907 advancements
[12:36:07] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[12:36:07] [Render thread/INFO]: 准备生成区域中:0%
[12:36:08] [Render thread/INFO]: 准备生成区域中:0%
[12:36:08] [Render thread/INFO]: 准备生成区域中:0%
[12:36:08] [Render thread/INFO]: 准备生成区域中:0%
[12:36:09] [Render thread/INFO]: 准备生成区域中:0%
[12:36:11] [Render thread/INFO]: 准备生成区域中:0%
[12:36:11] [Render thread/INFO]: 准备生成区域中:0%
[12:36:11] [Render thread/INFO]: 准备生成区域中:0%
[12:36:11] [Render thread/INFO]: 准备生成区域中:0%
[12:36:13] [Render thread/INFO]: 准备生成区域中:1%
[12:36:13] [Render thread/INFO]: 准备生成区域中:1%
[12:36:13] [Render thread/INFO]: 准备生成区域中:1%
[12:36:14] [Render thread/INFO]: 准备生成区域中:41%
[12:36:14] [Render thread/INFO]: 准备生成区域中:41%
[12:36:14] [Render thread/INFO]: 准备生成区域中:41%
[12:36:17] [Render thread/INFO]: 准备生成区域中:51%
[12:36:17] [Render thread/INFO]: 准备生成区域中:51%
[12:36:17] [Render thread/INFO]: 准备生成区域中:51%
[12:36:17] [Render thread/INFO]: 准备生成区域中:51%
[12:36:17] [Render thread/INFO]: 准备生成区域中:51%
[12:36:17] [Render thread/INFO]: 准备生成区域中:51%
[12:36:17] [Render thread/INFO]: 准备生成区域中:51%
[12:36:17] [Server thread/INFO]: Changing view distance to 7, from 10
[12:36:17] [Render thread/INFO]: Time elapsed: 10800 ms
[12:36:20] [Server thread/INFO]: Xiao_Jin[local:E:071d00f1] logged in with entity id 8 at (-51.5, 4.0, -14.5)
[12:36:20] [Server thread/INFO]: Xiao_Jin加入了游戏
[12:36:20] [Server thread/INFO]: Saving and pausing game...
[12:36:20] [Server thread/INFO]: Saving chunks for level '新的世界'/minecraft:overworld
[12:36:21] [Server thread/INFO]: Saving chunks for level '新的世界'/minecraft:the_end
[12:36:21] [Server thread/INFO]: Saving chunks for level '新的世界'/minecraft:the_nether
[12:36:21] [Render thread/INFO]: Loaded 0 advancements
[12:36:45] [Server thread/INFO]: Saving and pausing game...
[12:36:45] [Server thread/INFO]: Saving chunks for level '新的世界'/minecraft:overworld
[12:36:45] [Server thread/INFO]: Saving chunks for level '新的世界'/minecraft:the_end
[12:36:45] [Server thread/INFO]: Saving chunks for level '新的世界'/minecraft:the_nether
[12:36:46] [Server thread/INFO]: Xiao_Jin lost connection: 连接中止
[12:36:46] [Server thread/INFO]: Xiao_Jin退出了游戏
[12:36:46] [Server thread/INFO]: Stopping singleplayer server as player logged out
[12:36:46] [Server thread/INFO]: Stopping server
[12:36:46] [Server thread/INFO]: Saving players
[12:36:46] [Server thread/INFO]: Saving worlds
[12:36:46] [Server thread/INFO]: Saving chunks for level '新的世界'/minecraft:overworld
[12:36:46] [Server thread/INFO]: ThreadedAnvilChunkStorage (新的世界 (3)): All chunks are saved
[12:36:46] [Server thread/INFO]: Saving chunks for level '新的世界'/minecraft:the_end
[12:36:46] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved
[12:36:46] [Server thread/INFO]: Saving chunks for level '新的世界'/minecraft:the_nether
[12:36:46] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved
[12:36:46] [Server thread/INFO]: ThreadedAnvilChunkStorage (新的世界 (3)): All chunks are saved
[12:36:46] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved
[12:36:46] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved
[12:36:48] [Render thread/INFO]: Stopping!

Mojang的Yggdrasil服务换实现方式了,我刚刚研究了一下

{
    "user": {
        "id": "6e08b120a8684fb689bba42bff4872ea"
    },
    "accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2ZTA4YjEyMGE4Njg0ZmI2ODliYmE0MmJmZjYwNDNlYSIsIm5iZiI6MTU3MTA2MTUyNSwieWdndCI6IjIwNzA2Mjk1ZDc4MjQ0ZjlhMmUwMmM4ZDgwMjYyMzM4Iiwic3ByIjoiMTNlMDVkZmY0ODIwNGEwMzk4MTI0MTkxZjI4NmM0MDQiLCJyb2xlcyI6W10sImlzcyI6ImludGVybmFsLWF1dGhlbnRpY2F0aW9uIiwiZXhwIjoxNTcxMjM0MzI1LCJpYXQiOjE1NzEwNjE1MjV9.vjmvRRO_yV_HkghafwwkNlFJTceqLETuN7x1IaXtzno",
    "clientToken": "",
    "availableProfiles": [
        {
            "name": "username",
            "id": "13e05dff42408a0399175191f286c404"
        }
    ],
    "selectedProfile": {
        "name": "username",
        "id": "13e05dff42408a0399175191f286c404"
    }
}

{
    "accessToken": "random access token",      // hexadecimal or 3 base64 strings separated by periods
    "clientToken": "client identifier",        // identical to the one received
    "availableProfiles": [                     // only present if the agent field was received
        {
            "agent": "minecraft",              // Presumably same value as before
            "id": "profile identifier",        // hexadecimal
            "name": "player name",
            "userId": "hex string",
            "createdAt": 1325376000000,        // Milliseconds since Jan 1 1970
            "legacyProfile": true or false,    // Present even when false
            "suspended": true or false,        // probably false
            "paid": true or false,             // probably true
            "migrated": true or false,         // Seems to be false even for migrated accounts...?  (https://bugs.mojang.com/browse/WEB-1461)
            "legacy": true or false            // Only appears in the response if true. Default to false.  Redundant to the newer legacyProfile...
        }
    ],
    "selectedProfile": {                       // only present if the agent field was received
        "id": "uuid without dashes",
        "name": "player name",
        "userId": "hex string",
        "createdAt": 1325376000000,
        "legacyProfile": true or false,
        "suspended": true or false,
        "paid": true or false,
        "migrated": true or false,
        "legacy": true or false
    },
    "user": {                                  // only present if requestUser was true in the request payload
        "id": "user identifier",               // hexadecimal
        "email": "[email protected]",         // Hashed(?) value for unmigrated accounts
        "username": "[email protected]",      // Regular name for unmigrated accounts, email for migrated ones
        "registerIp": "198.51.100.*",          // IP address with the last digit censored
        "migratedFrom": "minecraft.net",
        "migratedAt": 1420070400000,
        "registeredAt": 1325376000000,         // May be a few minutes earlier than createdAt for profile
        "passwordChangedAt": 1569888000000,
        "dateOfBirth": -2208988800000,
        "suspended": false,
        "blocked": false,
        "secured": true,
        "migrated": false,                     // Seems to be false even when migratedAt and migratedFrom are present...
        "emailVerified": true,
        "legacyUser": false,
        "verifiedByParent": false,
        "properties": [
            {
                "name": "preferredLanguage",   // might not be present for all accounts
                "value": "en"                  // Java locale format (https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toString--)
            },
            {
                "name": "twitch_access_token", // only present if a twitch account is associated (see https://account.mojang.com/me/settings)
                "value": "twitch oauth token"  // OAuth 2.0 Token; alphanumerical; e.g. https://api.twitch.tv/kraken?oauth_token=[...]
                                               // the Twitch API is documented here: https://github.com/justintv/Twitch-API
            }
        ]
    }
}

新版少了很多字段,并且AT生成变成了JWT 2019-10-14

JWT 算法为 HS256
payload 中保存的内容为

{
  "sub": "6e08b120a8684fb689bba42bff4872ea", --用户ID
  "nbf": 1571061525, --验证过期时间
  "yggt": "20706295d78244f9a2e01d9e80267339", --根据缩写可能是yggdrasil的令牌
  "spr": "13e05dff42408a0399175191f286c404",  --根据比对是角色UUID
  "roles": [], --各种规则?
  "iss": "internal-authentication", --签发者
  "exp": 1571234325, --签名过期时间
  "iat": 1571061525  --签发时间
}

未定义与实现皮肤的传统加载接口

接口用途 用户名 -> 皮肤材质
接口地址 http://skins.minecraft.net/MinecraftSkins/{USERNAME}.png
调用位置 net.minecraft.client.entity.AbstractClientPlayer.getDownloadImageSkin(ResourceLocation resourceLocationIn, String username)
调用情况 已知用于旁观模式菜单(SpectatorMenu)中的头像加载

CustomSkinLoader目前暂时采用曲线救国的方案将其转移到SkinManager调用已定义的接口加载材质
望您可以定义并实现这一接口

移除 org.json 依赖

org.json 的许可证中包含以下内容:

The Software shall be used for Good, not Evil.

这可能会给本项目带来法律问题。需要将 org.json 依赖移除,并寻找一个替代品。

另见: stleary/JSON-java#353

当获取配置失败时终止启动

目前,如果 authlib-injector 在获取配置的过程中出现错误(如遇到网络问题),则会放弃配置并继续运行。这样客户端/服务端依然能启动,但并未被 authlib-injector 修改。这有时会给调试工作带来麻烦。
因此,我打算在 authlib-injector 获取配置失败时,以非零返回值退出进程。这一点仅影响通过启动参数加载的 authlib-injector,不影响通过 Attach API 在运行时加载的 authlib-injector。

如果诸位有更好的解决办法或建议,欢迎提出。(或是表明赞成或者反对)

[proposal] 启动器指定端类型

概述

目前,authlib-injector 难以检测其运行环境是 MC 客户端还是 MC 服务端。这个提案要求启动器向启动命令中加入额外的参数,来告知 authlib-injector 其运行环境。

这个参数称为端类型参数。它是一个 JVM 参数,格式为 -Dauthlibinjector.side={端类型},其中端类型为 client 或 server。

对启动器的变化

  1. 启动时须指定端类型为 client。
    -Dauthlibinjector.side=client
  2. 配置预获取由可选变为强制。
    原因:若不使用配置预获取,authlib-injector 会在启动时从 Yggdrasil API 获取配置。如果网络不稳定,则操作可能失败,进而导致 MC 无法启动。将获取配置的工作交由启动器完成,则可以在启动游戏前就发现网络问题,并提示用户。

对服务端配置的变化

  1. 如果要使用配置预获取,则还需指定端类型为 server。
    -Dauthlibinjector.side=server
  2. 如果不使用配置预获取(仅添加 javaagent 参数),则不需要任何改动。

其他

  1. 配置预获取参数 -Dorg.to2mbn.authlibinjector.config.prefetched 已改名为 -Dauthlibinjector.yggdrasil.prefetched。原参数仍然可用,且在将来也不会移除。
    此项修改已在提交 e4d2311 中,并且在 1.1.19-36fc21d 中可用。
  2. 当未指定端类型参数时,authlib-injector 认为使用了配置预获取的是客户端,未使用的是服务端。
    原因:一般而言,只有启动器会使用配置预获取功能。

欢迎诸位就本提案提出意见。

[bug] 向 Yggdrasil 服务器 POST 的信息错误

问题描述

向 Yggdrasil 服务器 POST 的信息错误
显示:认证服务器正在维护

附上日志

minecraft-exported-logs-2020-04-23T15-49-17.log

[authlib-injector.launch] [INFO] Version: 1.1.27-5ef5f8e
[authlib-injector.launch] [FINE] Detected side: CLIENT
[authlib-injector.config] [INFO] API root: https://mcskin.littleservice.cn/api/yggdrasil/
[authlib-injector.config] [INFO] Prefetched metadata detected
[authlib-injector.config] [FINE] Metadata: {"meta":{"serverName":"LittleSkin","implementationName":"Yggdrasil API for Blessing Skin","implementationVersion":"4.4.0","links":{"homepage":"https:\/\/mcskin.littleservice.cn","register":"https:\/\/mcskin.littleservice.cn\/auth\/register"}},"skinDomains":["skin.prinzeugen.net","mcskin.littleservice.cn"],"signaturePublickey":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArGcNOOFIqLJSqoE3u0hj\ntOEnOcET3wj9Drss1BE6sBqgPo0bMulOULhqjkc\/uH\/wyosYnzw3xaazJt87jTHh\nJ8BPMxCeQMoyEdRoS3Jnj1G0Kezj4A2b61PJJM1DpvDAcqQBYsrSdpBJ+52MjoGS\nvJoeQO5XUlJVQm21\/HmJnqsPhzcA6HgY71RHYE5xnhpWJiPxLKUPtmt6CNYUQQoS\no2v36XWgMmLBZhAbNOPxYX+1ioxKamjhLO29UhwtgY9U6PWEO7\/SBfXzyRPTzhPV\n2nHq7KJqd8IIrltslv6i\/4FEM81ivS\/mm+PN3hYlIYK6z6Ymii1nrQAplsJ67OGq\nYHtWKOvpfTzOollugsRihkAG4OB6hM0Pr45jjC3TIc7eO7kOgIcGUGUQGuuugDEz\nJ1N9FFWnN\/H6P9ukFeg5SmGC5+wmUPZZCtNBLr8o8sI5H7QhK7NgwCaGFoYuiAGL\ngz3k\/3YwJ40BbwQayQ2gIqenz+XOFIAlajv+\/nyfcDvZH9vGNKP9lVcHXUT5YRnS\nZSHo5lwvVrYUrqEAbh\/zDz8QMEyiujWvUkPhZs9fh6fimUGxtm8mFIPCtPJVXjeY\nwD3Lvt3aIB1JHdUTJR3eEc4eIaTKMwMPyJRzVn5zKsitaZz3nn\/cOA\/wZC9oqyEU\nmc9h6ZMRTRUEE4TtaJyg9lMCAwEAAQ==\n-----END PUBLIC KEY-----\n"}
[authlib-injector.config] [FINE] Parsed metadata: YggdrasilConfiguration [apiRoot=https://mcskin.littleservice.cn/api/yggdrasil/, skinDomains=[skin.prinzeugen.net, mcskin.littleservice.cn], decodedPublickey=Optional[Sun RSA public key, 4096 bits
  modulus: 703341022079292672117834809642813454947205717819976869691035011464340374097341561815075061295962821503821889017258163013620890297485949824623916013282058406852194654293439731660792131036308629114119124459143637499075489459133834794403760902722873864730869395374447208180372410566177382060435857514800531148552669895567333226514747355395918437087351552262836651754891577513159115382925848834960993678968391764437354905499178002435808649343278626139444918705443484507545277921290646887561763748226632249892574393500817627650895849935805669017367301687662877178312666081658003279041130457578181680356055336737679573633480666755827426816980151192968247349094517909780497449870136684389239039674344986483095024781094033783584282626219714339631257243849078404899462229285284234043421774732664960471577013988299575513108247972103507432168151372910822057616619553445780075738045598935382401765772383054689377659488114992477103520067017636712507571569740532485325978133145594333469046331412253404487791063896345147405356027020225125718717261513963338552874121624247178105595670436007039896254375208854326124970383415346446423049556309036542192435009880564911270490508210189063923984309567408249048378259856386300946061535781204509742640199251
  public exponent: 65537], meta={implementationName=Yggdrasil API for Blessing Skin, implementationVersion=4.4.0, links={"homepage":"https:\/\/mcskin.littleservice.cn","register":"https:\/\/mcskin.littleservice.cn\/auth\/register"}, serverName=LittleSkin}]
[15:47:09] [main/INFO]: Loading for game Minecraft 1.15.2
[15:47:13] [main/INFO]: [FabricLoader] Loading 34 mods: [email protected], [email protected]+aea78a250c, [email protected]+e08a73050c, [email protected]+dfdb52d60c, [email protected]+build.194, [email protected]+203491ea0c, [email protected]+b7f9825d0c, [email protected]+203491ea0c, [email protected]+3b05f68e0c, [email protected]+b7f9825d0c, [email protected]+a1bd31180c, [email protected]+b7f9825d0c, [email protected]+534104900c, [email protected]+534104900c, [email protected]+b7f9825d0c, [email protected]+06c939b30c, [email protected]+821cdba70c, [email protected]+b7f9825d0c, [email protected]+abd915800c, [email protected]+12515ed90c, [email protected]+f3d8141b0c, [email protected]+b7f9825d0c, [email protected]+c6a8ea890c, [email protected]+b7f9825d0c, [email protected]+5a0f9a600c, [email protected]+e08a73050c, [email protected]+b7f9825d0c, [email protected], [email protected]+build.290-1.15, [email protected]+dfdb52d60c, [email protected]+dfdb52d60c, [email protected]+ec40b2e10c, [email protected]+12515ed90c, [email protected]+e4c9a9c30c
[15:47:13] [main/WARN]: Mod `wurst` (v7.1.2-MC1.15.2) does not respect SemVer - comparison support is limited.
[15:47:13] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8 Source=file:/C:/Users/jin/AppData/Roaming/.minecraft/libraries/net/fabricmc/sponge-mixin/0.8+build.18/sponge-mixin-0.8+build.18.jar Service=Knot/Fabric Env=CLIENT
[authlib-injector.transform] [INFO] Transformed [net.minecraft.client.main.Main] with [Main Arguments Transformer]
[authlib-injector.transform] [FINE] Main arguments: --username <player> --version HMCL 3.3.168 --gameDir C:\Users\jin\AppData\Roaming\.minecraft --assetsDir C:\Users\jin\AppData\Roaming\.minecraft\assets --assetIndex 1.15 --uuid <uuid> --accessToken <access token> --userType mojang --versionType HMCL 3.3.168/Fabric --width 854 --height 480
[authlib-injector.transform] [FINE] Version series detected: 1.15
[authlib-injector.httpd] [INFO] Httpd is running on port 55887
[authlib-injector.transform] [FINE] Transformed url [http://skins.minecraft.net/MinecraftSkins/%s.png] to [http://127.0.0.1:55887/http/skins.minecraft.net/MinecraftSkins/%s.png]
[authlib-injector.transform] [INFO] Transformed [net.minecraft.class_742] with [Constant URL Transformer]
[authlib-injector.transform] [INFO] Transformed [com.mojang.authlib.HttpAuthenticationService] with [Authlib Log Interceptor]
[authlib-injector.transform] [INFO] Registered log handler on sun.misc.Launcher$AppClassLoader@18b4aac2
[authlib-injector.transform] [FINE] Transformed url [https://sessionserver.mojang.com/session/minecraft/profile/] to [http://127.0.0.1:55887/https/sessionserver.mojang.com/session/minecraft/profile/]
[authlib-injector.transform] [FINE] Transformed url [https://sessionserver.mojang.com/session/minecraft/join] to [https://mcskin.littleservice.cn/api/yggdrasil/sessionserver/session/minecraft/join]
[authlib-injector.transform] [FINE] Transformed url [https://sessionserver.mojang.com/session/minecraft/hasJoined] to [https://mcskin.littleservice.cn/api/yggdrasil/sessionserver/session/minecraft/hasJoined]
[authlib-injector.transform] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService] with [Constant URL Transformer]
[authlib-injector.transform] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService] with [Texture Whitelist Transformer]
[authlib-injector.transform] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService] with [Yggdrasil Public Key Transformer]
[15:47:16] [main/INFO]: Setting user: <player>
[15:47:27] [main/INFO]: [STDOUT]: Starting Wurst Client...
[15:47:30] [main/INFO]: [Indigo] Registering Indigo renderer!
[15:47:30] [main/INFO]: Backend library: LWJGL version 3.2.2 build 10
[15:47:35] [main/INFO]: Narrator library for x64 successfully loaded
[15:47:36] [main/INFO]: Reloading ResourceManager: Default, Fabric Renderer API (v1), Fabric Networking Block Entity (v0), Fabric Key Bindings (v0), Fabric Renderer - Indigo, Fabric Containers (v0), Fabric Biomes (v1), Fabric Crash Report Info (v1), Fabric Events Interaction (v0), Fabric API Base, Fabric Rendering (v0), Fabric Rendering (v1), Fabric Rendering Data Attachment (v1), Fabric Resource Loader (v0), Fabric Textures (v0), Fabric Content Registries (v0), Fabric Tag Extensions (v0), Fabric Rendering Fluids (v1), Fabric Registry Sync (v0), Fabric Commands (v0), Fabric BlockRenderLayer Registration (v1), Fabric Mining Levels (v0), Fabric Renderer Registries (v1), Fabric Loot Tables (v1), Fabric Events Lifecycle (v0), Wurst Client, Fabric API, Fabric Models (v0), Fabric Item Groups (v0), Fabric Networking (v0), Fabric Object Builders (v0), mcwzh-meme.zip
[15:47:41] [Realms Notification Availability checker #1/INFO]: Could not authorize you against Realms server: Invalid session id
[15:48:00] [main/INFO]: OpenAL initialized.
[15:48:00] [main/INFO]: Sound engine started
[15:48:01] [main/INFO]: Created: 1024x1024x2 minecraft:textures/atlas/blocks.png-atlas
[15:48:01] [main/INFO]: Created: 256x128x2 minecraft:textures/atlas/signs.png-atlas
[15:48:01] [main/INFO]: Created: 1024x512x2 minecraft:textures/atlas/banner_patterns.png-atlas
[15:48:01] [main/INFO]: Created: 1024x512x2 minecraft:textures/atlas/shield_patterns.png-atlas
[15:48:01] [main/INFO]: Created: 256x256x2 minecraft:textures/atlas/chest.png-atlas
[15:48:01] [main/INFO]: Created: 512x256x2 minecraft:textures/atlas/beds.png-atlas
[15:48:01] [main/INFO]: Created: 512x256x2 minecraft:textures/atlas/shulker_boxes.png-atlas
[15:48:02] [main/INFO]: Created: 256x256x0 minecraft:textures/atlas/particles.png-atlas
[15:48:02] [main/INFO]: Created: 256x256x0 minecraft:textures/atlas/paintings.png-atlas
[15:48:02] [main/INFO]: Created: 256x128x0 minecraft:textures/atlas/mob_effects.png-atlas
[15:48:19] [main/INFO]: Connecting to 118.25.5.20., 25565
Opening connection to https://mcskin.littleservice.cn/api/yggdrasil/sessionserver/session/minecraft/join
Writing POST data to https://mcskin.littleservice.cn/api/yggdrasil/sessionserver/session/minecraft/join: {"accessToken":"<access token>","selectedProfile":"<uuid>","serverId":"c3c6af29d13251d6acf2a3f2856bcceddd9283"}
Reading data from https://mcskin.littleservice.cn/api/yggdrasil/sessionserver/session/minecraft/join
Reading error page from https://mcskin.littleservice.cn/api/yggdrasil/sessionserver/session/minecraft/join
Successful read, server response was 500
Response: <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="robots" content="noindex,nofollow">
    <title>500 Internal Server Error - LittleSkin</title>
          <link rel="stylesheet" href="https://resource.littleservice.cn/app/spectre.07b4b91.css">
      </head>

  <body class="bg-gray">
    <div class="text-right m-2 p-2">
      <a href="https://mcskin.littleservice.cn/api/yggdrasil/sessionserver/session/minecraft/join?lang=zh_CN"
      class="mx-2 p-1 label label-secondary"
    >中文 (简体)</a>
      <a href="https://mcskin.littleservice.cn/api/yggdrasil/sessionserver/session/minecraft/join?lang=en"
      class="mx-2 p-1 label label-primary"
    >English</a>
      <a href="https://mcskin.littleservice.cn/api/yggdrasil/sessionserver/session/minecraft/join?lang=es_ES"
      class="mx-2 p-1 label label-secondary"
    >Espa?ol</a>
  </div>

    <div class="hero d-flex">
      <div class="hero-body text-center">
        <h1><a class="text-primary" href="https://mcskin.littleservice.cn">LittleSkin</a></h1>
        <div class="divider"></div>
        <h3 style="margin-top: 15vh;">500 Internal Server Error</h3>
          <p>
    Details: Unexpected characters near &quot;t belong to any players.&#039;&quot; at line 22 (near &quot;uuid: &#039;Invalid Profile UUID [:profile]. It doesn&#039;t belong to any players.&#039;&quot;).
  </p>
        <div class="divider"></div>
              </div>
    </div>
      </body>
</html>

[15:49:15] [main/INFO]: Stopping!

Workaround for MC-52974 ? (局域网联机时其他玩家无法看见房主的皮肤)

首先感谢 bd_lvrou 的反馈让我注意到了这个 bug。

MC-52974 是 MC 历史最悠久的 bug 之一,影响从 1.7.9 到 1.12.2 的所有 MC 版本,直到 1.13 才被修复。其表现是:局域网联机时,其他玩家无法看到房主(Host)的皮肤。该 bug 在 Mojang 的 Jira 上有详细描述,在此我就不多说明了。

由于很多 MC 用户还在使用旧版本,所以我希望 authlib-injector 能够提供一个 workaround 以解决此问题。但鄙人不才,始终没有找到令人满意的解决方法,不知诸君有何高见?

未经检查的用户上传材质可能导致远程代码执行

参考:

如果攻击者向 Yggdrasil 服务端上传了一个精心设计的材质,并且 Yggdrasil 服务端对此材质并未进行任何处理,那么客户端将其获取到本地后,可能造成远程代码执行(前提是客户端处理此图像的类库存在漏洞)。

需要注意的是,此漏洞单独而言无法对客户端造成威胁,必须配合客户端的其他漏洞才能被利用。
而由于图像处理类库出现漏洞的情况较为罕见,该漏洞被利用的可能性较低。

解决方法:Yggdrasil 服务端必须对用户上传的材质进行处理,绝不能直接保存用户上传的材质。具体方法为将上传的图像解析后再重新写入,丢弃掉任何与图像本身无关的成分。在解析前需要对图像大小等进行检查,以防拒绝服务攻击。

注意:即使 PNG 文件的文件大小很小,但其存储的图像也可能奇大无比(即 PNG Bomb)。

参考:https://www.bamsoftware.com/hacks/deflate.html

此解决方法需要写入文档。

[proposal] 允许正版登录用户进入服务器

外置登录导致正版用户不能自由的进入所有服务器,必须退出切换外置(非正版)登录。
建议应该保证正版用户的权益

实现方式

GET /sessionserver/session/minecraft/hasJoined?username={username}&serverId={serverId}&ip={ip}实现部分,向上转发请求(mojang服务器),验证玩家是否正版登录

保存信息

在玩家正版验证成功后,为玩家建立一个外置登录账号(密码:待定,uuid为正版UUID),防止其他玩家盗注或者登录,也为正版用户往后使用外置登录提供基础

遭遇已有外置登录账号

正版登录获取的uuid不可能(99.9%)与以往非正版uuid一样,对于使用uuid的插件,可以视为两个用户.
你也可以按照服务器不同情况,设置其他方案
注意: 前面的保存信息会产生一个uuid相同的账号,此账号可给与标注

预防攻击

向上转发时,可以设定单位时间连接数量,超出时,可以放弃正版验证

已知问题

正版客户端会不信任盗版用户的皮肤站

[proposal] CORS 支持

概述

此提案旨在要求 Yggdrasil 服务端对部分 API 启用 CORS,以允许其被其他站点上的网页应用调用。

实现

在 CORS Preflight 请求的响应中,Yggdrasil 服务端应包含以下头字段:

  • Access-Control-Allow-Origin: *
    • 允许来自任意站点的请求。
  • Access-Control-Expose-Headers: X-Authlib-Injector-API-Location
    • 允许浏览器将 ALI 头(见 #18)暴露给网页应用。
  • Access-Control-Allow-Methods
    • 此头字段的值为允许的请求方法,会在下面具体说明。

Yggdrasil 服务端应对以下 API 开启 CORS(其他的也可以开启,但不作强制要求):

  • 查询角色属性 /sessionserver/session/minecraft/profile/{uuid}
    • 允许的请求方法:GET
  • 按名称批量查询角色 /api/profiles/minecraft
    • 允许的请求方法:POST
  • 服务端信息获取 /
    • 允许的请求方法:GET
  • 皮肤站首页,或者其他你想要公开 ALI 的页面
    • 允许的请求方法:GET
    • 这将允许网页应用读取皮肤站首页的内容,进而通过 ALI 得到 Yggdrasil API 的地址。

安全考虑

  • CORS Preflight 请求的响应中,不允许包含 Access-Control-Allow-Credentials: true 头字段。
    • 当你在非 Yggdrasil API 接口(如上文中的皮肤站首页)上开启 CORS 时,尤其要注意这一点。否则恶意页面可以窃取 CSRF Token,进而发起 CSRF 攻击。
  • 当皮肤站处于内网时,应谨慎开启 CORS,否则可能导致数据泄露。
    • 恶意网页可以通过 CORS 访问到处于内网的皮肤站(而该皮肤站在外网无法被访问),进而导致数据泄露。

欢迎诸位就本提案提出意见。

支持通过代理服务器访问Mojang

新年快乐!authlib-injector作者
我在开服时遇到这个问题:

  • 服务器无法连接到mojang服务器

这导致无法使用name@mojang的皮肤。

请问是否支持自定义代理服务器。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.