235 lines
7.6 KiB
JavaScript
235 lines
7.6 KiB
JavaScript
const fs = require('fs-extra')
|
|
const got = require('got')
|
|
const path = require('path')
|
|
|
|
const ConfigManager = require('./configmanager')
|
|
const { DEFAULT_REMOTE_DISTRO_URL, setRemoteDistributionUrl } = require('./distromanager')
|
|
|
|
const LOCAL_CATALOG_PATH = path.join(__dirname, '..', 'launcher', 'catalog.json')
|
|
|
|
function normalizeText(value){
|
|
return typeof value === 'string' ? value.trim() : ''
|
|
}
|
|
|
|
function normalizeNullableText(value){
|
|
const nextValue = normalizeText(value)
|
|
return nextValue.length > 0 ? nextValue : null
|
|
}
|
|
|
|
function toStoredProfile(rawProfile){
|
|
const kind = normalizeText(rawProfile.kind) || 'modpack'
|
|
const storedProfile = {
|
|
id: normalizeText(rawProfile.id),
|
|
name: normalizeText(rawProfile.name),
|
|
kind,
|
|
description: normalizeText(rawProfile.description),
|
|
details: normalizeText(rawProfile.details),
|
|
distributionUrl: normalizeNullableText(rawProfile.distributionUrl),
|
|
defaultServerAddress: normalizeText(rawProfile.defaultServerAddress),
|
|
allowCustomServerAddress: rawProfile.allowCustomServerAddress === true,
|
|
worldArchiveUrl: normalizeNullableText(rawProfile.worldArchiveUrl),
|
|
worldDirectoryName: normalizeNullableText(rawProfile.worldDirectoryName),
|
|
serverBundleUrl: normalizeNullableText(rawProfile.serverBundleUrl),
|
|
serverDirectoryName: normalizeText(rawProfile.serverDirectoryName) || 'server',
|
|
serverLaunchCommand: normalizeNullableText(rawProfile.serverLaunchCommand),
|
|
serverWorkingDirectory: normalizeNullableText(rawProfile.serverWorkingDirectory),
|
|
serverPort: Number.isFinite(Number(rawProfile.serverPort)) ? Number(rawProfile.serverPort) : 25565,
|
|
tunnelCommand: normalizeNullableText(rawProfile.tunnelCommand),
|
|
tunnelAddressRegex: normalizeNullableText(rawProfile.tunnelAddressRegex),
|
|
artwork: normalizeText(rawProfile.artwork)
|
|
}
|
|
|
|
if(kind !== 'map'){
|
|
storedProfile.worldArchiveUrl = null
|
|
storedProfile.worldDirectoryName = null
|
|
}
|
|
|
|
if(kind !== 'server-pack'){
|
|
storedProfile.serverBundleUrl = null
|
|
storedProfile.serverDirectoryName = 'server'
|
|
storedProfile.serverLaunchCommand = null
|
|
storedProfile.serverWorkingDirectory = null
|
|
storedProfile.serverPort = 25565
|
|
storedProfile.tunnelCommand = null
|
|
storedProfile.tunnelAddressRegex = null
|
|
}
|
|
|
|
return storedProfile
|
|
}
|
|
|
|
function normalizeProfile(rawProfile, sourceType = 'catalog'){
|
|
const storedProfile = toStoredProfile(rawProfile)
|
|
const launchIssues = []
|
|
const hostIssues = []
|
|
|
|
if(storedProfile.distributionUrl == null){
|
|
launchIssues.push('distribution URL이 필요합니다.')
|
|
}
|
|
|
|
if(storedProfile.kind === 'map'){
|
|
if(storedProfile.worldArchiveUrl == null){
|
|
launchIssues.push('맵 ZIP 또는 로컬 월드 경로가 필요합니다.')
|
|
}
|
|
if(storedProfile.worldDirectoryName == null){
|
|
launchIssues.push('월드 폴더 이름이 필요합니다.')
|
|
}
|
|
}
|
|
|
|
if(storedProfile.kind === 'server-pack' && storedProfile.serverBundleUrl == null){
|
|
hostIssues.push('로컬 호스팅을 하려면 서버 번들 ZIP 또는 디렉터리 경로가 필요합니다.')
|
|
}
|
|
|
|
const launchReady = launchIssues.length === 0
|
|
const hostReady = storedProfile.kind === 'server-pack' ? hostIssues.length === 0 : false
|
|
|
|
return {
|
|
...storedProfile,
|
|
sourceType,
|
|
isCustom: sourceType === 'custom',
|
|
configured: launchReady,
|
|
launchReady,
|
|
launchIssues,
|
|
hostReady,
|
|
hostIssues
|
|
}
|
|
}
|
|
|
|
function resolveCatalogFileSource(source){
|
|
if(source.startsWith('file://')){
|
|
return decodeURIComponent(source.substring('file://'.length))
|
|
}
|
|
return path.resolve(source)
|
|
}
|
|
|
|
async function readCatalogSource(source){
|
|
if(/^https?:\/\//i.test(source)){
|
|
return got(source, {
|
|
responseType: 'json'
|
|
}).json()
|
|
}
|
|
|
|
return fs.readJson(resolveCatalogFileSource(source))
|
|
}
|
|
|
|
exports.getLocalCatalogPath = function(){
|
|
return LOCAL_CATALOG_PATH
|
|
}
|
|
|
|
exports.loadCatalog = async function(){
|
|
const configuredSource = ConfigManager.getLibraryCatalogSource()
|
|
const source = configuredSource != null && configuredSource.trim().length > 0 ? configuredSource.trim() : LOCAL_CATALOG_PATH
|
|
let rawCatalog = {
|
|
version: 1,
|
|
profiles: []
|
|
}
|
|
let sourceError = null
|
|
|
|
try {
|
|
rawCatalog = await readCatalogSource(source)
|
|
} catch (error) {
|
|
sourceError = error
|
|
}
|
|
|
|
const rawProfiles = Array.isArray(rawCatalog.profiles) ? rawCatalog.profiles : []
|
|
|
|
return {
|
|
version: rawCatalog.version ?? 1,
|
|
source,
|
|
sourceError,
|
|
profiles: rawProfiles
|
|
.filter((profile) => profile != null && typeof profile.id === 'string' && typeof profile.name === 'string')
|
|
.map((profile) => normalizeProfile(profile, 'catalog'))
|
|
.sort((left, right) => left.name.localeCompare(right.name, 'ko'))
|
|
}
|
|
}
|
|
|
|
exports.getInstalledProfiles = async function(){
|
|
const catalog = await exports.loadCatalog()
|
|
const installedProfiles = ConfigManager.getInstalledLibraryProfiles()
|
|
|
|
return installedProfiles.map((installedProfile) => {
|
|
const latestProfile = catalog.profiles.find((profile) => profile.id === installedProfile.id)
|
|
return latestProfile != null
|
|
? {
|
|
...installedProfile,
|
|
...latestProfile,
|
|
installedAt: installedProfile.installedAt
|
|
}
|
|
: installedProfile
|
|
})
|
|
}
|
|
|
|
exports.installProfile = async function(profileId){
|
|
const catalog = await exports.loadCatalog()
|
|
const profile = catalog.profiles.find((entry) => entry.id === profileId)
|
|
|
|
if(profile == null){
|
|
throw new Error(`Unknown profile: ${profileId}`)
|
|
}
|
|
|
|
const installedProfile = {
|
|
...profile,
|
|
installedAt: new Date().toISOString()
|
|
}
|
|
|
|
ConfigManager.upsertInstalledLibraryProfile(installedProfile)
|
|
|
|
if(ConfigManager.getSelectedLibraryProfile() == null){
|
|
ConfigManager.setSelectedLibraryProfile(profile.id)
|
|
}
|
|
|
|
ConfigManager.save()
|
|
return installedProfile
|
|
}
|
|
|
|
exports.removeProfile = function(profileId){
|
|
ConfigManager.removeInstalledLibraryProfile(profileId)
|
|
ConfigManager.save()
|
|
}
|
|
|
|
exports.selectProfile = function(profileId){
|
|
ConfigManager.setSelectedLibraryProfile(profileId)
|
|
ConfigManager.save()
|
|
}
|
|
|
|
exports.getSelectedProfileId = function(){
|
|
return ConfigManager.getSelectedLibraryProfile()
|
|
}
|
|
|
|
exports.getSelectedProfileSync = function(){
|
|
const selectedProfileId = ConfigManager.getSelectedLibraryProfile()
|
|
if(selectedProfileId == null){
|
|
return null
|
|
}
|
|
return ConfigManager.getInstalledLibraryProfile(selectedProfileId)
|
|
}
|
|
|
|
exports.resolveServerAddress = function(profile){
|
|
if(profile == null){
|
|
return null
|
|
}
|
|
|
|
const manualAddress = ConfigManager.getLibraryServerAddressOverride(profile.id)
|
|
if(manualAddress != null && manualAddress.length > 0){
|
|
return manualAddress
|
|
}
|
|
|
|
if(typeof profile.defaultServerAddress === 'string' && profile.defaultServerAddress.length > 0){
|
|
return profile.defaultServerAddress
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
exports.setServerAddressOverride = function(profileId, address){
|
|
ConfigManager.setLibraryServerAddressOverride(profileId, address)
|
|
ConfigManager.save()
|
|
}
|
|
|
|
exports.applyConfiguredProfile = function(){
|
|
const selectedProfile = exports.getSelectedProfileSync()
|
|
const distributionUrl = selectedProfile?.distributionUrl ?? DEFAULT_REMOTE_DISTRO_URL
|
|
setRemoteDistributionUrl(distributionUrl)
|
|
return selectedProfile
|
|
}
|