xmdhs
本帖最后由 xmdhs 于 2021-7-31 16:53 编辑

gml-c-shared

简单说就是一个启动器库,可以以此简单的启动游戏和补全游戏文件。

是对于 gomclauncher 的封装,导出为 c 语言的动态链接库。大概大多数语言都能很好的调用。  

开源地址

mit 协议开源 https://github.com/xmdhs/gml-c-shared

支持功能

功能状况
正版登录
authlib-injector 外置登录
微软登录
原版游戏下载
多线程下载部分[1]
forge 等自动安装× [2]


注意事项

返回的 char* 和 char** 类型是分配在堆上的,需要释放内存,可以使用

  1. char *a;
  2. Freechar(a,0)
复制代码

释放 char*

  1. char **a
  2. Freechar(a,长度)
复制代码

释放 char**

当然不释放内存也完全没问题的,启动器本身不会长久运行,而且这点内存泄漏了也没多少。

char* 使用 utf-8 编码,无论在 liunx 还是 windows 都一样,所以直接用 c 语言调用的话,可能需要转码。

下载源的镜像使用了 bmclapi,需按照协议注明

BMCLAPI下的所有文件,除BMCLAPI本身的源码之外,归源站点所有
BMCLAPI会尽量保证文件的完整性、有效性和实时性,对于使用BMCLAPI带来的一切纠纷,与BMCLAPI无关。
BMCLAPI和BMCL不同,属于非开源项目
所有使用BMCLAPI的程序必需在下载界面或其他可视部分标明来源
禁止在BMCLAPI上二次封装其他协议  

https://bmclapidoc.bangbang93.com/

文档

