mirror of
https://github.com/rfvgyhn/min-ed-launcher.git
synced 2026-02-04 14:05:42 +00:00
Allow for overriding a product's filter.
Useful for when FDev makes a copy/paste error for a new product (i.e. when they released Odyssey with an "edh" filter instead of "edo") fixes #25
This commit is contained in:
@ -4,10 +4,16 @@
|
||||
|
||||
### Breaking Changes
|
||||
- Restart option has moved to the `/restart delay` argument and is no longer specified in the config file.
|
||||
|
||||
This allows for creating separate shortcuts, one for normal gameplay and one for restarting.
|
||||
|
||||
Instead of specifying `restart: { enabled: true, shutdownTimeout: 3 }`, modify your launch options to include `/restart 3`.
|
||||
|
||||
### New Features
|
||||
- Add ability to override a product's filter via the config file
|
||||
|
||||
Useful for when FDev makes a copy/paste error for a new product (i.e. when they released Odyssey with an "edh" filter instead of "edo")
|
||||
|
||||
## [0.4.0] - 2021-05-11
|
||||
|
||||
### New Features
|
||||
|
||||
40
README.md
40
README.md
@ -160,31 +160,35 @@ Linux: `$XDG_CONFIG_HOME/min-ed-launcher/settings.json` (`~/.config` if `$XDG_CO
|
||||
| language | Sets the game's language. Supported values are _en_ and the names of the language folders in Elite's install directory |
|
||||
| autoUpdate | Automatically update games that are out of date |
|
||||
| maxConcurrentDownloads | Maximum number of simultaneous downloads when downloading updates |
|
||||
| forceUpdate | Be default, Steam and Epic updates are handled by their respective platform. In cases like the Odyssey alpha, FDev doesn't provide updates through Steam or Epic. This allows the launcher to force updates to be done via FDev servers by providing a comma delimited list of SKUs |
|
||||
| forceUpdate | By default, Steam and Epic updates are handled by their respective platform. In cases like the Odyssey alpha, FDev doesn't provide updates through Steam or Epic. This allows the launcher to force updates to be done via FDev servers by providing a comma delimited list of SKUs |
|
||||
| processes | Additional applications to launch before launching the game |
|
||||
| filterOverrides | Manually override a product's filter for use with launch options filter flag (e.g. /edo, /edh, etc...) |
|
||||
|
||||
When specifying a path for either `gameLocation` or `processes.fileName` on Windows, it's required to escape backslashes. Make sure to use a
|
||||
double backslash (`\\`) instead of a single backslash (`\`).
|
||||
|
||||
```json
|
||||
{
|
||||
"apiUri": "https://api.zaonce.net",
|
||||
"watchForCrashes": false,
|
||||
"gameLocation": null,
|
||||
"language": "en",
|
||||
"autoUpdate": true,
|
||||
"maxConcurrentDownloads": 4,
|
||||
"forceUpdate": "PUBLIC_TEST_SERVER_OD",
|
||||
"processes": [
|
||||
{
|
||||
"fileName": "C:\\path\\to\\app.exe",
|
||||
"arguments": "--arg1 --arg2"
|
||||
},
|
||||
{
|
||||
"fileName": "C:\\path\\to\\app2.exe",
|
||||
"arguments": "--arg1 --arg2"
|
||||
}
|
||||
]
|
||||
"apiUri": "https://api.zaonce.net",
|
||||
"watchForCrashes": false,
|
||||
"gameLocation": null,
|
||||
"language": "en",
|
||||
"autoUpdate": true,
|
||||
"maxConcurrentDownloads": 4,
|
||||
"forceUpdate": "PUBLIC_TEST_SERVER_OD",
|
||||
"processes": [
|
||||
{
|
||||
"fileName": "C:\\path\\to\\app.exe",
|
||||
"arguments": "--arg1 --arg2"
|
||||
},
|
||||
{
|
||||
"fileName": "C:\\path\\to\\app2.exe",
|
||||
"arguments": "--arg1 --arg2"
|
||||
}
|
||||
],
|
||||
"filterOverrides": [
|
||||
{ "sku": "FORC-FDEV-DO-1000", "filter": "edo" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -327,7 +327,9 @@ let run settings launcherVersion cancellationToken = task {
|
||||
Log.debug "Getting authorized products"
|
||||
match! Api.getAuthorizedProducts settings.Platform None connection with
|
||||
| Ok authorizedProducts ->
|
||||
let authorizedProducts = authorizedProducts |> List.map (AuthorizedProduct.fixDirectoryPath productsDir settings.Platform Directory.Exists)
|
||||
let applyFixes = AuthorizedProduct.fixDirectoryPath productsDir settings.Platform Directory.Exists
|
||||
>> AuthorizedProduct.fixFilters settings.FilterOverrides
|
||||
let authorizedProducts = authorizedProducts |> List.map applyFixes
|
||||
let names = authorizedProducts |> List.map (fun p -> p.Name)
|
||||
Log.debug $"Authorized Products: %s{String.Join(',', names)}"
|
||||
Log.info "Checking for updates"
|
||||
@ -448,7 +450,7 @@ let run settings launcherVersion cancellationToken = task {
|
||||
if settings.AutoRun then
|
||||
playableProducts
|
||||
|> Array.filter (fun p -> settings.ProductWhitelist.Count = 0
|
||||
|| p.Filters |> Set.union settings.ProductWhitelist |> Set.count > 0)
|
||||
|| p.Filters |> Set.intersect settings.ProductWhitelist |> Set.count > 0)
|
||||
|> Array.tryHead
|
||||
else if playableProducts.Length > 0 then
|
||||
promptForProductToPlay playableProducts cancellationToken
|
||||
|
||||
@ -19,4 +19,12 @@ let fixDirectoryPath productsDir platform directoryExists (product: AuthorizedPr
|
||||
if dirExists product.Directory then product.Directory else product.Sku
|
||||
| Frontier _ | Oculus _ | Dev ->
|
||||
if dirExists product.Sku then product.Sku else product.Directory
|
||||
{ product with Directory = dir }
|
||||
{ product with Directory = dir }
|
||||
|
||||
// Allows for overriding a product's filter. Useful for when FDev makes a copy/paste error
|
||||
// for a new product (i.e. when they released Odyssey with an "edh" filter instead of "edo")
|
||||
let fixFilters overrides (product: AuthorizedProduct) =
|
||||
overrides
|
||||
|> Map.tryFind product.Sku
|
||||
|> Option.map (fun filter -> { product with Filter = filter })
|
||||
|> Option.defaultValue product
|
||||
@ -24,7 +24,8 @@ let defaults =
|
||||
AutoUpdate = true
|
||||
MaxConcurrentDownloads = 4
|
||||
ForceUpdate = Set.empty
|
||||
Processes = List.empty }
|
||||
Processes = List.empty
|
||||
FilterOverrides = Map.empty }
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type FrontierCredResult = Found of string * string * string option | NotFound of string | UnexpectedFormat of string | Error of string
|
||||
@ -135,10 +136,8 @@ let parseArgs defaults (findCbLaunchDir: Platform -> Result<string,string>) (arg
|
||||
| "/autorun", _ -> { s with AutoRun = true }
|
||||
| "/autoquit", _ -> { s with AutoQuit = true }
|
||||
| "/forcelocal", _ -> { s with ForceLocal = true }
|
||||
| "/ed", _ -> { s with ProductWhitelist = s.ProductWhitelist.Add "ed" }
|
||||
| "/edh", _ -> { s with ProductWhitelist = s.ProductWhitelist.Add "edh" }
|
||||
| "/eda", _ -> { s with ProductWhitelist = s.ProductWhitelist.Add "eda" }
|
||||
| "/edo", _ -> { s with ProductWhitelist = s.ProductWhitelist.Add "edo" }
|
||||
| arg, _ when arg.StartsWith('/')
|
||||
&& arg.Length > 1 -> { s with ProductWhitelist = s.ProductWhitelist.Add (arg.TrimStart('/')) }
|
||||
| _ -> s) defaults
|
||||
|
||||
match cbLaunchDir
|
||||
@ -148,10 +147,9 @@ let parseArgs defaults (findCbLaunchDir: Platform -> Result<string,string>) (arg
|
||||
applyDeviceAuth settings
|
||||
| Result.Error msg -> Result.Error msg |> Task.fromResult
|
||||
|
||||
[<CLIMutable>]
|
||||
type ProcessConfig =
|
||||
{ FileName: string
|
||||
Arguments: string option }
|
||||
|
||||
[<CLIMutable>] type ProcessConfig = { FileName: string; Arguments: string option }
|
||||
[<CLIMutable>] type FilterConfig = { Sku: string; Filter: string }
|
||||
[<CLIMutable>]
|
||||
type Config =
|
||||
{ ApiUri: string
|
||||
@ -163,26 +161,33 @@ type Config =
|
||||
[<DefaultValue("4")>]
|
||||
MaxConcurrentDownloads: int
|
||||
ForceUpdate: string list
|
||||
Processes: ProcessConfig list }
|
||||
Processes: ProcessConfig list
|
||||
FilterOverrides: FilterConfig list }
|
||||
let parseConfig fileName =
|
||||
let configRoot = ConfigurationBuilder()
|
||||
.AddJsonFile(fileName, false)
|
||||
.Build()
|
||||
let parseKvps section keyName valueName map =
|
||||
configRoot.GetSection(section).GetChildren()
|
||||
|> Seq.choose (fun section ->
|
||||
let key = section.GetValue<string>(keyName)
|
||||
let value = section.GetValue<string>(valueName)
|
||||
if String.IsNullOrWhiteSpace(key) then
|
||||
None
|
||||
else
|
||||
map key value)
|
||||
|> Seq.toList
|
||||
match AppConfig(configRoot).Get<Config>() with
|
||||
| Ok config ->
|
||||
// FsConfig doesn't support list of records so handle it manually
|
||||
let processes =
|
||||
configRoot.GetSection("processes").GetChildren()
|
||||
|> Seq.map (fun section ->
|
||||
let fileName = section.GetValue<string>("fileName")
|
||||
let arguments = section.GetValue<string>("arguments")
|
||||
if fileName = null then
|
||||
None
|
||||
else
|
||||
Some { FileName = fileName; Arguments = if arguments = null then None else Some arguments })
|
||||
|> Seq.choose id
|
||||
|> Seq.toList
|
||||
{ config with Processes = processes } |> ConfigParseResult.Ok
|
||||
parseKvps "processes" "fileName" "arguments" (fun key value ->
|
||||
Some { FileName = key; Arguments = if value = null then None else Some value })
|
||||
let filterOverrides =
|
||||
parseKvps "filterOverrides" "sku" "filter" (fun key value ->
|
||||
if String.IsNullOrWhiteSpace(value) then None
|
||||
else Some { Sku = key; Filter = value })
|
||||
{ config with Processes = processes; FilterOverrides = filterOverrides } |> ConfigParseResult.Ok
|
||||
| Error error -> Error error
|
||||
|
||||
let getSettings args appDir fileConfig = task {
|
||||
@ -208,7 +213,7 @@ let getSettings args appDir fileConfig = task {
|
||||
pInfo.RedirectStandardError <- true
|
||||
pInfo.WindowStyle <- ProcessWindowStyle.Minimized
|
||||
pInfo)
|
||||
|
||||
let filterOverrides = fileConfig.FilterOverrides |> Seq.map (fun o -> o.Sku, o.Filter) |> Map.ofSeq
|
||||
let fallbackDirs platform =
|
||||
match platform with
|
||||
| Epic d -> Epic.potentialInstallPaths d.AppId |> findCbLaunchDir
|
||||
@ -224,4 +229,5 @@ let getSettings args appDir fileConfig = task {
|
||||
MaxConcurrentDownloads = fileConfig.MaxConcurrentDownloads
|
||||
PreferredLanguage = fileConfig.Language
|
||||
Processes = processes
|
||||
FilterOverrides = filterOverrides
|
||||
WatchForCrashes = fileConfig.WatchForCrashes }) }
|
||||
@ -45,7 +45,8 @@ type LauncherSettings =
|
||||
AutoUpdate: bool
|
||||
MaxConcurrentDownloads: int
|
||||
ForceUpdate: string Set
|
||||
Processes: ProcessStartInfo list }
|
||||
Processes: ProcessStartInfo list
|
||||
FilterOverrides: Map<string, string> }
|
||||
type ServerStatus = Healthy
|
||||
type LocalVersion = Version
|
||||
type LauncherStatus =
|
||||
|
||||
@ -5,5 +5,6 @@
|
||||
"autoUpdate": true,
|
||||
"maxConcurrentDownloads": 4,
|
||||
"forceUpdate": "",
|
||||
"processes": []
|
||||
"processes": [],
|
||||
"filterOverrides": []
|
||||
}
|
||||
@ -54,4 +54,27 @@ let tests =
|
||||
Expect.equal actual.Directory product.Directory ""
|
||||
}
|
||||
]
|
||||
|
||||
testList "fixFilters" [
|
||||
let product =
|
||||
{ Name = ""; Filter = "oldFilter"; Directory = ""; GameArgs = ""; ServerArgs = ""; SortKey = 0; Sku = "sku"; TestApi = false }
|
||||
test "No change when overrides is empty" {
|
||||
let actual = AuthorizedProduct.fixFilters Map.empty product
|
||||
|
||||
Expect.equal actual product ""
|
||||
}
|
||||
test "No change when no matching sku" {
|
||||
let overrides = [ "asdf", "newFilter" ] |> Map.ofList
|
||||
let actual = AuthorizedProduct.fixFilters overrides product
|
||||
|
||||
Expect.equal actual product ""
|
||||
}
|
||||
test "Updates filter when matching sku" {
|
||||
let newFilter = "newFilter"
|
||||
let overrides = [ product.Sku, newFilter ] |> Map.ofList
|
||||
let actual = AuthorizedProduct.fixFilters overrides product
|
||||
|
||||
Expect.equal actual.Filter newFilter ""
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@ -171,8 +171,11 @@ let tests =
|
||||
let! settings = parseWithFallback (fun _ -> Ok expectedDir) [||]
|
||||
Expect.equal settings.CbLauncherDir expectedDir ""
|
||||
}
|
||||
|
||||
testProperty "Unknown arg doesn't change any values" <|
|
||||
fun (args:string[]) ->
|
||||
// /* args are considered whitelist args and not unknown
|
||||
let args = args |> Array.filter (fun arg -> arg = null || (arg.StartsWith('/') && arg.Length < 2))
|
||||
let settings = parse args
|
||||
settings.Wait()
|
||||
settings.Result = Settings.defaults
|
||||
|
||||
Reference in New Issue
Block a user