Skip to content

Commit 02bf08b

Browse files
committed
支持CF整合包安装,加快了安装速度
1 parent 6bb5d1c commit 02bf08b

File tree

5 files changed

+243
-265
lines changed

5 files changed

+243
-265
lines changed

build.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/bash
22
set -e
33
git_hash=$(git rev-parse --short HEAD 2>/dev/null)
4-
version="1.2.2-${git_hash}"
4+
version="1.3.0-${git_hash}"
55
echo "Build: AutoInstall-${version}"
6-
go build -ldflags "-X main.gitversion=${version}" -o dist/$BUILD_NAME main.go
6+
go build -ldflags "-X main.gitversion=${version} -X github.com/autoinst/AutoInstall/pkg.cfapiKey=${{ secrets.CF_API_KEY }}" -o dist/$BUILD_NAME main.go
77
ls dist

main.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ func main() {
2222
instFile := "inst.json"
2323
var config core.InstConfig
2424
fmt.Println("AutoInstall-" + gitversion + " https://github.com/autoinst/AutoInstall")
25-
if cfapiKey == "" {
26-
fmt.Println("无法获取CF APIKEY,自己构建的?")
27-
}
2825
pkg.Search(config.MaxConnections, config.Argsment)
2926
if _, err := os.Stat(instFile); err == nil {
3027
data, err := os.ReadFile(instFile)
@@ -48,6 +45,7 @@ func main() {
4845
}
4946
fmt.Printf("下载源: %s\n", config.Download)
5047
pkg.Common(config, cleaninst)
48+
pkg.WaitDownloads()
5149
} else if os.IsNotExist(err) {
5250
fmt.Println("inst.json 文件不存在")
5351
} else {

pkg/cf.go

Lines changed: 72 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ type CurseForgeManifest struct {
2929
} `json:"files"`
3030
}
3131

32+
var cfapiKey string
33+
3234
// resolveCFDownloadURL 使用 CurseForge API 获取可用直链
3335
// 需要环境变量 CF_API_KEY,可在 https://console.curseforge.com/ 申请
3436
func resolveCFDownloadURL(projectID, fileID int) (string, error) {
35-
cfapiKey := os.Getenv("CF_API_KEY")
3637
if cfapiKey == "" {
3738
return "", fmt.Errorf("缺少 CF_API_KEY")
39+
os.Exit(128)
3840
}
3941
// 直接获取下载直链
4042
req, err := http.NewRequest("GET", fmt.Sprintf("https://api.curseforge.com/v1/mods/%d/files/%d/download-url", projectID, fileID), nil)
@@ -50,7 +52,7 @@ func resolveCFDownloadURL(projectID, fileID int) (string, error) {
5052
defer resp.Body.Close()
5153
if resp.StatusCode != http.StatusOK {
5254
b, _ := io.ReadAll(resp.Body)
53-
return "", fmt.Errorf("CF API 响应异常: %d %s", resp.StatusCode, string(b))
55+
return "", fmt.Errorf("响应异常: %d %s", resp.StatusCode, string(b))
5456
}
5557
var out struct {
5658
Data string `json:"data"`
@@ -65,7 +67,6 @@ func resolveCFDownloadURL(projectID, fileID int) (string, error) {
6567
}
6668

6769
func CurseForge(file string, MaxCon int, Args string) {
68-
// 1) 读取 manifest.json
6970
mf := "manifest.json"
7071
if file != "" && strings.HasSuffix(strings.ToLower(file), ".json") {
7172
mf = file
@@ -74,54 +75,32 @@ func CurseForge(file string, MaxCon int, Args string) {
7475
mfFile, err := os.Open(mfPath)
7576
if err != nil {
7677
fmt.Println("未找到 manifest.json,停止 CurseForge 安装流程")
77-
os.Exit(0)
78+
return
7879
}
7980
defer mfFile.Close()
8081

8182
var manifest CurseForgeManifest
8283
if err := json.NewDecoder(mfFile).Decode(&manifest); err != nil {
83-
panic(fmt.Errorf("解析 manifest.json 失败: %w", err))
84+
fmt.Println("解析 manifest.json 失败:", err)
85+
return
8486
}
8587

86-
// 2) 迁移 overrides 内容到根目录
87-
overridesPath := filepath.Join("./", manifest.Overrides)
88-
if manifest.Overrides == "" {
89-
overridesPath = filepath.Join("./", "overrides")
88+
overridesDir := manifest.Overrides
89+
if overridesDir == "" {
90+
overridesDir = "overrides"
9091
}
91-
if stat, statErr := os.Stat(overridesPath); statErr == nil && stat.IsDir() {
92-
err = filepath.Walk(overridesPath, func(path string, info os.FileInfo, walkErr error) error {
93-
if walkErr != nil {
94-
return walkErr
95-
}
96-
if !info.IsDir() {
97-
relPath, err := filepath.Rel(overridesPath, path)
98-
if err != nil {
99-
return err
100-
}
101-
destPath := filepath.Join("./", relPath)
102-
if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
103-
return err
104-
}
105-
if err := os.Rename(path, destPath); err != nil {
106-
return err
107-
}
108-
}
109-
return nil
110-
})
111-
if err != nil {
112-
panic(fmt.Sprintf("移动 overrides 文件失败: %v", err))
113-
}
114-
_ = os.RemoveAll(overridesPath)
92+
if err := moveOverrides(filepath.Join("./", overridesDir)); err != nil {
93+
fmt.Println("移动 overrides 文件失败:", err)
94+
return
11595
}
11696

117-
// 3) 生成 inst.json(Minecraft 版本 + 加载器信息)
11897
inst := core.InstConfig{
11998
Version: manifest.Minecraft.Version,
12099
Download: "bmclapi",
121100
MaxConnections: 32,
122101
Argsment: "-Xmx{maxmen}M -Xms{maxmen}M -XX:+AlwaysPreTouch -XX:+DisableExplicitGC -XX:+ParallelRefProcEnabled -XX:+PerfDisableSharedMem -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1HeapRegionSize=8M -XX:G1HeapWastePercent=5 -XX:G1MaxNewSizePercent=40 -XX:G1MixedGCCountTarget=4 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1NewSizePercent=30 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:G1ReservePercent=20 -XX:InitiatingHeapOccupancyPercent=15 -XX:MaxGCPauseMillis=200 -XX:MaxTenuringThreshold=1 -XX:SurvivorRatio=32 -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true",
123102
}
124-
// 从 modLoaders 选择 primary 或第一个
103+
125104
loaderID := ""
126105
if len(manifest.Minecraft.ModLoaders) > 0 {
127106
for _, ml := range manifest.Minecraft.ModLoaders {
@@ -134,7 +113,7 @@ func CurseForge(file string, MaxCon int, Args string) {
134113
loaderID = manifest.Minecraft.ModLoaders[0].ID
135114
}
136115
}
137-
// 常见格式: forge-<ver> / fabric-<loader>
116+
138117
if strings.HasPrefix(strings.ToLower(loaderID), "neoforge-") {
139118
inst.Loader = "neoforge"
140119
inst.LoaderVersion = strings.TrimPrefix(loaderID, "neoforge-")
@@ -145,90 +124,86 @@ func CurseForge(file string, MaxCon int, Args string) {
145124
inst.Loader = "fabric"
146125
inst.LoaderVersion = strings.TrimPrefix(loaderID, "fabric-")
147126
} else {
148-
// 默认回退 fabric(部分清单可能只有 minecraft 原版)
149127
if loaderID == "" {
150128
inst.Loader = "vanilla"
151129
inst.LoaderVersion = ""
152130
} else {
153-
// 未识别时尽量按原样放入 fabric 字段,避免阻断
154131
inst.Loader = "fabric"
155132
inst.LoaderVersion = loaderID
156133
}
157134
}
158135
jsonData, err := json.MarshalIndent(inst, "", " ")
159136
if err != nil {
160-
panic(err)
137+
fmt.Println("生成 inst.json 失败:", err)
138+
return
161139
}
162140
if err := os.WriteFile("inst.json", jsonData, 0777); err != nil {
163-
panic(err)
141+
fmt.Println("写入 inst.json 失败:", err)
142+
return
164143
}
165144

166-
// 4) 下载 mods:通过 CF API 解析下载直链;放置到 ./mods 目录
167145
if len(manifest.Files) == 0 {
168146
return
169147
}
170148

171-
if os.Getenv("CF_API_KEY") == "" {
172-
fmt.Println("未设置 CF_API_KEY,跳过 CurseForge 模组下载。已完成 overrides 应用与 inst.json 生成。")
173-
fmt.Println("如需自动下载 CF 模组,请设置环境变量 CF_API_KEY 后重试。")
174-
return
175-
}
149+
DownloadWg.Add(1)
150+
go func() {
151+
defer DownloadWg.Done()
152+
var wg sync.WaitGroup
153+
maxConcurrency := 24
154+
if MaxCon > 0 {
155+
maxConcurrency = MaxCon
156+
}
157+
semaphore := make(chan struct{}, maxConcurrency)
158+
errChan := make(chan error, len(manifest.Files))
176159

177-
var wg sync.WaitGroup
178-
maxConcurrency := 24
179-
if MaxCon > 0 {
180-
maxConcurrency = MaxCon
181-
}
182-
semaphore := make(chan struct{}, maxConcurrency)
183-
errChan := make(chan error, len(manifest.Files))
160+
modsDir := filepath.Join(".", "mods")
161+
_ = os.MkdirAll(modsDir, os.ModePerm)
184162

185-
modsDir := filepath.Join(".", "mods")
186-
_ = os.MkdirAll(modsDir, os.ModePerm)
163+
for _, mf := range manifest.Files {
164+
if !mf.Required {
165+
continue
166+
}
167+
wg.Add(1)
168+
semaphore <- struct{}{}
187169

188-
for _, mf := range manifest.Files {
189-
if !mf.Required {
190-
continue
170+
go func(entry struct {
171+
ProjectID int `json:"projectID"`
172+
FileID int `json:"fileID"`
173+
Required bool `json:"required"`
174+
}) {
175+
defer func() { <-semaphore; wg.Done() }()
176+
177+
url, err := resolveCFDownloadURL(entry.ProjectID, entry.FileID)
178+
if err != nil {
179+
errChan <- err
180+
return
181+
}
182+
183+
segs := strings.Split(url, "/")
184+
filename := fmt.Sprintf("%d.jar", entry.FileID)
185+
if len(segs) > 0 && segs[len(segs)-1] != "" {
186+
filename = segs[len(segs)-1]
187+
}
188+
dst := filepath.Join(modsDir, filename)
189+
if err := os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
190+
errChan <- err
191+
return
192+
}
193+
fmt.Println("尝试下载:", url)
194+
if err := core.DownloadFile(url, dst); err != nil {
195+
errChan <- fmt.Errorf("下载失败(Project %d, File %d): %v", entry.ProjectID, entry.FileID, err)
196+
return
197+
}
198+
}(mf)
191199
}
192-
wg.Add(1)
193-
semaphore <- struct{}{}
194-
195-
go func(entry struct {
196-
ProjectID int `json:"projectID"`
197-
FileID int `json:"fileID"`
198-
Required bool `json:"required"`
199-
}) {
200-
defer func() { <-semaphore; wg.Done() }()
201-
202-
// 解析直链并以 URL 最末文件名保存
203-
url, err := resolveCFDownloadURL(entry.ProjectID, entry.FileID)
200+
201+
wg.Wait()
202+
close(errChan)
203+
for err := range errChan {
204204
if err != nil {
205-
errChan <- err
206-
return
207-
}
208-
// 从 URL 提取文件名
209-
segs := strings.Split(url, "/")
210-
filename := fmt.Sprintf("%d.jar", entry.FileID)
211-
if len(segs) > 0 && segs[len(segs)-1] != "" {
212-
filename = segs[len(segs)-1]
213-
}
214-
dst := filepath.Join(modsDir, filename)
215-
if err := os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
216-
errChan <- err
217-
return
205+
fmt.Println("下载出错:", err)
218206
}
219-
fmt.Println("尝试下载:", url)
220-
if err := core.DownloadFile(url, dst); err != nil {
221-
errChan <- fmt.Errorf("下载失败(Project %d, File %d): %v", entry.ProjectID, entry.FileID, err)
222-
return
223-
}
224-
}(mf)
225-
}
226-
227-
wg.Wait()
228-
close(errChan)
229-
for err := range errChan {
230-
if err != nil {
231-
panic(err)
232207
}
233-
}
208+
}()
234209
}

0 commit comments

Comments
 (0)