Merge branch 'mod_management'

This commit is contained in:
SpikeHD
2022-07-26 20:39:54 -07:00
43 changed files with 2100 additions and 275 deletions

View File

@@ -50,6 +50,13 @@ export interface Configuration {
// Swag stuff
akebi_path?: string
migoto_path?: string
reshade_path?: string
last_extras?: {
migoto: boolean
akebi: boolean
reshade: boolean
}
}
export async function setConfigOption<K extends keyof Configuration>(key: K, value: Configuration[K]): Promise<void> {

205
src/utils/gamebanana.ts Normal file
View File

@@ -0,0 +1,205 @@
import { invoke } from '@tauri-apps/api'
import { getConfigOption } from './configuration'
// Generated with https://transform.tools/json-to-typescript I'm lazy cry about it
interface GamebananaResponse {
_idRow: number
_sModelName: string
_sSingularTitle: string
_sIconClasses: string
_sName: string
_sProfileUrl: string
_aPreviewMedia: PreviewMedia
_tsDateAdded: number
_bHasFiles: boolean
_aSubmitter: Submitter
_aRootCategory: RootCategory
_bIsNsfw: boolean
_sInitialVisibility: string
_nLikeCount?: number
_bIsOwnedByAccessor: boolean
_nViewCount?: number
_nPostCount?: number
_tsDateUpdated?: number
}
interface PreviewMedia {
_aImages?: Image[]
_aMetadata?: Metadata
}
interface Image {
_sType: string
_sBaseUrl: string
_sFile: string
_sFile530?: string
_sFile100: string
_sFile220?: string
_sCaption?: string
}
interface Metadata {
_sState?: string
_sSnippet: string
_nPostCount?: number
_nBounty?: number
}
interface Submitter {
_idRow: number
_sName: string
_bIsOnline: boolean
_bHasRipe: boolean
_sProfileUrl: string
_sAvatarUrl: string
_sUpicUrl?: string
_aClearanceLevels?: string[]
_sHdAvatarUrl?: string
}
interface RootCategory {
_sName: string
_sProfileUrl: string
_sIconUrl: string
}
export interface ModData {
id: number
name: string
images: string[]
dateadded: number
submitter: {
name: string
url: string
}
nsfw: boolean
likes: number
views: number
type: string
}
export interface PartialModData {
name: string
images: string[]
submitter: {
name: string
}
likes: number
views: number
}
interface GamebananaDownloads {
_bIsTrashed: boolean
_bIsWithheld: boolean
_aFiles: File[]
_sLicense: string
}
interface File {
_idRow: number
_sFile: string
_nFilesize: number
_sDescription: string
_tsDateAdded: number
_nDownloadCount: number
_sAnalysisState: string
_sDownloadUrl: string
_sMd5Checksum: string
_sClamAvResult: string
_sAnalysisResult: string
_bContainsExe: boolean
}
interface ModDownload {
filename: string
downloadUrl: string
filesize: number
containsExe: boolean
}
export async function getMods(mode: string) {
const resp = JSON.parse(
await invoke('list_submissions', {
mode,
})
)
return formatGamebananaData(resp)
}
export async function formatGamebananaData(obj: GamebananaResponse[]) {
if (!obj) return []
return obj
.map((itm) => {
const img = itm?._aPreviewMedia?._aImages
return {
id: itm._idRow,
name: itm._sName,
images: img
? img.map((i) => {
return i._sBaseUrl + '/' + i._sFile220
})
: [],
dateadded: itm._tsDateAdded,
submitter: {
name: itm._aSubmitter._sName,
url: itm._aSubmitter._sProfileUrl,
},
nsfw: itm._bIsNsfw,
likes: itm?._nLikeCount || 0,
views: itm?._nViewCount || 0,
type: itm._sSingularTitle,
} as ModData
})
.filter((itm) => itm.type === 'Mod')
}
export async function getInstalledMods() {
const migotoPath = await getConfigOption('migoto_path')
if (!migotoPath) return []
const mods = (await invoke('list_mods', {
path: migotoPath,
})) as Record<string, string>
// These are returned as JSON strings, so we have to parse them
return Object.keys(mods).map((path) => {
const info = JSON.parse(mods[path]) as ModData | PartialModData
const modPathArr = path.replace(/\\/g, '/').split('/')
// If there is a file in this path, remove it from the path
if (modPathArr[modPathArr.length - 1].includes('.')) modPathArr.pop()
return {
path: modPathArr.join('/'),
info,
}
})
}
export async function getModDownload(modId: string) {
const resp = JSON.parse(
await invoke('get_download_links', {
modId,
})
) as GamebananaDownloads
return formatDownloadsData(resp)
}
export async function formatDownloadsData(obj: GamebananaDownloads) {
if (!obj) return []
return obj._aFiles.map((itm) => {
return {
filename: itm._sFile,
downloadUrl: `https://files.gamebanana.com/mods/${itm._sFile}`,
filesize: itm._nFilesize,
containsExe: itm._bContainsExe,
} as ModDownload
})
}

71
src/utils/mods.ts Normal file
View File

@@ -0,0 +1,71 @@
import { invoke } from '@tauri-apps/api'
import { getConfigOption } from './configuration'
export async function getModsFolder() {
const migotoPath = await getConfigOption('migoto_path')
if (!migotoPath) return null
// Remove exe from path
const pathArr = migotoPath.replace(/\\/g, '/').split('/')
pathArr.pop()
return pathArr.join('/') + '/Mods/'
}
export async function disableMod(modId: string) {
const path = (await getModsFolder()) + modId
const pathExists = await invoke('dir_exists', {
path,
})
if (!pathExists) return console.log("Path doesn't exist")
const modName = path.replace(/\\/g, '/').split('/').pop()
await invoke('rename', {
path,
newName: `DISABLED_${modName}`,
})
}
export async function enableMod(modId: string) {
const path = (await getModsFolder()) + `DISABLED_${modId}`
const modName = path.replace(/\\/g, '/').split('/').pop()
const pathExists = await invoke('dir_exists', {
path,
})
if (!pathExists) return console.log("Path doesn't exist")
if (!modName?.includes('DISABLED_')) return
const newName = modName.replace('DISABLED_', '')
await invoke('rename', {
path,
newName,
})
}
export async function getModFolderName(modId: string) {
const modsFolder = await getModsFolder()
if (!modsFolder) return null
const modEnabled = await invoke('dir_exists', {
path: modsFolder + modId,
})
const modDisabled = await invoke('dir_exists', {
path: modsFolder + 'DISABLED_' + modId,
})
if (!modEnabled && !modDisabled) return null
if (modEnabled) return modId
if (modDisabled) return 'DISABLED_' + modId
}
export async function modIsEnabled(modId: string) {
return !(await getModFolderName(modId))?.includes('DISABLED_')
}

View File

@@ -1,15 +1,30 @@
import { invoke } from '@tauri-apps/api'
import { listen } from '@tauri-apps/api/event'
export function unzip(file: string, dest: string, onFinish?: () => void) {
invoke('unzip', {
zipfile: file,
destpath: dest,
})
interface UnzipPayload {
file: string
new_folder: string
}
listen('extract_end', ({ payload }) => {
if (payload === file && onFinish) {
onFinish()
}
export function unzip(
file: string,
dest: string,
topLevelStrip?: boolean,
folderIfLoose?: boolean
): Promise<UnzipPayload> {
return new Promise((resolve) => {
invoke('unzip', {
zipfile: file,
destpath: dest,
topLevelStrip,
folderIfLoose,
})
listen('extract_end', ({ payload }) => {
// @ts-expect-error Payload is an object
if (payload?.file === file) {
resolve(payload as UnzipPayload)
}
})
})
}