同步1.1.9更新内容

This commit is contained in:
WGzeyu 2020-05-20 02:38:53 +08:00
parent 1bcdf5df97
commit 2822c10198
17 changed files with 716 additions and 153 deletions

View file

@ -23,6 +23,7 @@ namespace ModAssistant
public static bool ReinstallInstalledMods;
public static string Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
public static List<string> SavedMods = ModAssistant.Properties.Settings.Default.SavedMods.Split(',').ToList();
public static MainWindow window;
public static bool Update = true;
public static bool GUI = true;
@ -70,10 +71,10 @@ namespace ModAssistant
ReinstallInstalledMods = ModAssistant.Properties.Settings.Default.ReinstallInstalled;
await ArgumentHandler(e.Args);
await Init(Update, GUI);
await Init();
}
private async Task Init(bool Update, bool GUI)
private async Task Init()
{
if (Update)
{
@ -82,9 +83,13 @@ namespace ModAssistant
if (GUI)
{
MainWindow window = new MainWindow();
window = new MainWindow();
window.Show();
}
else
{
//Application.Current.Shutdown();
}
}
private async Task ArgumentHandler(string[] args)
@ -156,6 +161,12 @@ namespace ModAssistant
args = Shift(args, 2);
break;
case "--runforever":
while (true)
{
}
default:
Utils.SendNotify((string)Current.FindResource("App:UnrecognizedArgument"));
args = Shift(args);

View file

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using static ModAssistant.Http;
@ -17,11 +19,13 @@ namespace ModAssistant.API
public static async Task<BeatSaverMap> GetFromKey(string Key, bool showNotification = true)
{
if (showNotification) OneClickInstaller.Status.Show();
return await GetMap(Key, "key", showNotification);
}
public static async Task<BeatSaverMap> GetFromHash(string Hash, bool showNotification = true)
{
if (showNotification) OneClickInstaller.Status.Show();
return await GetMap(Hash, "hash", showNotification);
}
@ -42,42 +46,57 @@ namespace ModAssistant.API
BeatSaverMap map = new BeatSaverMap();
map.Success = false;
if (showNotification) Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Installing"), id)}");
try
{
BeatSaverApiResponse beatsaver = await GetResponse(BeatSaverURLPrefix + urlSegment + id);
map.Name = await InstallMap(beatsaver.map, showNotification);
map.Success = true;
if (beatsaver != null && beatsaver.map != null)
{
map.response = beatsaver;
map.Name = await InstallMap(beatsaver.map, showNotification);
map.Success = true;
}
}
catch (Exception e)
{
ModAssistant.Utils.Log($"Failed downloading BeatSaver map: {id} | Error: {e}", "ERROR");
ModAssistant.Utils.Log($"Failed downloading BeatSaver map: {id} | Error: {e.Message}", "ERROR");
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Failed"), (map.Name ?? id))}");
}
return map;
}
private static async Task<BeatSaverApiResponse> GetResponse(string url, bool showNotification = true)
private static async Task<BeatSaverApiResponse> GetResponse(string url, bool showNotification = true, int retries = 3)
{
if (retries == 0)
{
ModAssistant.Utils.Log($"Max tries reached: Skipping {url}", "ERROR");
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitSkip"), url)}");
throw new Exception("Max retries allowed");
}
BeatSaverApiResponse response = new BeatSaverApiResponse();
try
{
var resp = await HttpClient.GetAsync(url);
response.statusCode = resp.StatusCode;
response.ratelimit = GetRatelimit(resp.Headers);
string body = await resp.Content.ReadAsStringAsync();
if ((int)resp.StatusCode == 429)
{
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitHit"), response.ratelimit.ResetTime)}");
await response.ratelimit.Wait();
return await GetResponse(url, showNotification, retries - 1);
}
if (response.statusCode == HttpStatusCode.OK)
{
if (response.ratelimit.IsSafe)
{
string body = await resp.Content.ReadAsStringAsync();
response.map = JsonSerializer.Deserialize<BeatSaverApiResponseMap>(body);
return response;
}
else
{
return response;
}
response.map = JsonSerializer.Deserialize<BeatSaverApiResponseMap>(body);
return response;
}
else
{
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Failed"), url)}");
return response;
}
}
@ -91,30 +110,8 @@ namespace ModAssistant.API
}
}
private static BeatSaverRatelimit GetRatelimit(HttpResponseHeaders headers)
{
BeatSaverRatelimit ratelimit = new BeatSaverRatelimit();
var _rateLimitRemaining = headers.GetValues("Rate-Limit-Remaining").GetEnumerator();
var _rateLimitReset = headers.GetValues("Rate-Limit-Reset").GetEnumerator();
var _rateLimitTotal = headers.GetValues("Rate-Limit-Total").GetEnumerator();
_rateLimitRemaining.MoveNext();
_rateLimitReset.MoveNext();
_rateLimitTotal.MoveNext();
ratelimit.Remaining = Int32.Parse(_rateLimitRemaining.Current);
ratelimit.Reset = Int32.Parse(_rateLimitReset.Current);
ratelimit.Total = Int32.Parse(_rateLimitTotal.Current);
ratelimit.ResetTime = UnixTimestampToDateTime((long)ratelimit.Reset);
return ratelimit;
}
public static DateTime UnixTimestampToDateTime(double unixTime)
{
DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond);
return new DateTime(unixStart.Ticks + unixTimeStampInTicks, System.DateTimeKind.Utc);
}
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int memcmp(byte[] b1, byte[] b2, long count);
public static async Task<string> InstallMap(BeatSaverApiResponseMap Map, bool showNotification = true)
{
@ -126,34 +123,64 @@ namespace ModAssistant.API
#pragma warning disable CS0162 // Unreachable code detected
if (BypassDownloadCounter)
{
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.directDownload, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification);
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.directDownload, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification, true);
}
else
{
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.downloadURL, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification);
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.downloadURL, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification, true);
}
#pragma warning restore CS0162 // Unreachable code detected
if (File.Exists(zip))
{
using (FileStream stream = new FileStream(zip, FileMode.Open))
using (ZipArchive archive = new ZipArchive(stream))
{
foreach (ZipArchiveEntry file in archive.Entries)
{
string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName));
if (!Directory.Exists(fileDirectory))
{
Directory.CreateDirectory(fileDirectory);
}
byte[] zipMagicNumber = { 80, 75, 3, 4 };
byte[] magicNumber = new byte[4];
if (!string.IsNullOrEmpty(file.Name))
try
{
using (FileStream fs = new FileStream(zip, FileMode.Open, FileAccess.Read))
{
fs.Read(magicNumber, 0, magicNumber.Length);
fs.Close();
}
}
catch
{
return null;
}
if (!(magicNumber.Length == zipMagicNumber.Length && memcmp(magicNumber, zipMagicNumber, magicNumber.Length) == 0))
{
ModAssistant.Utils.Log($"Failed extracting BeatSaver map: {zip} \n| Content: {string.Join("\n", File.ReadAllLines(zip))}", "ERROR");
throw new Exception("File not a zip.");
}
try
{
using (FileStream stream = new FileStream(zip, FileMode.Open))
using (ZipArchive archive = new ZipArchive(stream))
{
foreach (ZipArchiveEntry file in archive.Entries)
{
file.ExtractToFile(Path.Combine(directory, file.FullName), true);
string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName));
if (!Directory.Exists(fileDirectory))
{
Directory.CreateDirectory(fileDirectory);
}
if (!string.IsNullOrEmpty(file.Name))
{
file.ExtractToFile(Path.Combine(directory, file.FullName), true);
}
}
}
}
catch (Exception e)
{
File.Delete(zip);
ModAssistant.Utils.Log($"Failed extracting BeatSaver map: {zip} | Error: {e} \n| Content: {string.Join("\n", File.ReadAllLines(zip))}", "ERROR");
throw new Exception("File extraction failed.");
}
File.Delete(zip);
}
else
@ -165,11 +192,77 @@ namespace ModAssistant.API
string title = (string)Application.Current.FindResource("OneClick:SongDownload:FailedTitle");
MessageBox.Show($"{line1}\n{line2}", title);
}
return null;
throw new Exception("Zip file not found.");
}
return mapName;
}
public static BeatSaver.BeatSaverRatelimit GetRatelimit(HttpResponseHeaders headers)
{
BeatSaver.BeatSaverRatelimit ratelimit = new BeatSaver.BeatSaverRatelimit();
if (headers.TryGetValues("Rate-Limit-Remaining", out IEnumerable<string> _remaining))
{
var Remaining = _remaining.GetEnumerator();
Remaining.MoveNext();
ratelimit.Remaining = Int32.Parse(Remaining.Current);
Remaining.Dispose();
}
if (headers.TryGetValues("Rate-Limit-Reset", out IEnumerable<string> _reset))
{
var Reset = _reset.GetEnumerator();
Reset.MoveNext();
ratelimit.Reset = Int32.Parse(Reset.Current);
ratelimit.ResetTime = UnixTimestampToDateTime((long)ratelimit.Reset);
Reset.Dispose();
}
if (headers.TryGetValues("Rate-Limit-Total", out IEnumerable<string> _total))
{
var Total = _total.GetEnumerator();
Total.MoveNext();
ratelimit.Total = Int32.Parse(Total.Current);
Total.Dispose();
}
return ratelimit;
}
public static DateTime UnixTimestampToDateTime(double unixTime)
{
DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond);
return new DateTime(unixStart.Ticks + unixTimeStampInTicks, System.DateTimeKind.Utc);
}
public static async Task Download(string url, string output, int retries = 3)
{
if (retries == 0)
{
ModAssistant.Utils.Log($"Max tries reached: Couldn't download {url}", "ERROR");
throw new Exception("Max retries allowed");
}
var resp = await HttpClient.GetAsync(url);
if ((int)resp.StatusCode == 429)
{
var ratelimit = new BeatSaver.BeatSaverRatelimit();
ratelimit = GetRatelimit(resp.Headers);
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitHit"), ratelimit.ResetTime)}");
await ratelimit.Wait();
await Download(url, output, retries - 1);
}
using (var stream = await resp.Content.ReadAsStreamAsync())
using (var fs = new FileStream(output, FileMode.OpenOrCreate, FileAccess.Write))
{
await stream.CopyToAsync(fs);
}
}
public class BeatSaverMap
{
public BeatSaverApiResponse response { get; set; }
@ -180,25 +273,16 @@ namespace ModAssistant.API
public class BeatSaverApiResponse
{
public HttpStatusCode statusCode { get; set; }
public BeatSaverRatelimit ratelimit { get; set;}
public BeatSaverRatelimit ratelimit { get; set; }
public BeatSaverApiResponseMap map { get; set; }
}
public class BeatSaverRatelimit
{
public int Remaining { get; set; }
public int Total { get; set; }
public int Reset { get; set; }
public int? Remaining { get; set; }
public int? Total { get; set; }
public int? Reset { get; set; }
public DateTime ResetTime { get; set; }
public bool IsSafe
{
get
{
if (Remaining > 3) return true;
else return false;
}
}
public async Task Wait()
{
await Task.Delay(new TimeSpan(ResetTime.Ticks - DateTime.Now.Ticks));

View file

@ -41,7 +41,7 @@ namespace ModAssistant.API
}
}
public static async Task DownloadFrom(string file, bool gui = false, System.Windows.Controls.ProgressBar progress = null)
public static async Task DownloadFrom(string file)
{
if (Path.Combine(BeatSaberPath, PlaylistsFolder) != Path.GetDirectoryName(file))
{
@ -52,15 +52,9 @@ namespace ModAssistant.API
int Errors = 0;
int Minimum = 0;
int Value = 0;
if (progress != null)
{
progress.Minimum = 0;
progress.Maximum = 1;
progress.Value = 0;
}
Playlist playlist = JsonSerializer.Deserialize<Playlist>(File.ReadAllText(file));
int Maximum = playlist.songs.Length;
if (progress != null) progress.Maximum = playlist.songs.Length;
foreach (Playlist.Song song in playlist.songs)
{
@ -74,27 +68,20 @@ namespace ModAssistant.API
response = await BeatSaver.GetFromKey(song.key, false);
}
Value++;
if (progress != null) progress.Value++;
if (gui)
if (response.Success)
{
if (response.Success)
{
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:InstallingPlaylist"), TextProgress(Minimum, Maximum, Value))}";
}
else
{
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:FailedPlaylistSong"), song.songName)}";
ModAssistant.Utils.Log($"Failed installing BeatSaver map: {song.songName} | {song.key} | {song.hash}");
await Task.Delay(3 * 1000);
Errors++;
}
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:InstallingPlaylist"), TextProgress(Minimum, Maximum, Value))} {response.Name}");
}
else
{
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:FailedPlaylistSong"), song.songName)}");
ModAssistant.Utils.Log($"Failed installing BeatSaver map: {song.songName} | {song.key} | {song.hash} | ({response?.response?.ratelimit?.Remaining})");
await Task.Delay(3 * 1000);
Errors++;
}
}
if (gui)
{
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:FinishedPlaylist"), Errors, playlist.playlistTitle)}";
}
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:FinishedPlaylist"), Errors, playlist.playlistTitle)}");
}
private static string TextProgress(int min, int max, int value)
@ -103,7 +90,7 @@ namespace ModAssistant.API
{
return $" {string.Concat(Enumerable.Repeat("", 10))} [{value}/{max}]";
}
int interval = (int)Math.Floor((double)value / ( ((double)max - (double)min ) / (double)10));
int interval = (int)Math.Floor((double)value / (((double)max - (double)min) / (double)10));
return $" {string.Concat(Enumerable.Repeat("", interval))}{string.Concat(Enumerable.Repeat("", 10 - interval))} [{value}/{max}]";
}

