Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always track changes to mods.yml when installing mods to a profile #1508

Merged
merged 1 commit into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/views/DownloadModModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ import ConflictManagementProvider from '../../providers/generic/installing/Confl
import { MOD_LOADER_VARIANTS } from '../../r2mm/installing/profile_installers/ModLoaderVariantRecord';
import ModalCard from '../ModalCard.vue';
import * as PackageDb from '../../r2mm/manager/PackageDexieStore';
import { installModsAfterDownload } from '../../utils/ProfileUtils';
import { installModsToProfile } from '../../utils/ProfileUtils';

interface DownloadProgress {
assignId: number;
Expand Down Expand Up @@ -374,7 +374,7 @@ let assignId = 0;
const profile = this.profile.asImmutableProfile();

try {
const modList = await installModsAfterDownload(downloadedMods, profile);
const modList = await installModsToProfile(downloadedMods, profile);
await this.$store.dispatch('profile/updateModList', modList);

const err = await ConflictManagementProvider.instance.resolveConflicts(modList, this.profile);
Expand Down
5 changes: 4 additions & 1 deletion src/r2mm/mods/ProfileModList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ export default class ProfileModList {
// Add all tree contents to buffer.
for (const file of tree.getRecursiveFiles()) {
const fileLower = file.toLowerCase();
if (this.SUPPORTED_CONFIG_FILE_EXTENSIONS.filter(value => fileLower.endsWith(value)).length > 0) {
if (
this.SUPPORTED_CONFIG_FILE_EXTENSIONS.some(value => fileLower.endsWith(value)) &&
!fileLower.endsWith('mods.yml')
) {
await builder.addBuffer(path.relative(profile.getProfilePath(), file), await FsProvider.instance.readFile(file));
}
}
Expand Down
87 changes: 28 additions & 59 deletions src/utils/ProfileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,17 @@ async function extractConfigsToImportedProfile(
progressCallback: (status: string) => void
) {
const zipEntries = await ZipProvider.instance.getEntries(file);
const excludedFiles = ["export.r2x", "mods.yml"];

for (const [index, entry] of zipEntries.entries()) {
if (entry.entryName.startsWith('config/') || entry.entryName.startsWith("config\\")) {
await ZipProvider.instance.extractEntryTo(
file,
entry.entryName,
path.join(
Profile.getRootDir(),
profileName,
'BepInEx'
)
);
} else if (entry.entryName.toLowerCase() !== "export.r2x") {
await ZipProvider.instance.extractEntryTo(
file,
entry.entryName,
path.join(
Profile.getRootDir(),
profileName
)
)
if (!excludedFiles.includes(entry.entryName.toLowerCase())) {
let outputPath = path.join(Profile.getRootDir(), profileName);

if (entry.entryName.startsWith('config/') || entry.entryName.startsWith("config\\")) {
outputPath = path.join(outputPath, 'BepInEx');
}

await ZipProvider.instance.extractEntryTo(file, entry.entryName, outputPath);
}

const progress = Math.floor(((index + 1) / zipEntries.length) * 100);
Expand All @@ -71,20 +61,24 @@ async function extractConfigsToImportedProfile(
* This is more performant than calling ProfileModList.addMod() on a
* loop, as that causes multiple disc operations per mod.
*/
export async function installModsAfterDownload(
export async function installModsToProfile(
comboList: ThunderstoreCombo[],
profile: ImmutableProfile
profile: ImmutableProfile,
disabledModsOverride?: string[],
progressCallback?: (status: string) => void
): Promise<ManifestV2[]> {
const profileMods = await ProfileModList.getModList(profile);
if (profileMods instanceof R2Error) {
throw profileMods;
}

const installedVersions = profileMods.map((m) => m.getDependencyString());
const disabledMods = profileMods.filter((m) => !m.isEnabled()).map((m) => m.getName());
const disabledMods = disabledModsOverride || profileMods.filter((m) => !m.isEnabled()).map((m) => m.getName());
let currentMod;

try {
for (const comboMod of comboList) {
for (const [index, comboMod] of comboList.entries()) {
currentMod = comboMod;
const manifestMod = new ManifestV2().fromThunderstoreMod(comboMod.getMod(), comboMod.getVersion());

if (installedVersions.includes(manifestMod.getDependencyString())) {
Expand All @@ -109,52 +103,26 @@ export async function installModsAfterDownload(
} else {
profileMods.push(manifestMod);
}

if (typeof progressCallback === "function") {
const progress = Math.floor(((index + 1) / comboList.length) * 100);
progressCallback(`Copying mods to profile: ${progress}%`);
}
}
} catch (e) {
const originalError = R2Error.fromThrownValue(e);
const modName = currentMod ? currentMod.getMod().getFullName() : 'Unknown';
throw new R2Error(
'Installing downloaded mods to profile failed',
`
The mod and its dependencies might not be installed properly.
The original error was: ${originalError.name}: ${originalError.message}
`,
'The original error might provide hints about what went wrong.'
`Failed to install mod [${modName}] to profile`,
'All mods/dependencies might not be installed properly. Please try again.',
`The original error might provide hints about what went wrong: ${originalError.name}: ${originalError.message}`
);
}

throwForR2Error(await ProfileModList.saveModList(profile, profileMods));
return profileMods;
}

/**
* Install mods to target profile without syncing changes to mods.yml file.
* Syncing is futile, as the mods.yml is copied from the imported profile.
*/
async function installModsToImportedProfile(
comboList: ThunderstoreCombo[],
modList: ExportMod[],
profile: ImmutableProfile,
progressCallback: (status: string) => void
) {
const disabledMods = modList.filter((m) => !m.isEnabled()).map((m) => m.getName());

for (const [index, comboMod] of comboList.entries()) {
const manifestMod = new ManifestV2().fromThunderstoreMod(comboMod.getMod(), comboMod.getVersion());
throwForR2Error(
await ProfileInstallerProvider.instance.installMod(manifestMod, profile)
);

if (disabledMods.includes(manifestMod.getName())) {
throwForR2Error(
await ProfileInstallerProvider.instance.disableMod(manifestMod, profile)
);
}

const progress = Math.floor(((index + 1) / comboList.length) * 100);
progressCallback(`Copying mods to profile: ${progress}%`);
}
}

export async function parseYamlToExportFormat(yamlContent: string) {
const parsedYaml = await yaml.parse(yamlContent);
return new ExportFormat(
Expand Down Expand Up @@ -196,7 +164,8 @@ export async function populateImportedProfile(
}

try {
await installModsToImportedProfile(comboList, exportModList, profile, progressCallback);
const disabledMods = exportModList.filter((m) => !m.isEnabled()).map((m) => m.getName());
await installModsToProfile(comboList, profile, disabledMods, progressCallback);
await extractConfigsToImportedProfile(zipPath, profile.getProfileName(), progressCallback);
} catch (e) {
await FileUtils.recursiveRemoveDirectoryIfExists(profile.getProfilePath());
Expand Down
Loading