Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e741552e7 | |||
| 33a724a796 |
@@ -25,30 +25,6 @@ jobs:
|
|||||||
# echo "MY_LOWER=$lower_value" >> $GITHUB_ENV
|
# echo "MY_LOWER=$lower_value" >> $GITHUB_ENV
|
||||||
# If you want to use it as an output of this step:
|
# If you want to use it as an output of this step:
|
||||||
echo "lowercase=$lower_value" >> $GITHUB_OUTPUT
|
echo "lowercase=$lower_value" >> $GITHUB_OUTPUT
|
||||||
- name: Convert ref to buildx safe value
|
|
||||||
id: docker_tag_from_ref
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
# Grab the raw ref
|
|
||||||
REF="${{ github.ref }}"
|
|
||||||
|
|
||||||
# Strip the "refs/*/" prefix (refs/heads/, refs/tags/…)
|
|
||||||
TAG=${REF#refs/*/}
|
|
||||||
|
|
||||||
# Replace characters that Docker tags disallow
|
|
||||||
# * "/" → "-"
|
|
||||||
# * ":" → "-"
|
|
||||||
# * Any other non‑alphanumeric / . / _ / - → "-"
|
|
||||||
TAG=${TAG//\//-}
|
|
||||||
TAG=${TAG//:/-}
|
|
||||||
TAG=${TAG//[^a-zA-Z0-9._-]/-}
|
|
||||||
|
|
||||||
# (Optional) force lower‑case – Docker tags are case‑sensitive,
|
|
||||||
# but many people prefer lower‑case
|
|
||||||
TAG=${TAG,,}
|
|
||||||
|
|
||||||
# Export to the action's output
|
|
||||||
echo "docker-tag=${TAG}" >> $GITHUB_OUTPUT
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 1. Checkout repository
|
# 1. Checkout repository
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@@ -94,7 +70,7 @@ jobs:
|
|||||||
push: false
|
push: false
|
||||||
tags: |
|
tags: |
|
||||||
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ github.sha }}
|
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ github.sha }}
|
||||||
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ steps.docker_tag_from_ref.outputs.docker-tag }}
|
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ github.ref_name }}
|
||||||
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:latest
|
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:latest
|
||||||
build-args: |
|
build-args: |
|
||||||
# Add any build args here
|
# Add any build args here
|
||||||
@@ -113,7 +89,7 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ github.sha }}
|
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ github.sha }}
|
||||||
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ steps.docker_tag_from_ref.outputs.docker-tag }}
|
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ github.ref_name }}
|
||||||
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:latest
|
${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:latest
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||||
@@ -132,5 +108,5 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "Pushed image tags:"
|
echo "Pushed image tags:"
|
||||||
echo "- ${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ github.sha }}"
|
echo "- ${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ github.sha }}"
|
||||||
echo "- ${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ steps.docker_tag_from_ref.outputs.docker-tag }}"
|
echo "- ${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:${{ github.ref_name }}"
|
||||||
echo "- ${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:latest"
|
echo "- ${{ vars.REGISTRY_HOST }}/${{ steps.github_repository_to_lowercase.outputs.lowercase }}:latest"
|
||||||
@@ -61,7 +61,7 @@ public class AuthStore : IDisposable, IAuthStore
|
|||||||
private static string DetermineCredentialsPath(string? settingsCredentialsFile, IHostEnvironment env)
|
private static string DetermineCredentialsPath(string? settingsCredentialsFile, IHostEnvironment env)
|
||||||
{
|
{
|
||||||
if (settingsCredentialsFile == null) return Path.Combine("app","data","credentials.json");
|
if (settingsCredentialsFile == null) return Path.Combine("app","data","credentials.json");
|
||||||
return Path.IsPathRooted(settingsCredentialsFile) ? settingsCredentialsFile : Path.Combine(env.ContentRootPath,"data",settingsCredentialsFile);
|
return Path.IsPathRooted(settingsCredentialsFile) ? settingsCredentialsFile : Path.Combine(env.ContentRootPath,"app","data",settingsCredentialsFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using TinfoilVibeServer.Authentication;
|
using TinfoilVibeServer.Authentication;
|
||||||
@@ -13,31 +12,6 @@ builder.Logging.AddDebug();
|
|||||||
|
|
||||||
builder.Services.AddMemoryCache();
|
builder.Services.AddMemoryCache();
|
||||||
var dataRoot = builder.Configuration["CONFIG_ROOT"] ?? "/app/config/";
|
var dataRoot = builder.Configuration["CONFIG_ROOT"] ?? "/app/config/";
|
||||||
// 1️⃣ Load the embedded default
|
|
||||||
var defaultResource = typeof(Program).Assembly
|
|
||||||
.GetManifestResourceStream("TinfoilVibeServer.appsettings.default.json")!; // adjust namespace
|
|
||||||
var defaultConfig = JsonDocument.Parse(defaultResource).RootElement;
|
|
||||||
|
|
||||||
// 2️⃣ Try to write the file if it doesn't exist
|
|
||||||
var configPath = Path.Combine(dataRoot, "appsettings.json");
|
|
||||||
if (!File.Exists(configPath))
|
|
||||||
{
|
|
||||||
// write the embedded JSON straight to disk
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.WriteAllText(configPath, defaultConfig.GetRawText());
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var tempFactory = LoggerFactory.Create(loggingBuilder =>
|
|
||||||
{
|
|
||||||
loggingBuilder.AddConsole();
|
|
||||||
loggingBuilder.AddDebug();
|
|
||||||
});
|
|
||||||
var logger = tempFactory.CreateLogger<Program>();
|
|
||||||
logger.LogError(e, "Failed to write default config file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = new ConfigurationBuilder()
|
var config = new ConfigurationBuilder()
|
||||||
.AddJsonFile(Path.Combine(dataRoot,"appsettings.json"), optional: false, reloadOnChange: true)
|
.AddJsonFile(Path.Combine(dataRoot,"appsettings.json"), optional: false, reloadOnChange: true)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class ConfigManager
|
|||||||
|
|
||||||
public ConfigManager()
|
public ConfigManager()
|
||||||
{
|
{
|
||||||
_configPath = Path.Combine(AppContext.BaseDirectory, "config", "appsettings.json");
|
_configPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
||||||
Load();
|
Load();
|
||||||
|
|
||||||
_watcher = new FileSystemWatcher
|
_watcher = new FileSystemWatcher
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
@@ -35,49 +34,21 @@ namespace TinfoilVibeServer.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class NSPExtractor : INSPExtractor
|
public sealed class NSPExtractor : INSPExtractor
|
||||||
{
|
{
|
||||||
private KeySet? _keySet;
|
private readonly KeySet _keySet;
|
||||||
|
|
||||||
public KeySet? KeySet
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_keySet != null) return _keySet;
|
|
||||||
if (_options.CurrentValue.KeyFile == null) return null;
|
|
||||||
var dataRoot = _environment.ContentRootPath ?? "/app/config";
|
|
||||||
if (Path.IsPathRooted(_options.CurrentValue.KeyFile))
|
|
||||||
{
|
|
||||||
_keySet = ExternalKeyReader.ReadKeyFile(_options.CurrentValue.KeyFile);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_keySet = ExternalKeyReader.ReadKeyFile(Path.Combine(dataRoot, "config", _options.CurrentValue.KeyFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
return _keySet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly IOptionsMonitor<NSPExtractorOptions> _options;
|
|
||||||
private readonly ILogger<INSPExtractor> _logger;
|
private readonly ILogger<INSPExtractor> _logger;
|
||||||
private readonly IHostEnvironment _environment;
|
|
||||||
|
|
||||||
public NSPExtractor(IOptionsMonitor<NSPExtractorOptions> options, ILogger<INSPExtractor> logger, IHostEnvironment environment)
|
public NSPExtractor(IOptions<NSPExtractorOptions> options, ILogger<INSPExtractor> logger, IHostEnvironment environment)
|
||||||
{
|
{
|
||||||
_options = options;
|
var dataRoot = environment.ContentRootPath ?? "/app/config";
|
||||||
_options.OnChange(o =>
|
if (Path.IsPathRooted(options.Value.keyFile))
|
||||||
{
|
{
|
||||||
if (o.KeyFile == null)
|
_keySet = ExternalKeyReader.ReadKeyFile(options.Value.keyFile);
|
||||||
{
|
}
|
||||||
_logger?.LogInformation("No KeySet specified, skipping key validation");
|
else
|
||||||
}
|
{
|
||||||
|
_keySet = ExternalKeyReader.ReadKeyFile(Path.Combine(dataRoot, "config", options.Value.keyFile));
|
||||||
if (!File.Exists(o.KeyFile))
|
}
|
||||||
{
|
|
||||||
_logger?.LogWarning("KeySet file {KeyFile} does not exist", o.KeyFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_environment = environment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -94,8 +65,6 @@ namespace TinfoilVibeServer.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public NcaMetadataWithHash? ExtractFromStream(Stream stream)
|
public NcaMetadataWithHash? ExtractFromStream(Stream stream)
|
||||||
{
|
{
|
||||||
if (KeySet == null) return null;
|
|
||||||
|
|
||||||
if (!stream.CanSeek) return null;
|
if (!stream.CanSeek) return null;
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
@@ -108,7 +77,7 @@ namespace TinfoilVibeServer.Services
|
|||||||
|
|
||||||
if (IsXciFileSystem(stream))
|
if (IsXciFileSystem(stream))
|
||||||
{
|
{
|
||||||
var xci = new Xci(KeySet, storage);
|
var xci = new Xci(_keySet, storage);
|
||||||
List<DirectoryEntryEx> ncaEntries;
|
List<DirectoryEntryEx> ncaEntries;
|
||||||
if (xci.HasPartition(XciPartitionType.Secure))
|
if (xci.HasPartition(XciPartitionType.Secure))
|
||||||
{
|
{
|
||||||
@@ -126,7 +95,7 @@ namespace TinfoilVibeServer.Services
|
|||||||
using var ncaFile = fileRef.Release();
|
using var ncaFile = fileRef.Release();
|
||||||
using var ncaFileStorage = new FileStorage(ncaFile);
|
using var ncaFileStorage = new FileStorage(ncaFile);
|
||||||
|
|
||||||
var nca = new Nca(KeySet, ncaFileStorage);
|
var nca = new Nca(_keySet, ncaFileStorage);
|
||||||
if (hash == null)
|
if (hash == null)
|
||||||
{
|
{
|
||||||
// Hash the *first* NCA stream – the stream we just opened
|
// Hash the *first* NCA stream – the stream we just opened
|
||||||
@@ -153,8 +122,6 @@ namespace TinfoilVibeServer.Services
|
|||||||
|
|
||||||
private NcaMetadataWithHash? ExtractNSPFromStream(StreamStorage storage)
|
private NcaMetadataWithHash? ExtractNSPFromStream(StreamStorage storage)
|
||||||
{
|
{
|
||||||
if (KeySet == null) return null;
|
|
||||||
|
|
||||||
List<DirectoryEntryEx> ncaEntries;
|
List<DirectoryEntryEx> ncaEntries;
|
||||||
_logger.LogInformation("Processing as NSP");
|
_logger.LogInformation("Processing as NSP");
|
||||||
var partition = new PartitionFileSystem();
|
var partition = new PartitionFileSystem();
|
||||||
@@ -172,7 +139,7 @@ namespace TinfoilVibeServer.Services
|
|||||||
using var ncaFile = fileRef.Release();
|
using var ncaFile = fileRef.Release();
|
||||||
using var ncaFileStorage = new FileStorage(ncaFile);
|
using var ncaFileStorage = new FileStorage(ncaFile);
|
||||||
|
|
||||||
var nca = new Nca(KeySet, ncaFileStorage);
|
var nca = new Nca(_keySet, ncaFileStorage);
|
||||||
if (hash == null)
|
if (hash == null)
|
||||||
{
|
{
|
||||||
// Hash the *first* NCA stream – the stream we just opened
|
// Hash the *first* NCA stream – the stream we just opened
|
||||||
@@ -241,8 +208,6 @@ namespace TinfoilVibeServer.Services
|
|||||||
}
|
}
|
||||||
private bool IsXciFileSystem(Stream stream)
|
private bool IsXciFileSystem(Stream stream)
|
||||||
{
|
{
|
||||||
if (KeySet == null) return false;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!stream.CanSeek) return false;
|
if (!stream.CanSeek) return false;
|
||||||
@@ -251,7 +216,7 @@ namespace TinfoilVibeServer.Services
|
|||||||
var storage = new StreamStorage(stream, true);
|
var storage = new StreamStorage(stream, true);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var xciBlock = new Xci(KeySet, storage);
|
var xciBlock = new Xci(_keySet, storage);
|
||||||
_logger.LogInformation("XCI found");
|
_logger.LogInformation("XCI found");
|
||||||
return xciBlock.HasPartition(XciPartitionType.Secure);
|
return xciBlock.HasPartition(XciPartitionType.Secure);
|
||||||
}
|
}
|
||||||
@@ -270,8 +235,6 @@ namespace TinfoilVibeServer.Services
|
|||||||
|
|
||||||
public string ExtractHashFromStream(Stream nspStream)
|
public string ExtractHashFromStream(Stream nspStream)
|
||||||
{
|
{
|
||||||
if (KeySet == null) return string.Empty;
|
|
||||||
|
|
||||||
if (!IsPfs0FileSystem(nspStream))
|
if (!IsPfs0FileSystem(nspStream))
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
@@ -296,7 +259,7 @@ namespace TinfoilVibeServer.Services
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var nca = new Nca(KeySet, ncaFileStorage);
|
var nca = new Nca(_keySet, ncaFileStorage);
|
||||||
if (nca.Header.ContentType != NcaContentType.Meta)
|
if (nca.Header.ContentType != NcaContentType.Meta)
|
||||||
continue; // only the meta NCA contains title metadata
|
continue; // only the meta NCA contains title metadata
|
||||||
|
|
||||||
@@ -320,7 +283,7 @@ namespace TinfoilVibeServer.Services
|
|||||||
|
|
||||||
public class NSPExtractorOptions
|
public class NSPExtractorOptions
|
||||||
{
|
{
|
||||||
public string? KeyFile { get; set; }
|
public string keyFile { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -529,22 +529,6 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileContainedInRootDirectories = false;
|
|
||||||
foreach (var optionsRootDirectory in _options.RootDirectories)
|
|
||||||
{
|
|
||||||
if (fileEntry.Path.StartsWith(optionsRootDirectory))
|
|
||||||
{
|
|
||||||
fileContainedInRootDirectories = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fileContainedInRootDirectories)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Entry {Path} is not contained in any root directory", fileEntry.Path);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_options.RomExtensions.Contains(Path.GetExtension(fileEntry.Path)))
|
if (_options.RomExtensions.Contains(Path.GetExtension(fileEntry.Path)))
|
||||||
{
|
{
|
||||||
if (fileEntry.Path.Contains(ArchivePathSeparator))
|
if (fileEntry.Path.Contains(ArchivePathSeparator))
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ public sealed class TitleDatabaseService : IHostedService
|
|||||||
private readonly IOptionsMonitor<TitleDbOptions> _options;
|
private readonly IOptionsMonitor<TitleDbOptions> _options;
|
||||||
private readonly ILogger<TitleDatabaseService> _logger;
|
private readonly ILogger<TitleDatabaseService> _logger;
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
|
private readonly INSPExtractor _nspExtractor;
|
||||||
private readonly string _cacheFolder; // Where the JSON is cached.
|
private readonly string _cacheFolder; // Where the JSON is cached.
|
||||||
|
private readonly List<string> _rootDirectories; // directories that contain game files
|
||||||
|
|
||||||
private readonly IMemoryCache _cache;
|
private readonly IMemoryCache _cache;
|
||||||
private readonly ISnapshotService _snapshotService;
|
private readonly ISnapshotService _snapshotService;
|
||||||
@@ -48,6 +50,7 @@ public sealed class TitleDatabaseService : IHostedService
|
|||||||
/// directories that contain the NSP files.
|
/// directories that contain the NSP files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TitleDatabaseService(
|
public TitleDatabaseService(
|
||||||
|
IConfiguration configuration,
|
||||||
IOptionsMonitor<TitleDbOptions> options,
|
IOptionsMonitor<TitleDbOptions> options,
|
||||||
ILogger<TitleDatabaseService> logger,
|
ILogger<TitleDatabaseService> logger,
|
||||||
ISnapshotService snapshotService,
|
ISnapshotService snapshotService,
|
||||||
@@ -59,10 +62,11 @@ public sealed class TitleDatabaseService : IHostedService
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_snapshotService = snapshotService;
|
_snapshotService = snapshotService;
|
||||||
_httpFactory = httpFactory;
|
_httpFactory = httpFactory;
|
||||||
|
_nspExtractor = nspExtractor;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
|
|
||||||
_cacheFolder = Path.Combine(AppContext.BaseDirectory, "data", "titledb-cache");
|
_cacheFolder = Path.Combine(AppContext.BaseDirectory, "titledb-cache");
|
||||||
new List<string>
|
_rootDirectories = new List<string>
|
||||||
{
|
{
|
||||||
// You can extend this list – it is the set of directories that
|
// You can extend this list – it is the set of directories that
|
||||||
// are scanned when the service starts up.
|
// are scanned when the service starts up.
|
||||||
|
|||||||
@@ -58,8 +58,5 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Remove="obj\**" />
|
<EmbeddedResource Remove="obj\**" />
|
||||||
<EmbeddedResource Include="appsettings.default.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</EmbeddedResource>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*",
|
|
||||||
|
|
||||||
"CredentialsFile": "/app/data/credentials.json",
|
|
||||||
"FingerprintsFile": "/app/data/fingerprints.json",
|
|
||||||
"BlacklistFile": "/app/data/blacklist.json",
|
|
||||||
"MaxFailedAttempts": 5,
|
|
||||||
"Snapshot" : {
|
|
||||||
"RootDirectories": [ ],
|
|
||||||
"ArchiveExtensions": [ ".zip", ".rar", ".7z" ],
|
|
||||||
"RomExtensions": [ ".xci", ".nsp", ".xcz" ],
|
|
||||||
"CacheTtl": 60,
|
|
||||||
"SnapshotFile": "/app/data/snapshot.json",
|
|
||||||
"SnapshotBackupFile": "/app/data/snapshot.bak"
|
|
||||||
},
|
|
||||||
"NSPExtractor": {
|
|
||||||
"KeyFile": "/app/config/prod.keys"
|
|
||||||
},
|
|
||||||
"IndexBuilder": {
|
|
||||||
"ApiBaseUrl": "http://tinfoil.localhost:8080",
|
|
||||||
"IndexDirectories": [
|
|
||||||
"https://url1",
|
|
||||||
"sdmc:/url2",
|
|
||||||
"http://url3"
|
|
||||||
],
|
|
||||||
"Success" : "Welcome to Tinfoil Vibe Server!"
|
|
||||||
},
|
|
||||||
"TitleDb": {
|
|
||||||
"CountryCode": "AU",
|
|
||||||
"Language": "en",
|
|
||||||
"TtlSeconds" : 90
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user