View file

@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Windows;
using static ModAssistant.Http;
namespace ModAssistant.API
{
@ -11,6 +14,18 @@ namespace ModAssistant.API
{
public static readonly string BeatSaberPath = App.BeatSaberInstallDirectory;
public static void SetMessage(string message)
{
if (App.window == null)
{
OneClickStatus.Instance.MainText = message;
}
else
{
MainWindow.Instance.MainText = message;
}
}
public static async Task DownloadAsset(string link, string folder, bool showNotifcation, string fileName = null)
{
await DownloadAsset(link, folder, fileName, null, showNotifcation);
@ -21,7 +36,7 @@ namespace ModAssistant.API
await DownloadAsset(link, folder, fileName, displayName, true);
}
public static async Task DownloadAsset(string link, string folder, string fileName, string displayName, bool showNotification)
public static async Task DownloadAsset(string link, string folder, string fileName, string displayName, bool showNotification, bool beatsaver = false)
{
if (string.IsNullOrEmpty(BeatSaberPath))
{
@ -43,17 +58,18 @@ namespace ModAssistant.API
displayName = Path.GetFileNameWithoutExtension(fileName);
}
await ModAssistant.Utils.Download(link, fileName);
if (beatsaver) await BeatSaver.Download(link, fileName);
else await ModAssistant.Utils.Download(link, fileName);
if (showNotification)
{
ModAssistant.Utils.SendNotify(string.Format((string)Application.Current.FindResource("OneClick:InstalledAsset"), displayName));
SetMessage(string.Format((string)Application.Current.FindResource("OneClick:InstalledAsset"), displayName));
}
}
catch
{
ModAssistant.Utils.SendNotify((string)Application.Current.FindResource("OneClick:AssetInstallFailed"));
SetMessage((string)Application.Current.FindResource("OneClick:AssetInstallFailed"));
}
}
}
}

