本帖最后由 xmdhs 于 2021-7-31 16:53 编辑
gml-c-shared
简单说就是一个启动器库,可以以此简单的启动游戏和补全游戏文件。
是对于 gomclauncher 的封装,导出为 c 语言的动态链接库。大概大多数语言都能很好的调用。
开源地址
mit 协议开源 https://github.com/xmdhs/gml-c-shared
支持功能
注意事项
返回的 char* 和 char** 类型是分配在堆上的,需要释放内存,可以使用
复制代码
释放 char*
复制代码
释放 char**
当然不释放内存也完全没问题的,启动器本身不会长久运行,而且这点内存泄漏了也没多少。
char* 使用 utf-8 编码,无论在 liunx 还是 windows 都一样,所以直接用 c 语言调用的话,可能需要转码。
下载源的镜像使用了 bmclapi,需按照协议注明
https://bmclapidoc.bangbang93.com/
文档
偷懒,直接在头文件上写注释,应该足够了吧
复制代码
示例
使用 java,用 jna 库调用,简单的下载游戏和启动。
复制代码
下载
其实推荐自行编译。需要安装 golang https://golang.google.cn/ 和 gcc 或者 clang (windows 下不能用 clang)。
然后在项目内执行 go build -trimpath -ldflags "-w -s" -buildmode=c-shared -o libgml.dll
就可以看到 libgml.dll 和 libgml.h,后缀不重要,可以自己改的。
已经编译好的 https://github.com/xmdhs/gml-c-shared/actions
linux.zip
(2.44 MB, 下载次数: 4)
windows.zip
(2.25 MB, 下载次数: 15)
macos 就自己编译吧。
gml-c-shared
简单说就是一个启动器库,可以以此简单的启动游戏和补全游戏文件。
是对于 gomclauncher 的封装,导出为 c 语言的动态链接库。大概大多数语言都能很好的调用。
开源地址
mit 协议开源 https://github.com/xmdhs/gml-c-shared
支持功能
功能 | 状况 |
正版登录 | √ |
authlib-injector 外置登录 | √ |
微软登录 | √ |
原版游戏下载 | √ |
多线程下载 | 部分[1] |
forge 等自动安装 | × [2] |
- 单个文件的下载不是并发的
- 这个其实可以很简单的用命令行安装,配合 https://github.com/bangbang93/forge-install-bootstrapper
注意事项
返回的 char* 和 char** 类型是分配在堆上的,需要释放内存,可以使用
- char *a;
- Freechar(a,0)
释放 char*
- char **a
- Freechar(a,长度)
释放 char**
当然不释放内存也完全没问题的,启动器本身不会长久运行,而且这点内存泄漏了也没多少。
char* 使用 utf-8 编码,无论在 liunx 还是 windows 都一样,所以直接用 c 语言调用的话,可能需要转码。
下载源的镜像使用了 bmclapi,需按照协议注明
BMCLAPI下的所有文件,除BMCLAPI本身的源码之外,归源站点所有
BMCLAPI会尽量保证文件的完整性、有效性和实时性,对于使用BMCLAPI带来的一切纠纷,与BMCLAPI无关。
BMCLAPI和BMCL不同,属于非开源项目
所有使用BMCLAPI的程序必需在下载界面或其他可视部分标明来源
禁止在BMCLAPI上二次封装其他协议
https://bmclapidoc.bangbang93.com/
文档
偷懒,直接在头文件上写注释,应该足够了吧
- //仅作注释用,不要导入此文件
- //char* 均使用 utf-8 编码
- typedef struct
- {
- //游戏路径,需要创建一个 .minecraft 文件夹,例如 D:/mc/.minecraft
- char *Minecraftpath;
- //分配内存大小,单位为 mb
- long long RAM;
- //玩家名
- char *Name;
- //uuid
- char *UUID;
- //正版/外置登录相关,离线登录可随便设置
- char *AccessToken;
- //要启动的游戏版本,可用 ListVersion 方法找到被识别的版本
- char *Version;
- //外置登录的 api 地址。
- char *ApiAddress;
- //自定义的 java 参数,字符串数组,可以通过 NewChar 和 SetChar 来构建
- char **Flag;
- //字符串数组的长度
- int flag_len;
- //是否开启外置登录
- int independent;
- } Gameinfo;
- typedef struct
- {
- //code 对应的信息见下方注释
- int code;
- //详细的错误信息
- char *msg;
- } err;
- /*code | msg
- -----|----------------------------------------------
- -1 | 未知错误
- 0 | 正常
- 1 | 文件不存在
- 2 | json 错误
- 3 | minecraft json 格式错误
- 4 | 找不到该版本
- 5 | 下载失败次数过多
- 6 | authlib 登录时,有多个账户,且并没有选择(也就是没有设置 username 参数)
- 7 | 没有这个角色
- 8 | 通常是密码错误,或者登录过期
- 9 | authlib 找不到可用档案,也就是没有创建角色之类的
- 10 | accessToken 失效
- 11 | 登录微软账户时打开的浏览器中,打开了其他页面
- 12 | 尝试重新登录微软账户
- 13 | 没有购买游戏或者没有迁移账号
- 14 | 没有安装 chrome 或者新版本的 edge(windows only)
- */
- typedef struct
- {
- //字符串数组
- char **charlist;
- //字符串数组的长度
- int len;
- //错误
- err e;
- } GmlReturn;
- //当下载文件时,发生错误时将调用此函数。
- typedef void (*Fail)(char*);
- //下载文件成功后,会调用此函数
- //第一个参数表示下载成功的类型
- /*第一个参数见下方表格解释。第二个参数传递剩余没下载的文件数量,成功下载一次,调用一次。
- code | msg
- -----|-------
- 1 | 下载游戏核心
- 2 | 下载资源文件
- 3 | 下载游戏库
- */
- typedef void (*Ok)(int,int);
- //下载或检查游戏,成功或失败时,将调用此函数
- typedef void (*gmlfinish)(err);
- typedef struct
- {
- char *Username;
- char *ClientToken;
- char *UUID;
- char *AccessToken;
- char *ApiAddress;
- //可用的用户名,当 Auth 返回 6 错误时,可以选择此列表中的用户名,设置在 Auth 的 username 参数上,来选择档案。
- //只有外置登录有单账号,多档案。
- char **availableProfiles;
- //长度
- int availableProfilesLen;
- } AuthDate;
- typedef struct
- {
- char *Username;
- char *UUID;
- char *AccessToken;
- } MsAuthDate;
- #ifdef __cplusplus
- extern "C" {
- #endif
- //获取字符串数组中的字符串
- extern char* Getchar(char** charlist, long long int index);
- //释放字符串数组的内存,下面的函数返回的字符串数组都是分配在堆上的,需要用此函数释放
- extern void Freechar(char** charlist, long long int len);
- //创建一个字符串数组
- extern char** NewChar(long long int len);
- //设置字符串数组指定的位置的字符串
- extern void SetChar(char** cc, long long int index, char* achar);
- //其实就是导出了 c 语言的 malloc 函数,为了避免变量名冲突,所以首字母大写了
- extern void* Malloc(int i);
- //生成启动游戏需要的参数
- extern GmlReturn GenCmdArgs(Gameinfo g);
- //设置下载文件和正版登录的代理,非线程安全,全局共用
- extern err SetProxy(char* httpProxy);
- //下载游戏
- //version 下载版本,可通过 ListDownloadVersion 查找可下载的版本
- //Type 下载使用的下载源,留空将按照权重的随机使用三个下载源,也可以自行设置。例如 vanilla|bmclapi 表示随机使用原版下载源和 bmclapi 下载源。mcbbs 表示只使用 mcbbs 下载源
- //downInt 下载协程数,通常 64 即可,因为一个文件还是使用一个协程下载,多了没意义
- //Minecraftpath 下载的路径,例如 D:/mc/.minecraft
- //调用后将立刻返回一个 int64,可以使用 Cancel 函数,将此 int64 传入,取消下载操作
- extern long long int Download(char* version, char* Type, char* Minecraftpath, int downInt, Fail fail, Ok ok, gmlfinish finish);
- //检查游戏的完整性,第一次某个版本时,必须检查一次。
- extern long long int Check(char* version, char* Type, char* Minecraftpath, int downInt, Fail fail, Ok ok, gmlfinish finish);
- //取消下载或者检查游戏完整性
- extern void Cancel(long long int id);
- //列出可启动版本
- //path 例如 D:/mc/.minecraft/version
- extern GmlReturn ListVersion(char* path);
- //查看可下载游戏版本类型,比如正式版之类的。 type 参数和 Download 里的意义一样
- extern GmlReturn ListDownloadType(char* Type);
- //查看此类型中所有的可下载的版本
- extern GmlReturn ListDownloadVersion(char* VerType, char* Type);
- /* Return type for Auth */
- struct Auth_return {
- AuthDate r0;
- err r1;
- };
- //外置登录和正版登录
- //clientToken 客户端 id,可随机生成,需保证每个用户对应的 ClientToken 是不变的,否则会要求重新登录。建议直接 md5 用户名就行。
- //ApiAddress 可不输入完整的 api 地址,会按照协议补全,如果正版登录,则无需设置此项。
- //username 外置登录时需要,具体见 AuthDate 处的注释
- //不要保存密码,保存 AccessToken
- extern struct Auth_return Auth(char* ApiAddress, char* username, char* email, char* password, char* clientToken);
- //验证 AccessToken 的有效性,建议每次启动游戏前,都验证一次
- extern err Validate(char* AccessToken, char* ClientToken, char* ApiAddress);
- /* Return type for Refresh */
- struct Refresh_return {
- AuthDate r0;
- err r1;
- };
- //若 Validate 验证 AccessToken 失效,则可通过此方法刷新,如果刷新无效,则重新登录就是。
- extern struct Refresh_return Refresh(char* AccessToken, char* ClientToken, char* ApiAddress);
- /* Return type for MsAuth */
- struct MsAuth_return {
- MsAuthDate r0;
- err r1;
- };
- //微软登录,将弹出一个窗口让玩家在其中进行登录,需要安装 chrome,或者新版 edge(windows only)
- extern struct MsAuth_return MsAuth();
- /* Return type for MsAuthValidate */
- struct MsAuthValidate_return {
- MsAuthDate r0;
- err r1;
- };
- //验证微软登录的 AccessToken 有效性,建议首次登录后也使用此方法验证,因为即使没有购买游戏,MsAuth 也会返回有效的内容。
- extern struct MsAuthValidate_return MsAuthValidate(char* AccessToken);
- #ifdef __cplusplus
- }
- #endif
示例
使用 java,用 jna 库调用,简单的下载游戏和启动。
- import com.sun.jna.*;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.nio.charset.StandardCharsets;
- import java.util.*;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- public class Main {
- public interface fail extends Callback {
- void Fail(Pointer c);
- }
- public static class c implements fail {
- @Override
- public void Fail(Pointer c) {
- String s = c.getString(0, "UTF-8");
- System.out.println("java: " + s);
- }
- }
- public interface ok extends Callback {
- void Ok(int i1, int i2);
- }
- private static Map<Integer, String> m = new HashMap<>();
- static {
- m.put(1, "下载游戏核心");
- m.put(2, "下载资源文件");
- m.put(3, "下载游戏库");
- }
- public static class f implements ok {
- @Override
- public void Ok(int i1, int i2) {
- String s = m.get(i1);
- System.out.println(s + "剩余:" + i2);
- }
- }
- public static class GoErr extends Structure implements Structure.ByValue {
- public int r0;
- public Pointer r1;
- protected List<String> getFieldOrder() {
- return Arrays.asList("r0", "r1");
- }
- }
- public interface finish extends Callback {
- void gmlfinish(GoErr c);
- }
- public static class f3 implements finish {
- @Override
- public void gmlfinish(GoErr c) {
- if (c.r0 == 0) {
- lock.lock();
- condition.signalAll();
- lock.unlock();
- return;
- }
- String s = c.r1.getString(0, "UTF-8");
- System.out.println("code: " + c.r0 + " msg: " + s);
- lock.lock();
- condition.signalAll();
- lock.unlock();
- }
- }
- public static class GmlReturn extends Structure implements Structure.ByValue {
- public Pointer r0;
- public int r1;
- public GoErr r2;
- protected List<String> getFieldOrder() {
- return Arrays.asList("r0", "r1", "r2");
- }
- }
- public static class AuthDate extends Structure implements Structure.ByValue {
- public Pointer Username;
- public Pointer ClientToken;
- public Pointer UUID;
- public Pointer AccessToken;
- public Pointer ApiAddress;
- public Pointer availableProfiles;
- public int availableProfilesLen;
- protected List<String> getFieldOrder() {
- return Arrays.asList("Username", "ClientToken", "UUID", "AccessToken", "ApiAddress", "availableProfiles", "availableProfilesLen");
- }
- }
- public static class Auth_return extends Structure implements Structure.ByValue {
- public AuthDate r0;
- public GoErr r1;
- protected List<String> getFieldOrder() {
- return Arrays.asList("r0", "r1");
- }
- }
- public interface Lib extends Library {
- Lib INSTANCE = Native.load("C:/Users/xmdhs/Desktop/untitled/libgml.dll", Lib.class);
- long Download(Pointer version, Pointer type, Pointer path, int downint, Callback fail, Callback ok, Callback finish);
- long Check(Pointer version, Pointer type, Pointer path, int downint, Callback fail, Callback ok, Callback finish);
- GmlReturn GenCmdArgs(Gameinfo g);
- Pointer NewChar(long len);
- void SetChar(Pointer cc, long index, Pointer achar);
- void Freechar(Pointer cc, long len);
- Pointer Malloc(int i);
- Auth_return Auth(Pointer ApiAddress, Pointer username, Pointer email, Pointer password, Pointer clientToken);
- }
- public static class Gameinfo extends Structure implements Structure.ByValue {
- public Pointer Minecraftpath;
- //分配内存大小,单位为 mb
- public long RAM;
- //玩家名
- public Pointer Name;
- //uuid
- public Pointer UUID;
- //正版/外置登录相关,离线登录可随便设置
- public Pointer AccessToken;
- //要启动的游戏版本,可用 ListVersion 方法找到被识别的版本
- public Pointer Version;
- //外置登录的 api 地址。
- public Pointer ApiAddress;
- //自定义的 java 参数,字符串数组,可以通过 NewChar 和 SetChar 来构建
- public Pointer Flag;
- //字符串数组的长度
- public int flag_len;
- //是否开启外置登录
- public int independent;
- protected List<String> getFieldOrder() {
- return Arrays.asList("Minecraftpath", "RAM", "Name", "UUID", "AccessToken", "Version", "ApiAddress", "Flag", "flag_len", "independent");
- }
- }
- private static Pointer String2char(String s) {
- Pointer m = Lib.INSTANCE.Malloc(s.getBytes(StandardCharsets.UTF_8).length + 1);
- m.setString(0, s, StandardCharsets.UTF_8.name());
- return m;
- }
- private static final Lock lock = new ReentrantLock();
- private static final Condition condition = lock.newCondition();
- static c fail = new c();
- static f ok = new f();
- static finish finish = new f3();
- public static void main(String[] args) throws InterruptedException, IOException {
- Auth_return ar = Lib.INSTANCE.Auth(String2char(""), String2char(""),
- String2char("[email protected]"), String2char("Aaaa"),
- String2char("123"));
- if (ar.r1.r0 != 0){
- System.out.println(ar.r1.r1.getString(0,"UTF-8"));
- return;
- }
- Pointer a1 = String2char("1.17");
- Lib.INSTANCE.Download(a1, String2char(""), String2char("C:/Users/xmdhs/Desktop/新建文件夹/.minecraft"),
- 64, fail, ok, finish);
- //其实通过 String2char 创建的字符串,都需要用 Freechar 释放,但是这里偷懒了,反正进程结束也会释放。
- Lib.INSTANCE.Freechar(a1, 0);
- lock.lock();
- condition.await();
- lock.unlock();
- Gameinfo g = new Gameinfo();
- g.AccessToken = ar.r0.AccessToken;
- g.ApiAddress = String2char("");
- Pointer list = Lib.INSTANCE.NewChar(1);
- Lib.INSTANCE.SetChar(list, 0, String2char("-XX:+UseG1GC"));
- g.Flag = list;
- g.flag_len = 1;
- g.independent = 1;
- g.Minecraftpath = String2char("C:/Users/xmdhs/Desktop/新建文件夹/.minecraft");
- g.Name = ar.r0.Username;
- g.UUID = ar.r0.UUID;
- g.RAM = 2000;
- g.Version = String2char("1.17");
- GmlReturn r = Lib.INSTANCE.GenCmdArgs(g);
- if (r.r2.r0 != 0) {
- System.out.println(r.r2.r0 + ": " + r.r2.r1.getString(0, StandardCharsets.UTF_8.name()));
- return;
- }
- String[] l = r.r0.getStringArray(0, r.r1, StandardCharsets.UTF_8.name());
- System.out.println(Arrays.toString(l));
- String[] ll = new String[l.length + 1];
- ll[0] = "java";
- int i = 1;
- for (String v : l) {
- ll[i] = v;
- i++;
- }
- Lib.INSTANCE.Freechar(r.r0, r.r1);
- Lib.INSTANCE.Freechar(list, 1);
- ProcessBuilder p = new ProcessBuilder(ll);
- Process pp = p.start();
- BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(pp.getInputStream()));
- BufferedReader stderrReader = new BufferedReader(new InputStreamReader(pp.getErrorStream()));
- String line;
- while ((line = stdoutReader.readLine()) != null) {
- System.out.println(line);
- }
- while ((line = stderrReader.readLine()) != null) {
- System.out.println(line);
- }
- System.out.println(pp.waitFor());
- }
- }
下载
其实推荐自行编译。需要安装 golang https://golang.google.cn/ 和 gcc 或者 clang (windows 下不能用 clang)。
然后在项目内执行 go build -trimpath -ldflags "-w -s" -buildmode=c-shared -o libgml.dll
就可以看到 libgml.dll 和 libgml.h,后缀不重要,可以自己改的。
已经编译好的 https://github.com/xmdhs/gml-c-shared/actions


macos 就自己编译吧。
竟然有Linux版本的 太感谢了
评论少得可怜......话说我也看得一知半解

谢了awa,学习到了
评论少得可怜......话说我也看得一知半解