Use BMCLAPI mirror & update build

Route Minecraft/Fabric/Forge downloads through BMCLAPI mirror and update installer logic. Added mirror rewrite utility (rewriteToMirror), mirror-aware downloadFile, and mirrorFetch; switched version manifest and metadata endpoints to bmclapi2.bangbang93.com and wired maven/assets hosts into xmcl/installer calls. Cleaned up compiled electron JS files and consolidated runtime files under electron-dist; updated electron-builder.yml (compression, asar, NSIS settings, license/installer assets and languages). Minor env/path changes (VITE_APP_ICON paths), added .npmrc (electron_mirror), new Koring.yml, pnpm-workspace and public installer assets. Also added/updated multiple frontend pages, store components, and TypeScript handlers to integrate the changes.
This commit is contained in:
2026-08-04 01:28:16 +08:00
parent 06269698a6
commit cb0afd99c0
73 changed files with 3108 additions and 2898 deletions
+79 -12
View File
@@ -14,6 +14,18 @@ import {
getFabricLoaders,
getQuiltLoaderVersionsByMinecraft,
} from '@xmcl/installer';
import { rewriteToMirror } from './installer';
// ==================== BMCLAPI 镜像源配置 ====================
// 版本清单(@xmcl/installer 的 getVersionList 通过 remote 参数覆盖)
const BMCLAPI_VERSION_MANIFEST = 'https://bmclapi2.bangbang93.com/mc/game/version_manifest.json';
// Maven 仓库镜像(Libraries / Forge / NeoForge / Fabric 构件)
const BMCLAPI_MAVEN = 'https://bmclapi2.bangbang93.com/maven';
// 资源文件镜像(assets
const BMCLAPI_ASSETS = 'https://bmclapi2.bangbang93.com/assets';
// 供 @xmcl/installer FetchOptions 使用的镜像 fetchmeta.fabricmc.net 等元数据接口走镜像)
const mirrorFetch: typeof fetch = (url, init) => fetch(rewriteToMirror(String(url)), init);
export interface InstanceRuntime {
minecraft: string;
@@ -232,18 +244,24 @@ export async function installInstanceGame(
callbacks?.onProgress?.({ stage: 'installing-minecraft', current: 0, total: 100, message: `Installing Minecraft ${runtime.minecraft}...` });
// Install Minecraft
// First get version list to find the version info
const versionList = await getVersionList();
// First get version list to find the version info(走 BMCLAPI 镜像)
const versionList = await getVersionList({ remote: BMCLAPI_VERSION_MANIFEST, fetch: mirrorFetch });
const versionInfo = versionList.versions.find((v) => v.id === runtime.minecraft);
if (!versionInfo) {
throw new Error(`Minecraft version ${runtime.minecraft} not found`);
}
await xmclInstall({ id: versionInfo.id, url: versionInfo.url }, instancePath);
await xmclInstall({ id: versionInfo.id, url: versionInfo.url }, instancePath, {
// 版本 JSON / 客户端 JAR 走镜像(BMCLAPI 会回源官方地址)
json: (v) => rewriteToMirror(v.url),
client: (v) => (v.downloads.client ? rewriteToMirror(v.downloads.client.url) : []),
mavenHost: BMCLAPI_MAVEN,
assetsHost: BMCLAPI_ASSETS,
});
// Install mod loaders
if (runtime.forge) {
callbacks?.onProgress?.({ stage: 'installing-forge', current: 0, total: 100, message: `Installing Forge ${runtime.forge}...` });
await installForge({ version: runtime.forge, mcversion: runtime.minecraft }, instancePath);
await installForge({ version: runtime.forge, mcversion: runtime.minecraft }, instancePath, { mavenHost: BMCLAPI_MAVEN });
}
if (runtime.fabricLoader) {
@@ -252,6 +270,7 @@ export async function installInstanceGame(
minecraftVersion: runtime.minecraft,
version: runtime.fabricLoader,
minecraft: instancePath,
fetch: mirrorFetch,
});
}
@@ -266,7 +285,7 @@ export async function installInstanceGame(
if (runtime.neoForged) {
callbacks?.onProgress?.({ stage: 'installing-neoforge', current: 0, total: 100, message: `Installing NeoForge ${runtime.neoForged}...` });
await installNeoForged('neoforge', runtime.neoForged, instancePath, {});
await installNeoForged('neoforge', runtime.neoForged, instancePath, { mavenHost: BMCLAPI_MAVEN });
}
// Note: OptiFine requires downloading the installer JAR first
@@ -277,9 +296,9 @@ export async function installInstanceGame(
callbacks?.onProgress?.({ stage: 'installing-dependencies', current: 0, total: 100, message: 'Installing dependencies...' });
// Install all dependencies (libraries + assets)
// Install all dependencies (libraries + assets)(走 BMCLAPI 镜像)
const resolved: ResolvedVersion = await Version.parse(instancePath, runtime.minecraft);
await installDependencies(resolved);
await installDependencies(resolved, { mavenHost: BMCLAPI_MAVEN, assetsHost: BMCLAPI_ASSETS });
callbacks?.onProgress?.({ stage: 'done', current: 100, total: 100, message: 'Installation complete' });
@@ -370,12 +389,13 @@ export async function diagnoseInstance(
// Version list APIs
export async function getMinecraftVersionList(type?: string) {
const manifest = await getVersionList();
// 版本清单一律走 BMCLAPI 镜像
const manifest = await getVersionList({ remote: BMCLAPI_VERSION_MANIFEST, fetch: mirrorFetch });
let versions = manifest.versions;
if (type && type !== 'all') {
versions = versions.filter((v) => v.type === type);
versions = versions.filter((v: { type: string }) => v.type === type);
}
return { versions };
return { versions, latest: manifest.latest };
}
export async function getForgeVersionList(mcVersion?: string) {
@@ -384,11 +404,12 @@ export async function getForgeVersionList(mcVersion?: string) {
}
export async function getFabricVersionList(mcVersion?: string) {
// Fabric 元数据走 BMCLAPI 镜像(meta.fabricmc.net → fabric-meta
if (mcVersion) {
const loaders = await getFabricLoaders();
const loaders = await getFabricLoaders({ fetch: mirrorFetch });
return { versions: loaders.map((l) => l.version) };
}
const loaders = await getFabricLoaders();
const loaders = await getFabricLoaders({ fetch: mirrorFetch });
return { versions: loaders.map((l) => l.version) };
}
@@ -396,3 +417,49 @@ export async function getQuiltVersionList(mcVersion?: string) {
const loaders = await getQuiltLoaderVersionsByMinecraft({ minecraftVersion: mcVersion || '*' });
return { versions: loaders.map((l) => l.loader.version) };
}
// 扫描目录中已安装版本的信息
export interface ScannedVersion {
id: string;
type: string;
releaseTime?: string;
/** JAR 文件是否存在 */
hasJar: boolean;
/** JSON 文件是否存在 */
hasJson: boolean;
}
// 扫描指定目录,返回 versions 子目录中所有已安装版本的详细信息
export function scanGameDirectories(gamePath: string): ScannedVersion[] {
const versionsPath = path.join(gamePath, 'versions');
if (!fs.existsSync(versionsPath)) return [];
try {
return fs.readdirSync(versionsPath, { withFileTypes: true })
.filter((e) => e.isDirectory())
.map((e) => {
const id = e.name;
const versionDir = path.join(versionsPath, id);
const jsonPath = path.join(versionDir, `${id}.json`);
const jarPath = path.join(versionDir, `${id}.jar`);
const hasJson = fs.existsSync(jsonPath);
const hasJar = fs.existsSync(jarPath);
let type = "unknown";
let releaseTime: string | undefined;
// 尝试读取 version JSON 获取类型和时间
if (hasJson) {
try {
const raw = fs.readFileSync(jsonPath, 'utf-8');
const json = JSON.parse(raw);
type = json.type || "unknown";
releaseTime = json.releaseTime;
} catch {}
}
return { id, type, releaseTime, hasJar, hasJson };
});
} catch {
return [];
}
}