View file

@ -9,6 +9,7 @@ namespace ModAssistant
class OneClickInstaller
{
private static readonly string[] Protocols = new[] { "modelsaber", "beatsaver", "bsplaylist" };
public static OneClickStatus Status = new OneClickStatus();
public static async Task InstallAsset(string link)
{
@ -37,11 +38,14 @@ namespace ModAssistant
private static async Task ModelSaber(Uri uri)
{
Status.Show();
API.Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Installing"), System.Web.HttpUtility.UrlDecode(uri.Segments.Last()))}");
await API.ModelSaber.GetModel(uri);
}
private static async Task Playlist(Uri uri)
{
Status.Show();
await API.Playlists.DownloadAll(uri);
}

View file

@ -72,7 +72,7 @@
<sys:String x:Key="Mods:FailedExtract">{0} {1} {2} {3} Mods:FailedExtract</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">{0} {1} Mods:FailedExtractMaxReached</sys:String>
<sys:String x:Key="Mods:SearchLabel">Mods:SearchLabel</sys:String>
<!-- About Page -->
<sys:String x:Key="About:Title">About:Title</sys:String>
<sys:String x:Key="About:PageTitle">About:PageTitle</sys:String>
@ -100,6 +100,7 @@
<sys:String x:Key="Options:EnableOneClickInstalls">Options:EnableOneClickInstalls</sys:String>
<sys:String x:Key="Options:BeatSaver">Options:BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">Options:ModelSaber</sys:String>
<sys:String x:Key="Options:Playlists">Options:Playlists</sys:String>
<sys:String x:Key="Options:GameType">Options:GameType</sys:String>
<sys:String x:Key="Options:GameType:Steam">Options:GameType:Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Options:GameType:Oculus</sys:String>
@ -157,6 +158,10 @@
<sys:String x:Key="OneClick:AssetInstallFailed">OneClick:AssetInstallFailed</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick:ProtocolHandler:Registered</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick:ProtocolHandler:Unregistered</sys:String>
<sys:String x:Key="OneClick:Installing">{0} OneClick:Installing</sys:String>
<sys:String x:Key="OneClick:RatelimitSkip">{0} OneClick:RatelimitSkip</sys:String>
<sys:String x:Key="OneClick:RatelimitHit">{0} OneClick:RatelimitHit</sys:String>
<sys:String x:Key="OneClick:Failed">{0} OneClick:Failed</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">Themes:ThemeNotFound</sys:String>

View file

@ -140,6 +140,7 @@
<sys:String x:Key="Options:EnableOneClickInstalls">Enable OneClick™ Installs</sys:String>
<sys:String x:Key="Options:BeatSaver">BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">ModelSaber</sys:String>
<sys:String x:Key="Options:Playlists">Playlists</sys:String>
<sys:String x:Key="Options:GameType">Game Type</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
@ -216,6 +217,10 @@
<sys:String x:Key="OneClick:AssetInstallFailed">Failed to install.</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick™ Install handlers registered!</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick™ Install handlers unregistered!</sys:String>
<sys:String x:Key="OneClick:Installing">Installing: {0}</sys:String>
<sys:String x:Key="OneClick:RatelimitSkip">Max tries reached: Skipping {0}</sys:String>
<sys:String x:Key="OneClick:RatelimitHit">Ratelimit hit. Resuming in {0}</sys:String>
<sys:String x:Key="OneClick:Failed">Download failed: {0}</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">Theme not found, reverting to default theme...</sys:String>

View file

@ -46,13 +46,13 @@
<Span x:Key="Intro:Terms:Term1">
Les mods
<Bold>dysfonctionneront</Bold> à chaque mise à jour. C'est normal, et ce
<Bold>n'</Bold>est
<Bold>n'</Bold> est
<Bold>pas</Bold> la faute de Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term2">
Les mods
<Bold>causeront</Bold> des bugs et des problèmes de performance. Ce
<Bold>n'</Bold>est
<Bold>n'</Bold> est
<Bold>pas</Bold> la faute de Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term3">
@ -63,7 +63,7 @@
<Span x:Key="Intro:ReviewsBeatGamesFault">
<Bold>NE</Bold> laissez
<Bold>PAS</Bold> de commentaires négatifs parce que les mods ne fonctionnent plus. Ce
<Bold>n'</Bold>est
<Bold>n'</Bold> est
<Bold>pas</Bold> la faute de Beat Games.
<LineBreak/> Ils n'essaient pas de faire disparaître les mods.
</Span>
@ -77,7 +77,7 @@
Veuillez lire le Guide du Débutant sur le
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/fr/pc-modding.html">
Wiki
</Hyperlink>.
</Hyperlink> .
</Span>
<sys:String x:Key="Intro:AgreeButton">J'accepte</sys:String>
<sys:String x:Key="Intro:DisagreeButton">Je refuse</sys:String>
@ -107,7 +107,7 @@
<sys:String x:Key="Mods:UninstallBox:Body2">Cela pourrait faire dysfonctionner d'autres mods installés</sys:String>
<sys:String x:Key="Mods:FailedExtract">Échec de l'extraction de {0}, nouvelle tentative dans {1} secondes. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">Échec de l'extraction de {0} après le maximum de tentatives ({1}), abandon. Ce mod pourrait ne pas fonctionner correctement, continuez à vos propres risques</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Mods:SearchLabel">Recherche...</sys:String>
<!-- About Page -->
@ -149,11 +149,11 @@
<sys:String x:Key="Options:GameType">Type du jeu</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Tools">Outils</sys:String>
<sys:String x:Key="Options:InstallPlaylist">Installer une playlist</sys:String>
<sys:String x:Key="Options:InstallingPlaylist">Installation de la playlist : {0}</sys:String>
<sys:String x:Key="Options:FailedPlaylistSong">Échec de la musique : {0}</sys:String>
<sys:String x:Key="Options:FinishedPlaylist">[{0} échecs] Installation de la playlist terminée : {1}</sys:String>
<sys:String x:Key="Options:Diagnostics">Diagnostic</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Ouvrir les logs</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Ouvrir AppData</sys:String>
@ -191,22 +191,22 @@
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
ICI
</Hyperlink>
</Bold>.
</Bold> .
</Span>
<Span x:Key="Invalid:List:Line2">
Si votre copie du jeu
<Bold>n'</Bold>est
<Bold>n'</Bold> est
<Bold>pas</Bold> piratée, veuillez
<Hyperlink NavigateUri="https://bsmg.wiki/fr/support/#installation-propre" local:HyperlinkExtensions.IsExternal="True">
faire une installation propre
</Hyperlink>.
</Hyperlink> .
</Span>
<Span x:Key="Invalid:List:Line3">
Si cela n'a pas fonctionné, demandez de l'aide au support dans le canal textuel
<Span Foreground="Blue">#support</Span> dans
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
BSMG
</Hyperlink>.
</Hyperlink> .
</Span>
<sys:String x:Key="Invalid:BoughtGame1">Si vous utilisiez une version piratée mais avez acheté le jeu depuis</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">Sélectionner un dossier</sys:String>

View file

@ -63,16 +63,16 @@
<LineBreak/> Non è loro intenzione "rompere" le mod.
</Span>
<Span x:Key="Intro:ReviewsRustySpoon">
Se continuerò a trovare persone che lasciano feedback negativi
Se continuerò a trovare persone che lasciano feedback negativi
<Italic>perchè</Italic> le mod smettono di funzionare,
<LineBreak/>
<Bold>Mi assicurerò di farle sparire dalla circolazione</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
Ti invitiamo a leggere la guida introduttiva sulla
Ti invitiamo a leggere la guida introduttiva sulla
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/pc-modding.html">
Wiki
</Hyperlink>.
</Hyperlink> .
</Span>
<sys:String x:Key="Intro:AgreeButton">Accetto</sys:String>
<sys:String x:Key="Intro:DisagreeButton">Non accetto</sys:String>
@ -102,7 +102,7 @@
<sys:String x:Key="Mods:UninstallBox:Body2">Continuando, altre mod potrebbero smettere di funzionare</sys:String>
<sys:String x:Key="Mods:FailedExtract">Impossibile estrarre {0}, prossimo tentativo in {1} secondi. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">Non sono riuscito ad estrarre {0} dopo aver raggiunto il numero massimo di tentativi ({1}), salto questa mod. Questa protrebbe anche non funzionare correttamente, quindi procedi a tuo rischio e pericolo</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Mods:SearchLabel">Cerca...</sys:String>
<!-- About Page -->
<sys:String x:Key="About:Title">Info</sys:String>
@ -113,11 +113,11 @@
<sys:String x:Key="About:List:Item3">Un solo eseguibile</sys:String>
<sys:String x:Key="About:List:Item4">Uso responsabile</sys:String>
<Span x:Key="About:SupportAssistant">
Se ti piace questo programma, e volessi supportarmi, puoi visitare la mia
Se ti piace questo programma, e volessi supportarmi, puoi visitare la mia
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs.assistant.moe/Donate/">
pagina delle donazioni
</Hyperlink>
oppure il mio
oppure il mio
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://www.patreon.com/AssistantMoe">
Patreon
</Hyperlink>
@ -143,11 +143,11 @@
<sys:String x:Key="Options:GameType">Tipo di Installazione</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Tools">Strumenti</sys:String>
<sys:String x:Key="Options:InstallPlaylist">Installa Playlist</sys:String>
<sys:String x:Key="Options:InstallingPlaylist">Installazione Playlist: {0}</sys:String>
<sys:String x:Key="Options:FailedPlaylistSong">Installazione canzone fallita: {0}</sys:String>
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Installazione playlist terminata: {1}</sys:String>
<sys:String x:Key="Options:Diagnostics">Diagnostica</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Apri il Log</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Apri AppData</sys:String>
@ -185,21 +185,21 @@
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
QUI
</Hyperlink>
</Bold>.
</Bold> .
</Span>
<Span x:Key="Invalid:List:Line2">
Se la tua copia del gioco
<Bold>non è</Bold> piratata, ti invitiamo a
<Hyperlink NavigateUri="https://bsmg.wiki/support#clean-installation" local:HyperlinkExtensions.IsExternal="True">
reinstallare il gioco
</Hyperlink>.
</Hyperlink> .
</Span>
<Span x:Key="Invalid:List:Line3">
Se questo non aiuta, puoi sempre chiedere aiuto nel canale
<Span Foreground="Blue">#support</Span> all'interno del
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
Server Discord di BSMG
</Hyperlink>.
</Hyperlink> .
</Span>
<sys:String x:Key="Invalid:BoughtGame1">Se avevi la versione piratata, ma hai comprato il gioco</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">Seleziona cartella</sys:String>

View file

@ -36,19 +36,23 @@
<sys:String x:Key="Intro:PageTitle">Welkom bij Mod Assistant</sys:String>
<sys:String x:Key="Intro:Terms:Header">Lees deze pagina alstublieft volledig en aandachtig</sys:String>
<Span x:Key="Intro:Terms:Line1">
Door het gebruiken van dit programma verlkaar ik de volgende voorwaarden te hebben gelezen en hier mee akkoord te gaan:
Door het gebruiken van dit programma verklaar ik de volgende voorwaarden te hebben gelezen en hier mee akkoord te gaan:
</Span>
<Span x:Key="Intro:Terms:Line2">
Beat Saber
Ondersteund mods <Bold>niet</Bold> van zichzelf, dit betekent dat:
Ondersteund mods
<Bold>niet</Bold> van zichzelf, dit betekent dat:
</Span>
<Span x:Key="Intro:Terms:Term1">
Mods
Elke update <Bold>niet meer werken</Bold>, dit is normaal en <Bold>niet</Bold> de fout van Beat Games.
Elke update
<Bold>niet meer werken</Bold> , dit is normaal en
<Bold>niet</Bold> de fout van Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term2">
Mods
<Bold>zullen</Bold> bugs en prestatievermindering veroorzaken. Dit is <Bold>niet</Bold> een fout van Beat Games.
<Bold>zullen</Bold> bugs en prestatievermindering veroorzaken. Dit is
<Bold>niet</Bold> een fout van Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term3">
Mods worden
@ -56,7 +60,8 @@
<Bold>vrije tijd.</Bold> Wees alstublieft geduldig en begripvol.
</Span>
<Span x:Key="Intro:ReviewsBeatGamesFault">
Laat <Bold>GEEN</Bold> negatieve beoordelingen achter op beat saber omdat mods niet meer werken. Dit is
Laat
<Bold>GEEN</Bold> negatieve beoordelingen achter op beat saber omdat mods niet meer werken. Dit is
<Bold>niet</Bold> de fout van Beat Games.
<LineBreak/> Ze proberen niet mods ontoegankelijk te maken.
</Span>
@ -67,17 +72,17 @@
<Bold>zal ik persoonlijk met een roestige lepel mods niet meer laten werken</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
Lees alstublieft de 'Beginners Guide' op de wiki
Lees alstublieft de 'Beginners Guide' op de
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/pc-modding.html">
Wiki
</Hyperlink>. (engels)
</Hyperlink> . (engels)
</Span>
<sys:String x:Key="Intro:AgreeButton">Accepteer</sys:String>
<sys:String x:Key="Intro:DisagreeButton">Accepteer niet</sys:String>
<sys:String x:Key="Intro:ClosingApp">Sluit applicatie af: U accepteerde de voorwaarden niet.</sys:String>
<sys:String x:Key="Intro:VersionDownloadFailed">Kon de versie lijst niet downloaden</sys:String>
<sys:String x:Key="Intro:ModsTabDisabled">Mods tabblad uitgeschakeld. Herstart het programma om opnieuw te proberen.</sys:String>
<sys:String x:Key="Intro:ModsTabEnabled">U kan nu het mods tabblad gebruiken!</sys:String>
<sys:String x:Key="Intro:ModsTabEnabled">U kunt nu het mods tabblad gebruiken!</sys:String>
<!-- Mods Page -->
<sys:String x:Key="Mods:Title">Mods</sys:String>
@ -96,11 +101,12 @@
<sys:String x:Key="Mods:FinishedInstallingMods">Klaar met mods installeren</sys:String>
<sys:String x:Key="Mods:ModDownloadLinkMissing">Kon de download link voor {0} niet vinden</sys:String>
<sys:String x:Key="Mods:UninstallBox:Title">{0} deïnstalleren?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1"> Weet U zeker dat U {0} wilt verwijderen?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1">Weet U zeker dat U {0} wilt verwijderen?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body2">Dit zou uw andere mods niet meer kunnen laten werken</sys:String>
<sys:String x:Key="Mods:FailedExtract">Kon {0} niet extracten, probeer opniew over {1} seconden. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">Kon {0} niet extracten na maximaal aantal pogingen ({1}), Overslaan. Deze mod werkt msischien niet goed dus ga verder op eigen risico</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String>
<!-- NEEDS TRANSLATING -->
<!-- About Page -->
<sys:String x:Key="About:Title">Over</sys:String>
@ -141,11 +147,16 @@
<sys:String x:Key="Options:GameType">Game Type</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Tools">Tools</sys:String>
<!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String>
<!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String>
<!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String>
<!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String>
<!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Diagnostics">Diagnostiek</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Open Logs</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Open AppData</sys:String>
@ -183,21 +194,21 @@
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
HIER
</Hyperlink>
</Bold>.
</Bold> .
</Span>
<Span x:Key="Invalid:List:Line2">
Als Uw game versie
<Bold>niet</Bold> gepirateerd is, doe dan alstublieft
<Bold>niet</Bold> gepirateerd is, doe dan alstublieft
<Hyperlink NavigateUri="https://bsmg.wiki/support#clean-installation" local:HyperlinkExtensions.IsExternal="True">
een "schone" installatie
</Hyperlink>.
</Hyperlink> .
</Span>
<Span x:Key="Invalid:List:Line3">
Als dat allebei niet helpt, vraag om hulp in de
Als dat allebei niet helpt, vraag om hulp in de
<Span Foreground="Blue">#support</Span> channel in
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
BSMG
</Hyperlink>. (Engels)
</Hyperlink> . (Engels)
</Span>
<sys:String x:Key="Invalid:BoughtGame1">Als U een gepirateerde versie van het spel had maar nu het spel hebt gekocht</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">Selecteer map</sys:String>

View file

@ -0,0 +1,238 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModAssistant"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ResourceDictionaryName">i18n:ru-RU</sys:String>
<!-- App -->
<sys:String x:Key="App:InstallDirDialog:Title">Не получается найти папку с установленным Beat Saber!</sys:String>
<sys:String x:Key="App:InstallDirDialog:OkCancel">Нажмите ОК, чтобы попробовать снова или ЗАКРЫТЬ, чтобы закрыть приложение.</sys:String>
<sys:String x:Key="App:InvalidArgument">Недопустимый аргумент! '{0}' требуется выбор.</sys:String>
<sys:String x:Key="App:UnrecognizedArgument">Нераспознанный аргумент. Закрытие Mod Assistant.</sys:String>
<sys:String x:Key="App:UnhandledException">Произошло необработанное исключениеd</sys:String>
<sys:String x:Key="App:Exception">Исключение</sys:String>
<!-- Main Window -->
<sys:String x:Key="MainWindow:WindowTitle">ModAssistant</sys:String>
<sys:String x:Key="MainWindow:IntroButton">Вступление</sys:String>
<sys:String x:Key="MainWindow:ModsButton">Модификации</sys:String>
<sys:String x:Key="MainWindow:AboutButton">Информация</sys:String>
<sys:String x:Key="MainWindow:OptionsButton">Настройки</sys:String>
<sys:String x:Key="MainWindow:GameVersionLabel">Версия игры</sys:String>
<sys:String x:Key="MainWindow:VersionLabel">Версия</sys:String>
<sys:String x:Key="MainWindow:ModInfoButton">Информация о модификации</sys:String>
<sys:String x:Key="MainWindow:InstallButtonTop">Установить</sys:String>
<sys:String x:Key="MainWindow:InstallButtonBottom">или обновить</sys:String>
<sys:String x:Key="MainWindow:GameVersionLoadFailed">Не удаётся загрузить список версий игры, Вкладка с модификациями недоступна.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Title">Обнаружена новая версия игры!</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line1">Похоже на то, что было обновление игры.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line2">Пожалуйста, проверьте дважды, что выбрана корректная версия игры в нижнем левом углу!</sys:String>
<sys:String x:Key="MainWindow:NoModSelected">Нет выбранных модификаций!</sys:String>
<sys:String x:Key="MainWindow:NoModInfoPage">{0} Не имеет страницы с информацией.</sys:String>
<!-- Intro Page -->
<sys:String x:Key="Intro:Title">Вступление</sys:String>
<sys:String x:Key="Intro:PageTitle">Добро пожаловать в Mod Assistant</sys:String>
<sys:String x:Key="Intro:Terms:Header">Прочитайте эту страницу полностью и внимательно</sys:String>
<Span x:Key="Intro:Terms:Line1">
Используя эту программу, вы прочитали и принимаете эти условия:
</Span>
<Span x:Key="Intro:Terms:Line2">
Beat Saber
<Bold>не имеет</Bold> нативной поддержки модификаций. Это означает:
</Span>
<Span x:Key="Intro:Terms:Term1">
Модификации
<Bold>могут прекращать работоспособность</Bold> каждое обновление. Это нормально, и
<Bold>это не</Bold> вина Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term2">
Модификации
<Bold>могут</Bold> вызывать ошибки и проблемы с производительностью. Это
<Bold>не</Bold> вина Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term3">
Модификации создаются
<Bold>бесплатно</Bold> людьми в их
<Bold>свободное время.</Bold> Пожалуйста, будьте терпиливыми и взаимопонимающими.
</Span>
<Span x:Key="Intro:ReviewsBeatGamesFault">
<Bold>НЕ</Bold> оставляйте отрицательные отзывы потому что модификация не работает. Это
<Bold>не</Bold> вина Beat Games.
<LineBreak/> Beat Games не пытается уничтожить модификации.
</Span>
<Span x:Key="Intro:ReviewsRustySpoon">
Если я увижу людей, которые продолжают оставлять негативные комментарии
<Italic>потому что</Italic> модификации не работают,
<LineBreak/>
<Bold>Я лично уничтожу модификации ржавой ложкой</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
Пожалуйста, прочитайте инструкцию для начинающих на
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/pc-modding.html">
Вики
</Hyperlink>.
</Span>
<sys:String x:Key="Intro:AgreeButton">Я согласен</sys:String>
<sys:String x:Key="Intro:DisagreeButton">Я не согласен</sys:String>
<sys:String x:Key="Intro:ClosingApp">Приложение закрывается: Вы не приняли пользовательскую политику соглашений.</sys:String>
<sys:String x:Key="Intro:VersionDownloadFailed">Не удаётся получить список версий</sys:String>
<sys:String x:Key="Intro:ModsTabDisabled">Вкладка с модификациями недоступна. Пожалуйста перезапустите, чтобы попробовать снова.</sys:String>
<sys:String x:Key="Intro:ModsTabEnabled">Теперь вы можете использовать вкладку с модификациями!</sys:String>
<!-- Mods Page -->
<sys:String x:Key="Mods:Title">Модификации</sys:String>
<sys:String x:Key="Mods:Header:Name">Название</sys:String>
<sys:String x:Key="Mods:Header:Installed">Установленная</sys:String>
<sys:String x:Key="Mods:Header:Latest">Последняя</sys:String>
<sys:String x:Key="Mods:Header:Description">Описание</sys:String>
<sys:String x:Key="Mods:Header:Uninstall">Удалить</sys:String>
<sys:String x:Key="Mods:UninstallButton">Удалить</sys:String>
<sys:String x:Key="Mods:LoadFailed">Не удается загрузить список модификаций</sys:String>
<sys:String x:Key="Mods:CheckingInstalledMods">Проверка установленных модификаций</sys:String>
<sys:String x:Key="Mods:LoadingMods">Загрузка модификаций</sys:String>
<sys:String x:Key="Mods:FinishedLoadingMods">Загрузка модификаций окончена</sys:String>
<sys:String x:Key="Mods:InstallingMod">Установка {0}</sys:String>
<sys:String x:Key="Mods:InstalledMod">Установлено {0}</sys:String>
<sys:String x:Key="Mods:FinishedInstallingMods">Установка модификаций окончена.</sys:String>
<sys:String x:Key="Mods:ModDownloadLinkMissing">Не удаётся получить ссылку на скачивание {0}</sys:String>
<sys:String x:Key="Mods:UninstallBox:Title">Удаление {0}?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1">Вы уверены, что хотите удалить {0}?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body2">Это может сломать остальные модификации</sys:String>
<sys:String x:Key="Mods:FailedExtract">Не удалось извлечь {0}, попробуйте снова через {1} секунд. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">Не удалось извлечь {0} после попыток ({1}), пропускается. Эта модификация может работать некорректно, используйте на свой страх и риск</sys:String>
<sys:String x:Key="Mods:SearchLabel">Поиск...</sys:String>
<!-- About Page -->
<sys:String x:Key="About:Title">Информация</sys:String>
<sys:String x:Key="About:PageTitle">Про Mod Assistant</sys:String>
<sys:String x:Key="About:List:Header">Я ассистент, и я создал Mod Assistant для помощи модификациям с некоторыми личными принципами:</sys:String>
<sys:String x:Key="About:List:Item1">Простота</sys:String>
<sys:String x:Key="About:List:Item2">Портативность </sys:String>
<sys:String x:Key="About:List:Item3">Приложение одним файлом</sys:String>
<sys:String x:Key="About:List:Item4">Ответственное использование</sys:String>
<Span x:Key="About:SupportAssistant">
Если вам нравится программа и вы хотите меня поддержать, пожалуйста, посетите
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs.assistant.moe/Donate/">
страницу для пожертвований
</Hyperlink>
или мой
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://www.patreon.com/AssistantMoe">
Patreon
</Hyperlink>
</Span>
<sys:String x:Key="About:SpecialThanks">Отдельное спасибо ♥</sys:String>
<sys:String x:Key="About:Donate">Поддержать</sys:String>
<sys:String x:Key="About:HeadpatsButton">Погладить по голове</sys:String>
<sys:String x:Key="About:HugsButton">Обнять</sys:String>
<!-- Options Page -->
<sys:String x:Key="Options:Title">Опции</sys:String>
<sys:String x:Key="Options:PageTitle">Настройки</sys:String>
<sys:String x:Key="Options:InstallFolder">Папка установки</sys:String>
<sys:String x:Key="Options:SelectFolderButton">Выбрать папку</sys:String>
<sys:String x:Key="Options:OpenFolderButton">Открыть папку</sys:String>
<sys:String x:Key="Options:SaveSelectedMods">Сохранить выбранные модификации</sys:String>
<sys:String x:Key="Options:CheckInstalledMods">Обнаружение установленных модификаций</sys:String>
<sys:String x:Key="Options:SelectInstalledMods">Select Installed Mods</sys:String>
<sys:String x:Key="Options:ReinstallInstalledMods">Переустановить установленные модификации</sys:String>
<sys:String x:Key="Options:EnableOneClickInstalls">Включить OneClick™ установки</sys:String>
<sys:String x:Key="Options:BeatSaver">BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">ModelSaber</sys:String>
<sys:String x:Key="Options:GameType">Тип игры</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Инструменты</sys:String>
<sys:String x:Key="Options:InstallPlaylist">Установить плейлист</sys:String>
<sys:String x:Key="Options:InstallingPlaylist">Установка плейлиста: {0}</sys:String>
<sys:String x:Key="Options:FailedPlaylistSong">Ошибка с песней: {0}</sys:String>
<sys:String x:Key="Options:FinishedPlaylist">[{0} ошибок] Установка плейлиста окончена: {1}</sys:String>
<sys:String x:Key="Options:Diagnostics">Диагностика</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Открыть логи</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Открыть AppData</sys:String>
<sys:String x:Key="Options:UninstallBSIPAButton">Удалить BSIPA</sys:String>
<sys:String x:Key="Options:RemoveAllModsButton">Удалить все модификации</sys:String>
<sys:String x:Key="Options:ApplicationTheme">Тема приложения</sys:String>
<sys:String x:Key="Options:ExportTemplateButton">Экспортировать шаблон</sys:String>
<sys:String x:Key="Options:UploadingLog">Загрузка логов</sys:String>
<sys:String x:Key="Options:LogUrlCopied">Ссылка на лог успешно скопирована!</sys:String>
<sys:String x:Key="Options:LogUploadFailed">Загрузка логов не удалась </sys:String>
<sys:String x:Key="Options:LogUploadFailed:Title">Загрузка логов не удалась!</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Body">Не удаётся загрузить файл с логом на Teknik, пожалуйста, попробуйте снова или отправьте файл вручную.</sys:String>
<sys:String x:Key="Options:GettingModList">Получаем список модификаций</sys:String>
<sys:String x:Key="Options:FindingBSIPAVersion">Поиск BSIPA версии</sys:String>
<sys:String x:Key="Options:BSIPAUninstalled">BSIPA удалён</sys:String>
<sys:String x:Key="Options:YeetModsBox:Title">Удалить все модификации?</sys:String>
<sys:String x:Key="Options:YeetModsBox:RemoveAllMods">Вы уверены, что хотите удалить ВСЕ модификации?</sys:String>
<sys:String x:Key="Options:YeetModsBox:CannotBeUndone">Это действие нельзя отменить.</sys:String>
<sys:String x:Key="Options:AllModsUninstalled">Все модификации удалены!</sys:String>
<sys:String x:Key="Options:CurrentThemeRemoved">Текущая тема была удалена, возвращаемся к стандартной теме...</sys:String>
<sys:String x:Key="Options:ThemeFolderNotFound">Папка с темами не найдена! Пробую экспортировать шаблон...</sys:String>
<sys:String x:Key="Options:AppDataNotFound">Папка AppData не найдена! Попробуйте запустить игру.</sys:String>
<!-- Loading Page -->
<sys:String x:Key="Loading:Loading">Загрузка модификаций</sys:String>
<!-- Invalid Page -->
<sys:String x:Key="Invalid:Title">Недействительно</sys:String>
<sys:String x:Key="Invalid:PageTitle">Обнаружена недействительная установка</sys:String>
<sys:String x:Key="Invalid:PageSubtitle">Ваша установленная игра сломана или недействительная</sys:String>
<sys:String x:Key="Invalid:List:Header">Это могло произойти, если у вас нелицензионная копия игры, или вы скопировали нелицензионную копию поверх лицензионной</sys:String>
<Span x:Key="Invalid:List:Line1">
Если ваша копия игры нелицензионная,
<Bold>пожалуйста, купите игру
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
ТУТ
</Hyperlink>
</Bold>.
</Span>
<Span x:Key="Invalid:List:Line2">
Если ваша копия игры
<Bold>лицензионная</Bold>, пожалуйста
<Hyperlink NavigateUri="https://bsmg.wiki/support#clean-installation" local:HyperlinkExtensions.IsExternal="True">
переустановите игру заново
</Hyperlink>.
</Span>
<Span x:Key="Invalid:List:Line3">
Если из этого ничего вам не помогает, попросите помощи в
<Span Foreground="Blue">#support</Span> канал в
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
BSMG
</Hyperlink>.
</Span>
<sys:String x:Key="Invalid:BoughtGame1">Если вы использовали нелицензионную версию игру, но купили игру</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">выберете папку</sys:String>
<sys:String x:Key="Invalid:BoughtGame2">Вам будет необходимо перезапустить Mod Assistant после того, как вы выберете папку с лицензионной копией игры</sys:String>
<!-- OneClick Class -->
<sys:String x:Key="OneClick:MapDownloadFailed">Не удаётся получить информацию о карте.</sys:String>
<sys:String x:Key="OneClick:SongDownloadFailed">Не удаётся загрузить песню.</sys:String>
<sys:String x:Key="OneClick:SongDownload:Failed">Не удаётся загрузить песню.</sys:String>
<sys:String x:Key="OneClick:SongDownload:NetworkIssues">Возможно это ошибки с BeatSaver или вашим интернет соединением.</sys:String>
<sys:String x:Key="OneClick:SongDownload:FailedTitle">Ошибка с загрузкой песни ZIP</sys:String>
<sys:String x:Key="OneClick:InstallDirNotFound">Установочный путь к Beat Saber не найден.</sys:String>
<sys:String x:Key="OneClick:InstalledAsset">Установлено: {0}</sys:String>
<sys:String x:Key="OneClick:AssetInstallFailed">Ошибка установки.</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick™ установки зарегистрированы</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick™ установки не зарегистрированы!</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">Тема не найдена, возвращаемся к стандартной теме...</sys:String>
<sys:String x:Key="Themes:ThemeSet">Установлена тема: {0}.</sys:String>
<sys:String x:Key="Themes:ThemeMissing">{0} не существует.</sys:String>
<sys:String x:Key="Themes:SavedTemplateTheme">Шаблон темы &quot;{0}&quot; сохранён в папку с темами.</sys:String>
<sys:String x:Key="Themes:TemplateThemeExists">Шаблон темы уже существует!</sys:String>
<sys:String x:Key="Themes:FailedToLoadXaml">Не удаётся загрузить .xaml файл для темы {0}: {1}</sys:String>
<!-- Updater Class -->
<sys:String x:Key="Updater:CheckFailed">Не удаётся проверить обновления.</sys:String>
<sys:String x:Key="Updater:DownloadFailed">Не удаётся загрузить обновление.</sys:String>
<!-- Utils Class -->
<sys:String x:Key="Utils:NotificationTitle">Mod Assistant</sys:String>
<sys:String x:Key="Utils:NoInstallFolder">Не удаётся обнаружить папку с Beat Saber. Пожалуйста, укажите путь вручную.</sys:String>
<sys:String x:Key="Utils:RunAsAdmin">Mod Assistant требует запустить эту задачу с правами администратора. Пожалуйста, попробуйте заново.</sys:String>
<sys:String x:Key="Utils:InstallDir:DialogTitle">Укажите папку с установленным Beat Saber</sys:String>
<sys:String x:Key="Utils:CannotOpenFolder">Не удаётся открыть папку: {0}</sys:String>
</ResourceDictionary>

View file

@ -78,6 +78,9 @@
<Compile Include="Classes\Updater.cs" />
<Compile Include="Libs\semver\SemVersion.cs" />
<Compile Include="Libs\semver\IntExtensions.cs" />
<Compile Include="OneClickStatus.xaml.cs">
<DependentUpon>OneClickStatus.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Intro.xaml.cs">
<DependentUpon>Intro.xaml</DependentUpon>
</Compile>
@ -123,10 +126,18 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Localisation\ru.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Localisation\zh.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="OneClickStatus.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Pages\Intro.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View file

@ -0,0 +1,98 @@
<Window x:Class="ModAssistant.OneClickStatus"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ModAssistant"
mc:Ignorable="d"
Title="OneClick Installer" Height="800" Width="600" WindowStyle="ToolWindow" ResizeMode="NoResize">
<Window.Resources>
<local:DivideDoubleByTwoConverter x:Key="DivideDoubleByTwoConverter" />
<Style x:Key="Spin" TargetType="{x:Type Image}">
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="0" CenterX="{Binding Path=ActualWidth, Converter={StaticResource DivideDoubleByTwoConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Image}}" CenterY="{Binding Path=ActualHeight, Converter={StaticResource DivideDoubleByTwoConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Image}}" />
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard x:Name="RotateStarCompass">
<DoubleAnimation
AutoReverse="False"
RepeatBehavior="Forever"
Storyboard.TargetProperty="RenderTransform.Angle"
From="0"
To="360"
Duration="0:0:3" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Fill="{DynamicResource ModAssistantBackground}" Grid.RowSpan="2"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Margin="60,0"
VerticalAlignment="Center"
Source="{DynamicResource loadingInnerDrawingImage}"
Stretch="Uniform" />
<Image
Grid.Row="0"
Margin="60,0"
VerticalAlignment="Center"
Source="{DynamicResource loadingMiddleDrawingImage}"
Stretch="Uniform" />
<Image
Grid.Row="0"
Margin="60,0"
VerticalAlignment="Center"
Source="{DynamicResource loadingOuterDrawingImage}"
Stretch="Uniform"
Style="{StaticResource Spin}" />
</Grid>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Border
Grid.Row="0"
Margin="10,0,10,10"
BorderBrush="{DynamicResource BottomStatusBarOutline}"
BorderThickness="1">
<ScrollViewer
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
Background="{DynamicResource BottomStatusBarBackground}"
Margin="0">
<TextBox
Name="HistoryTextBlock"
Margin="0"
Padding="5"
Background="{DynamicResource BottomStatusBarBackground}"
BorderThickness="0"
Foreground="{DynamicResource TextColor}" />
</ScrollViewer>
</Border>
</Grid>
</Grid>
</Window>

View file

@ -0,0 +1,64 @@
using System;
using System.Windows;
using System.Windows.Data;
namespace ModAssistant
{
/// <summary>
/// Interaction logic for OneClickStatus.xaml
/// </summary>
public partial class OneClickStatus : Window
{
public static OneClickStatus Instance;
public string HistoryText
{
get
{
return HistoryTextBlock.Text;
}
set
{
Dispatcher.Invoke(new Action(() => { OneClickStatus.Instance.HistoryTextBlock.Text = value; }));
}
}
public string MainText
{
get
{
return HistoryTextBlock.Text;
}
set
{
Dispatcher.Invoke(new Action(() => {
OneClickStatus.Instance.HistoryTextBlock.Text = string.IsNullOrEmpty(MainText) ? $"{value}" : $"{value}\n{MainText}";
}));
}
}
public OneClickStatus()
{
InitializeComponent();
Instance = this;
}
}
[ValueConversion(typeof(double), typeof(double))]
public class DivideDoubleByTwoConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType != typeof(double))
{
throw new InvalidOperationException("The target must be a double");
}
double d = (double)value;
return ((double)d) / 2;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View file

@ -31,6 +31,7 @@
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
@ -200,6 +201,23 @@
IsChecked="{Binding ModelSaberProtocolHandlerEnabled}"
Unchecked="ModelSaberProtocolHandler_Unchecked" />
<TextBlock
Grid.Row="10"
Margin="50,5,5,5"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="Bold"
Text="{DynamicResource Options:Playlists}" />
<CheckBox
Name="PlaylistProtocolHandler"
Grid.Row="10"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Checked="PlaylistsProtocolHandler_Checked"
IsChecked="{Binding PlaylistsProtocolHandlerEnabled}"
Unchecked="PlaylistsProtocolHandler_Unchecked" />
<StackPanel
Grid.Row="12"
Margin="5"

View file

@ -24,6 +24,7 @@ namespace ModAssistant.Pages
public bool ReinstallInstalledMods { get; set; }
public bool ModelSaberProtocolHandlerEnabled { get; set; }
public bool BeatSaverProtocolHandlerEnabled { get; set; }
public bool PlaylistsProtocolHandlerEnabled { get; set; }
public string LogURL { get; private set; }
public Options()
@ -51,6 +52,7 @@ namespace ModAssistant.Pages
{
ModelSaberProtocolHandlerEnabled = OneClickInstaller.IsRegistered("modelsaber");
BeatSaverProtocolHandlerEnabled = OneClickInstaller.IsRegistered("beatsaver");
PlaylistsProtocolHandlerEnabled = OneClickInstaller.IsRegistered("bsplaylist");
}
private void SelectDirButton_Click(object sender, RoutedEventArgs e)
@ -133,6 +135,15 @@ namespace ModAssistant.Pages
{
OneClickInstaller.Unregister("beatsaver");
}
public void PlaylistsProtocolHandler_Checked(object sender, RoutedEventArgs e)
{
OneClickInstaller.Register("bsplaylist");
}
public void PlaylistsProtocolHandler_Unchecked(object sender, RoutedEventArgs e)
{
OneClickInstaller.Unregister("bsplaylist");
}
private void SelectInstalled_Checked(object sender, RoutedEventArgs e)
{
@ -314,7 +325,7 @@ namespace ModAssistant.Pages
string playlistFile = Utils.GetManualFile();
if (File.Exists(playlistFile))
{
Task.Run(() => { API.Playlists.DownloadFrom(playlistFile, true).Wait(); });
Task.Run(() => { API.Playlists.DownloadFrom(playlistFile).Wait(); });
}
}
}

View file

@ -51,5 +51,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.801.0")]
[assembly: AssemblyFileVersion("1.1.801.0")]
[assembly: AssemblyVersion("1.1.901.0")]
[assembly: AssemblyFileVersion("1.1.901.0")]