├── .gitignore ├── LICENSE ├── README.md ├── build.go ├── config.example.json ├── go.mod ├── main.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | buildtool* 2 | config*.json 3 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Juby210 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEPRECATED: Use new template that uses gradle plugin. https://github.com/Aliucord/plugins-template 2 | 3 | # Aliucord buildtool 4 | 5 | Tool to build Aliucord and plugins for Aliucord. 6 | 7 | ### Usage 8 | 9 | Save config.example.json to config.json and fill it out first 10 | 11 | Build Aliucord: 12 | 13 | ``` 14 | ./buildtool 15 | ``` 16 | 17 | Build a plugin: 18 | 19 | ``` 20 | ./buildtool -p PLUGIN_NAME 21 | ``` 22 | 23 | Build all plugins: 24 | 25 | ``` 26 | ./buildtool -p "*" 27 | ``` 28 | -------------------------------------------------------------------------------- /build.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/zip" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | func build(project string) { 14 | gradlew(os.Stdout, config.Aliucord, ":"+project+":compileDebugJavaWithJavac") 15 | 16 | javacBuild, err := filepath.Abs(fmt.Sprintf("%s/%s/build/intermediates/javac/debug", config.Aliucord, project)) 17 | handleErr(err) 18 | 19 | f, _ := os.Create(javacBuild + "/classes.zip") 20 | zipw := zip.NewWriter(f) 21 | zipAndD8(f, zipw, javacBuild, "/classes.zip", config.Outputs) 22 | 23 | isAliucord := project == "Aliucord" 24 | 25 | out := project 26 | if *outName != "" { 27 | out = *outName 28 | } 29 | suffix := ".dex" 30 | if isAliucord { 31 | suffix = ".zip" 32 | } 33 | if !strings.HasSuffix(out, suffix) { 34 | out += suffix 35 | } 36 | 37 | dexFile := config.Outputs + "/classes.dex" 38 | if isAliucord { 39 | makeZipWithClasses(config.Outputs, out, nil) 40 | handleErr(os.Remove(dexFile)) 41 | } else { 42 | handleErr(os.Rename(dexFile, config.Outputs+"/"+out)) 43 | } 44 | 45 | colorPrint(success, "Successfully built "+project) 46 | } 47 | 48 | func buildPlugin(pluginName string) { 49 | plugin, err := filepath.Abs(config.Plugins + "/" + pluginName) 50 | handleErr(err) 51 | _, err = os.Stat(plugin) 52 | handleErr(err) 53 | 54 | gradlew(os.Stdout, config.Plugins, pluginName+":compileDebugJavaWithJavac") 55 | 56 | javacBuild := plugin + "/build/intermediates/javac/debug" 57 | 58 | f, _ := os.Create(javacBuild + "/classes.zip") 59 | zipw := zip.NewWriter(f) 60 | zipAndD8(f, zipw, javacBuild, "/classes.zip", config.OutputsPlugins) 61 | 62 | outputsPlugins, err := filepath.Abs(config.OutputsPlugins) 63 | handleErr(err) 64 | 65 | out := pluginName + ".zip" 66 | if *outName != "" { 67 | out = *outName 68 | if !strings.HasSuffix(out, ".zip") { 69 | out += ".zip" 70 | } 71 | } 72 | 73 | src, err := filepath.Abs(config.Plugins + "/" + pluginName + "/src/main") 74 | if err == nil { 75 | files, err := ioutil.ReadDir(src + "/res") 76 | if err == nil && len(files) > 0 { 77 | tmpApk := outputsPlugins + "/" + pluginName + "-tmp.apk" 78 | 79 | execCmd(os.Stdout, outputsPlugins, "aapt2", "compile", "--dir", src+"/res", "-o", "tmpres.zip") 80 | execCmd(os.Stdout, outputsPlugins, "aapt2", "link", "-I", config.AndroidSDK+"/platforms/android-"+config.AndroidSDKVersion+"/android.jar", 81 | "-R", "tmpres.zip", "--manifest", src+"/AndroidManifest.xml", "-o", tmpApk) 82 | os.Remove(outputsPlugins + "/tmpres.zip") 83 | 84 | zipr, _ := zip.OpenReader(tmpApk) 85 | f, _ = os.Create(outputsPlugins + "/" + out) 86 | defer f.Close() 87 | zipw = zip.NewWriter(f) 88 | defer zipw.Close() 89 | 90 | for _, zipFile := range zipr.File { 91 | if zipFile.Name == "AndroidManifest.xml" { 92 | continue 93 | } 94 | 95 | zipFiler, _ := zipFile.Open() 96 | zipFilew, _ := zipw.Create(zipFile.Name) 97 | io.Copy(zipFilew, zipFiler) 98 | zipFiler.Close() 99 | } 100 | zipr.Close() 101 | 102 | f, _ = os.Open(outputsPlugins + "/classes.dex") 103 | zipFilew, _ := zipw.Create("classes.dex") 104 | io.Copy(zipFilew, f) 105 | f.Close() 106 | 107 | writePluginEntry(zipw, pluginName) 108 | 109 | os.Remove(tmpApk) 110 | } else { 111 | makeZipWithClasses(config.OutputsPlugins, out, &pluginName) 112 | } 113 | } else { 114 | makeZipWithClasses(config.OutputsPlugins, out, &pluginName) 115 | } 116 | 117 | os.Remove(outputsPlugins + "/classes.dex") 118 | colorPrint(success, "Successfully built plugin "+pluginName) 119 | } 120 | 121 | func zipAndD8(f *os.File, zipw *zip.Writer, javacBuild, zipName, outputPath string) { 122 | filepath.Walk(javacBuild+"/classes", func(path string, f os.FileInfo, err error) error { 123 | if err != nil { 124 | colorPrint(red, err) 125 | return nil 126 | } 127 | 128 | if f.IsDir() { 129 | return nil 130 | } 131 | 132 | file, _ := os.Open(path) 133 | defer file.Close() 134 | 135 | zipf, _ := zipw.Create(strings.Split(strings.ReplaceAll(path, "\\", "/"), "javac/debug/classes/")[1]) 136 | io.Copy(zipf, file) 137 | 138 | return nil 139 | }) 140 | 141 | zipw.Close() 142 | f.Close() 143 | 144 | output, err := filepath.Abs(outputPath) 145 | handleErr(err) 146 | 147 | execCmd(os.Stdout, output, "d8", javacBuild+zipName) 148 | } 149 | 150 | func makeZipWithClasses(outdir, out string, pluginName *string) { 151 | f, _ := os.Create(outdir + "/" + out) 152 | defer f.Close() 153 | zipw := zip.NewWriter(f) 154 | defer zipw.Close() 155 | 156 | f, _ = os.Open(outdir + "/classes.dex") 157 | zipFilew, _ := zipw.Create("classes.dex") 158 | io.Copy(zipFilew, f) 159 | f.Close() 160 | 161 | if pluginName != nil { 162 | writePluginEntry(zipw, *pluginName) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "aliucord": "../repo", 3 | "plugins": "../plugins", 4 | "androidSDK": "C:\\Users\\User\\AppData\\Local\\Android\\Sdk", 5 | "androidSDKVersion": "30", 6 | "outputs": "../builds", 7 | "outputsPlugins": "../buildsPlugins" 8 | } 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module buildtool 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os/exec" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | type cfg struct { 16 | Aliucord, Plugins, AndroidSDK, AndroidSDKVersion, Outputs, OutputsPlugins string 17 | } 18 | 19 | const ( 20 | reset = "\033[0m" 21 | red = "\033[1;31m" 22 | success = "\033[1;32m" 23 | warn = "\033[1;33m" 24 | info = "\033[1;34m" 25 | ) 26 | 27 | var ( 28 | configPath = flag.String("config", "config.json", "Config path") 29 | plugin = flag.String("plugin", "", "Plugin name to build") 30 | outName = flag.String("output", "", "Output file name") 31 | injector = flag.Bool("injector", false, "Build the injector") 32 | 33 | config cfg 34 | ) 35 | 36 | func main() { 37 | flag.StringVar(plugin, "p", *plugin, "Alias for plugin") 38 | flag.StringVar(outName, "o", *outName, "Alias for output") 39 | flag.Parse() 40 | 41 | log.SetFlags(log.Lshortfile) 42 | 43 | b, err := ioutil.ReadFile(*configPath) 44 | handleErr(err) 45 | handleErr(json.Unmarshal(b, &config)) 46 | 47 | if config.AndroidSDKVersion == "" { 48 | colorPrint(warn, "NOTE: AndroidSDKVersion not set in config. Defaulting to v29. This will change to v30 in the future.") 49 | config.AndroidSDKVersion = "29" // NOTE: warn in next versions to update config and use android 30 sdk 50 | } 51 | 52 | err = exec.Command("d8", "--version").Run() 53 | if err != nil { 54 | buildToolNotFound("d8") 55 | } 56 | err = exec.Command("aapt2", "version").Run() 57 | if err != nil { 58 | buildToolNotFound("aapt2") 59 | } 60 | 61 | if *injector { 62 | build("Injector") 63 | } else if *plugin == "" { 64 | build("Aliucord") 65 | } else if *plugin == "*" { 66 | regex := regexp.MustCompile(`':(\w+)'`) 67 | buffer := bytes.NewBufferString("") 68 | 69 | gradlew(buffer, config.Plugins, "projects") 70 | 71 | plugins := regex.FindAllStringSubmatch(buffer.String(), -1) 72 | 73 | for i, plugin := range plugins { 74 | pluginName := plugin[1] //Match the first group, since at index 0 we have the full string 75 | 76 | if pluginName == "Aliucord" || pluginName == "DiscordStubs" { 77 | continue 78 | } 79 | 80 | if i > 0 { 81 | fmt.Println() 82 | } 83 | 84 | colorPrint(info, "Building plugin: "+pluginName) 85 | buildPlugin(pluginName) 86 | } 87 | } else { 88 | buildPlugin(strings.TrimSpace(*plugin)) 89 | } 90 | } 91 | 92 | func buildToolNotFound(tool string) { 93 | fatal(tool + " not found. Please add the Android build-tools (Android/Sdk/build-tools/VERSION) to your PATH and try again") 94 | } 95 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/zip" 5 | "fmt" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "runtime" 10 | "strings" 11 | ) 12 | 13 | func handleErr(err error) { 14 | if err != nil { 15 | fatal(err) 16 | } 17 | } 18 | 19 | func fatal(args ...interface{}) { 20 | colorPrint(red, args...) 21 | os.Exit(1) 22 | } 23 | 24 | func colorPrint(color string, args ...interface{}) { 25 | fmt.Print(color) 26 | fmt.Print(args...) 27 | fmt.Println(reset) 28 | } 29 | 30 | func gradlew(stdout io.Writer, dir string, args ...string) { 31 | if runtime.GOOS == "windows" { 32 | execCmd(stdout, dir, "cmd", "/k", "gradlew.bat "+strings.Join(args, " ")+" && exit") 33 | } else { 34 | execCmd(stdout, dir, "./gradlew", args...) 35 | } 36 | } 37 | 38 | func execCmd(stdout io.Writer, dir string, c string, args ...string) { 39 | cmd := exec.Command(c, args...) 40 | cmd.Dir = dir 41 | cmd.Stderr = os.Stderr 42 | cmd.Stdout = stdout 43 | handleErr(cmd.Run()) 44 | } 45 | 46 | func writePluginEntry(zipw *zip.Writer, pluginName string) { 47 | zipFilew, _ := zipw.Create("ac-plugin") 48 | zipFilew.Write([]byte(pluginName)) 49 | } 50 | --------------------------------------------------------------------------------