@@ -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/ 申请
3436func 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
6769func 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