From e266387784c8e4e26c9a8fec1731242999fd210f Mon Sep 17 00:00:00 2001 From: claude-bot Date: Tue, 5 May 2026 19:16:09 +0900 Subject: [PATCH] Add admin distribution editor --- README.md | 1 + admin/data/catalog.json | 10 +- admin/data/distributions/.gitkeep | 1 + admin/public/app.js | 217 +++- admin/public/index.html | 35 +- admin/public/styles.css | 55 +- admin/server.js | 108 +- app/assets/launcher/catalog.json | 10 +- docs/admin-site.md | 16 +- docs/launcher-catalog.md | 18 +- docs/sample_distribution.json | 1598 +---------------------------- 11 files changed, 417 insertions(+), 1652 deletions(-) create mode 100644 admin/data/distributions/.gitkeep diff --git a/README.md b/README.md index cbb28f7..de1f7d3 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ npm run admin ``` 설치 페이지용 프로필을 웹 UI로 관리할 수 있습니다. +- `distribution.json`도 사이트 안에서 직접 만들고 수정할 수 있습니다. - 문서: [docs/admin-site.md](docs/admin-site.md) diff --git a/admin/data/catalog.json b/admin/data/catalog.json index 9c3320c..9bff7a3 100644 --- a/admin/data/catalog.json +++ b/admin/data/catalog.json @@ -6,10 +6,8 @@ "name": "Concatenation Lite", "kind": "modpack", "description": "기존 MRS 분배 인덱스를 사용하는 예시 프로필입니다. 프로필 카탈로그 구조가 동작하는지 확인할 때 사용할 수 있습니다.", - "details": "Mystic Red Space에서 운영하던 기존 분배 인덱스를 그대로 사용합니다.\n\n사용자는 이 항목을 라이브러리에 추가한 뒤 바로 실행할 수 있습니다.\n관리자는 distribution URL과 기본 접속 주소를 유지하거나 교체해서 실서비스용 항목으로 바꿀 수 있습니다.", - "distributionUrl": "https://cdn.mysticred.space/launcher/distribution.json", - "defaultServerAddress": "play.mysticred.space", - "allowCustomServerAddress": true + "details": "Mystic Red Space에서 운영하던 기존 분배 인덱스를 그대로 사용합니다.\n\n사용자는 이 항목을 라이브러리에 추가한 뒤 바로 실행할 수 있습니다.\n관리자는 distribution 파일만 교체해서 실서비스용 항목으로 바꿀 수 있습니다.", + "distributionUrl": "https://cdn.mysticred.space/launcher/distribution.json" }, { "id": "template-original-map", @@ -18,8 +16,6 @@ "description": "오리지널 맵용 템플릿입니다. vanilla 또는 맵 전용 distribution URL과 월드 ZIP 경로를 채우면 싱글플레이 빠른 실행에 사용할 수 있습니다.", "details": "관리자는 이 항목에 월드 ZIP과 클라이언트 distribution URL을 미리 등록합니다.\n\n사용자는 설치 페이지에서 설명을 읽고 라이브러리에 추가한 뒤, 라이브러리에서 맵 자료를 준비하고 바로 실행할 수 있습니다.", "distributionUrl": "https://example.com/launcher/vanilla-map-distribution.json", - "defaultServerAddress": "", - "allowCustomServerAddress": false, "worldArchiveUrl": "https://example.com/maps/original-map.zip", "worldDirectoryName": "Original Map" }, @@ -30,8 +26,6 @@ "description": "플러그인 맵 + 서버 접속용 템플릿입니다. 클라이언트 distribution, 서버 번들 ZIP, 서버 시작 명령, 선택형 터널 명령을 연결하면 됩니다.", "details": "관리자는 클라이언트 배포 파일, 서버 번들, 서버 시작 명령을 미리 등록합니다.\n\n사용자는 설치 페이지에서 상세 내용을 확인하고 라이브러리에 추가한 뒤, 라이브러리에서 직접 서버를 켜거나 수동 주소를 넣어 접속할 수 있습니다.", "distributionUrl": "https://example.com/launcher/server-pack-client-distribution.json", - "defaultServerAddress": "", - "allowCustomServerAddress": true, "serverBundleUrl": "https://example.com/serverpacks/plugin-world-server.zip", "serverDirectoryName": "plugin-world-server", "serverLaunchCommand": "java -jar server.jar nogui", diff --git a/admin/data/distributions/.gitkeep b/admin/data/distributions/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/admin/data/distributions/.gitkeep @@ -0,0 +1 @@ + diff --git a/admin/public/app.js b/admin/public/app.js index ace971f..551f51d 100644 --- a/admin/public/app.js +++ b/admin/public/app.js @@ -21,6 +21,14 @@ const duplicateProfileButton = document.getElementById('duplicateProfileButton') const deleteProfileButton = document.getElementById('deleteProfileButton') const mapSection = document.getElementById('mapSection') const serverPackSection = document.getElementById('serverPackSection') +const editDistributionButton = document.getElementById('editDistributionButton') +const createDistributionButton = document.getElementById('createDistributionButton') +const distributionEditorModal = document.getElementById('distributionEditorModal') +const distributionEditorHint = document.getElementById('distributionEditorHint') +const distributionEditorTextarea = document.getElementById('distributionEditorTextarea') +const closeDistributionEditorButton = document.getElementById('closeDistributionEditorButton') +const loadDistributionTemplateButton = document.getElementById('loadDistributionTemplateButton') +const saveDistributionFileButton = document.getElementById('saveDistributionFileButton') const fieldElements = { id: document.getElementById('field-id'), @@ -29,8 +37,6 @@ const fieldElements = { description: document.getElementById('field-description'), details: document.getElementById('field-details'), distributionUrl: document.getElementById('field-distributionUrl'), - defaultServerAddress: document.getElementById('field-defaultServerAddress'), - allowCustomServerAddress: document.getElementById('field-allowCustomServerAddress'), worldArchiveUrl: document.getElementById('field-worldArchiveUrl'), worldDirectoryName: document.getElementById('field-worldDirectoryName'), serverBundleUrl: document.getElementById('field-serverBundleUrl'), @@ -50,6 +56,10 @@ function slugify(value){ .replace(/^-+|-+$/g, '') } +function isRemoteUrl(value){ + return /^https?:\/\//i.test(String(value ?? '').trim()) +} + function createProfile(kind){ const timestamp = Date.now() return { @@ -59,8 +69,6 @@ function createProfile(kind){ description: '', details: '', distributionUrl: '', - defaultServerAddress: '', - allowCustomServerAddress: kind !== 'map', worldArchiveUrl: '', worldDirectoryName: '', serverBundleUrl: '', @@ -143,6 +151,27 @@ function syncKindSections(kind){ serverPackSection.hidden = kind !== 'server-pack' } +function updateDistributionEditorHint(profile, pathOverride){ + if(!profile){ + distributionEditorHint.textContent = '프로필에 연결할 distribution.json 내용을 사이트 안에서 직접 관리합니다.' + return + } + + const currentPath = String(pathOverride ?? profile.distributionUrl ?? '').trim() + + if(currentPath.length === 0){ + distributionEditorHint.textContent = '연결된 distribution 파일이 없습니다. 샘플을 불러오거나 새로 저장해서 이 프로필에 연결하세요.' + return + } + + if(isRemoteUrl(currentPath)){ + distributionEditorHint.textContent = `현재 값은 원격 URL입니다: ${currentPath} | 원격 URL은 여기서 직접 수정할 수 없습니다. 샘플을 불러온 뒤 새 파일로 저장하세요.` + return + } + + distributionEditorHint.textContent = `현재 연결된 distribution 파일: ${currentPath}` +} + function populateEditor(){ const profile = getSelectedProfile() const hasSelection = profile != null @@ -154,6 +183,7 @@ function populateEditor(){ if(!profile){ editorHint.textContent = '왼쪽에서 프로필을 선택하거나 새 프로필을 추가하세요.' + updateDistributionEditorHint(null) return } @@ -165,8 +195,6 @@ function populateEditor(){ fieldElements.description.value = profile.description ?? '' fieldElements.details.value = profile.details ?? '' fieldElements.distributionUrl.value = profile.distributionUrl ?? '' - fieldElements.defaultServerAddress.value = profile.defaultServerAddress ?? '' - fieldElements.allowCustomServerAddress.checked = profile.allowCustomServerAddress === true fieldElements.worldArchiveUrl.value = profile.worldArchiveUrl ?? '' fieldElements.worldDirectoryName.value = profile.worldDirectoryName ?? '' fieldElements.serverBundleUrl.value = profile.serverBundleUrl ?? '' @@ -178,6 +206,7 @@ function populateEditor(){ fieldElements.tunnelAddressRegex.value = profile.tunnelAddressRegex ?? '' syncKindSections(profile.kind) + updateDistributionEditorHint(profile) } function updateSelectedProfile(patch){ @@ -185,9 +214,14 @@ function updateSelectedProfile(patch){ if(!profile){ return } + Object.assign(profile, patch) markDirty(true) renderSidebar() + + if(Object.prototype.hasOwnProperty.call(patch, 'distributionUrl')){ + updateDistributionEditorHint(profile, patch.distributionUrl) + } } function bindTextField(fieldName){ @@ -214,14 +248,6 @@ function bindTextField(fieldName){ }) } -function bindCheckboxField(fieldName){ - fieldElements[fieldName].addEventListener('change', (event) => { - updateSelectedProfile({ - [fieldName]: event.target.checked - }) - }) -} - function bindNumberField(fieldName){ fieldElements[fieldName].addEventListener('input', (event) => { const value = Number.parseInt(event.target.value || '25565', 10) @@ -232,13 +258,15 @@ function bindNumberField(fieldName){ } function bindProfileForm(){ + profileEditorForm.addEventListener('submit', (event) => { + event.preventDefault() + }) + bindTextField('id') bindTextField('name') bindTextField('description') bindTextField('details') bindTextField('distributionUrl') - bindTextField('defaultServerAddress') - bindCheckboxField('allowCustomServerAddress') bindTextField('worldArchiveUrl') bindTextField('worldDirectoryName') bindTextField('serverBundleUrl') @@ -257,10 +285,7 @@ function bindProfileForm(){ profile.kind = event.target.value - if(profile.kind === 'map'){ - profile.allowCustomServerAddress = false - } - if(profile.kind !== 'server-pack' && !profile.serverDirectoryName){ + if(profile.kind === 'server-pack' && !profile.serverDirectoryName){ profile.serverDirectoryName = `${slugify(profile.id || profile.name) || 'profile'}-server` } @@ -312,6 +337,11 @@ async function uploadIntoField(targetField, accept){ fieldElements[targetField].value = result.file.path markDirty(true) renderSidebar() + + if(targetField === 'distributionUrl'){ + updateDistributionEditorHint(profile, result.file.path) + } + showStatus(`업로드 완료: ${result.file.path}`, 'success') } catch (error) { console.error(error) @@ -322,6 +352,152 @@ async function uploadIntoField(targetField, accept){ picker.click() } +function openDistributionEditorModal(){ + distributionEditorModal.hidden = false + document.body.style.overflow = 'hidden' +} + +function closeDistributionEditorModal(){ + distributionEditorModal.hidden = true + document.body.style.overflow = '' +} + +async function loadDistributionTemplate(){ + const response = await fetch('/api/distribution/template') + const result = await response.json() + if(!response.ok || result.ok !== true){ + throw new Error(result.message || 'distribution 샘플을 불러오지 못했습니다.') + } + + distributionEditorTextarea.value = result.content +} + +async function openDistributionEditor(mode){ + const profile = getSelectedProfile() + if(!profile){ + showStatus('먼저 프로필을 선택하세요.', 'error') + return + } + + openDistributionEditorModal() + distributionEditorTextarea.value = '' + updateDistributionEditorHint(profile) + + try { + const currentPath = String(profile.distributionUrl ?? '').trim() + + if(mode === 'create' || currentPath.length === 0){ + await loadDistributionTemplate() + updateDistributionEditorHint(profile, '') + showStatus('distribution 템플릿을 불러왔습니다.', 'success') + return + } + + if(isRemoteUrl(currentPath)){ + await loadDistributionTemplate() + updateDistributionEditorHint(profile, currentPath) + showStatus('원격 URL은 직접 수정할 수 없어 샘플을 대신 불러왔습니다.', 'info') + return + } + + showStatus('distribution 파일을 불러오는 중...', 'info') + const response = await fetch(`/api/distribution/content?path=${encodeURIComponent(currentPath)}`) + const result = await response.json() + if(!response.ok || result.ok !== true){ + throw new Error(result.message || 'distribution 파일을 불러오지 못했습니다.') + } + + distributionEditorTextarea.value = result.content + updateDistributionEditorHint(profile, currentPath) + showStatus('distribution 파일을 불러왔습니다.', 'success') + } catch (error) { + console.error(error) + showStatus(error instanceof Error ? error.message : 'distribution 파일을 불러오지 못했습니다.', 'error') + } +} + +async function saveDistributionFile(){ + const profile = getSelectedProfile() + if(!profile){ + showStatus('먼저 프로필을 선택하세요.', 'error') + return + } + + try { + saveDistributionFileButton.disabled = true + showStatus('distribution 파일 저장 중...', 'info') + + const response = await fetch('/api/distribution/save', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + profileId: profile.id, + content: distributionEditorTextarea.value + }) + }) + const result = await response.json() + if(!response.ok || result.ok !== true){ + throw new Error(result.message || 'distribution 파일 저장에 실패했습니다.') + } + + profile.distributionUrl = result.file.path + fieldElements.distributionUrl.value = result.file.path + markDirty(true) + renderSidebar() + populateEditor() + closeDistributionEditorModal() + showStatus('distribution 파일을 저장했습니다. 카탈로그 저장을 누르면 런처에 반영됩니다.', 'success') + } catch (error) { + console.error(error) + showStatus(error instanceof Error ? error.message : 'distribution 파일 저장에 실패했습니다.', 'error') + } finally { + saveDistributionFileButton.disabled = false + } +} + +function bindDistributionEditor(){ + editDistributionButton.addEventListener('click', async () => { + await openDistributionEditor('edit') + }) + + createDistributionButton.addEventListener('click', async () => { + await openDistributionEditor('create') + }) + + closeDistributionEditorButton.addEventListener('click', () => { + closeDistributionEditorModal() + }) + + loadDistributionTemplateButton.addEventListener('click', async () => { + try { + await loadDistributionTemplate() + updateDistributionEditorHint(getSelectedProfile(), '') + showStatus('distribution 템플릿을 다시 불러왔습니다.', 'success') + } catch (error) { + console.error(error) + showStatus(error instanceof Error ? error.message : 'distribution 템플릿을 불러오지 못했습니다.', 'error') + } + }) + + saveDistributionFileButton.addEventListener('click', async () => { + await saveDistributionFile() + }) + + distributionEditorModal.addEventListener('click', (event) => { + if(event.target === distributionEditorModal){ + closeDistributionEditorModal() + } + }) + + document.addEventListener('keydown', (event) => { + if(event.key === 'Escape' && distributionEditorModal.hidden === false){ + closeDistributionEditorModal() + } + }) +} + async function loadMeta(){ const response = await fetch('/api/meta') const meta = await response.json() @@ -450,6 +626,7 @@ function bindTopLevelActions(){ async function bootstrap(){ bindProfileForm() + bindDistributionEditor() bindTopLevelActions() try { diff --git a/admin/public/index.html b/admin/public/index.html index fbc3bb5..46b5299 100644 --- a/admin/public/index.html +++ b/admin/public/index.html @@ -99,20 +99,17 @@
- - +
+ distribution.json은 여기서 직접 업로드하거나 JSON 편집기로 새로 만들 수 있습니다. +
@@ -184,6 +181,24 @@ + + diff --git a/admin/public/styles.css b/admin/public/styles.css index dd2a892..cba6021 100644 --- a/admin/public/styles.css +++ b/admin/public/styles.css @@ -278,11 +278,17 @@ textarea { .uploadField { display: grid; - grid-template-columns: minmax(0, 1fr) auto; + grid-template-columns: minmax(0, 1fr) auto auto auto; gap: 10px; align-items: center; } +.fieldHelpText { + color: var(--muted); + font-size: 14px; + line-height: 1.5; +} + .primaryAction, .secondaryAction, .dangerAction { @@ -346,6 +352,53 @@ textarea { background: rgba(240, 191, 87, 0.1); } +.modalBackdrop { + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: rgba(5, 6, 6, 0.72); + backdrop-filter: blur(12px); + z-index: 20; +} + +.modalPanel { + display: flex; + flex-direction: column; + gap: 16px; + width: min(1100px, 100%); + max-height: calc(100vh - 48px); + padding: 24px; + border: 1px solid var(--line); + border-radius: 24px; + background: #111412; + box-shadow: var(--shadow); +} + +.modalHeader { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 16px; +} + +.distributionTextarea { + min-height: 420px; + resize: vertical; + font-family: "JetBrains Mono", "Fira Code", monospace; + font-size: 13px; + line-height: 1.55; +} + +.modalActions { + display: flex; + justify-content: flex-end; + gap: 10px; + flex-wrap: wrap; +} + @media (max-width: 1200px) { .adminShell { grid-template-columns: 1fr; diff --git a/admin/server.js b/admin/server.js index f4d1911..38cb9c5 100644 --- a/admin/server.js +++ b/admin/server.js @@ -8,9 +8,11 @@ const PORT = Number.parseInt(process.env.LAUNCHER_ADMIN_PORT || '8787', 10) const PROJECT_ROOT = path.resolve(__dirname, '..') const RUNTIME_DATA_DIR = path.join(__dirname, 'data') const UPLOADS_DIR = path.join(RUNTIME_DATA_DIR, 'uploads') +const DISTRIBUTIONS_DIR = path.join(RUNTIME_DATA_DIR, 'distributions') const RUNTIME_CATALOG_PATH = path.join(RUNTIME_DATA_DIR, 'catalog.json') const LAUNCHER_CATALOG_PATH = path.join(PROJECT_ROOT, 'app', 'assets', 'launcher', 'catalog.json') const PUBLIC_DIR = path.join(__dirname, 'public') +const SAMPLE_DISTRIBUTION_PATH = path.join(PROJECT_ROOT, 'docs', 'sample_distribution.json') const PROFILE_KINDS = new Set(['modpack', 'map', 'server-pack']) @@ -24,10 +26,6 @@ function normalizeMultilineText(value){ : '' } -function normalizeBoolean(value){ - return value === true -} - function normalizePort(value){ const port = Number.parseInt(String(value ?? ''), 10) if(Number.isFinite(port) && port >= 1 && port <= 65535){ @@ -36,6 +34,25 @@ function normalizePort(value){ return 25565 } +function resolveSafeProjectPath(relativePath){ + const resolvedPath = path.resolve(PROJECT_ROOT, relativePath) + if(!resolvedPath.startsWith(PROJECT_ROOT + path.sep) && resolvedPath !== PROJECT_ROOT){ + throw new Error('허용되지 않은 경로입니다.') + } + return resolvedPath +} + +function createDistributionFileName(profileId){ + const safeId = String(profileId ?? 'distribution') + .trim() + .toLowerCase() + .replace(/[^a-z0-9._-]+/g, '-') + .replace(/-+/g, '-') + .replace(/^[-_.]+|[-_.]+$/g, '') + + return `${safeId.length > 0 ? safeId : 'distribution'}.distribution.json` +} + function sanitizeProfile(rawProfile, index){ const kind = PROFILE_KINDS.has(rawProfile?.kind) ? rawProfile.kind : 'modpack' const sanitized = { @@ -44,9 +61,7 @@ function sanitizeProfile(rawProfile, index){ kind, description: normalizeText(rawProfile?.description), details: normalizeMultilineText(rawProfile?.details), - distributionUrl: normalizeText(rawProfile?.distributionUrl), - defaultServerAddress: normalizeText(rawProfile?.defaultServerAddress), - allowCustomServerAddress: normalizeBoolean(rawProfile?.allowCustomServerAddress) + distributionUrl: normalizeText(rawProfile?.distributionUrl) } if(kind === 'map'){ @@ -87,6 +102,7 @@ function toProjectRelativePath(targetPath){ async function ensureRuntimeCatalog(){ await fs.ensureDir(RUNTIME_DATA_DIR) await fs.ensureDir(UPLOADS_DIR) + await fs.ensureDir(DISTRIBUTIONS_DIR) if(!(await fs.pathExists(RUNTIME_CATALOG_PATH))){ if(await fs.pathExists(LAUNCHER_CATALOG_PATH)){ @@ -143,6 +159,7 @@ async function start(){ app.use(express.json({ limit: '5mb' })) app.use('/uploads', express.static(UPLOADS_DIR)) + app.use('/admin/data/distributions', express.static(DISTRIBUTIONS_DIR)) app.get('/api/meta', async (_req, res) => { res.json({ @@ -150,10 +167,53 @@ async function start(){ port: PORT, runtimeCatalogPath: toProjectRelativePath(RUNTIME_CATALOG_PATH), launcherCatalogPath: toProjectRelativePath(LAUNCHER_CATALOG_PATH), - localCatalogUrl: `http://${HOST}:${PORT}/catalog.json` + localCatalogUrl: `http://${HOST}:${PORT}/catalog.json`, + distributionsPath: toProjectRelativePath(DISTRIBUTIONS_DIR) }) }) + app.get('/api/distribution/template', async (_req, res, next) => { + try { + const content = await fs.readFile(SAMPLE_DISTRIBUTION_PATH, 'utf8') + res.json({ + ok: true, + content + }) + } catch (error) { + next(error) + } + }) + + app.get('/api/distribution/content', async (req, res, next) => { + try { + const requestedPath = normalizeText(req.query.path) + if(requestedPath.length === 0){ + res.status(400).json({ + ok: false, + message: '불러올 distribution 경로가 없습니다.' + }) + return + } + + if(/^https?:\/\//i.test(requestedPath)){ + res.status(400).json({ + ok: false, + message: '원격 URL은 사이트에서 직접 수정할 수 없습니다. 업로드하거나 새로 생성하세요.' + }) + return + } + + const resolvedPath = resolveSafeProjectPath(requestedPath) + const content = await fs.readFile(resolvedPath, 'utf8') + res.json({ + ok: true, + content + }) + } catch (error) { + next(error) + } + }) + app.get('/api/catalog', async (_req, res, next) => { try { res.json(await readCatalog()) @@ -201,6 +261,38 @@ async function start(){ } }) + app.post('/api/distribution/save', async (req, res, next) => { + try { + const profileId = normalizeText(req.body?.profileId) + const rawContent = typeof req.body?.content === 'string' ? req.body.content : '' + + if(profileId.length === 0){ + res.status(400).json({ + ok: false, + message: '프로필 ID가 필요합니다.' + }) + return + } + + const parsed = JSON.parse(rawContent) + const fileName = createDistributionFileName(profileId) + const targetPath = path.join(DISTRIBUTIONS_DIR, fileName) + const relativePath = toProjectRelativePath(targetPath) + + await fs.writeFile(targetPath, JSON.stringify(parsed, null, 2) + '\n', 'utf8') + + res.json({ + ok: true, + file: { + path: relativePath, + localUrl: `http://${HOST}:${PORT}/${relativePath}` + } + }) + } catch (error) { + next(error) + } + }) + app.get('/catalog.json', async (_req, res, next) => { try { res.sendFile(RUNTIME_CATALOG_PATH) diff --git a/app/assets/launcher/catalog.json b/app/assets/launcher/catalog.json index 9c3320c..9bff7a3 100644 --- a/app/assets/launcher/catalog.json +++ b/app/assets/launcher/catalog.json @@ -6,10 +6,8 @@ "name": "Concatenation Lite", "kind": "modpack", "description": "기존 MRS 분배 인덱스를 사용하는 예시 프로필입니다. 프로필 카탈로그 구조가 동작하는지 확인할 때 사용할 수 있습니다.", - "details": "Mystic Red Space에서 운영하던 기존 분배 인덱스를 그대로 사용합니다.\n\n사용자는 이 항목을 라이브러리에 추가한 뒤 바로 실행할 수 있습니다.\n관리자는 distribution URL과 기본 접속 주소를 유지하거나 교체해서 실서비스용 항목으로 바꿀 수 있습니다.", - "distributionUrl": "https://cdn.mysticred.space/launcher/distribution.json", - "defaultServerAddress": "play.mysticred.space", - "allowCustomServerAddress": true + "details": "Mystic Red Space에서 운영하던 기존 분배 인덱스를 그대로 사용합니다.\n\n사용자는 이 항목을 라이브러리에 추가한 뒤 바로 실행할 수 있습니다.\n관리자는 distribution 파일만 교체해서 실서비스용 항목으로 바꿀 수 있습니다.", + "distributionUrl": "https://cdn.mysticred.space/launcher/distribution.json" }, { "id": "template-original-map", @@ -18,8 +16,6 @@ "description": "오리지널 맵용 템플릿입니다. vanilla 또는 맵 전용 distribution URL과 월드 ZIP 경로를 채우면 싱글플레이 빠른 실행에 사용할 수 있습니다.", "details": "관리자는 이 항목에 월드 ZIP과 클라이언트 distribution URL을 미리 등록합니다.\n\n사용자는 설치 페이지에서 설명을 읽고 라이브러리에 추가한 뒤, 라이브러리에서 맵 자료를 준비하고 바로 실행할 수 있습니다.", "distributionUrl": "https://example.com/launcher/vanilla-map-distribution.json", - "defaultServerAddress": "", - "allowCustomServerAddress": false, "worldArchiveUrl": "https://example.com/maps/original-map.zip", "worldDirectoryName": "Original Map" }, @@ -30,8 +26,6 @@ "description": "플러그인 맵 + 서버 접속용 템플릿입니다. 클라이언트 distribution, 서버 번들 ZIP, 서버 시작 명령, 선택형 터널 명령을 연결하면 됩니다.", "details": "관리자는 클라이언트 배포 파일, 서버 번들, 서버 시작 명령을 미리 등록합니다.\n\n사용자는 설치 페이지에서 상세 내용을 확인하고 라이브러리에 추가한 뒤, 라이브러리에서 직접 서버를 켜거나 수동 주소를 넣어 접속할 수 있습니다.", "distributionUrl": "https://example.com/launcher/server-pack-client-distribution.json", - "defaultServerAddress": "", - "allowCustomServerAddress": true, "serverBundleUrl": "https://example.com/serverpacks/plugin-world-server.zip", "serverDirectoryName": "plugin-world-server", "serverLaunchCommand": "java -jar server.jar nogui", diff --git a/docs/admin-site.md b/docs/admin-site.md index 51a8cee..e49c3de 100644 --- a/docs/admin-site.md +++ b/docs/admin-site.md @@ -16,11 +16,23 @@ npm run admin - 프로필 추가 / 수정 / 삭제 / 복제 - `modpack`, `map`, `server-pack` 종류별 입력 폼 -- `distribution.json`, 맵 ZIP, 서버 번들 ZIP 업로드 +- `distribution.json` 업로드 / 새로 만들기 / 직접 편집 +- 맵 ZIP, 서버 번들 ZIP 업로드 - 저장 시 아래 두 파일을 동시에 갱신 - `admin/data/catalog.json` - `app/assets/launcher/catalog.json` +## distribution 편집 + +- `distribution 파일` 칸에서 JSON 업로드 가능 +- `JSON 편집` 버튼으로 현재 연결된 로컬 `distribution.json` 수정 가능 +- `새로 만들기` 버튼으로 샘플 템플릿에서 새 `distribution.json` 생성 가능 +- 저장된 distribution 파일은 아래에 생성됩니다. + + - `admin/data/distributions/` + +- 접속주소 필드는 관리자 사이트에서 다루지 않습니다. 런처 내부 접속 흐름과 분리해서 프로필 자료만 관리합니다. + ## 업로드 동작 업로드 버튼으로 올린 파일은 아래에 저장됩니다. @@ -43,6 +55,6 @@ npm run admin ## 추천 운영 방식 -1. 관리자 사이트에서 프로필과 자료 경로를 입력 +1. 관리자 사이트에서 프로필과 자료 파일을 입력 2. 로컬 런처에서 실제 표시와 실행 확인 3. 이후 필요하면 업로드 경로를 공개 URL 기반으로 확장 diff --git a/docs/launcher-catalog.md b/docs/launcher-catalog.md index 40b7557..28e88ff 100644 --- a/docs/launcher-catalog.md +++ b/docs/launcher-catalog.md @@ -19,9 +19,7 @@ "kind": "modpack", "description": "설명", "details": "설치 페이지 상세 패널에 표시할 긴 설명", - "distributionUrl": "https://example.com/launcher/distribution.json", - "defaultServerAddress": "example.com:25565", - "allowCustomServerAddress": true + "distributionUrl": "admin/data/distributions/my-modpack.distribution.json" }, { "id": "my-map", @@ -29,10 +27,9 @@ "kind": "map", "description": "싱글플레이 월드", "details": "월드와 플레이 방식에 대한 상세 설명", - "distributionUrl": "https://example.com/launcher/vanilla-map-distribution.json", + "distributionUrl": "admin/data/distributions/my-map.distribution.json", "worldArchiveUrl": "https://example.com/worlds/my-map.zip", - "worldDirectoryName": "My Map", - "allowCustomServerAddress": false + "worldDirectoryName": "My Map" }, { "id": "my-server-pack", @@ -40,14 +37,13 @@ "kind": "server-pack", "description": "클라이언트 + 로컬 서버 번들", "details": "서버 실행 방법, 권장 인원, 접속 방식 등 상세 설명", - "distributionUrl": "https://example.com/launcher/server-pack-client-distribution.json", + "distributionUrl": "admin/data/distributions/my-server-pack.distribution.json", "serverBundleUrl": "https://example.com/serverpacks/my-server-pack.zip", "serverDirectoryName": "my-server-pack", "serverLaunchCommand": "java -jar server.jar nogui", "serverPort": 25565, "tunnelCommand": "playit-cli --port ${port}", - "tunnelAddressRegex": "([a-zA-Z0-9.-]+:\\d+)", - "allowCustomServerAddress": true + "tunnelAddressRegex": "([a-zA-Z0-9.-]+:\\d+)" } ] } @@ -60,9 +56,7 @@ - `kind`: `modpack`, `map`, `server-pack` - `description`: 표시 설명 - `details`: 설치 페이지 상세 패널에 표시할 긴 설명 -- `distributionUrl`: Helios distribution.json URL 또는 로컬 경로 -- `defaultServerAddress`: 기본 자동 접속 주소 -- `allowCustomServerAddress`: 사용자가 라이브러리에서 주소를 덮어쓸 수 있는지 여부 +- `distributionUrl`: Helios distribution.json URL 또는 로컬 경로. 관리자 사이트에서 직접 만들거나 업로드 가능 - `worldArchiveUrl`: `kind: map` 일 때 사용할 월드 ZIP 또는 로컬 경로 - `worldDirectoryName`: 게임 `saves/` 아래에 설치될 월드 폴더 이름 - `serverBundleUrl`: `kind: server-pack` 일 때 사용할 서버 ZIP 또는 로컬 디렉터리/경로 diff --git a/docs/sample_distribution.json b/docs/sample_distribution.json index 57bdb24..90e0a30 100644 --- a/docs/sample_distribution.json +++ b/docs/sample_distribution.json @@ -1,1584 +1,16 @@ { - "version": "1.0.0", - "discord": { - "clientId": "385581240906022916", - "smallImageText": "WesterosCraft", - "smallImageKey": "seal-circle" - }, - "java": { - "oracle": "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html" - }, - "rss": "https://forum.westeroscraft.com/forum/news.2/index.rss", - "servers": [ - { - "id": "WesterosCraft-1.11.2", - "name": "WesterosCraft Production Server", - "description": "Main WesterosCraft server. Connect to enter the Realm.", - "icon": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-prod.png", - "version": "3.9.4", - "address": "mc.westeroscraft.com", - "minecraftVersion": "1.11.2", - "discord": { - "shortId": "Production", - "largeImageText": "WesterosCraft Production Server", - "largeImageKey": "server-prod" - }, - "mainServer": true, - "autoconnect": true, - "modules": [ - { - "id": "net.minecraftforge:forge:1.11.2-13.20.1.2429", - "name": "Minecraft Forge", - "type": "ForgeHosted", - "artifact": { - "size": 4450992, - "MD5": "3fcc9b0104f0261397d3cc897e55a1c5", - "url": "http://files.minecraftforge.net/maven/net/minecraftforge/forge/1.11.2-13.20.1.2429/forge-1.11.2-13.20.1.2429-universal.jar" - }, - "subModules": [ - { - "id": "net.minecraft:launchwrapper:1.12", - "name": "Mojang (LaunchWrapper)", - "type": "Library", - "artifact": { - "size": 32999, - "MD5": "934b2d91c7c5be4a49577c9e6b40e8da", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/launchwrapper-1.12.jar" - } - }, - { - "id": "org.ow2.asm:asm-all:5.0.3", - "name": "Mojang (ASM)", - "type": "Library", - "artifact": { - "size": 241639, - "MD5": "c5cc4613bbdfba3ccf5f0ab85390d0b8", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/asm-all-5.0.3.jar" - } - }, - { - "id": "org.scala-lang:scala-library:2.11.1@jar.pack.xz", - "name": "Minecraft Forge (scala-library)", - "type": "Library", - "artifact": { - "size": 1474672, - "MD5": "379c15c4f724421c6d5d7aecedaf87a6", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-library-2.11.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-compiler:2.11.1@jar.pack.xz", - "name": "Minecraft Forge (scala-compiler)", - "type": "Library", - "artifact": { - "size": 3076920, - "MD5": "7d89e952f2d5c74577310cd2c28e3f20", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-compiler-2.11.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-actors-migration_2.11:1.1.0@jar.pack.xz", - "name": "Minecraft Forge (scala-actors-migration)", - "type": "Library", - "artifact": { - "size": 21324, - "MD5": "04e3428b2600ace33c7ae2bf1f6c0a4c", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-actors-migration_2.11-1.1.0.jar.pack.xz" - } - }, - { - "id": "org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2@jar.pack.xz", - "name": "Minecraft Forge (scala-continuations-library)", - "type": "Library", - "artifact": { - "size": 7956, - "MD5": "ed9b1d27aba8ac4090a3749c4dfc895a", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-continuations-library_2.11-1.0.2.jar.pack.xz" - } - }, - { - "id": "org.scala-lang.plugins:scala-continuations-plugin_2.11.1:1.0.2@jar.pack.xz", - "name": "Minecraft Forge (scala-continuations-plugin)", - "type": "Library", - "artifact": { - "size": 46140, - "MD5": "a8232db22a72a981de6b1399eb86dff7", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-continuations-plugin_2.11.1-1.0.2.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-parser-combinators_2.11:1.0.1@jar.pack.xz", - "name": "Minecraft Forge (scala-parser-combinators)", - "type": "Library", - "artifact": { - "size": 85568, - "MD5": "2e50a7df17680daadacca69f07f8a16d", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-parser-combinators_2.11-1.0.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-reflect:2.11.1@jar.pack.xz", - "name": "Minecraft Forge (scala-reflect)", - "type": "Library", - "artifact": { - "size": 1070312, - "MD5": "84e5dc81c10e2bd74c579c9d0332fdd9", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-reflect-2.11.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-swing_2.11:1.0.1", - "name": "Minecraft Forge (scala-swing)", - "type": "Library", - "artifact": { - "size": 736795, - "MD5": "1d360289e697022a3f57abaad344b28f", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-swing_2.11-1.0.1.jar" - } - }, - { - "id": "org.scala-lang:scala-xml_2.11:1.0.2@jar.pack.xz", - "name": "Minecraft Forge (scala-xml)", - "type": "Library", - "artifact": { - "size": 217812, - "MD5": "cc891b094a4c32dedc56bfefe9b072ff", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-xml_2.11-1.0.2.jar.pack.xz" - } - }, - { - "id": "com.typesafe.akka:akka-actor_2.11:2.3.3@jar.pack.xz", - "name": "Minecraft Forge (akka-actor)", - "type": "Library", - "artifact": { - "size": 746612, - "MD5": "25cb22c3078e9fb3f7a861c912924862", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/akka-actor_2.11-2.3.3.jar.pack.xz" - } - }, - { - "id": "com.typesafe:config:1.2.1@jar.pack.xz", - "name": "Minecraft Forge (typesafe-config)", - "type": "Library", - "artifact": { - "size": 56636, - "MD5": "10ec4ccabc4e68aac9cf87165ead5d7d", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/config-1.2.1.jar.pack.xz" - } - }, - { - "id": "lzma:lzma:0.0.1", - "name": "Mojang (LZMA)", - "type": "Library", - "artifact": { - "size": 5762, - "MD5": "a3e3c3186e41c4a1a3027ba2bb23cdc6", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/lzma-0.0.1.jar" - } - }, - { - "id": "net.sf.trove4j:trove4j:3.0.3", - "name": "Trove4J", - "type": "Library", - "artifact": { - "size": 2523218, - "MD5": "8fc4d4e0129244f9fd39650c5f30feb2", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/trove4j-3.0.3.jar" - } - }, - { - "id": "java3d:vecmath:1.5.2", - "name": "Vecmath", - "type": "Library", - "artifact": { - "size": 318956, - "MD5": "e5d2b7f46c4800a32f62ce75676a5710", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/vecmath-1.5.2.jar" - } - }, - { - "id": "net.sf.jopt-simple:jopt-simple:4.6", - "name": "Jopt-simple", - "type": "Library", - "artifact": { - "size": 62477, - "MD5": "13560a58a79b46b82057686543e8d727", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/jopt-simple-4.6.jar" - } - }, - { - "id": "net.minecraftforge:MercuriusUpdater:1.11.2", - "name": "MercuriusUpdater", - "type": "Library", - "artifact": { - "size": 15146, - "MD5": "7556d06064ebbfa3b334a15092d725d0", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/MercuriusUpdater-1.11.2.jar" - } - } - ] - }, - { - "id": "com.westeroscraft:westerosblocks:3.2.0-beta-1-190", - "name": "WesterosBlocks", - "type": "ForgeMod", - "artifact": { - "size": 17376788, - "MD5": "370f4f1804c93f498f31af8dac509605", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/WesterosBlocks.jar" - } - }, - { - "id": "com.westeroscraft:westeroscraftrp:2019-01-21", - "name": "WesterosCraft Resource Pack", - "type": "File", - "artifact": { - "size": 46999843, - "MD5": "309eb9e5296e9f55cfecb7d4058245a4", - "path": "resourcepacks/WesterosCraft.zip", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/resourcepacks/WesterosCraft.zip" - } - }, - { - "id": "net.optifine:optifine:1.11.2_HD_U_C7", - "name": "Optifine", - "type": "ForgeMod", - "artifact": { - "size": 2254712, - "MD5": "0dd7761e908f9b245bb0dc0fac5649f5", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/OptiFine.jar" - } - }, - { - "id": "mezz:jei:1.11.2-4.5.0.290", - "name": "JustEnoughItems", - "type": "ForgeMod", - "artifact": { - "size": 538740, - "MD5": "f4d931f6db6210621a86fa1e7eae8016", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/jei.jar" - } - }, - { - "id": "org.blockartistry:dynsurround:1.11.2-3.4.9.3", - "name": "DynamicSurroundings", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 22291385, - "MD5": "65403c66d8b3655b372f58047941d206", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/DynamicSurroundings.jar" - }, - "subModules": [ - { - "id": "dsurround.cfg", - "name": "DynamicSurroundings General Configuration File", - "type": "File", - "artifact": { - "size": 20258, - "path": "/config/dsurround/dsurround.cfg", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/config/dsurround/dsurround.cfg" - } - }, - { - "id": "westeros.json", - "name": "DynamicSurroundings WesterosCraft Configuration File", - "type": "File", - "artifact": { - "size": 608, - "MD5": "44eab112ff24d0bd29974c270de868ba", - "path": "/config/dsurround/westeros.json", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/config/dsurround/westeros.json" - } - } - ] - }, - { - "id": "octarine-noise:betterfoliage:1.11.2-2.1.10", - "name": "BetterFoliage", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 4675903, - "MD5": "522fdf73b6e4343cb6243872fb7b4b6c", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/BetterFoliage.jar" - }, - "subModules": [ - { - "id": "betterfoliage.cfg", - "name": "BetterFoliage Configuration File", - "type": "File", - "artifact": { - "size": 7878, - "MD5": "6dd38f873c4129af05a2d6c500cbe954", - "path": "/config/betterfoliage.cfg", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/config/betterfoliage.cfg" - } - } - ] - }, - { - "id": "techbrew:journeymap:1.11.2-5.4.7", - "name": "JourneyMap", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 1735525, - "MD5": "1c3380502eb7b9a495581b2402d144df", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/journeymap.jar" - } - }, - { - "id": "com.github.hexomod:worldeditcuife2:2.1.1-mf-1.11.2-13.20.0.2228", - "name": "WorldEditCUI", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 461691, - "MD5": "439f82b69f3464969163c188818c677b", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/worldeditcuife.jar" - } - }, - { - "id": "mcp.mobius:waila:1.7.1_1.11.2", - "name": "Waila", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 542744, - "MD5": "26258a3557bf333e8f4ce8b1e9481031", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/Waila.jar" - } - }, - { - "id": "com.mumfrey:liteloader:1.11.2", - "name": "Liteloader", - "type": "LiteLoader", - "required": { - "value": false, - "def": false - }, - "artifact": { - "size": 1685422, - "MD5": "3a98b5ed95810bf164e71c1a53be568d", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/liteloader.jar" - }, - "subModules": [ - { - "id": "com.mumfrey:macrokeybindmod:0.14.4-1.11.2@litemod", - "name": "Macro/Keybind Mod", - "type": "LiteMod", - "required": { - "value": false, - "def": false - }, - "artifact": { - "size": 1670811, - "MD5": "16080785577b391d426c62c8d3138558", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/macrokeybindmod.litemod" - } - } - ] - }, - { - "id": "com.sonicether:seus:11.0", - "name": "Sonic Ether's Unbelievable Shaders", - "type": "File", - "artifact": { - "size": 175159, - "MD5": "bfa8c31d1da8131b59917bb2460205b1", - "path": "shaderpacks/SEUS v11.0.zip", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/shaderpacks/SEUS.zip" - } - }, - { - "id": "options.txt", - "name": "Default Client Options", - "type": "File", - "artifact": { - "size": 1973, - "path": "options.txt", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/options-1.11.2.txt" - } - }, - { - "id": "servers.dat", - "name": "Saved Client Servers", - "type": "File", - "artifact": { - "size": 84, - "MD5": "71d99e229d7d2b8d2a6423e46832a4b8", - "path": "servers.dat", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/servers.dat" - } - } - ] - }, - { - "id": "WesterosCraftTest-1.11.2", - "name": "WesterosCraft Test Server", - "description": "Main testing server. Experimental changes are live here.", - "icon": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-test.png", - "version": "3.9.4", - "address": "mc.westeroscraft.com:4444", - "minecraftVersion": "1.11.2", - "discord": { - "shortId": "Test Server", - "largeImageText": "WesterosCraft Test Server", - "largeImageKey": "server-test" - }, - "mainServer": false, - "autoconnect": true, - "modules": [ - { - "id": "net.minecraftforge:forge:1.11.2-13.20.1.2476", - "name": "Minecraft Forge", - "type": "ForgeHosted", - "artifact": { - "size": 4455536, - "MD5": "7cef816cc01d53a04a180f0214d2982a", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/forge-1.11.2-13.20.1.2476-universal.jar" - }, - "subModules": [ - { - "id": "net.minecraft:launchwrapper:1.12", - "name": "Mojang (LaunchWrapper)", - "type": "Library", - "artifact": { - "size": 32999, - "MD5": "934b2d91c7c5be4a49577c9e6b40e8da", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/launchwrapper-1.12.jar" - } - }, - { - "id": "org.ow2.asm:asm-all:5.0.3", - "name": "Mojang (ASM)", - "type": "Library", - "artifact": { - "size": 241639, - "MD5": "c5cc4613bbdfba3ccf5f0ab85390d0b8", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/asm-all-5.0.3.jar" - } - }, - { - "id": "org.scala-lang:scala-library:2.11.1@jar.pack.xz", - "name": "Minecraft Forge (scala-library)", - "type": "Library", - "artifact": { - "size": 1474672, - "MD5": "379c15c4f724421c6d5d7aecedaf87a6", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-library-2.11.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-compiler:2.11.1@jar.pack.xz", - "name": "Minecraft Forge (scala-compiler)", - "type": "Library", - "artifact": { - "size": 3076920, - "MD5": "7d89e952f2d5c74577310cd2c28e3f20", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-compiler-2.11.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-actors-migration_2.11:1.1.0@jar.pack.xz", - "name": "Minecraft Forge (scala-actors-migration)", - "type": "Library", - "artifact": { - "size": 21324, - "MD5": "04e3428b2600ace33c7ae2bf1f6c0a4c", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-actors-migration_2.11-1.1.0.jar.pack.xz" - } - }, - { - "id": "org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2@jar.pack.xz", - "name": "Minecraft Forge (scala-continuations-library)", - "type": "Library", - "artifact": { - "size": 7956, - "MD5": "ed9b1d27aba8ac4090a3749c4dfc895a", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-continuations-library_2.11-1.0.2.jar.pack.xz" - } - }, - { - "id": "org.scala-lang.plugins:scala-continuations-plugin_2.11.1:1.0.2@jar.pack.xz", - "name": "Minecraft Forge (scala-continuations-plugin)", - "type": "Library", - "artifact": { - "size": 46140, - "MD5": "a8232db22a72a981de6b1399eb86dff7", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-continuations-plugin_2.11.1-1.0.2.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-parser-combinators_2.11:1.0.1@jar.pack.xz", - "name": "Minecraft Forge (scala-parser-combinators)", - "type": "Library", - "artifact": { - "size": 85568, - "MD5": "2e50a7df17680daadacca69f07f8a16d", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-parser-combinators_2.11-1.0.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-reflect:2.11.1@jar.pack.xz", - "name": "Minecraft Forge (scala-reflect)", - "type": "Library", - "artifact": { - "size": 1070312, - "MD5": "84e5dc81c10e2bd74c579c9d0332fdd9", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-reflect-2.11.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-swing_2.11:1.0.1", - "name": "Minecraft Forge (scala-swing)", - "type": "Library", - "artifact": { - "size": 736795, - "MD5": "1d360289e697022a3f57abaad344b28f", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-swing_2.11-1.0.1.jar" - } - }, - { - "id": "org.scala-lang:scala-xml_2.11:1.0.2@jar.pack.xz", - "name": "Minecraft Forge (scala-xml)", - "type": "Library", - "artifact": { - "size": 217812, - "MD5": "cc891b094a4c32dedc56bfefe9b072ff", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/scala-xml_2.11-1.0.2.jar.pack.xz" - } - }, - { - "id": "com.typesafe.akka:akka-actor_2.11:2.3.3@jar.pack.xz", - "name": "Minecraft Forge (akka-actor)", - "type": "Library", - "artifact": { - "size": 746612, - "MD5": "25cb22c3078e9fb3f7a861c912924862", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/akka-actor_2.11-2.3.3.jar.pack.xz" - } - }, - { - "id": "com.typesafe:config:1.2.1@jar.pack.xz", - "name": "Minecraft Forge (typesafe-config)", - "type": "Library", - "artifact": { - "size": 56636, - "MD5": "10ec4ccabc4e68aac9cf87165ead5d7d", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/config-1.2.1.jar.pack.xz" - } - }, - { - "id": "lzma:lzma:0.0.1", - "name": "Mojang (LZMA)", - "type": "Library", - "artifact": { - "size": 5762, - "MD5": "a3e3c3186e41c4a1a3027ba2bb23cdc6", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/lzma-0.0.1.jar" - } - }, - { - "id": "net.sf.trove4j:trove4j:3.0.3", - "name": "Trove4J", - "type": "Library", - "artifact": { - "size": 2523218, - "MD5": "8fc4d4e0129244f9fd39650c5f30feb2", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/trove4j-3.0.3.jar" - } - }, - { - "id": "java3d:vecmath:1.5.2", - "name": "Vecmath", - "type": "Library", - "artifact": { - "size": 318956, - "MD5": "e5d2b7f46c4800a32f62ce75676a5710", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/vecmath-1.5.2.jar" - } - }, - { - "id": "net.sf.jopt-simple:jopt-simple:4.6", - "name": "Jopt-simple", - "type": "Library", - "artifact": { - "size": 62477, - "MD5": "13560a58a79b46b82057686543e8d727", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/jopt-simple-4.6.jar" - } - }, - { - "id": "net.minecraftforge:MercuriusUpdater:1.11.2", - "name": "MercuriusUpdater", - "type": "Library", - "artifact": { - "size": 15146, - "MD5": "7556d06064ebbfa3b334a15092d725d0", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/MercuriusUpdater-1.11.2.jar" - } - } - ] - }, - { - "id": "com.westeroscraft:westerosblocks:3.2.0-beta-1-190", - "name": "WesterosBlocks", - "type": "ForgeMod", - "artifact": { - "size": 17376788, - "MD5": "370f4f1804c93f498f31af8dac509605", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/WesterosBlocks.jar" - } - }, - { - "id": "com.westeroscraft:westeroscraftrp:2019-01-21", - "name": "WesterosCraft Resource Pack", - "type": "File", - "artifact": { - "size": 46999843, - "MD5": "309eb9e5296e9f55cfecb7d4058245a4", - "path": "resourcepacks/WesterosCraft.zip", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/resourcepacks/WesterosCraft.zip" - } - }, - { - "id": "net.optifine:optifine:1.11.2_HD_U_C7", - "name": "Optifine", - "type": "ForgeMod", - "artifact": { - "size": 2254712, - "MD5": "0dd7761e908f9b245bb0dc0fac5649f5", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/OptiFine.jar" - } - }, - { - "id": "mezz:jei:1.11.2-4.5.1.296", - "name": "JustEnoughItems", - "type": "ForgeMod", - "artifact": { - "size": 542399, - "MD5": "584b3099d34c9a1b8649385b90831b34", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/jei.jar" - } - }, - { - "id": "org.blockartistry:dynsurround:1.11.2-3.4.9.3", - "name": "DynamicSurroundings", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 22291385, - "MD5": "65403c66d8b3655b372f58047941d206", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/DynamicSurroundings.jar" - }, - "subModules": [ - { - "id": "dsurround.cfg", - "name": "DynamicSurroundings General Configuration File", - "type": "File", - "artifact": { - "size": 20849, - "path": "/config/dsurround/dsurround.cfg", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/config/dsurround/dsurround.cfg" - } - }, - { - "id": "westeros.json", - "name": "DynamicSurroundings WesterosCraft Configuration File", - "type": "File", - "artifact": { - "size": 608, - "MD5": "44eab112ff24d0bd29974c270de868ba", - "path": "/config/dsurround/westeros.json", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/config/dsurround/westeros.json" - } - } - ] - }, - { - "id": "octarine-noise:betterfoliage:1.11.2-2.1.10", - "name": "BetterFoliage", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 4675903, - "MD5": "522fdf73b6e4343cb6243872fb7b4b6c", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/BetterFoliage.jar" - }, - "subModules": [ - { - "id": "betterfoliage.cfg", - "name": "BetterFoliage Configuration File", - "type": "File", - "artifact": { - "size": 7878, - "MD5": "6dd38f873c4129af05a2d6c500cbe954", - "path": "/config/betterfoliage.cfg", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/config/betterfoliage.cfg" - } - } - ] - }, - { - "id": "techbrew:journeymap:1.11.2-5.5.2", - "name": "JourneyMap", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 1799560, - "MD5": "4315e9939bf64bfa963c8674cb13e838", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/journeymap.jar" - } - }, - { - "id": "com.github.hexomod:worldeditcuife2:2.1.2-mf-1.11.2-13.20.0.2228", - "name": "WorldEditCUI", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 461696, - "MD5": "53f6eef360af5329d9e52b5351657908", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/worldeditcuife.jar" - } - }, - { - "id": "mcp.mobius:waila:1.7.1_1.11.2", - "name": "Waila", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 542744, - "MD5": "26258a3557bf333e8f4ce8b1e9481031", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/Waila.jar" - } - }, - { - "id": "com.mumfrey:liteloader:1.11.2", - "name": "Liteloader", - "type": "LiteLoader", - "required": { - "value": false, - "def": false - }, - "artifact": { - "size": 1685422, - "MD5": "3a98b5ed95810bf164e71c1a53be568d", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.11.2/liteloader.jar" - }, - "subModules": [ - { - "id": "com.mumfrey:macrokeybindmod:0.14.4-1.11.2@litemod", - "name": "Macro/Keybind Mod", - "type": "LiteMod", - "required": { - "value": false, - "def": false - }, - "artifact": { - "size": 1670811, - "MD5": "16080785577b391d426c62c8d3138558", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/macrokeybindmod.litemod" - } - } - ] - }, - { - "id": "com.sonicether:seus:11.0", - "name": "Sonic Ether's Unbelievable Shaders", - "type": "File", - "artifact": { - "size": 175159, - "MD5": "bfa8c31d1da8131b59917bb2460205b1", - "path": "shaderpacks/SEUS v11.0.zip", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/shaderpacks/SEUS.zip" - } - }, - { - "id": "options.txt", - "name": "Default Client Options", - "type": "File", - "artifact": { - "size": 1973, - "path": "options.txt", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/options-1.11.2.txt" - } - }, - { - "id": "servers.dat", - "name": "Saved Client Servers", - "type": "File", - "artifact": { - "size": 87, - "MD5": "594de6063df993b5fde31c7290226ee4", - "path": "servers.dat", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/servers.dat" - } - } - ] - }, - { - "id": "WesterosCraftTest-1.12.2", - "name": "WesterosCraft 1.12.2 Test Server", - "description": "Tests for our version change to 1.12.2 are live here.", - "icon": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-test.png", - "version": "4.2.1", - "address": "mc.westeroscraft.com:4445", - "minecraftVersion": "1.12.2", - "discord": { - "shortId": "1.12.2 Test Server", - "largeImageText": "WesterosCraft 1.12.2 Test Server", - "largeImageKey": "server-test" - }, - "mainServer": false, - "autoconnect": true, - "modules": [ - { - "id": "net.minecraftforge:forge:1.12.2-14.23.5.2797", - "name": "Minecraft Forge", - "type": "ForgeHosted", - "artifact": { - "size": 4937218, - "MD5": "5320593704af58b3906e7106d27e41c8", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/forge-1.12.2-14.23.5.2797-universal.jar" - }, - "subModules": [ - { - "id": "net.minecraft:launchwrapper:1.12", - "name": "Mojang (LaunchWrapper)", - "type": "Library", - "artifact": { - "size": 32999, - "MD5": "934b2d91c7c5be4a49577c9e6b40e8da", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/launchwrapper-1.12.jar" - } - }, - { - "id": "org.ow2.asm:asm-all:5.2", - "name": "Mojang (ASM)", - "type": "Library", - "artifact": { - "size": 247787, - "MD5": "f5ad16c7f0338b541978b0430d51dc83", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/asm-all-5.2.jar" - } - }, - { - "id": "jline:jline:2.13", - "name": "Mojang (jline)", - "type": "Library", - "artifact": { - "size": 248566, - "MD5": "f251ba666cccb260ff7215b2cbeee8d4", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/jline-2.13.jar" - } - }, - { - "id": "org.scala-lang:scala-library:2.11.1@jar.pack.xz", - "name": "Minecraft Forge (scala-library)", - "type": "Library", - "artifact": { - "size": 1474672, - "MD5": "379c15c4f724421c6d5d7aecedaf87a6", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/scala-library-2.11.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-compiler:2.11.1@jar.pack.xz", - "name": "Minecraft Forge (scala-compiler)", - "type": "Library", - "artifact": { - "size": 3076920, - "MD5": "7d89e952f2d5c74577310cd2c28e3f20", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/scala-compiler-2.11.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-actors-migration_2.11:1.1.0@jar.pack.xz", - "name": "Minecraft Forge (scala-actors-migration)", - "type": "Library", - "artifact": { - "size": 21324, - "MD5": "04e3428b2600ace33c7ae2bf1f6c0a4c", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/scala-actors-migration_2.11-1.1.0.jar.pack.xz" - } - }, - { - "id": "org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2@jar.pack.xz", - "name": "Minecraft Forge (scala-continuations-library)", - "type": "Library", - "artifact": { - "size": 7956, - "MD5": "ed9b1d27aba8ac4090a3749c4dfc895a", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/scala-continuations-library_2.11-1.0.2.jar.pack.xz" - } - }, - { - "id": "org.scala-lang.plugins:scala-continuations-plugin_2.11.1:1.0.2@jar.pack.xz", - "name": "Minecraft Forge (scala-continuations-plugin)", - "type": "Library", - "artifact": { - "size": 46140, - "MD5": "a8232db22a72a981de6b1399eb86dff7", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/scala-continuations-plugin_2.11.1-1.0.2.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-parser-combinators_2.11:1.0.1@jar.pack.xz", - "name": "Minecraft Forge (scala-parser-combinators)", - "type": "Library", - "artifact": { - "size": 85568, - "MD5": "2e50a7df17680daadacca69f07f8a16d", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/scala-parser-combinators_2.11-1.0.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-reflect:2.11.1@jar.pack.xz", - "name": "Minecraft Forge (scala-reflect)", - "type": "Library", - "artifact": { - "size": 1070312, - "MD5": "84e5dc81c10e2bd74c579c9d0332fdd9", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/scala-reflect-2.11.1.jar.pack.xz" - } - }, - { - "id": "org.scala-lang:scala-swing_2.11:1.0.1", - "name": "Minecraft Forge (scala-swing)", - "type": "Library", - "artifact": { - "size": 736795, - "MD5": "1d360289e697022a3f57abaad344b28f", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/scala-swing_2.11-1.0.1.jar" - } - }, - { - "id": "org.scala-lang:scala-xml_2.11:1.0.2@jar.pack.xz", - "name": "Minecraft Forge (scala-xml)", - "type": "Library", - "artifact": { - "size": 217812, - "MD5": "cc891b094a4c32dedc56bfefe9b072ff", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/scala-xml_2.11-1.0.2.jar.pack.xz" - } - }, - { - "id": "com.typesafe.akka:akka-actor_2.11:2.3.3@jar.pack.xz", - "name": "Minecraft Forge (akka-actor)", - "type": "Library", - "artifact": { - "size": 746612, - "MD5": "25cb22c3078e9fb3f7a861c912924862", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/akka-actor_2.11-2.3.3.jar.pack.xz" - } - }, - { - "id": "com.typesafe:config:1.2.1@jar.pack.xz", - "name": "Minecraft Forge (typesafe-config)", - "type": "Library", - "artifact": { - "size": 56636, - "MD5": "10ec4ccabc4e68aac9cf87165ead5d7d", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/config-1.2.1.jar.pack.xz" - } - }, - { - "id": "lzma:lzma:0.0.1", - "name": "Mojang (LZMA)", - "type": "Library", - "artifact": { - "size": 5762, - "MD5": "a3e3c3186e41c4a1a3027ba2bb23cdc6", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/lzma-0.0.1.jar" - } - }, - { - "id": "net.sf.trove4j:trove4j:3.0.3", - "name": "Trove4J", - "type": "Library", - "artifact": { - "size": 2523218, - "MD5": "8fc4d4e0129244f9fd39650c5f30feb2", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/trove4j-3.0.3.jar" - } - }, - { - "id": "java3d:vecmath:1.5.2", - "name": "Vecmath", - "type": "Library", - "artifact": { - "size": 318956, - "MD5": "e5d2b7f46c4800a32f62ce75676a5710", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/vecmath-1.5.2.jar" - } - }, - { - "id": "net.sf.jopt-simple:jopt-simple:5.0.3", - "name": "Jopt-simple", - "type": "Library", - "artifact": { - "size": 78175, - "MD5": "0a5ec84e23df9d7cfb4063bc55f2744c", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/jopt-simple-5.0.3.jar" - } - }, - { - "id": "org.apache.maven:maven-artifact:3.5.3", - "name": "maven-artifact", - "type": "Library", - "artifact": { - "size": 54961, - "MD5": "7741ebf29690ee7d9dde9cf4376347fc", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/maven-artifact-3.5.3.jar" - } - }, - { - "id": "net.minecraftforge:MercuriusUpdater:1.12.2", - "name": "MercuriusUpdater", - "type": "Library", - "artifact": { - "size": 15098, - "MD5": "6eb9e61097bee3103a2fdc42746b76a4", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/MercuriusUpdater-1.12.2.jar" - } - } - ] - }, - { - "id": "com.westeroscraft:westerosblocks:4.0.0-beta-1-66", - "name": "WesterosBlocks", - "type": "ForgeMod", - "artifact": { - "size": 17286330, - "MD5": "e5c38ef42e9cc4b957122207dcf95f4f", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/WesterosBlocks.jar" - } - }, - { - "id": "com.westeroscraft:westeroscraftrp:2019-01-21", - "name": "WesterosCraft Resource Pack", - "type": "File", - "artifact": { - "size": 47002312, - "MD5": "86b169611ca0e51fdc47384d8423c402", - "path": "resourcepacks/WesterosCraft.zip", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/resourcepacks/WesterosCraft.zip" - } - }, - { - "id": "net.optifine:optifine:1.12.2_HD_U_E3", - "name": "Optifine", - "type": "ForgeMod", - "artifact": { - "size": 2444057, - "MD5": "7ec95c57ac1a224d6eb93bd370e4ac37", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/OptiFine.jar" - } - }, - { - "id": "mezz:jei:1.12.2-4.14.3.242", - "name": "JustEnoughItems", - "type": "ForgeMod", - "artifact": { - "size": 620682, - "MD5": "ae6d0e6e873ef6c20f41097dc7fee8c6", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/jei.jar" - } - }, - { - "id": "org.blockartistry:dynsurround:1.12.2-3.4.10.5", - "name": "DynamicSurroundings", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 22507134, - "MD5": "3d75602c66b7ccfc23c342e8d5e07469", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/DynamicSurroundings.jar" - }, - "subModules": [ - { - "id": "dsurround.cfg", - "name": "DynamicSurroundings General Configuration File", - "type": "File", - "artifact": { - "size": 22179, - "path": "/config/dsurround/dsurround.cfg", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/config/dsurround/dsurround.cfg" - } - }, - { - "id": "westeros.json", - "name": "DynamicSurroundings WesterosCraft Configuration File", - "type": "File", - "artifact": { - "size": 608, - "MD5": "44eab112ff24d0bd29974c270de868ba", - "path": "/config/dsurround/westeros.json", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/config/dsurround/westeros.json" - } - } - ] - }, - { - "id": "octarine-noise:betterfoliage:1.12-2.2.0", - "name": "BetterFoliage", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 926081, - "MD5": "e05a720c5900b9bac4e01179abe8eef6", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/BetterFoliage.jar" - }, - "subModules": [ - { - "id": "betterfoliage.cfg", - "name": "BetterFoliage Configuration File", - "type": "File", - "artifact": { - "size": 7878, - "MD5": "6dd38f873c4129af05a2d6c500cbe954", - "path": "/config/betterfoliage.cfg", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/config/betterfoliage.cfg" - } - } - ] - }, - { - "id": "forgelin:forgelin:1.8.2", - "name": "Forgelin", - "type": "ForgeMod", - "artifact": { - "size": 5124663, - "MD5": "59035365f7af35f599d1c4baade64d8b", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/Forgelin.jar" - } - }, - { - "id": "techbrew:journeymap:1.12.2-5.5.3", - "name": "JourneyMap", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 1831161, - "MD5": "d3b5a672d2393f9fe63903598d50c769", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/journeymap.jar" - } - }, - { - "id": "com.github.hexomod:worldeditcuife2:2.2.0-mf-1.12.2-14.23.5.2768", - "name": "WorldEditCUI", - "type": "ForgeMod", - "required": { - "value": false - }, - "artifact": { - "size": 459294, - "MD5": "2b8c1c3bc48c2d80b71daa658f656edb", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/worldeditcuife.jar" - } - }, - { - "id": "com.mumfrey:liteloader:1.12.2", - "name": "Liteloader", - "type": "LiteLoader", - "required": { - "value": false, - "def": false - }, - "artifact": { - "size": 1680383, - "MD5": "1420785ecbfed5aff4a586c5c9dd97eb", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/liteloader.jar" - }, - "subModules": [ - { - "id": "com.mumfrey:macrokeybindmod:0.15.4-1.12.1@litemod", - "name": "Macro/Keybind Mod", - "type": "LiteMod", - "required": { - "value": false, - "def": false - }, - "artifact": { - "size": 1726452, - "MD5": "9ba3ed960bbb676743a3b6c2e1efc484", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/macrokeybindmod.litemod" - } - } - ] - }, - { - "id": "com.sonicether:seus-renewed:1.0.0", - "name": "Sonic Ether's Unbelievable Shaders Renewed", - "type": "File", - "artifact": { - "size": 7062491, - "MD5": "e68cc9f8ffc8fad66583b9b2cc05356f", - "path": "shaderpacks/SEUS-Renewed.zip", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/shaderpacks/SEUS-Renewed.zip" - } - }, - { - "id": "options.txt", - "name": "Default Client Options", - "type": "File", - "artifact": { - "size": 1973, - "path": "options.txt", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/options-1.12.2.txt" - } - }, - { - "id": "servers.dat", - "name": "Saved Client Servers", - "type": "File", - "artifact": { - "size": 87, - "MD5": "64dc1db90935997f07dfac422206f1de", - "path": "servers.dat", - "url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/servers.dat" - } - } - ] - }, - { - "id": "WesterosCraftTest-1.13.2", - "name": "WesterosCraft 1.13.2 Test Server", - "description": "Tests for our version change to 1.13.2 are live here.", - "icon": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-test.png", - "version": "5.0.0", - "address": "mc.westeroscraft.com:4445", - "minecraftVersion": "1.13.2", - "discord": { - "shortId": "1.13.2 Test Server", - "largeImageText": "WesterosCraft 1.13.2 Test Server", - "largeImageKey": "server-test" - }, - "mainServer": false, - "autoconnect": false, - "modules": [ - { - "id": "net.minecraftforge:forge:1.13.2-25.0.219", - "name": "Minecraft Forge (base jar)", - "type": "ForgeHosted", - "artifact": { - "size": 159918, - "MD5": "c5a9c40711feb2b4ab0329c48256e30d", - "url": "" - }, - "subModules": [ - { - "id": "net.minecraftforge:forge:1.13.2-25.0.219:universal", - "name": "Minecraft Forge (universal jar)", - "type": "Library", - "artifact": { - "size": 2343272 , - "MD5": "338c495d266da44ac1955caf91cc14c4", - "url": "" - } - }, - { - "id": "net.minecraftforge:forge:1.13.2-25.0.219:client", - "name": "Minecraft Forge (client jar)", - "type": "Library", - "artifact": { - "size": 3065519, - "MD5": "df63f7288a8118c9b8836a3e53827aac", - "url": "" - } - }, - { - "id": "net.minecraft:client:1.13.2:extra", - "name": "Minecraft Forge (client extra)", - "type": "Library", - "artifact": { - "size": 9622361, - "MD5": "dc66e2219e3de6e7ca05847fd3c7746d", - "url": "" - } - }, - { - "id": "net.minecraft:client:1.13.2-20190213.203750:srg", - "name": "Minecraft Forge (client srg)", - "type": "Library", - "artifact": { - "size": 8198877, - "MD5": "80f355cffe270d2c2943301b96d98fa6", - "url": "" - } - }, - { - "id": "1.13.2-forge-25.0.219", - "name": "Minecraft Forge (version.json)", - "type": "VersionManifest", - "artifact": { - "size": 11511, - "MD5": "f13f9e20324d0a55c646d5fb8876b2bb", - "url": "" - } - }, - { - "id": "com.paulscode:soundsystem:201809301515", - "name": "Minecraft Forge (Paul's Code Soundsystem)", - "type": "Library", - "artifact": { - "size": 66607, - "MD5": "bf43e7b9f628534614b3fc5e4e69d209", - "url": "" - } - }, - { - "id": "org.ow2.asm:asm:6.2", - "name": "Minecraft Forge (asm)", - "type": "Library", - "artifact": { - "size": 111214, - "MD5": "7abdce94068615d690495f45eb6eb980", - "url": "" - } - }, - { - "id": "org.ow2.asm:asm-commons:6.2", - "name": "Minecraft Forge (asm-commons)", - "type": "Library", - "artifact": { - "size": 78919, - "MD5": "a031c9a32770c02c2f91d2bcbeceabcd", - "url": "" - } - }, - { - "id": "org.ow2.asm:asm-tree:6.2", - "name": "Minecraft Forge (asm-tree)", - "type": "Library", - "artifact": { - "size": 50370, - "MD5": "e7279981c6764dcd73a99705acf5c9a6", - "url": "" - } - }, - { - "id": "cpw.mods:modlauncher:2.1.1", - "name": "Minecraft Forge (modlauncher)", - "type": "Library", - "artifact": { - "size": 101586, - "MD5": "089ae3cf5afe10e96bb930b70dd99402", - "url": "" - } - }, - { - "id": "cpw.mods:grossjava9hacks:1.1.0", - "name": "Minecraft Forge (Gross Java 9 Hacks)", - "type": "Library", - "artifact": { - "size": 1759, - "MD5": "1adde27730734a786f461ed048d440a0", - "url": "" - } - }, - { - "id": "net.minecraftforge:accesstransformers:0.16.0-shadowed", - "name": "Minecraft Forge (Access Transformers)", - "type": "Library", - "artifact": { - "size": 444428, - "MD5": "dbe346d662c7bdff0988f941342932e6", - "url": "" - } - }, - { - "id": "net.minecraftforge:eventbus:0.9.2-service", - "name": "Minecraft Forge (EventBus)", - "type": "Library", - "artifact": { - "size": 38908, - "MD5": "77c6c079914a8369a185e11ae1a7a878", - "url": "" - } - }, - { - "id": "net.minecraftforge:forgespi:0.13.0", - "name": "Minecraft Forge (Forge SPI)", - "type": "Library", - "artifact": { - "size": 16607, - "MD5": "5634ba7001f8501a2b553430e2068751", - "url": "" - } - }, - { - "id": "net.minecraftforge:coremods:0.5.0", - "name": "Minecraft Forge (coremods)", - "type": "Library", - "artifact": { - "size": 12284, - "MD5": "d3da5879965cf6f37413d6d1a80d92a7", - "url": "" - } - }, - { - "id": "net.minecraftforge:unsafe:0.2.0", - "name": "Minecraft Forge (unsafe)", - "type": "Library", - "artifact": { - "size": 2834, - "MD5": "2d1016ebe4c1a63dd50a59d26bd12db1", - "url": "" - } - }, - { - "id": "com.electronwill.night-config:core:3.6.0", - "name": "Minecraft Forge (night-config core)", - "type": "Library", - "artifact": { - "size": 199763, - "MD5": "841adeca8f5e5e092c7bafb412dcb615", - "url": "" - } - }, - { - "id": "com.electronwill.night-config:toml:3.6.0", - "name": "Minecraft Forge (night-config toml)", - "type": "Library", - "artifact": { - "size": 31256, - "MD5": "2d1d5f1fa4735b4e654613b493512432", - "url": "" - } - }, - { - "id": "org.jline:jline:3.9.0", - "name": "Minecraft Forge (jline)", - "type": "Library", - "artifact": { - "size": 707273, - "MD5": "2db9aae140f810a72fc4a5cb5aa5cf9b", - "url": "" - } - }, - { - "id": "org.apache.maven:maven-artifact:3.6.0", - "name": "Minecraft Forge (maven-artifact)", - "type": "Library", - "artifact": { - "size": 55051, - "MD5": "89e95013b11f347e48c0525965600404", - "url": "" - } - }, - { - "id": "net.jodah:typetools:0.6.0", - "name": "Minecraft Forge (typetools)", - "type": "Library", - "artifact": { - "size": 14734, - "MD5": "9f65b6e90eb986fe25afc39b8ed3f43b", - "url": "" - } - }, - { - "id": "java3d:vecmath:1.5.2", - "name": "Minecraft Forge (vecmath)", - "type": "Library", - "artifact": { - "size": 318956, - "MD5": "e5d2b7f46c4800a32f62ce75676a5710", - "url": "" - } - }, - { - "id": "org.apache.logging.log4j:log4j-api:2.11.2", - "name": "Minecraft Forge (log4j-api)", - "type": "Library", - "artifact": { - "size": 266283, - "MD5": "3f7ee51e3dd0830de799dae0b90243dd", - "url": "" - } - }, - { - "id": "org.apache.logging.log4j:log4j-core:2.11.2", - "name": "Minecraft Forge (log4j-core)", - "type": "Library", - "artifact": { - "size": 1629585, - "MD5": "c8bd8b5c5aaaa07a3dcbf57de01c9266", - "url": "" - } - }, - { - "id": "net.minecrell:terminalconsoleappender:1.1.1", - "name": "Minecraft Forge (terminalconsoleappender)", - "type": "Library", - "artifact": { - "size": 15240, - "MD5": "a190e88073a41dfa1b1d47854c41230b", - "url": "" - } - }, - { - "id": "net.sf.jopt-simple:jopt-simple:5.0.4", - "name": "Minecraft Forge (jopt-simple)", - "type": "Library", - "artifact": { - "size": 78146, - "MD5": "eb0d9dffe9b0eddead68fe678be76c49", - "url": "" - } - } - - ] - }, - { - "id": "squeek:appleskin:1.0.11", - "name": "AppleSkin (test mod)", - "type": "ForgeMod", - "artifact": { - "size": 27382, - "MD5": "a1c0cfed77b2cb7b6699e59ba0252a50", - "url": "" - } - }, - { - "id": "com.blamejared:controlling:4.0.1", - "name": "Controlling (Test Mod)", - "type": "ForgeMod", - "artifact": { - "size": 34914, - "MD5": "b4dc10b9039518ce00845ca962e6a136", - "url": "" - } - } - ] - } - ] -} \ No newline at end of file + "version": "1.0.0", + "rss": "", + "servers": [ + { + "id": "example-profile", + "name": "Example Profile", + "description": "관리자 사이트에서 만든 distribution 템플릿입니다.", + "version": "1.20.1", + "minecraftVersion": "1.20.1", + "mainServer": true, + "autoconnect": false, + "modules": [] + } + ] +}