Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
ebkr committed Jun 18, 2022
2 parents 5ca1206 + bc46542 commit 63a5dee
Show file tree
Hide file tree
Showing 22 changed files with 1,188 additions and 458 deletions.
130 changes: 130 additions & 0 deletions docs/Adding a game.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Adding a game

## Requirements
### Game metadata
- Game image (png/jpg - 188x250px)
- App ID for available platforms.
- Folder name relative to root directory ( EG: _steamapps/common/_**Risk of Rain 2** OR _steamapps/common/_**BONEWORKS/BONEWORKS** )
- Game data directory (if applicable). Usually **executable name**

### Mod loader
- An uploaded mod loader package on TS
- Package identifier
- Mod loader type (BepInEx/MelonLoader/Northstar/etc)
- Format to unpack loader
- BepInEx contents is usually one folder deeper than the package root.

### An understanding of the game's installation rules
- Subdir vs State vs None
- If the mod loader is already used in existing places then this can usually just be copied and lightly modified.

## Adding a new game listing

### Add a new entry to GameManager.ts
- **DisplayName**
- How it appears when presented to the user.
- **InternalFolderName**
- The folder name where files related to profiles and cached packages are saved under.
- Server listings can share this InternalFolderName to have profiles shared between both game/server.
- _Try to keep this without spaces - Helps with max path character limit_
- **SettingsIdentifier**
- Used to scope settings for games.
- **SteamFolderName**
- Used to resolve the game dir. Only Steam related currently, but not a requirement.
- **ExeName**
- An array of different executable names (for all platforms). For `StorePlatform` types such as `OTHER` and `STEAM_DIRECT` then the first found executable is used and called when the game is launched.
- **DataFolderName**
- Only relevant for Mono (C#) Unity games. Only used for the `Preloader Fix` in the manager settings.
- **TsUrl**
- The Thunderstore API endpoint for the listing.
- **ExclusionsUrl**
- The URL used to load package exclusions. Generally just kept the same. No real reason to change until it becomes a large file as extra overhead to maintain.
- **Platforms**
- Array of `StorePlatformMetadata` for the different supported storefronts. Identifier is not required, however is expected for Steam and EGS (more potentially in future).
- **GameImage**
- Name of the file to display on the GameSelectionScreen view.
- The image file should be placed under `/src/assets/images/game_selection/`. Ensure resolution is 188x250px.
- **DisplayMode**
- Is the game selection presented to the user? (VISIBLE/HIDDEN).
- **InstanceType**
- Is it a game or a server? Only affects the tab the listing is displayed in on the GameSelectionScreen view.
- **PackageLoader**
- Default package loader to use for game launch rules. Saves having to write new rules each time.
- **AdditionalSearchStrings (Optional)**
- Other names that can be used to search for a game on the GameSelectionScreen view.
- EG: RoR2, NASB, TABS, etc.
- Pattern is lowercase although likely isn't necessary.

### Update SettingsDexieStore
You'll find a line similar to:
```ts
this.version(33).stores(store);
```
Bump this by one each time a game is added to GameManager.

### Creating new installation rules
- See all files under `/src/r2mm/installing/default_installation_rules/`
- `game_rules` stores all game specific rules.
- Make a new rule file here.
- `generic` is unused currently. Plans to have a standard "BepInEx" rule for example where other rules can build on top of it.
- `InstallationRuleApplicator.ts` is used to load the installation rules. The app will result in a white screen if this is not modified.

To add a new game rule you'll need the following:
- **gameName**
- The InternalFolderName for the game listing.
- **rules**
- The installation rules to be applied
- **relativeFileExclusions**
- Currently only for state-based installs. Used to strip specific files such as `manifest.json` to reduce conflict management abuse.

#### How to write rules
- **route**
- The path relative to the profile folder.
- **isDefaultLocation**
- If no files can be resolved, place them here following the `trackingMethod` given.
- **defaultFileExtensions**
- Similar to `isDefaultLocation` but for any matching extensions.
- **trackingMethod**
- SUBDIR
- Places files in their own namespaced folder inside of the route.
- STATE
- Places files as-is into the route. Has conflict management built-in.
- NONE
- Places files as-is into the route. These files do not have conflict management and cannot be managed further. (No disable/uninstall behaviour).
- Ideal for configs.
- **subRoutes**
- Useful to allow multiple nested resolutions where override folders may be required at both a high and low level of the route.
- See BONEWORKS install rules for an example.
- Allows us to grant or restrict access to the raw BepInEx folder for example depending on the ending of the route name.

> Remember to add this to `InstallationRuleApplicator.ts`
#### Quick note: Resolving a directory
To summarise, a directory is placed (according to the `trackingMethod`) if the end of the route name matches.

See: [Structuring your Thunderstore package](https://github.com/ebkr/r2modmanPlus/wiki/Structuring-your-Thunderstore-package) for detailed information on how rules are handled.

### How to handle the mod loader
We have a "nice" `ModLoaderVariantRecord.ts` file which records which mod loader packages are associate with each game, and how to handle them.
- Key is the InternalFolderName
- Array can contain multiple `ModLoaderPackageMapping` objects
- **PackageName**
- The TS package name
- **RootFolder**
- Used for BepInEx. The subfolder where the files are located to be extracted into the profile dir.
- **LoaderType**
- BEPINEX / MELON_LOADER / NORTHSTAR / etc

## Finally, test
You should always test manually to ensure no white screen is present on startup.

There is also a test suite to ensure that the installation rules work as intended (under `/test/folder-structure-testing`).
This suite works by adding files to any folders you create when running populator.mjs. (Should run depopulator.mjs prior to repopulating).

> Folder names such as `BIE_GameSpecific_GTFO_GameData_Files` are created by the populator. You do not need to create these files.
### Running populator tests
- You'll need to run either `test:unit:ci` or `test:unit:ui`
- Test files are located under `/test/jest/__tests__/impl/install_logic/game_specific/`
- These are generally only needed if any rules were modified after being copied.

24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@
"bulma-switch": "^2.0.0",
"bulma-tooltip": "^3.0.2",
"core-js": "^3.6.5",
"dexie": "^3.0.3",
"dexie": "^3.2.2",
"dot-prop": "^5.2.0",
"electron-updater": "^4.2.0",
"electron-updater": "^5.0.1",
"elliptic": "^6.5.4",
"fs-extra": "^8.1.0",
"fs-extra": "^10.1.0",
"glob-parent": "^6.0.2",
"highlight.js": "^10.4.1",
"moment": "^2.29.1",
"node-ipc": "^9.1.1",
"moment": "^2.29.3",
"node-ipc": "^11.1.0",
"quasar": "^1.14.7",
"quill": "^1.3.7",
"sanitize-filename": "^1.6.3",
"serialize-javascript": "^3.1.0",
"serialize-javascript": "^6.0.0",
"tar": "^6.1.11",
"trim-newlines": "^4.0.2",
"unzipper": "^0.10.5",
Expand Down Expand Up @@ -83,22 +83,22 @@
"babel-jest": "^27.0.2",
"chai": "^4.2.0",
"devtron": "^1.4.0",
"electron": "^11.1.1",
"electron": "^19.0.2",
"electron-builder": "22.9.1",
"electron-debug": "^3.0.1",
"electron-devtools-installer": "^3.0.0",
"electron-packager": "^14.1.1",
"electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0",
"electron-packager": "^15.5.1",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.0.0",
"eslint-loader": "^2.1.1",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-vue": "^5.0.0",
"identity-obj-proxy": "^3.0.0",
"majestic": "^1.2.24",
"majestic": "^1.8.1",
"minimist": "^1.2.2",
"mocha": "^8.0.1",
"mock-require": "^3.0.3",
"node-sass": "^4.13.1",
"node-sass": "^7.0.1",
"sinon": "^11.1.1",
"ts-node": "^8.10.2",
"typescript": "^4.5.5",
Expand Down
6 changes: 3 additions & 3 deletions src-electron/main-process/electron-main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { app, BrowserWindow, nativeTheme, protocol } from 'electron';
import { app, BrowserWindow, ipcMain, nativeTheme, protocol } from 'electron';
import Listeners from './ipcListeners';
import Persist from './window-state-persist';
import { ipcMain } from 'electron';
import path from 'path';
import ipcServer from 'node-ipc';
import * as fs from 'fs';
Expand Down Expand Up @@ -44,7 +43,8 @@ function createWindow() {
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
webSecurity: false
webSecurity: false,
contextIsolation: false,
},
icon: path.join(__dirname, 'icon.png'),
autoHideMenuBar: process.env.PROD
Expand Down
Binary file added src/assets/images/game_selection/OutwardDe.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/game_selection/Peglin.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/game_selection/VRising.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/views/DownloadModModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ let assignId = 0;
}
if (olderInstallOfMod !== undefined) {
if (!olderInstallOfMod.isEnabled()) {
await ProfileModList.updateMod(manifestMod, profile, mod => {
await ProfileModList.updateMod(manifestMod, profile, async mod => {
mod.disable();
});
await ProfileInstallerProvider.instance.disableMod(manifestMod, profile);
Expand Down
23 changes: 22 additions & 1 deletion src/model/game/GameManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,20 @@ export default class GameManager {
'Outward', ['Outward.exe'], 'Outward_Data',
'https://outward.thunderstore.io/api/v1/package/', 'https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md',
[
new StorePlatformMetadata(StorePlatform.STEAM, "794260"),
new StorePlatformMetadata(StorePlatform.STEAM_DIRECT, "794260"),
new StorePlatformMetadata(StorePlatform.EPIC_GAMES_STORE, "Viola"),
new StorePlatformMetadata(StorePlatform.OTHER)
], "Outward.jpg", GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX),

new Game('Outward Definitive', 'OutwardDe', 'OutwardDe',
'Outward/Outward_Defed', ['Outward Definitive Edition.exe'], 'Outward Definitive Edition_Data',
'https://outward.thunderstore.io/api/v1/package/', 'https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md',
[
new StorePlatformMetadata(StorePlatform.STEAM_DIRECT, "794260"),
new StorePlatformMetadata(StorePlatform.EPIC_GAMES_STORE, "f07a51af8ac845ea96f792fb485e04a3"),
new StorePlatformMetadata(StorePlatform.OTHER)
], "OutwardDe.jpg", GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX),

new Game('TaleSpire', 'TaleSpire', 'TaleSpire',
'TaleSpire', ['TaleSpire.exe'], 'TaleSpire_Data',
'https://talespire.thunderstore.io/api/v1/package/', 'https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md',
Expand Down Expand Up @@ -209,6 +218,18 @@ export default class GameManager {
[new StorePlatformMetadata(StorePlatform.STEAM_DIRECT, "1237970"), new StorePlatformMetadata(StorePlatform.ORIGIN, "")], "Titanfall2.jpg",
GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.NORTHSTAR, ["northstar", "ns", "tf2", "tf|2"]),

new Game("Peglin", "Peglin", "Peglin",
"Peglin", ["Peglin.exe"], "Peglin_Data",
"https://thunderstore.io/c/peglin/api/v1/package/", "https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md",
[new StorePlatformMetadata(StorePlatform.STEAM, "1296610"), new StorePlatformMetadata(StorePlatform.OTHER)], "Peglin.jpg",
GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX, []),

new Game("V Rising", "VRising", "VRising",
"VRising", ["VRising.exe"], "VRising_Data",
"https://thunderstore.io/c/v-rising/api/v1/package/", "https://raw.githubusercontent.com/ebkr/r2modmanPlus/master/modExclusions.md",
[new StorePlatformMetadata(StorePlatform.STEAM, "1604030")], "VRising.jpg",
GameSelectionDisplayMode.VISIBLE, GameInstanceType.GAME, PackageLoader.BEPINEX, ["vrising"]),

];

static get activeGame(): Game {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Manager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,7 @@ import GameInstructions from '../r2mm/launching/instructions/GameInstructions';
this.showError(profileErr);
continue;
}
const update: ManifestV2[] | R2Error = await ProfileModList.updateMod(mod, this.contextProfile!, (updatingMod: ManifestV2) => {
const update: ManifestV2[] | R2Error = await ProfileModList.updateMod(mod, this.contextProfile!, async (updatingMod: ManifestV2) => {
if (enabled) {
updatingMod.enable();
} else {
Expand Down
4 changes: 3 additions & 1 deletion src/pages/Profiles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,10 @@ export default class Profiles extends Vue {
for (const imported of modList) {
if (imported.getName() == comboMod.getMod().getFullName() && !imported.isEnabled()) {
await ProfileModList.updateMod(installResult, Profile.getActiveProfile(), async modToDisable => {
modToDisable.disable();
// Need to enable temporarily so the manager doesn't think it's re-disabling a disabled mod.
modToDisable.enable();
await ProfileInstallerProvider.instance.disableMod(modToDisable, Profile.getActiveProfile());
modToDisable.disable();
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/r2mm/installing/LocalModInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export default class LocalModInstaller extends LocalModInstallerProvider {
}
}
await FsProvider.instance.writeFile(path.join(cacheDirectory, manifest.getName(), manifest.getVersionNumber().toString(), "mm_v2_manifest.json"), JSON.stringify(manifest));
await ProfileInstallerProvider.instance.uninstallMod(manifest, profile);
const profileInstallResult = await ProfileInstallerProvider.instance.installMod(manifest, profile);
if (profileInstallResult instanceof R2Error) {
callback(false, profileInstallResult);
Expand All @@ -85,6 +86,7 @@ export default class LocalModInstaller extends LocalModInstallerProvider {
const fileSafe = file.split("\\").join("/");
await FsProvider.instance.copyFile(fileSafe, path.join(modCacheDirectory, path.basename(fileSafe)));
await FsProvider.instance.writeFile(path.join(modCacheDirectory, "mm_v2_manifest.json"), JSON.stringify(manifest));
await ProfileInstallerProvider.instance.uninstallMod(manifest, profile);
const profileInstallResult = await ProfileInstallerProvider.instance.installMod(manifest, profile);
if (profileInstallResult instanceof R2Error) {
callback(false, profileInstallResult);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import InstallRules_DysonSphereProgram from '../default_installation_rules/game_
import InstallRules_Valheim from '../default_installation_rules/game_rules/InstallRules_Valheim';
import InstallRules_GTFO from '../default_installation_rules/game_rules/InstallRules_GTFO';
import InstallRules_Outward from '../default_installation_rules/game_rules/InstallRules_Outward';
import InstallRules_OutwardDe from '../default_installation_rules/game_rules/InstallRules_OutwardDe';
import InstallRules_TaleSpire from '../default_installation_rules/game_rules/InstallRules_TaleSpire';
import InstallRules_H3VR from '../default_installation_rules/game_rules/InstallRules_H3VR';
import InstallRules_ROUNDS from '../default_installation_rules/game_rules/InstallRules_ROUNDS';
Expand All @@ -29,6 +30,8 @@ import InstallRules_Subnautica from '../default_installation_rules/game_rules/In
import InstallRules_SubnauticaBZ from '../default_installation_rules/game_rules/InstallRules_SubnauticaBZ';
import InstallRules_CoreKeeper from '../default_installation_rules/game_rules/InstallRules_CoreKeeper';
import InstallRules_Titanfall2 from '../default_installation_rules/game_rules/InstallRules_Titanfall2';
import InstallRules_Peglin from '../default_installation_rules/game_rules/InstallRules_Peglin';
import InstallRules_VRising from '../default_installation_rules/game_rules/InstallRules_VRising';

export default class InstallationRuleApplicator {

Expand All @@ -40,6 +43,7 @@ export default class InstallationRuleApplicator {
InstallRules_Valheim(),
InstallRules_GTFO(),
InstallRules_Outward(),
InstallRules_OutwardDe(),
InstallRules_TaleSpire(),
InstallRules_H3VR(),
InstallRules_ROUNDS(),
Expand All @@ -63,6 +67,8 @@ export default class InstallationRuleApplicator {
InstallRules_SubnauticaBZ(),
InstallRules_CoreKeeper(),
InstallRules_Titanfall2(),
InstallRules_Peglin(),
InstallRules_VRising(),
]
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { CoreRuleType } from '../../InstallationRules';
import * as path from 'path';

export default function(): CoreRuleType {

return {
gameName: "OutwardDe",
rules: [
{
route: path.join("BepInEx", "plugins"),
isDefaultLocation: true,
defaultFileExtensions: [".dll"],
trackingMethod: "SUBDIR",
subRoutes: []
},
{
route: path.join("BepInEx", "core"),
defaultFileExtensions: [],
trackingMethod: "SUBDIR",
subRoutes: []
},
{
route: path.join("BepInEx", "patchers"),
defaultFileExtensions: [],
trackingMethod: "SUBDIR",
subRoutes: []
},
{
route: path.join("BepInEx", "monomod"),
defaultFileExtensions: [".mm.dll"],
trackingMethod: "SUBDIR",
subRoutes: []
},
{
route: path.join("BepInEx", "config"),
defaultFileExtensions: [],
trackingMethod: "NONE",
subRoutes: []
}
]
}

}
Loading

0 comments on commit 63a5dee

Please sign in to comment.