偷懒,直接在头文件上写注释,应该足够了吧

  1. //仅作注释用,不要导入此文件


  2. //char* 均使用 utf-8 编码

  3. typedef struct
  4. {
  5.     //游戏路径,需要创建一个 .minecraft 文件夹,例如 D:/mc/.minecraft
  6.     char *Minecraftpath;
  7.     //分配内存大小,单位为 mb
  8.     long long RAM;
  9.     //玩家名
  10.     char *Name;
  11.     //uuid
  12.     char *UUID;
  13.     //正版/外置登录相关,离线登录可随便设置
  14.     char *AccessToken;
  15.     //要启动的游戏版本,可用 ListVersion 方法找到被识别的版本
  16.     char *Version;
  17.     //外置登录的 api 地址。
  18.     char *ApiAddress;
  19.     //自定义的 java 参数,字符串数组,可以通过 NewChar 和 SetChar 来构建
  20.     char **Flag;
  21.     //字符串数组的长度
  22.     int  flag_len;
  23.     //是否开启外置登录
  24.     int  independent;
  25. } Gameinfo;

  26. typedef struct
  27. {
  28.     //code 对应的信息见下方注释
  29.     int  code;
  30.     //详细的错误信息
  31.     char *msg;
  32. } err;

  33. /*code | msg
  34. -----|----------------------------------------------
  35. -1   | 未知错误
  36. 0    | 正常
  37. 1    | 文件不存在
  38. 2    | json 错误
  39. 3    | minecraft json 格式错误
  40. 4    | 找不到该版本
  41. 5    | 下载失败次数过多
  42. 6    | authlib 登录时,有多个账户,且并没有选择(也就是没有设置 username 参数)
  43. 7    | 没有这个角色
  44. 8    | 通常是密码错误,或者登录过期
  45. 9    | authlib 找不到可用档案,也就是没有创建角色之类的
  46. 10   | accessToken 失效
  47. 11   | 登录微软账户时打开的浏览器中,打开了其他页面
  48. 12   | 尝试重新登录微软账户
  49. 13   | 没有购买游戏或者没有迁移账号
  50. 14   | 没有安装 chrome 或者新版本的 edge(windows only)
  51. */

  52. typedef struct
  53. {
  54.     //字符串数组
  55.     char **charlist;
  56.     //字符串数组的长度
  57.     int len;
  58.     //错误
  59.     err e;

  60. } GmlReturn;

  61. //当下载文件时,发生错误时将调用此函数。
  62. typedef void (*Fail)(char*);

  63. //下载文件成功后,会调用此函数
  64. //第一个参数表示下载成功的类型
  65. /*第一个参数见下方表格解释。第二个参数传递剩余没下载的文件数量,成功下载一次,调用一次。

  66. code | msg
  67. -----|-------
  68. 1    | 下载游戏核心
  69. 2    | 下载资源文件
  70. 3    | 下载游戏库
  71. */
  72. typedef void (*Ok)(int,int);

  73. //下载或检查游戏,成功或失败时,将调用此函数
  74. typedef void (*gmlfinish)(err);

  75. typedef struct
  76. {
  77.     char *Username;
  78.     char *ClientToken;
  79.     char *UUID;
  80.     char *AccessToken;
  81.     char *ApiAddress;
  82.     //可用的用户名,当 Auth 返回 6 错误时,可以选择此列表中的用户名,设置在 Auth 的 username 参数上,来选择档案。
  83.     //只有外置登录有单账号,多档案。
  84.     char **availableProfiles;
  85.     //长度
  86.     int availableProfilesLen;
  87. } AuthDate;

  88. typedef struct
  89. {
  90.     char *Username;
  91.     char *UUID;
  92.     char *AccessToken;
  93. } MsAuthDate;

  94. #ifdef __cplusplus
  95. extern "C" {
  96. #endif

  97. //获取字符串数组中的字符串
  98. extern char* Getchar(char** charlist, long long int index);
  99. //释放字符串数组的内存,下面的函数返回的字符串数组都是分配在堆上的,需要用此函数释放
  100. extern void Freechar(char** charlist, long long int len);
  101. //创建一个字符串数组
  102. extern char** NewChar(long long int len);
  103. //设置字符串数组指定的位置的字符串
  104. extern void SetChar(char** cc, long long int index, char* achar);
  105. //其实就是导出了 c 语言的 malloc 函数,为了避免变量名冲突,所以首字母大写了
  106. extern void* Malloc(int i);
  107. //生成启动游戏需要的参数
  108. extern GmlReturn GenCmdArgs(Gameinfo g);
  109. //设置下载文件和正版登录的代理,非线程安全,全局共用
  110. extern err SetProxy(char* httpProxy);
  111. //下载游戏
  112. //version 下载版本,可通过 ListDownloadVersion 查找可下载的版本
  113. //Type 下载使用的下载源,留空将按照权重的随机使用三个下载源,也可以自行设置。例如 vanilla|bmclapi 表示随机使用原版下载源和 bmclapi 下载源。mcbbs 表示只使用 mcbbs 下载源
  114. //downInt 下载协程数,通常 64 即可,因为一个文件还是使用一个协程下载,多了没意义
  115. //Minecraftpath 下载的路径,例如 D:/mc/.minecraft
  116. //调用后将立刻返回一个 int64,可以使用 Cancel 函数,将此 int64 传入,取消下载操作
  117. extern long long int Download(char* version, char* Type, char* Minecraftpath, int downInt, Fail fail, Ok ok, gmlfinish finish);
  118. //检查游戏的完整性,第一次某个版本时,必须检查一次。
  119. extern long long int Check(char* version, char* Type, char* Minecraftpath, int downInt, Fail fail, Ok ok, gmlfinish finish);
  120. //取消下载或者检查游戏完整性
  121. extern void Cancel(long long int id);
  122. //列出可启动版本
  123. //path 例如 D:/mc/.minecraft/version
  124. extern GmlReturn ListVersion(char* path);
  125. //查看可下载游戏版本类型,比如正式版之类的。 type 参数和 Download 里的意义一样
  126. extern GmlReturn ListDownloadType(char* Type);
  127. //查看此类型中所有的可下载的版本
  128. extern GmlReturn ListDownloadVersion(char* VerType, char* Type);

  129. /* Return type for Auth */
  130. struct Auth_return {
  131.     AuthDate r0;
  132.     err r1;
  133. };
  134. //外置登录和正版登录
  135. //clientToken 客户端 id,可随机生成,需保证每个用户对应的 ClientToken 是不变的,否则会要求重新登录。建议直接 md5 用户名就行。
  136. //ApiAddress 可不输入完整的 api 地址,会按照协议补全,如果正版登录,则无需设置此项。
  137. //username 外置登录时需要,具体见 AuthDate 处的注释
  138. //不要保存密码,保存 AccessToken
  139. extern struct Auth_return Auth(char* ApiAddress, char* username, char* email, char* password, char* clientToken);
  140. //验证 AccessToken 的有效性,建议每次启动游戏前,都验证一次
  141. extern err Validate(char* AccessToken, char* ClientToken, char* ApiAddress);

  142. /* Return type for Refresh */
  143. struct Refresh_return {
  144.     AuthDate r0;
  145.     err r1;
  146. };
  147. //若 Validate 验证 AccessToken 失效,则可通过此方法刷新,如果刷新无效,则重新登录就是。
  148. extern struct Refresh_return Refresh(char* AccessToken, char* ClientToken, char* ApiAddress);

  149. /* Return type for MsAuth */
  150. struct MsAuth_return {
  151.     MsAuthDate r0;
  152.     err r1;
  153. };
  154. //微软登录,将弹出一个窗口让玩家在其中进行登录,需要安装 chrome,或者新版 edge(windows only)
  155. extern struct MsAuth_return MsAuth();

  156. /* Return type for MsAuthValidate */
  157. struct MsAuthValidate_return {
  158.     MsAuthDate r0;
  159.     err r1;
  160. };
  161. //验证微软登录的 AccessToken 有效性,建议首次登录后也使用此方法验证,因为即使没有购买游戏,MsAuth 也会返回有效的内容。
  162. extern struct MsAuthValidate_return MsAuthValidate(char* AccessToken);

  163. #ifdef __cplusplus
  164. }
  165. #endif
复制代码

示例

使用 java,用 jna 库调用,简单的下载游戏和启动。

  1. import com.sun.jna.*;

  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.nio.charset.StandardCharsets;
  6. import java.util.*;
  7. import java.util.concurrent.locks.Condition;
  8. import java.util.concurrent.locks.Lock;
  9. import java.util.concurrent.locks.ReentrantLock;

  10. public class Main {

  11.     public interface fail extends Callback {
  12.         void Fail(Pointer c);
  13.     }

  14.     public static class c implements fail {
  15.         @Override
  16.         public void Fail(Pointer c) {
  17.             String s = c.getString(0, "UTF-8");
  18.             System.out.println("java: " + s);
  19.         }
  20.     }

  21.     public interface ok extends Callback {
  22.         void Ok(int i1, int i2);
  23.     }

  24.     private static Map<Integer, String> m = new HashMap<>();

  25.     static {
  26.         m.put(1, "下载游戏核心");
  27.         m.put(2, "下载资源文件");
  28.         m.put(3, "下载游戏库");
  29.     }

  30.     public static class f implements ok {
  31.         @Override
  32.         public void Ok(int i1, int i2) {
  33.             String s = m.get(i1);
  34.             System.out.println(s + "剩余:" + i2);
  35.         }
  36.     }

  37.     public static class GoErr extends Structure implements Structure.ByValue {
  38.         public int r0;
  39.         public Pointer r1;

  40.         protected List<String> getFieldOrder() {
  41.             return Arrays.asList("r0", "r1");
  42.         }
  43.     }


  44.     public interface finish extends Callback {
  45.         void gmlfinish(GoErr c);
  46.     }

  47.     public static class f3 implements finish {
  48.         @Override
  49.         public void gmlfinish(GoErr c) {
  50.             if (c.r0 == 0) {
  51.                 lock.lock();
  52.                 condition.signalAll();
  53.                 lock.unlock();
  54.                 return;
  55.             }
  56.             String s = c.r1.getString(0, "UTF-8");
  57.             System.out.println("code: " + c.r0 + " msg: " + s);
  58.             lock.lock();
  59.             condition.signalAll();
  60.             lock.unlock();
  61.         }
  62.     }

  63.     public static class GmlReturn extends Structure implements Structure.ByValue {
  64.         public Pointer r0;
  65.         public int r1;
  66.         public GoErr r2;

  67.         protected List<String> getFieldOrder() {
  68.             return Arrays.asList("r0", "r1", "r2");
  69.         }
  70.     }

  71.     public static class AuthDate extends Structure implements Structure.ByValue {
  72.         public Pointer Username;
  73.         public Pointer ClientToken;
  74.         public Pointer UUID;
  75.         public Pointer AccessToken;
  76.         public Pointer ApiAddress;
  77.         public Pointer availableProfiles;
  78.         public int availableProfilesLen;

  79.         protected List<String> getFieldOrder() {
  80.             return Arrays.asList("Username", "ClientToken", "UUID", "AccessToken", "ApiAddress", "availableProfiles", "availableProfilesLen");
  81.         }
  82.     }

  83.     public static class Auth_return extends Structure implements Structure.ByValue {
  84.         public AuthDate r0;
  85.         public GoErr r1;

  86.         protected List<String> getFieldOrder() {
  87.             return Arrays.asList("r0", "r1");
  88.         }
  89.     }

  90.     public interface Lib extends Library {

  91.         Lib INSTANCE = Native.load("C:/Users/xmdhs/Desktop/untitled/libgml.dll", Lib.class);

  92.         long Download(Pointer version, Pointer type, Pointer path, int downint, Callback fail, Callback ok, Callback finish);

  93.         long Check(Pointer version, Pointer type, Pointer path, int downint, Callback fail, Callback ok, Callback finish);

  94.         GmlReturn GenCmdArgs(Gameinfo g);

  95.         Pointer NewChar(long len);

  96.         void SetChar(Pointer cc, long index, Pointer achar);

  97.         void Freechar(Pointer cc, long len);

  98.         Pointer Malloc(int i);

  99.         Auth_return Auth(Pointer ApiAddress, Pointer username, Pointer email, Pointer password, Pointer clientToken);
  100.     }

  101.     public static class Gameinfo extends Structure implements Structure.ByValue {
  102.         public Pointer Minecraftpath;
  103.         //分配内存大小,单位为 mb
  104.         public long RAM;
  105.         //玩家名
  106.         public Pointer Name;
  107.         //uuid
  108.         public Pointer UUID;
  109.         //正版/外置登录相关,离线登录可随便设置
  110.         public Pointer AccessToken;
  111.         //要启动的游戏版本,可用 ListVersion 方法找到被识别的版本
  112.         public Pointer Version;
  113.         //外置登录的 api 地址。
  114.         public Pointer ApiAddress;
  115.         //自定义的 java 参数,字符串数组,可以通过 NewChar 和 SetChar 来构建
  116.         public Pointer Flag;
  117.         //字符串数组的长度
  118.         public int flag_len;
  119.         //是否开启外置登录
  120.         public int independent;

  121.         protected List<String> getFieldOrder() {
  122.             return Arrays.asList("Minecraftpath", "RAM", "Name", "UUID", "AccessToken", "Version", "ApiAddress", "Flag", "flag_len", "independent");
  123.         }
  124.     }

  125.     private static Pointer String2char(String s) {
  126.         Pointer m = Lib.INSTANCE.Malloc(s.getBytes(StandardCharsets.UTF_8).length + 1);
  127.         m.setString(0, s, StandardCharsets.UTF_8.name());
  128.         return m;
  129.     }

  130.     private static final Lock lock = new ReentrantLock();
  131.     private static final Condition condition = lock.newCondition();

  132.     static c fail = new c();
  133.     static f ok = new f();
  134.     static finish finish = new f3();

  135.     public static void main(String[] args) throws InterruptedException, IOException {
  136.         Auth_return ar = Lib.INSTANCE.Auth(String2char(""), String2char(""),
  137.                 String2char("[email protected]"), String2char("Aaaa"),
  138.                 String2char("123"));
  139.         if (ar.r1.r0 != 0){
  140.             System.out.println(ar.r1.r1.getString(0,"UTF-8"));
  141.             return;
  142.         }

  143.         Pointer a1 = String2char("1.17");
  144.         Lib.INSTANCE.Download(a1, String2char(""), String2char("C:/Users/xmdhs/Desktop/新建文件夹/.minecraft"),
  145.                 64, fail, ok, finish);
  146.         //其实通过 String2char 创建的字符串,都需要用 Freechar 释放,但是这里偷懒了,反正进程结束也会释放。
  147.         Lib.INSTANCE.Freechar(a1, 0);
  148.         lock.lock();
  149.         condition.await();
  150.         lock.unlock();

  151.         Gameinfo g = new Gameinfo();
  152.         g.AccessToken = ar.r0.AccessToken;
  153.         g.ApiAddress = String2char("");
  154.         Pointer list = Lib.INSTANCE.NewChar(1);
  155.         Lib.INSTANCE.SetChar(list, 0, String2char("-XX:+UseG1GC"));
  156.         g.Flag = list;
  157.         g.flag_len = 1;
  158.         g.independent = 1;
  159.         g.Minecraftpath = String2char("C:/Users/xmdhs/Desktop/新建文件夹/.minecraft");
  160.         g.Name = ar.r0.Username;
  161.         g.UUID = ar.r0.UUID;
  162.         g.RAM = 2000;
  163.         g.Version = String2char("1.17");
  164.         GmlReturn r = Lib.INSTANCE.GenCmdArgs(g);
  165.         if (r.r2.r0 != 0) {
  166.             System.out.println(r.r2.r0 + ": " + r.r2.r1.getString(0, StandardCharsets.UTF_8.name()));
  167.             return;
  168.         }
  169.         String[] l = r.r0.getStringArray(0, r.r1, StandardCharsets.UTF_8.name());
  170.         System.out.println(Arrays.toString(l));

  171.         String[] ll = new String[l.length + 1];
  172.         ll[0] = "java";
  173.         int i = 1;
  174.         for (String v : l) {
  175.             ll[i] = v;
  176.             i++;
  177.         }
  178.         Lib.INSTANCE.Freechar(r.r0, r.r1);
  179.         Lib.INSTANCE.Freechar(list, 1);
  180.         ProcessBuilder p = new ProcessBuilder(ll);
  181.         Process pp = p.start();
  182.         BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(pp.getInputStream()));
  183.         BufferedReader stderrReader = new BufferedReader(new InputStreamReader(pp.getErrorStream()));
  184.         String line;
  185.         while ((line = stdoutReader.readLine()) != null) {
  186.             System.out.println(line);
  187.         }
  188.         while ((line = stderrReader.readLine()) != null) {
  189.             System.out.println(line);
  190.         }
  191.         System.out.println(pp.waitFor());

  192.     }
  193. }
复制代码

下载

其实推荐自行编译。需要安装 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 就自己编译吧。

kirito=-=
竟然有Linux版本的 太感谢了

zhou_xin_yi
评论少得可怜......话说我也看得一知半解

2640982408
谢了awa,学习到了

666lwj666
评论少得可怜......话说我也看得一知半解

第一页 上一页 下一页 最后一页