mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a977332c9 | |||
| 81c5a7928a | |||
| 4b60be5846 | |||
| f07706c4f4 | |||
| 64ed9d24b1 | |||
| da40448ca1 | |||
| 2e6224825f | |||
| 8caea035ed | |||
| 507e8e04f2 | |||
| 02c856b5d6 | |||
| d8cb6a3892 | |||
| b763a909b7 | |||
| 4bbfe72381 | |||
| 27f36dfa54 | |||
| f70df59e21 | |||
| 844ca12b9c | |||
| ee9e10170c | |||
| 40365b44e3 | |||
| 6c29081d84 | |||
| 1c9bf7dab0 | |||
| dd572226bb | |||
| 5e33efbb06 | |||
| 99d125d35d | |||
| ce6fb95aa2 |
@@ -598,8 +598,7 @@
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/6169409105101272202",
|
||||
"ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/c328e407367e9914abaf92f609501877ee5abb63.png",
|
||||
"ReleaseDate": "1680623885",
|
||||
"year": "2023",
|
||||
"recommended": true
|
||||
"year": "2023"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.29.4",
|
||||
@@ -700,7 +699,7 @@
|
||||
"ReleaseDate": "1708091473",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
{
|
||||
"BSVersion": "1.35.0",
|
||||
"BSManifest": "1490986193481243578",
|
||||
"OculusBinaryId": "6720809361352119",
|
||||
@@ -708,5 +707,85 @@
|
||||
"ReleaseImg": "https://clan.cloudflare.steamstatic.com/images//32055887/9a141be5c44ca6fc81551a2c7e7f28407df413ed.png",
|
||||
"ReleaseDate": "1709831174",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.36.0",
|
||||
"BSManifest": "8533509040167838423",
|
||||
"OculusBinaryId": "6863072637125790",
|
||||
"ReleaseURL": "https://store.steampowered.com/news/app/620980/view/4205873192610459250",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images//32055887/b8bd8a050e2bc3903cf18a277c5489ade01f7e75.png",
|
||||
"ReleaseDate": "1712681263",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.36.2",
|
||||
"BSManifest": "4832237377879752064",
|
||||
"OculusBinaryId": "6983660338400352",
|
||||
"ReleaseURL": "https://store.steampowered.com/news/app/620980/view/4181105931135110118",
|
||||
"ReleaseImg": "https://clan.cloudflare.steamstatic.com/images//32055887/74541417f14dee210bcfd89eefeabd7c0cc27c7d.png",
|
||||
"ReleaseDate": "1715008020",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.37.0",
|
||||
"BSManifest": "7270690075584855288",
|
||||
"OculusBinaryId": "7062062833893435",
|
||||
"ReleaseURL": "https://store.steampowered.com/news/app/620980/view/4161968170153497460",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images//32055887/f82d34c7582137a6748a28c5b02670ff4b235356.png",
|
||||
"ReleaseDate": "1717513346",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.37.1",
|
||||
"BSManifest": "9129356330007601179",
|
||||
"OculusBinaryId": "7313068492126200",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4253168598913260422",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/039be718c1494f948257cd5b09f0082986484a58.png",
|
||||
"ReleaseDate": "1720710529",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.37.2",
|
||||
"BSManifest": "6848299977215652352",
|
||||
"OculusBinaryId": "7438773609555687",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4349999161850643744",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/7c03af6d12fad23f1d439666d4be86b492d40003.png",
|
||||
"ReleaseDate": "1722945814",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.37.3",
|
||||
"BSManifest": "5834150512217183366",
|
||||
"OculusBinaryId": "7553569971409383",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/6808965920900622902",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/a1c9a6737cc5182fca8cddc7c8088eda6dd4d82d.png",
|
||||
"ReleaseDate": "1724061712",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.37.4",
|
||||
"BSManifest": "7585106640515547731",
|
||||
"OculusBinaryId": "7630971613669218",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4601078012425941968",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/23e6e6f219a56d2f9ea4a27b560c54ac1214aec3.png",
|
||||
"ReleaseDate": "1725548547",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.37.5",
|
||||
"BSManifest": "3122533396458693889",
|
||||
"OculusBinaryId": "7685108391588873",
|
||||
"ReleaseDate": "1725628356",
|
||||
"year": "2024"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.38.0",
|
||||
"BSManifest": "3482902979520746178",
|
||||
"OculusBinaryId": "7834818833284494",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4669760442595134838",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/57cfa043407c091e67b7e0a8ae86fe5590deae33.png",
|
||||
"ReleaseDate": "1728399815",
|
||||
"year": "2024",
|
||||
"recommended": true
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -235,7 +235,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"dotnet-required": ".NET 6 Benötigt"
|
||||
"dotnet-required": ".NET 8 Benötigt"
|
||||
},
|
||||
"msg": {
|
||||
"401": "Steam scheint uns BeatSaber nicht herunterladen zu lassen 😢",
|
||||
@@ -260,10 +260,10 @@
|
||||
"RateLimitExceeded": "Du hast es zu oft versucht, warte eine Weile und versuche es später erneut.",
|
||||
"TokenRejected": "Ihr Anmeldetoken wurde abgelehnt 😕 Bitte versuchen Sie es erneut.",
|
||||
"AccessDenied": "Der Zugang zu Steam wurde verweigert.",
|
||||
"dotnet-required": ".NET 6 Runtime muss installiert sein, um eine Version von BeatSaber herunterzuladen. Lade es herunter, indem Du auf die Schaltfläche unten klickst."
|
||||
"dotnet-required": ".NET 8 Runtime muss installiert sein, um eine Version von BeatSaber herunterzuladen. Lade es herunter, indem Du auf die Schaltfläche unten klickst."
|
||||
},
|
||||
"actions": {
|
||||
"download-dotnet": ".NET 6 Herunterladen"
|
||||
"download-dotnet": ".NET 8 Herunterladen"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -481,6 +481,15 @@
|
||||
"title": "Sicherung erstellt",
|
||||
"msg": "Das Freigeben des Ordners „UserData“ kann zu Fehlern führen. Bei Problemen trenne den Ordner, um die Sicherung wiederherzustellen"
|
||||
}
|
||||
},
|
||||
"linking-error": {
|
||||
"title": "Fehler beim Verknüpfen des Ordners",
|
||||
"msg": {
|
||||
"EPERM": "BSManager hat nicht die erforderlichen Berechtigungen, um den Ordner zu verknüpfen.",
|
||||
"EACCES": "BSManager hat nicht die erforderlichen Berechtigungen, um den Ordner zu verknüpfen.",
|
||||
"ENOSPC": "Die Festplatte ist voll, machen Sie Platz und versuchen Sie es erneut.",
|
||||
"UNKNOWN_ERROR": "Ein unbekannter Fehler ist beim Verknüpfen des Ordners aufgetreten."
|
||||
}
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -741,48 +750,48 @@
|
||||
"accuracy": "Präzision",
|
||||
"balanced": "Ausgeglichen",
|
||||
"challenge": "Herausforderung",
|
||||
"dancestyle": "Tanz",
|
||||
"dance-style": "Tanz",
|
||||
"fitness": "Fitness",
|
||||
"speed": "Geschwindigkeit",
|
||||
"tech": "Tech"
|
||||
},
|
||||
"map-styles": {
|
||||
"dance": "Tanz",
|
||||
"swing": "Swing",
|
||||
"nightcore": "Nightcore",
|
||||
"folk": "Folk",
|
||||
"family": "Familie",
|
||||
"ambient": "umgebung",
|
||||
"funk": "Funk",
|
||||
"jazz": "Jazz",
|
||||
"soul": "Soul",
|
||||
"speedcore": "Speedcore",
|
||||
"punk": "Punk",
|
||||
"rb": "R&B",
|
||||
"holiday": "Urlaub",
|
||||
"vocaloid": "Vokaloid",
|
||||
"jrock": "J-Rock",
|
||||
"trance": "Trance",
|
||||
"drumbass": "Basstrommel",
|
||||
"comedy": "Komödie",
|
||||
"instrumental": "Instrumental",
|
||||
"hardcore": "Hardcore",
|
||||
"kpop": "K-Pop",
|
||||
"indie": "Indie",
|
||||
"techno": "Techno",
|
||||
"house": "House",
|
||||
"game": "Videospiele",
|
||||
"film": "Filme",
|
||||
"alt": "Alternativ",
|
||||
"dubstep": "Dubstep",
|
||||
"metal": "Metal",
|
||||
"anime": "Anime",
|
||||
"hiphop": "HipHop",
|
||||
"jpop": "J-Pop",
|
||||
"rock": "Rock",
|
||||
"pop": "Pop",
|
||||
"electronic": "Elektronisch",
|
||||
"classical-orchestral": "Klassisch & Orchestra"
|
||||
"dance": "tanz",
|
||||
"swing": "swing",
|
||||
"nightcore": "nightcore",
|
||||
"folk-acoustic": "folk & akustisch",
|
||||
"kids-family": "kinder & familie",
|
||||
"ambient": "ambient",
|
||||
"funk-disco": "funk & disco",
|
||||
"jazz": "jazz",
|
||||
"soul": "soul",
|
||||
"speedcore": "speedcore",
|
||||
"punk": "punk",
|
||||
"rb": "r&b",
|
||||
"holiday": "urlaub",
|
||||
"vocaloid": "vocaloid",
|
||||
"j-rock": "j-rock",
|
||||
"trance": "trance",
|
||||
"drum-and-bass": "drum & bass",
|
||||
"comedy-meme": "komödie & meme",
|
||||
"instrumental": "instrumental",
|
||||
"hardcore": "hardcore",
|
||||
"k-pop": "k-pop",
|
||||
"indie": "indie",
|
||||
"techno": "techno",
|
||||
"house": "house",
|
||||
"video-game-soundtrack": "videospiel",
|
||||
"tv-movie-soundtrack": "TV & film",
|
||||
"alternative": "alternativ",
|
||||
"dubstep": "dubstep",
|
||||
"metal": "metal",
|
||||
"anime": "anime",
|
||||
"hip-hop-rap": "hip hop & rap",
|
||||
"j-pop": "j-pop",
|
||||
"rock": "rock",
|
||||
"pop": "pop",
|
||||
"electronic": "elektronisch",
|
||||
"classical-orchestral": "Klassisch & Orchestral"
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "KI",
|
||||
@@ -966,10 +975,6 @@
|
||||
"go-to-mods": "Gehe zu Mods",
|
||||
"not-remind": "Erinnere mich nicht"
|
||||
},
|
||||
"prevent-for-models-breaks": {
|
||||
"title": "Gebrochene Modelle",
|
||||
"desc": "Seit dem Update der Beat Saber Engine sind alle benutzerdefinierten Modellmods defekt und müssen aktualisiert werden. Sie wurden noch nicht aktualisiert."
|
||||
},
|
||||
"export-success": {
|
||||
"title": "Export abgeschlossen 🎉"
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"dotnet-required": ".NET 6 Required"
|
||||
"dotnet-required": ".NET 8 Required"
|
||||
},
|
||||
"msg": {
|
||||
"401": "Steam doesn't seem to want to let us download Beat Saber 😢",
|
||||
@@ -260,10 +260,10 @@
|
||||
"RateLimitExceeded": "You've tried too many times, wait a while and try again later.",
|
||||
"TokenRejected": "Your login token has been rejected 😕 Please try again.",
|
||||
"AccessDenied": "Access to Steam has been denied.",
|
||||
"dotnet-required": ".NET 6 Runtime must be installed in order to download a version of Beat Saber. Download it by clicking the button below."
|
||||
"dotnet-required": ".NET 8 Runtime must be installed in order to download a version of Beat Saber. Download it by clicking the button below."
|
||||
},
|
||||
"actions": {
|
||||
"download-dotnet": "Download .NET 6"
|
||||
"download-dotnet": "Download .NET 8"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -481,6 +481,15 @@
|
||||
"title": "Backup created",
|
||||
"msg": "Sharing the 'UserData' folder can generate errors, in case of problems unlink the folder to restore the backup"
|
||||
}
|
||||
},
|
||||
"linking-error": {
|
||||
"title": "Error while linking folder",
|
||||
"msg": {
|
||||
"EPERM": "BSManager does not have the necessary permissions to link the folder.",
|
||||
"EACCES": "BSManager does not have the necessary permissions to link the folder.",
|
||||
"ENOSPC": "The disk is full, make some space and try again.",
|
||||
"UNKNOWN_ERROR":"An unknown error has occurred while linking the folder."
|
||||
}
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -741,7 +750,7 @@
|
||||
"accuracy": "accuracy",
|
||||
"balanced": "balanced",
|
||||
"challenge": "challenge",
|
||||
"dancestyle": "dance",
|
||||
"dance-style": "dance",
|
||||
"fitness": "fitness",
|
||||
"speed": "speed",
|
||||
"tech": "tech"
|
||||
@@ -750,10 +759,10 @@
|
||||
"dance": "dance",
|
||||
"swing": "swing",
|
||||
"nightcore": "nightcore",
|
||||
"folk": "folk",
|
||||
"family": "family",
|
||||
"folk-acoustic": "folk & acoustic",
|
||||
"kids-family": "kids & family",
|
||||
"ambient": "ambient",
|
||||
"funk": "funk",
|
||||
"funk-disco": "funk & disco",
|
||||
"jazz": "jazz",
|
||||
"soul": "soul",
|
||||
"speedcore": "speedcore",
|
||||
@@ -761,24 +770,24 @@
|
||||
"rb": "r&b",
|
||||
"holiday": "holiday",
|
||||
"vocaloid": "vocaloid",
|
||||
"jrock": "j-rock",
|
||||
"j-rock": "j-rock",
|
||||
"trance": "trance",
|
||||
"drumbass": "drum & bass",
|
||||
"comedy": "comedy",
|
||||
"drum-and-bass": "drum & bass",
|
||||
"comedy-meme": "comedy & meme",
|
||||
"instrumental": "instrumental",
|
||||
"hardcore": "hardcore",
|
||||
"kpop": "k-pop",
|
||||
"k-pop": "k-pop",
|
||||
"indie": "indie",
|
||||
"techno": "techno",
|
||||
"house": "house",
|
||||
"game": "video game",
|
||||
"film": "film",
|
||||
"alt": "alternative",
|
||||
"video-game-soundtrack": "video game",
|
||||
"tv-movie-soundtrack": "TV & film",
|
||||
"alternative": "alternative",
|
||||
"dubstep": "dubstep",
|
||||
"metal": "metal",
|
||||
"anime": "anime",
|
||||
"hiphop": "hiphop",
|
||||
"jpop": "j-pop",
|
||||
"hip-hop-rap": "hip hop & rap",
|
||||
"j-pop": "j-pop",
|
||||
"rock": "rock",
|
||||
"pop": "pop",
|
||||
"electronic": "electronic",
|
||||
@@ -966,10 +975,6 @@
|
||||
"go-to-mods": "Go to mods",
|
||||
"not-remind": "Don't remind me"
|
||||
},
|
||||
"prevent-for-models-breaks": {
|
||||
"title": "Broken models",
|
||||
"desc": "Since the Beat Saber engine update, all custom model mods have broken and need to be updated. They have not been updated yet."
|
||||
},
|
||||
"export-success": {
|
||||
"title": "Export completed 🎉"
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"dotnet-required": ".NET 6 Requerido"
|
||||
"dotnet-required": ".NET 8 Requerido"
|
||||
},
|
||||
"msg": {
|
||||
"401": "Parece que Steam no quiere dejarnos descargar Beat Saber 😢",
|
||||
@@ -260,10 +260,10 @@
|
||||
"RateLimitExceeded": "Lo has intentado demasiadas veces, espera un poco y vuelve a intentarlo más tarde.",
|
||||
"TokenRejected": "Tu token de inicio de sesión ha sido rechazada 😕 Por favor, inténtalo de nuevo.",
|
||||
"AccessDenied": "El acceso a Steam ha sido denegado.",
|
||||
"dotnet-required": "Se debe instalar el tiempo de ejecución de .NET 6 para descargar una versión de Beat Saber. Descárgalo haciendo clic en el botón de abajo."
|
||||
"dotnet-required": "Se debe instalar el tiempo de ejecución de .NET 8 para descargar una versión de Beat Saber. Descárgalo haciendo clic en el botón de abajo."
|
||||
},
|
||||
"actions": {
|
||||
"download-dotnet": "Descargar .NET 6"
|
||||
"download-dotnet": "Descargar .NET 8"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -481,6 +481,15 @@
|
||||
"title": "Backup creado",
|
||||
"msg": "Compartir la carpeta 'UserData' puede generar errores, en caso de problemas desvincula la carpeta para restaurar la copia de seguridad"
|
||||
}
|
||||
},
|
||||
"linking-error": {
|
||||
"title": "Error al enlazar la carpeta",
|
||||
"msg": {
|
||||
"EPERM": "BSManager no tiene los permisos necesarios para enlazar la carpeta.",
|
||||
"EACCES": "BSManager no tiene los permisos necesarios para enlazar la carpeta.",
|
||||
"ENOSPC": "El disco está lleno, libera espacio e inténtalo de nuevo.",
|
||||
"UNKNOWN_ERROR": "Se ha producido un error desconocido al enlazar la carpeta."
|
||||
}
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -741,19 +750,19 @@
|
||||
"accuracy": "precisión",
|
||||
"balanced": "equilibrado",
|
||||
"challenge": "desafío",
|
||||
"dancestyle": "baile",
|
||||
"dance-style": "baile",
|
||||
"fitness": "fitness",
|
||||
"speed": "speed",
|
||||
"tech": "tech"
|
||||
},
|
||||
"map-styles": {
|
||||
"dance": "baile",
|
||||
"dance": "danza",
|
||||
"swing": "swing",
|
||||
"nightcore": "nightcore",
|
||||
"folk": "folk",
|
||||
"family": "familia",
|
||||
"ambient": "ambiente",
|
||||
"funk": "funk",
|
||||
"folk-acoustic": "folk & acústico",
|
||||
"kids-family": "niños & familia",
|
||||
"ambient": "ambiental",
|
||||
"funk-disco": "funk & disco",
|
||||
"jazz": "jazz",
|
||||
"soul": "soul",
|
||||
"speedcore": "speedcore",
|
||||
@@ -761,28 +770,28 @@
|
||||
"rb": "r&b",
|
||||
"holiday": "vacaciones",
|
||||
"vocaloid": "vocaloid",
|
||||
"jrock": "j-rock",
|
||||
"j-rock": "j-rock",
|
||||
"trance": "trance",
|
||||
"drumbass": "drum & bass",
|
||||
"comedy": "comedia",
|
||||
"drum-and-bass": "drum & bass",
|
||||
"comedy-meme": "comedia & meme",
|
||||
"instrumental": "instrumental",
|
||||
"hardcore": "hardcore",
|
||||
"kpop": "k-pop",
|
||||
"k-pop": "k-pop",
|
||||
"indie": "indie",
|
||||
"techno": "tecno",
|
||||
"techno": "techno",
|
||||
"house": "house",
|
||||
"game": "videojuego",
|
||||
"film": "film",
|
||||
"alt": "alternativa",
|
||||
"video-game-soundtrack": "videojuego",
|
||||
"tv-movie-soundtrack": "TV & cine",
|
||||
"alternative": "alternativo",
|
||||
"dubstep": "dubstep",
|
||||
"metal": "metal",
|
||||
"anime": "anime",
|
||||
"hiphop": "hiphop",
|
||||
"jpop": "j-pop",
|
||||
"hip-hop-rap": "hip hop & rap",
|
||||
"j-pop": "j-pop",
|
||||
"rock": "rock",
|
||||
"pop": "pop",
|
||||
"electronic": "electrónico",
|
||||
"classical-orchestral": "Clásico y orquestal"
|
||||
"electronic": "electrónica",
|
||||
"classical-orchestral": "Clásica & Orquestal"
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "IA",
|
||||
@@ -966,10 +975,6 @@
|
||||
"go-to-mods": "Ir a mods",
|
||||
"not-remind": "No volver a recordarme"
|
||||
},
|
||||
"prevent-for-models-breaks": {
|
||||
"title": "Modelos rotos",
|
||||
"desc": "Desde la actualización del motor de Beat Saber, todos los mods de modelos personalizados se han roto y necesitan ser actualizados. Todavía no han sido actualizados."
|
||||
},
|
||||
"export-success": {
|
||||
"title": "Exportación completada 🎉"
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"dotnet-required": ".NET 6 Requis"
|
||||
"dotnet-required": ".NET 8 Requis"
|
||||
},
|
||||
"msg": {
|
||||
"401": "Steam ne semble pas vouloir nous laisser télécharger Beat Saber 😢",
|
||||
@@ -260,10 +260,10 @@
|
||||
"RateLimitExceeded": "Tu as essayé trop de fois attends un peu et recommence plus tard.",
|
||||
"TokenRejected": "Votre token de connexion a été rejeté 😕 Veuillez réessayer.",
|
||||
"AccessDenied": "L'accès à Steam a été refusé.",
|
||||
"dotnet-required": ".NET 6 Runtime doit être installé pour pouvoir télécharger une version de Beat Saber. Télécharge-le en cliquant sur le bouton ci-dessous."
|
||||
"dotnet-required": ".NET 8 Runtime doit être installé pour pouvoir télécharger une version de Beat Saber. Télécharge-le en cliquant sur le bouton ci-dessous."
|
||||
},
|
||||
"actions": {
|
||||
"download-dotnet": "Télécharger .NET 6"
|
||||
"download-dotnet": "Télécharger .NET 8"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -481,7 +481,17 @@
|
||||
"title": "Sauvegarde créée",
|
||||
"msg": "Le partage du dossier 'UserData' peut générer des erreurs, en cas de soucis déliez le dossier pour restaurer la sauvegarde"
|
||||
}
|
||||
},
|
||||
"linking-error": {
|
||||
"title": "Erreur lors de la liaison du dossier",
|
||||
"msg": {
|
||||
"EPERM": "BSManager n'a pas les autorisations nécessaires pour lier le dossier.",
|
||||
"EACCES": "BSManager n'a pas les autorisations nécessaires pour lier le dossier.",
|
||||
"ENOSPC": "Le disque est plein, faites de la place et réessayez.",
|
||||
"UNKNOWN_ERROR": "Une erreur inconnue est survenue lors de la liaison du dossier."
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"success": {
|
||||
@@ -741,48 +751,48 @@
|
||||
"accuracy": "précision",
|
||||
"balanced": "équilibrée",
|
||||
"challenge": "challenge",
|
||||
"dancestyle": "dance",
|
||||
"dance-style": "dance",
|
||||
"fitness": "fitness",
|
||||
"speed": "vitesse",
|
||||
"tech": "tech"
|
||||
},
|
||||
"map-styles": {
|
||||
"dance": "dance",
|
||||
"dance": "danse",
|
||||
"swing": "swing",
|
||||
"nightcore": "nightcore",
|
||||
"folk": "folk",
|
||||
"family": "famille",
|
||||
"ambient": "ambiante",
|
||||
"funk": "funk",
|
||||
"folk-acoustic": "folk & acoustique",
|
||||
"kids-family": "enfants & famille",
|
||||
"ambient": "ambiant",
|
||||
"funk-disco": "funk & disco",
|
||||
"jazz": "jazz",
|
||||
"soul": "soul",
|
||||
"speedcore": "speedcore",
|
||||
"punk": "punk",
|
||||
"rb": "r&b",
|
||||
"holiday": "vacance",
|
||||
"holiday": "vacances",
|
||||
"vocaloid": "vocaloid",
|
||||
"jrock": "j-rock",
|
||||
"j-rock": "j-rock",
|
||||
"trance": "trance",
|
||||
"drumbass": "drum & bass",
|
||||
"comedy": "comédie",
|
||||
"drum-and-bass": "drum & bass",
|
||||
"comedy-meme": "comédie & meme",
|
||||
"instrumental": "instrumental",
|
||||
"hardcore": "hardcore",
|
||||
"kpop": "k-pop",
|
||||
"indie": "indé",
|
||||
"k-pop": "k-pop",
|
||||
"indie": "indie",
|
||||
"techno": "techno",
|
||||
"house": "house",
|
||||
"game": "jeu vidéo",
|
||||
"film": "film",
|
||||
"alt": "alternative",
|
||||
"video-game-soundtrack": "jeu vidéo",
|
||||
"tv-movie-soundtrack": "TV & film",
|
||||
"alternative": "alternatif",
|
||||
"dubstep": "dubstep",
|
||||
"metal": "metal",
|
||||
"metal": "métal",
|
||||
"anime": "anime",
|
||||
"hiphop": "hiphop",
|
||||
"jpop": "j-pop",
|
||||
"hip-hop-rap": "hip hop & rap",
|
||||
"j-pop": "j-pop",
|
||||
"rock": "rock",
|
||||
"pop": "pop",
|
||||
"electronic": "électronique",
|
||||
"classical-orchestral": "classique & orchestrale"
|
||||
"classical-orchestral": "Classique & Orchestral"
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "IA",
|
||||
@@ -966,10 +976,6 @@
|
||||
"go-to-mods": "Aller aux mods",
|
||||
"not-remind": "Ne plus me rappeler"
|
||||
},
|
||||
"prevent-for-models-breaks": {
|
||||
"title": "Modèles cassés",
|
||||
"desc": "Depuis la mise à jour du moteur Beat Saber, tous les mods de modèles personnalisés ne sont plus fonctionnels et doivent être mis à jour. Ils n'ont pas encore été mis à jour."
|
||||
},
|
||||
"export-success": {
|
||||
"title": "Export terminé 🎉"
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"dotnet-required": ".NET 6が必要です!"
|
||||
"dotnet-required": ".NET 8が必要です!"
|
||||
},
|
||||
"msg": {
|
||||
"401": "SteamがBeat Saberのダウンロードを許可してくれないようだ。😢",
|
||||
@@ -260,10 +260,10 @@
|
||||
"RateLimitExceeded": "何度も試しているのでしばらくたって、もう一度お試しください。",
|
||||
"TokenRejected": "ログイントークンが拒否されました 😕 もう一度お試しください。",
|
||||
"AccessDenied": "Steamへのアクセスが拒否されました。",
|
||||
"dotnet-required": "Beat Saberをダウンロードするには、.NET 6 Runtimeがインストールされている必要があります。下のボタンをクリックしてダウンロードしてください。"
|
||||
"dotnet-required": "Beat Saberをダウンロードするには、.NET 8 Runtimeがインストールされている必要があります。下のボタンをクリックしてダウンロードしてください。"
|
||||
},
|
||||
"actions": {
|
||||
"download-dotnet": ".NET 6をダウンロード"
|
||||
"download-dotnet": ".NET 8をダウンロード"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -481,6 +481,15 @@
|
||||
"title": "バックアップを作成しました",
|
||||
"msg": "'UserData'フォルダを共有するとエラーが発生する可能性があります。問題が発生した場合は、フォルダをリンク解除してバックアップを復元してください。"
|
||||
}
|
||||
},
|
||||
"linking-error": {
|
||||
"title": "フォルダのリンク中にエラーが発生しました",
|
||||
"msg": {
|
||||
"EPERM": "BSManagerにはフォルダをリンクするための必要な権限がありません。",
|
||||
"EACCES": "BSManagerにはフォルダをリンクするための必要な権限がありません。",
|
||||
"ENOSPC": "ディスクがいっぱいです。空き容量を作ってもう一度試してください。",
|
||||
"UNKNOWN_ERROR": "フォルダをリンク中に不明なエラーが発生しました。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -750,39 +759,39 @@
|
||||
"dance": "ダンス",
|
||||
"swing": "スウィング",
|
||||
"nightcore": "ナイトコア",
|
||||
"folk": "フォーク",
|
||||
"family": "ファミリー",
|
||||
"folk-acoustic": "フォーク & アコースティック",
|
||||
"kids-family": "キッズ & ファミリー",
|
||||
"ambient": "アンビエント",
|
||||
"funk": "ファンク",
|
||||
"funk-disco": "ファンク & ディスコ",
|
||||
"jazz": "ジャズ",
|
||||
"soul": "ソウル",
|
||||
"speedcore": "スピードコア ",
|
||||
"speedcore": "スピードコア",
|
||||
"punk": "パンク",
|
||||
"rb": "r&b",
|
||||
"holiday": "休日",
|
||||
"holiday": "ホリデー",
|
||||
"vocaloid": "ボーカロイド",
|
||||
"jrock": "j-rock",
|
||||
"j-rock": "J-ロック",
|
||||
"trance": "トランス",
|
||||
"drumbass": "ドラムンベース",
|
||||
"comedy": "コメディ",
|
||||
"instrumental": "器楽",
|
||||
"drum-and-bass": "ドラム & ベース",
|
||||
"comedy-meme": "コメディ & ミーム",
|
||||
"instrumental": "インストゥルメンタル",
|
||||
"hardcore": "ハードコア",
|
||||
"kpop": "k-pop",
|
||||
"k-pop": "k-pop",
|
||||
"indie": "インディー",
|
||||
"techno": "テクノ",
|
||||
"house": "家",
|
||||
"game": "ビデオゲーム",
|
||||
"film": "映画音楽",
|
||||
"alt": "オルタナティヴ",
|
||||
"house": "ハウス",
|
||||
"video-game-soundtrack": "ゲーム音楽",
|
||||
"tv-movie-soundtrack": "TV & 映画",
|
||||
"alternative": "オルタナティブ",
|
||||
"dubstep": "ダブステップ",
|
||||
"metal": "メタル",
|
||||
"anime": "アニメ",
|
||||
"hiphop": "ヒップポップ",
|
||||
"jpop": "j-pop",
|
||||
"hip-hop-rap": "ヒップホップ & ラップ",
|
||||
"j-pop": "J-ポップ",
|
||||
"rock": "ロック",
|
||||
"pop": "ポップ",
|
||||
"electronic": "エレクトロニック",
|
||||
"classical-orchestral": "Classical & Orchestral"
|
||||
"classical-orchestral": "クラシック & オーケストラ"
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "AI",
|
||||
@@ -966,10 +975,6 @@
|
||||
"go-to-mods": "Modインストーラーに移動",
|
||||
"not-remind": "二度と表示しないで"
|
||||
},
|
||||
"prevent-for-models-breaks": {
|
||||
"title": "モデルが壊れています!",
|
||||
"desc": "Beat Saberのエンジンアップデート以来、すべてのカスタムモデルのMODが壊れており、更新が必要です。まだ更新されていません。"
|
||||
},
|
||||
"export-success": {
|
||||
"title": "エクスポート完了🎉"
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"dotnet-required": "Требуется .NET 6"
|
||||
"dotnet-required": "Требуется .NET 8"
|
||||
},
|
||||
"msg": {
|
||||
"401": "Похоже Steam не хочет, чтобы мы скачали Beat Saber 😢",
|
||||
@@ -260,10 +260,10 @@
|
||||
"RateLimitExceeded": "Слишком много попыток, вернитесь позже и попробуйте снова.",
|
||||
"TokenRejected": "Ваш токен для входа был отклонен 😕 Пожалуйста, попробуйте снова.",
|
||||
"AccessDenied": "Доступ к Steam был отклонен.",
|
||||
"dotnet-required": "Среда запуска .NET 6 должна быть установлена, чтобы скачать Beat Saber. Скачайте её с помощью кнопки ниже."
|
||||
"dotnet-required": "Среда запуска .NET 8 должна быть установлена, чтобы скачать Beat Saber. Скачайте её с помощью кнопки ниже."
|
||||
},
|
||||
"actions": {
|
||||
"download-dotnet": "Скачать .NET 6"
|
||||
"download-dotnet": "Скачать .NET 8"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -481,6 +481,15 @@
|
||||
"title": "Бэкап создан",
|
||||
"msg": "Общая папка 'UserData' может создавать ошибки. В случае проблем отвяжите папку, чтобы восстановить бэкап."
|
||||
}
|
||||
},
|
||||
"linking-error": {
|
||||
"title": "Ошибка при связывании папки",
|
||||
"msg": {
|
||||
"EPERM": "BSManager не имеет необходимых прав для связывания папки.",
|
||||
"EACCES": "BSManager не имеет необходимых прав для связывания папки.",
|
||||
"ENOSPC": "Диск заполнен, освободите место и попробуйте снова.",
|
||||
"UNKNOWN_ERROR": "Произошла неизвестная ошибка при связывании папки."
|
||||
}
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -740,7 +749,7 @@
|
||||
"accuracy": "точность",
|
||||
"balanced": "баланс",
|
||||
"challenge": "испытание",
|
||||
"dancestyle": "танец",
|
||||
"dance-style": "танец",
|
||||
"fitness": "фитнес",
|
||||
"speed": "скорость",
|
||||
"tech": "техника"
|
||||
@@ -749,36 +758,36 @@
|
||||
"dance": "танец",
|
||||
"swing": "свинг",
|
||||
"nightcore": "найткор",
|
||||
"folk": "народные",
|
||||
"family": "семейные",
|
||||
"folk-acoustic": "фолк и акустика",
|
||||
"kids-family": "дети и семья",
|
||||
"ambient": "эмбиент",
|
||||
"funk": "фанк",
|
||||
"funk-disco": "фанк и диско",
|
||||
"jazz": "джаз",
|
||||
"soul": "соул",
|
||||
"speedcore": "спидкор",
|
||||
"punk": "панк",
|
||||
"rb": "ар-н-би",
|
||||
"holiday": "праздник",
|
||||
"vocaloid": "вокалоиды",
|
||||
"jrock": "джей-рок",
|
||||
"rb": "r&b",
|
||||
"holiday": "праздники",
|
||||
"vocaloid": "вокалоид",
|
||||
"j-rock": "джей-рок",
|
||||
"trance": "транс",
|
||||
"drumbass": "драм-н-бейс",
|
||||
"comedy": "комедия",
|
||||
"instrumental": "инструментальные",
|
||||
"drum-and-bass": "драм-н-бейс",
|
||||
"comedy-meme": "комедия и мемы",
|
||||
"instrumental": "инструментал",
|
||||
"hardcore": "хардкор",
|
||||
"kpop": "кей-поп",
|
||||
"k-pop": "k-pop",
|
||||
"indie": "инди",
|
||||
"techno": "техно",
|
||||
"house": "хаус",
|
||||
"game": "видеоигры",
|
||||
"film": "фильмы",
|
||||
"alt": "альтернативные",
|
||||
"dubstep": "дапстеп",
|
||||
"video-game-soundtrack": "видеоигра",
|
||||
"tv-movie-soundtrack": "ТВ и кино",
|
||||
"alternative": "альтернатива",
|
||||
"dubstep": "дабстеп",
|
||||
"metal": "метал",
|
||||
"anime": "аниме",
|
||||
"hiphop": "хипхоп",
|
||||
"jpop": "джей-поп",
|
||||
"rock": "прк",
|
||||
"hip-hop-rap": "хип-хоп и рэп",
|
||||
"j-pop": "j-pop",
|
||||
"rock": "рок",
|
||||
"pop": "поп",
|
||||
"electronic": "электроника",
|
||||
"classical-orchestral": "Классика и Оркестр"
|
||||
@@ -965,10 +974,6 @@
|
||||
"go-to-mods": "Перейти к модам",
|
||||
"not-remind": "Больше не напоминать"
|
||||
},
|
||||
"prevent-for-models-breaks": {
|
||||
"title": "Сломанные модели",
|
||||
"desc": "С момента обновления движка Beat Saber, все пользовательские модификации моделей сломались и требуют обновления. Они еще не были обновлены."
|
||||
},
|
||||
"export-success": {
|
||||
"title": "Экспорт завершен 🎉"
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"dotnet-required": "需要 .NET 6"
|
||||
"dotnet-required": "需要 .NET 8"
|
||||
},
|
||||
"msg": {
|
||||
"401": "Steam 似乎不想讓我們下載 Beat Saber😢",
|
||||
@@ -260,10 +260,10 @@
|
||||
"RateLimitExceeded": "你已經嘗試了太多次,請稍等片刻,稍後再試。",
|
||||
"TokenRejected": "你的登錄令牌已被拒絕 😕 請重試。",
|
||||
"AccessDenied": "訪問 Steam 被拒絕。",
|
||||
"dotnet-required": "必須安裝 .NET 6 Runtime 才能下載 Beat Saber 版本。單擊下面的按鈕下載它。"
|
||||
"dotnet-required": "必須安裝 .NET 8 Runtime 才能下載 Beat Saber 版本。單擊下面的按鈕下載它。"
|
||||
},
|
||||
"actions": {
|
||||
"download-dotnet": "下載 .NET 6"
|
||||
"download-dotnet": "下載 .NET 8"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -481,6 +481,15 @@
|
||||
"title": "備份已創建",
|
||||
"msg": "共享 “UserData” 文件夾可能會產生錯誤,如果出現問題,請取消關聯該文件夾以恢復備份"
|
||||
}
|
||||
},
|
||||
"linking-error": {
|
||||
"title": "連結文件夾時出錯",
|
||||
"msg": {
|
||||
"EPERM": "BSManager沒有必要的權限來連結文件夾。",
|
||||
"EACCES": "BSManager沒有必要的權限來連結文件夾。",
|
||||
"ENOSPC": "磁碟已滿,請騰出空間後再試。",
|
||||
"UNKNOWN_ERROR": "連結文件夾時發生未知錯誤。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -741,48 +750,48 @@
|
||||
"accuracy": "精確度",
|
||||
"balanced": "平衡",
|
||||
"challenge": "挑戰",
|
||||
"dancestyle": "舞蹈",
|
||||
"dance-style": "舞蹈",
|
||||
"fitness": "健身",
|
||||
"speed": "速度",
|
||||
"tech": "技術"
|
||||
},
|
||||
"map-styles": {
|
||||
"dance": "Dance",
|
||||
"swing": "Swing",
|
||||
"nightcore": "Nightcore",
|
||||
"folk": "Folk",
|
||||
"family": "Family",
|
||||
"ambient": "Ambient",
|
||||
"funk": "Funk",
|
||||
"jazz": "Jazz",
|
||||
"soul": "Soul",
|
||||
"speedcore": "Speedcore",
|
||||
"punk": "Punk",
|
||||
"rb": "R&B",
|
||||
"holiday": "Holiday",
|
||||
"vocaloid": "Vocaloid",
|
||||
"jrock": "J-rock",
|
||||
"trance": "Trance",
|
||||
"drumbass": "Drum & Bass",
|
||||
"comedy": "Comedy",
|
||||
"instrumental": "Instrumental",
|
||||
"hardcore": "Hardcore",
|
||||
"kpop": "K-pop",
|
||||
"indie": "Indie",
|
||||
"techno": "Techno",
|
||||
"house": "House",
|
||||
"game": "Video game",
|
||||
"film": "Film",
|
||||
"alt": "Alternative",
|
||||
"dubstep": "Dubstep",
|
||||
"metal": "Metal",
|
||||
"anime": "Anime",
|
||||
"hiphop": "Hiphop",
|
||||
"jpop": "J-pop",
|
||||
"rock": "Rock",
|
||||
"pop": "Pop",
|
||||
"electronic": "Electronic",
|
||||
"classical-orchestral": "Classical & Orchestral"
|
||||
"dance": "舞蹈",
|
||||
"swing": "搖擺",
|
||||
"nightcore": "夜核",
|
||||
"folk-acoustic": "民謠 & 原聲",
|
||||
"kids-family": "兒童 & 家庭",
|
||||
"ambient": "氛圍音樂",
|
||||
"funk-disco": "放克 & 迪斯科",
|
||||
"jazz": "爵士",
|
||||
"soul": "靈魂",
|
||||
"speedcore": "極速核",
|
||||
"punk": "龐克",
|
||||
"rb": "r&b",
|
||||
"holiday": "假日",
|
||||
"vocaloid": "聲庫",
|
||||
"j-rock": "日式搖滾",
|
||||
"trance": "迷幻",
|
||||
"drum-and-bass": "鼓 & 貝斯",
|
||||
"comedy-meme": "喜劇 & 表情包",
|
||||
"instrumental": "純音樂",
|
||||
"hardcore": "硬核",
|
||||
"k-pop": "k-pop",
|
||||
"indie": "獨立",
|
||||
"techno": "電子舞曲",
|
||||
"house": "浩室音樂",
|
||||
"video-game-soundtrack": "遊戲音樂",
|
||||
"tv-movie-soundtrack": "電視 & 電影",
|
||||
"alternative": "另類",
|
||||
"dubstep": "電音鼓",
|
||||
"metal": "金屬",
|
||||
"anime": "動漫",
|
||||
"hip-hop-rap": "嘻哈 & 說唱",
|
||||
"j-pop": "日式流行",
|
||||
"rock": "搖滾",
|
||||
"pop": "流行",
|
||||
"electronic": "電子",
|
||||
"classical-orchestral": "古典 & 管弦樂"
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "AI",
|
||||
@@ -966,10 +975,6 @@
|
||||
"go-to-mods": "前往 Mod 頁",
|
||||
"not-remind": "不再提醒我"
|
||||
},
|
||||
"prevent-for-models-breaks": {
|
||||
"title": "已損壞的模型",
|
||||
"desc": "自從Beat Saber引擎更新以來,所有自定義模型的MOD都已損壞,需要更新。它們還沒有被更新。"
|
||||
},
|
||||
"export-success": {
|
||||
"title": "導出成功 🎉"
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"dotnet-required": "需要 .NET 6"
|
||||
"dotnet-required": "需要 .NET 8"
|
||||
},
|
||||
"msg": {
|
||||
"401": "Steam 似乎不想让我们下载 Beat Saber😢",
|
||||
@@ -260,10 +260,10 @@
|
||||
"RateLimitExceeded": "你已经尝试了太多次,请稍等片刻,稍后再试。",
|
||||
"TokenRejected": "你的登录令牌已被拒绝 😕 请重试。",
|
||||
"AccessDenied": "访问 Steam 被拒绝。",
|
||||
"dotnet-required": "必须安装 .NET 6 Runtime 才能下载 Beat Saber 版本。单击下面的按钮下载它。"
|
||||
"dotnet-required": "必须安装 .NET 8 Runtime 才能下载 Beat Saber 版本。单击下面的按钮下载它。"
|
||||
},
|
||||
"actions": {
|
||||
"download-dotnet": "下载 .NET 6"
|
||||
"download-dotnet": "下载 .NET 8"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -481,6 +481,15 @@
|
||||
"title": "备份已创建",
|
||||
"msg": "共享 “UserData” 文件夹可能会产生错误,如果出现问题,请取消关联该文件夹以恢复备份"
|
||||
}
|
||||
},
|
||||
"linking-error": {
|
||||
"title": "链接文件夹时出错",
|
||||
"msg": {
|
||||
"EPERM": "BSManager没有必要的权限来链接文件夹。",
|
||||
"EACCES": "BSManager没有必要的权限来链接文件夹。",
|
||||
"ENOSPC": "磁盘已满,请腾出空间后再试。",
|
||||
"UNKNOWN_ERROR": "链接文件夹时发生未知错误。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -741,48 +750,48 @@
|
||||
"accuracy": "精确度",
|
||||
"balanced": "平衡",
|
||||
"challenge": "挑战",
|
||||
"dancestyle": "舞蹈",
|
||||
"dance-style": "舞蹈",
|
||||
"fitness": "健身",
|
||||
"speed": "速度",
|
||||
"tech": "技术"
|
||||
},
|
||||
"map-styles": {
|
||||
"dance": "dance",
|
||||
"swing": "swing",
|
||||
"nightcore": "nightcore",
|
||||
"folk": "folk",
|
||||
"family": "family",
|
||||
"ambient": "ambient",
|
||||
"funk": "funk",
|
||||
"jazz": "jazz",
|
||||
"soul": "soul",
|
||||
"speedcore": "speedcore",
|
||||
"punk": "punk",
|
||||
"dance": "舞蹈",
|
||||
"swing": "摇摆",
|
||||
"nightcore": "夜核",
|
||||
"folk-acoustic": "民谣 & 原声",
|
||||
"kids-family": "儿童 & 家庭",
|
||||
"ambient": "氛围音乐",
|
||||
"funk-disco": "放克 & 迪斯科",
|
||||
"jazz": "爵士",
|
||||
"soul": "灵魂",
|
||||
"speedcore": "极速核",
|
||||
"punk": "朋克",
|
||||
"rb": "r&b",
|
||||
"holiday": "holiday",
|
||||
"vocaloid": "vocaloid",
|
||||
"jrock": "j-rock",
|
||||
"trance": "trance",
|
||||
"drumbass": "drum & bass",
|
||||
"comedy": "comedy",
|
||||
"instrumental": "instrumental",
|
||||
"hardcore": "hardcore",
|
||||
"kpop": "k-pop",
|
||||
"indie": "indie",
|
||||
"techno": "techno",
|
||||
"house": "house",
|
||||
"game": "video game",
|
||||
"film": "film",
|
||||
"alt": "alternative",
|
||||
"dubstep": "dubstep",
|
||||
"metal": "metal",
|
||||
"anime": "anime",
|
||||
"hiphop": "hiphop",
|
||||
"jpop": "j-pop",
|
||||
"rock": "rock",
|
||||
"pop": "pop",
|
||||
"electronic": "electronic",
|
||||
"classical-orchestral": "Classical & Orchestral"
|
||||
"holiday": "假日",
|
||||
"vocaloid": "声库",
|
||||
"j-rock": "日式摇滚",
|
||||
"trance": "迷幻",
|
||||
"drum-and-bass": "鼓 & 贝斯",
|
||||
"comedy-meme": "喜剧 & 表情包",
|
||||
"instrumental": "纯音乐",
|
||||
"hardcore": "硬核",
|
||||
"k-pop": "k-pop",
|
||||
"indie": "独立",
|
||||
"techno": "电子舞曲",
|
||||
"house": "浩室音乐",
|
||||
"video-game-soundtrack": "游戏音乐",
|
||||
"tv-movie-soundtrack": "电视 & 电影",
|
||||
"alternative": "另类",
|
||||
"dubstep": "电音鼓",
|
||||
"metal": "金属",
|
||||
"anime": "动漫",
|
||||
"hip-hop-rap": "嘻哈 & 说唱",
|
||||
"j-pop": "日式流行",
|
||||
"rock": "摇滚",
|
||||
"pop": "流行",
|
||||
"electronic": "电子",
|
||||
"classical-orchestral": "古典 & 管弦乐"
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "AI",
|
||||
@@ -966,10 +975,6 @@
|
||||
"go-to-mods": "前往 Mod 页",
|
||||
"not-remind": "不再提醒我"
|
||||
},
|
||||
"prevent-for-models-breaks": {
|
||||
"title": "已损坏的模型",
|
||||
"desc": "自从Beat Saber引擎更新以来,所有自定义模型的MOD都已损坏,需要更新。它们还没有被更新。"
|
||||
},
|
||||
"export-success": {
|
||||
"title": "导出成功 🎉"
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETCoreApp,Version=v6.0",
|
||||
"name": ".NETCoreApp,Version=v8.0/win-x64",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETCoreApp,Version=v6.0": {
|
||||
"DepotDownloader/2.5.0": {
|
||||
".NETCoreApp,Version=v8.0": {},
|
||||
".NETCoreApp,Version=v8.0/win-x64": {
|
||||
"DepotDownloader/2.7.3": {
|
||||
"dependencies": {
|
||||
"QRCoder": "1.4.3",
|
||||
"SteamKit2": "2.5.0-Beta.1",
|
||||
"protobuf-net": "3.2.16"
|
||||
"Microsoft.Windows.CsWin32": "0.3.106",
|
||||
"SteamKit2": "3.0.0-Beta.4",
|
||||
"protobuf-net": "3.2.30"
|
||||
},
|
||||
"runtime": {
|
||||
"DepotDownloader.dll": {}
|
||||
@@ -23,60 +24,64 @@
|
||||
"System.Security.Principal.Windows": "5.0.0"
|
||||
}
|
||||
},
|
||||
"protobuf-net/3.2.16": {
|
||||
"Microsoft.Windows.CsWin32/0.3.106": {
|
||||
"dependencies": {
|
||||
"protobuf-net.Core": "3.2.16"
|
||||
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "60.0.34-preview",
|
||||
"Microsoft.Windows.WDK.Win32Metadata": "0.11.4-experimental"
|
||||
}
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Docs/0.1.42-alpha": {},
|
||||
"Microsoft.Windows.SDK.Win32Metadata/60.0.34-preview": {},
|
||||
"Microsoft.Windows.WDK.Win32Metadata/0.11.4-experimental": {
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "60.0.34-preview"
|
||||
}
|
||||
},
|
||||
"protobuf-net/3.2.30": {
|
||||
"dependencies": {
|
||||
"protobuf-net.Core": "3.2.30"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/protobuf-net.dll": {
|
||||
"assemblyVersion": "3.0.0.0",
|
||||
"fileVersion": "3.2.16.54102"
|
||||
"fileVersion": "3.2.30.709"
|
||||
}
|
||||
}
|
||||
},
|
||||
"protobuf-net.Core/3.2.16": {
|
||||
"protobuf-net.Core/3.2.30": {
|
||||
"dependencies": {
|
||||
"System.Collections.Immutable": "7.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/protobuf-net.Core.dll": {
|
||||
"assemblyVersion": "3.0.0.0",
|
||||
"fileVersion": "3.2.16.54102"
|
||||
"fileVersion": "3.2.30.709"
|
||||
}
|
||||
}
|
||||
},
|
||||
"QRCoder/1.4.3": {
|
||||
"runtime": {
|
||||
"lib/net6.0/QRCoder.dll": {
|
||||
"assemblyVersion": "1.4.3.0",
|
||||
"fileVersion": "1.4.3.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SteamKit2/2.5.0-Beta.1": {
|
||||
"SteamKit2/3.0.0-Beta.4": {
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.Registry": "5.0.0",
|
||||
"protobuf-net": "3.2.16"
|
||||
"System.IO.Hashing": "8.0.0",
|
||||
"protobuf-net": "3.2.30"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/SteamKit2.dll": {
|
||||
"assemblyVersion": "2.5.0.0",
|
||||
"fileVersion": "2.5.0.0"
|
||||
"lib/net8.0/SteamKit2.dll": {
|
||||
"assemblyVersion": "3.0.0.0",
|
||||
"fileVersion": "3.0.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Collections.Immutable/7.0.0": {
|
||||
"dependencies": {
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
},
|
||||
"System.Collections.Immutable/7.0.0": {},
|
||||
"System.IO.Hashing/8.0.0": {
|
||||
"runtime": {
|
||||
"lib/net6.0/System.Collections.Immutable.dll": {
|
||||
"assemblyVersion": "7.0.0.0",
|
||||
"fileVersion": "7.0.22.51805"
|
||||
"lib/net8.0/System.IO.Hashing.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.23.53103"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe/6.0.0": {},
|
||||
"System.Security.AccessControl/5.0.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "5.0.0",
|
||||
@@ -87,7 +92,7 @@
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"DepotDownloader/2.5.0": {
|
||||
"DepotDownloader/2.7.3": {
|
||||
"type": "project",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
@@ -106,33 +111,54 @@
|
||||
"path": "microsoft.win32.registry/5.0.0",
|
||||
"hashPath": "microsoft.win32.registry.5.0.0.nupkg.sha512"
|
||||
},
|
||||
"protobuf-net/3.2.16": {
|
||||
"Microsoft.Windows.CsWin32/0.3.106": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-W6RM2B2f5O7SigBGfkXFYzI/a7Mks+rCHtuGlE/b5O3NMupDon7I0akjCLhrfcnqIfdWDkDafseav/MnIzUSHA==",
|
||||
"path": "protobuf-net/3.2.16",
|
||||
"hashPath": "protobuf-net.3.2.16.nupkg.sha512"
|
||||
"sha512": "sha512-Mx5fK7uN6fwLR4wUghs6//HonAnwPBNmC2oonyJVhCUlHS/r6SUS3NkBc3+gaQiv+0/9bqdj1oSCKQFkNI+21Q==",
|
||||
"path": "microsoft.windows.cswin32/0.3.106",
|
||||
"hashPath": "microsoft.windows.cswin32.0.3.106.nupkg.sha512"
|
||||
},
|
||||
"protobuf-net.Core/3.2.16": {
|
||||
"Microsoft.Windows.SDK.Win32Docs/0.1.42-alpha": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-ZFoY0lfMUWGJ4RoyI0oNj2o2l8t08n3k27YyDD0kYWQ+vXj0hTbWEwYWOpGAfCWvyUPkq9Jcc/4uR74GICZL2g==",
|
||||
"path": "protobuf-net.core/3.2.16",
|
||||
"hashPath": "protobuf-net.core.3.2.16.nupkg.sha512"
|
||||
"sha512": "sha512-Z/9po23gUA9aoukirh2ItMU2ZS9++Js9Gdds9fu5yuMojDrmArvY2y+tq9985tR3cxFxpZO1O35Wjfo0khj5HA==",
|
||||
"path": "microsoft.windows.sdk.win32docs/0.1.42-alpha",
|
||||
"hashPath": "microsoft.windows.sdk.win32docs.0.1.42-alpha.nupkg.sha512"
|
||||
},
|
||||
"QRCoder/1.4.3": {
|
||||
"Microsoft.Windows.SDK.Win32Metadata/60.0.34-preview": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-fWuFqjm8GTlEb2GqBl3Hi8HZZeZQwBSHxvRPtPjyNbT82H0ff0JwavKRBmMaXCno1Av6McPC8aJzri0Mj2w9Jw==",
|
||||
"path": "qrcoder/1.4.3",
|
||||
"hashPath": "qrcoder.1.4.3.nupkg.sha512"
|
||||
"sha512": "sha512-TA3DUNi4CTeo+ItTXBnGZFt2159XOGSl0UOlG5vjDj4WHqZjhwYyyUnzOtrbCERiSaP2Hzg7otJNWwOSZgutyA==",
|
||||
"path": "microsoft.windows.sdk.win32metadata/60.0.34-preview",
|
||||
"hashPath": "microsoft.windows.sdk.win32metadata.60.0.34-preview.nupkg.sha512"
|
||||
},
|
||||
"SteamKit2/2.5.0-Beta.1": {
|
||||
"Microsoft.Windows.WDK.Win32Metadata/0.11.4-experimental": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-fwwdF0cZZEIdNpcUc1+B9zbP5Q48A3DFOAclPhBh2dMTU03aROaIDktFW+Ww/NN1Z/Emp/LwVqHB+hCM8uxS4Q==",
|
||||
"path": "steamkit2/2.5.0-beta.1",
|
||||
"hashPath": "steamkit2.2.5.0-beta.1.nupkg.sha512"
|
||||
"sha512": "sha512-bf5MCmUyZf0gBlYQjx9UpRAZWBkRndyt9XicR+UNLvAUAFTZQbu6YaX/sNKZlR98Grn0gydfh/yT4I3vc0AIQA==",
|
||||
"path": "microsoft.windows.wdk.win32metadata/0.11.4-experimental",
|
||||
"hashPath": "microsoft.windows.wdk.win32metadata.0.11.4-experimental.nupkg.sha512"
|
||||
},
|
||||
"protobuf-net/3.2.30": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-C/UTlmxEJHAHpqm8xQK1UyJKaIynVCSNG4mVrbLgnZ7ccH28nN49O8iMJvKEodTgVbnimvy+3mIiAdW6mATwnw==",
|
||||
"path": "protobuf-net/3.2.30",
|
||||
"hashPath": "protobuf-net.3.2.30.nupkg.sha512"
|
||||
},
|
||||
"protobuf-net.Core/3.2.30": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-v2ZxxYrz+X212ukSx+uqkLuPu414bvmSAnTyf+PBUKR9ENJxO4P/csorA/27456MCp1JNoMssDj/f91RDiwBfQ==",
|
||||
"path": "protobuf-net.core/3.2.30",
|
||||
"hashPath": "protobuf-net.core.3.2.30.nupkg.sha512"
|
||||
},
|
||||
"SteamKit2/3.0.0-Beta.4": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-gDLccGTbvg5RzqQE75uqL3z2Z0F8MTOnier97DdbBGi9F6DDbZQvquHf0INOAEyN7S5Ku+CgaKnkm409UD7avA==",
|
||||
"path": "steamkit2/3.0.0-beta.4",
|
||||
"hashPath": "steamkit2.3.0.0-beta.4.nupkg.sha512"
|
||||
},
|
||||
"System.Collections.Immutable/7.0.0": {
|
||||
"type": "package",
|
||||
@@ -141,12 +167,12 @@
|
||||
"path": "system.collections.immutable/7.0.0",
|
||||
"hashPath": "system.collections.immutable.7.0.0.nupkg.sha512"
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe/6.0.0": {
|
||||
"System.IO.Hashing/8.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
|
||||
"path": "system.runtime.compilerservices.unsafe/6.0.0",
|
||||
"hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
|
||||
"sha512": "sha512-ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==",
|
||||
"path": "system.io.hashing/8.0.0",
|
||||
"hashPath": "system.io.hashing.8.0.0.nupkg.sha512"
|
||||
},
|
||||
"System.Security.AccessControl/5.0.0": {
|
||||
"type": "package",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net6.0",
|
||||
"tfm": "net8.0",
|
||||
"rollForward": "LatestMajor",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "6.0.0"
|
||||
"version": "8.0.0"
|
||||
},
|
||||
"configProperties": {
|
||||
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false
|
||||
"System.Globalization.Invariant": true,
|
||||
"System.Globalization.PredefinedCulturesOnly": true,
|
||||
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
+29
-51
@@ -65,7 +65,7 @@
|
||||
"@types/crypto-js": "^4.2.1",
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/node": "^20",
|
||||
"@types/node-fetch": "^2.6.3",
|
||||
"@types/pako": "^2.0.1",
|
||||
"@types/react": "^18.0.33",
|
||||
@@ -2478,12 +2478,6 @@
|
||||
"@types/responselike": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cacheable-request/node_modules/@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/color": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.3.tgz",
|
||||
@@ -2701,12 +2695,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/keyv/node_modules/@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
||||
@@ -2726,10 +2714,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "17.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz",
|
||||
"integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==",
|
||||
"devOptional": true
|
||||
"version": "20.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.1.tgz",
|
||||
"integrity": "sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.3",
|
||||
@@ -2887,12 +2879,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/responselike/node_modules/@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||
@@ -18202,6 +18188,13 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unique-filename": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz",
|
||||
@@ -21179,14 +21172,6 @@
|
||||
"@types/keyv": "^3.1.4",
|
||||
"@types/node": "*",
|
||||
"@types/responselike": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/color": {
|
||||
@@ -21404,14 +21389,6 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
@@ -21433,10 +21410,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "17.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz",
|
||||
"integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==",
|
||||
"devOptional": true
|
||||
"version": "20.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.1.tgz",
|
||||
"integrity": "sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==",
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "2.6.3",
|
||||
@@ -21593,14 +21573,6 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/retry": {
|
||||
@@ -33114,6 +33086,12 @@
|
||||
"which-boxed-primitive": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"unique-filename": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz",
|
||||
|
||||
+8
-2
@@ -14,7 +14,7 @@
|
||||
"start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
|
||||
"start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
|
||||
"test": "jest",
|
||||
"publish": "npm run build && electron-builder -c.win.certificateSha1=842a817a51e2a1d360fcd62f54bf5f9193e919e1 --publish always --win --x64"
|
||||
"publish": "npm run build && electron-builder -c.win.certificateSha1=206941d969c4fa8a0e04d9427def361e13b02fd0 --publish always --win --x64"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
@@ -76,6 +76,9 @@
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"signingHashAlgorithms": [
|
||||
"sha256"
|
||||
],
|
||||
"target": [
|
||||
"nsis",
|
||||
"nsis-web"
|
||||
@@ -158,7 +161,7 @@
|
||||
"@types/crypto-js": "^4.2.1",
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/node": "^20",
|
||||
"@types/node-fetch": "^2.6.3",
|
||||
"@types/pako": "^2.0.1",
|
||||
"@types/react": "^18.0.33",
|
||||
@@ -317,5 +320,8 @@
|
||||
"!assets/**"
|
||||
],
|
||||
"logLevel": "quiet"
|
||||
},
|
||||
"volta": {
|
||||
"node": "20.18.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.4.6",
|
||||
"version": "1.4.12",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bs-manager",
|
||||
"version": "1.4.6",
|
||||
"version": "1.4.12",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.4.8",
|
||||
"version": "1.4.12",
|
||||
"description": "BSManager",
|
||||
"main": "./dist/main/main.js",
|
||||
"author": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CopyOptions, copy, createReadStream, ensureDir, move, realpath, stat, symlink } from "fs-extra";
|
||||
import { CopyOptions, MoveOptions, copy, createReadStream, ensureDir, move, pathExists, pathExistsSync, realpath, stat, symlink } from "fs-extra";
|
||||
import { access, mkdir, rm, readdir, unlink, lstat, readlink } from "fs/promises";
|
||||
import path from "path";
|
||||
import { Observable, concatMap, from } from "rxjs";
|
||||
@@ -7,6 +7,7 @@ import { BsmException } from "shared/models/bsm-exception.model";
|
||||
import crypto from "crypto";
|
||||
import { execSync } from "child_process";
|
||||
import { tryit } from "../../shared/helpers/error.helpers";
|
||||
import { CustomError } from "../../shared/models/exceptions/custom-error.class"
|
||||
|
||||
export async function pathExist(path: string): Promise<boolean> {
|
||||
try {
|
||||
@@ -77,37 +78,37 @@ export async function getFilesInFolder(folderPath: string): Promise<string[]> {
|
||||
|
||||
return dirEntries.filter(entry => entry.isFile()).map(file => path.join(folderPath, file.name));
|
||||
}
|
||||
|
||||
export function moveFolderContent(src: string, dest: string): Observable<Progression> {
|
||||
export function moveFolderContent(src: string, dest: string, option?: MoveOptions): Observable<Progression> {
|
||||
const progress: Progression = { current: 0, total: 0 };
|
||||
return new Observable<Progression>(subscriber => {
|
||||
subscriber.next(progress);
|
||||
(async () => {
|
||||
const srcExist = await pathExist(src);
|
||||
const srcExist = await pathExists(src);
|
||||
|
||||
if (!srcExist) {
|
||||
return subscriber.complete();
|
||||
}
|
||||
|
||||
ensureFolderExist(dest);
|
||||
await ensureFolderExist(dest);
|
||||
|
||||
const files = await readdir(src, { encoding: "utf-8" });
|
||||
const files = await readdir(src, { encoding: "utf-8", withFileTypes: true });
|
||||
progress.total = files.length;
|
||||
|
||||
const promises = files.map(async file => {
|
||||
const srcFullPath = path.join(src, file);
|
||||
const destFullPath = path.join(dest, file);
|
||||
if (await pathExist(destFullPath)) {
|
||||
progress.current++;
|
||||
return subscriber.next(progress);
|
||||
for(const file of files){
|
||||
const srcFullPath = path.join(src, file.name);
|
||||
const destFullPath = path.join(dest, file.name);
|
||||
|
||||
const srcChilds = file.isDirectory() ? await readdir(srcFullPath, { encoding: "utf-8", recursive: true }) : [];
|
||||
const allChildsAlreadyExist = srcChilds.every(child => pathExistsSync(path.join(destFullPath, child)));
|
||||
|
||||
if(file.isFile() || !allChildsAlreadyExist){
|
||||
await move(srcFullPath, destFullPath, option);
|
||||
}
|
||||
await move(srcFullPath, destFullPath);
|
||||
|
||||
progress.current++;
|
||||
subscriber.next(progress);
|
||||
});
|
||||
|
||||
Promise.allSettled(promises).then(() => subscriber.complete());
|
||||
})();
|
||||
}
|
||||
})().catch(err => subscriber.error(CustomError.fromError(err, err?.code))).finally(() => subscriber.complete());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,7 +176,7 @@ export async function dirSize(dirPath: string): Promise<number>{
|
||||
const realPath = await realpath(fullPath);
|
||||
const stat = await lstat(realPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
if (stat.isDirectory()) {
|
||||
return dirSize(fullPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ ipc.on<ImportVersionOptions>("import-version", (req, reply) => {
|
||||
reply(versionManager.importVersion(req.args));
|
||||
});
|
||||
|
||||
// #region Steam
|
||||
// #region Steam
|
||||
|
||||
ipc.on("is-dotnet-6-installed", (_, reply) => {
|
||||
ipc.on("is-dotnet-installed", (_, reply) => {
|
||||
const installer = BsSteamDownloaderService.getInstance();
|
||||
reply(from(installer.isDotNet6Installed()));
|
||||
reply(from(installer.isDotNetInstalled()));
|
||||
});
|
||||
|
||||
ipc.on("bs-download.installation-folder", (_, reply) => {
|
||||
@@ -83,4 +83,4 @@ ipc.on("bs-oculus-clear-auth-token", async (_, reply) => {
|
||||
reply(from(oculusDownloader.clearAuthToken()));
|
||||
});
|
||||
|
||||
// #endregion
|
||||
// #endregion
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "path";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BsvMapDetail, RawMapInfoData } from "shared/models/maps";
|
||||
import { BsvMapDetail } from "shared/models/maps";
|
||||
import { BsmLocalMap, BsmLocalMapsProgress, DeleteMapsProgress } from "shared/models/maps/bsm-local-map.interface";
|
||||
import { BSLocalVersionService } from "../bs-local-version.service";
|
||||
import { InstallationLocationService } from "../installation-location.service";
|
||||
@@ -22,6 +22,7 @@ import { FolderLinkerService } from "../folder-linker.service";
|
||||
import { allSettled } from "../../../shared/helpers/promise.helpers";
|
||||
import { splitIntoChunk } from "../../../shared/helpers/array.helpers";
|
||||
import { IpcService } from "../ipc.service";
|
||||
import { parseMapInfoDat } from "../../../shared/parsers/maps/map-info.parser";
|
||||
|
||||
export class LocalMapsManagerService {
|
||||
private static instance: LocalMapsManagerService;
|
||||
@@ -84,27 +85,37 @@ export class LocalMapsManagerService {
|
||||
}
|
||||
|
||||
private async computeMapHash(mapPath: string, rawInfoString: string): Promise<string> {
|
||||
const mapRawInfo: RawMapInfoData = JSON.parse(rawInfoString);
|
||||
const mapInfo = parseMapInfoDat(JSON.parse(rawInfoString));
|
||||
const shasum = crypto.createHash("sha1");
|
||||
shasum.update(rawInfoString);
|
||||
|
||||
|
||||
const hashFile = (filePath: string): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stream = createReadStream(filePath);
|
||||
stream.on("data", data => shasum.update(data));
|
||||
stream.on("data", (data: Buffer) => shasum.update(data as unknown as Uint8Array));
|
||||
stream.on("error", reject);
|
||||
stream.on("close", resolve);
|
||||
});
|
||||
};
|
||||
|
||||
for (const set of mapRawInfo._difficultyBeatmapSets) {
|
||||
for (const diff of set._difficultyBeatmaps) {
|
||||
const diffFilePath = path.join(mapPath, diff._beatmapFilename);
|
||||
|
||||
for (const diff of mapInfo.difficulties) {
|
||||
if(diff.beatmapFilename){
|
||||
const diffFilePath = path.join(mapPath, diff.beatmapFilename);
|
||||
await hashFile(diffFilePath);
|
||||
}
|
||||
|
||||
if(diff.lightshowDataFilename) {
|
||||
const lightshowFilePath = path.join(mapPath, diff.lightshowDataFilename);
|
||||
await hashFile(lightshowFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
return shasum.digest("hex");
|
||||
|
||||
const hash = shasum.digest("hex");
|
||||
|
||||
console.log(mapPath, hash);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
private async loadMapInfoFromPath(mapPath: string): Promise<BsmLocalMap> {
|
||||
@@ -116,23 +127,25 @@ export class LocalMapsManagerService {
|
||||
}
|
||||
|
||||
const rawInfoString = await readFile(infoFile, { encoding: "utf-8" });
|
||||
const mapInfo = parseMapInfoDat(JSON.parse(rawInfoString));
|
||||
|
||||
const rawInfo: RawMapInfoData = JSON.parse(rawInfoString);
|
||||
const coverUrl = new URL(`file:///${path.join(mapPath, rawInfo._coverImageFilename)}`).href;
|
||||
const songUrl = new URL(`file:///${path.join(mapPath, rawInfo._songFilename)}`).href;
|
||||
const coverUrl = new URL(`file:///${path.join(mapPath, mapInfo.coverImageFilename)}`).href;
|
||||
const songUrl = new URL(`file:///${path.join(mapPath, mapInfo.songFilename)}`).href;
|
||||
|
||||
const hash = await this.computeMapHash(mapPath, rawInfoString);
|
||||
|
||||
return { rawInfo, coverUrl, songUrl, hash, path: mapPath };
|
||||
return { mapInfo, coverUrl, songUrl, hash, path: mapPath };
|
||||
}
|
||||
|
||||
private async downloadMapZip(zipUrl: string): Promise<{ zip: StreamZip.StreamZipAsync; zipPath: string }> {
|
||||
const fileName = `${path.basename(zipUrl, ".zip")}-${crypto.randomUUID()}.zip`;
|
||||
const tempPath = this.utils.getTempPath();
|
||||
await ensureFolderExist(this.utils.getTempPath());
|
||||
const dest = path.join(tempPath, fileName);
|
||||
|
||||
const zipPath = (await lastValueFrom(this.reqService.downloadFile(zipUrl, dest))).data;
|
||||
const zipPath = (await lastValueFrom(this.reqService.downloadFile(zipUrl, {
|
||||
destFolder: tempPath,
|
||||
filename: fileName
|
||||
}))).data;
|
||||
const zip = new StreamZip.async({ file: zipPath });
|
||||
|
||||
return { zip, zipPath };
|
||||
@@ -147,7 +160,7 @@ export class LocalMapsManagerService {
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public getMaps(version?: BSVersion): Observable<BsmLocalMapsProgress> {
|
||||
@@ -252,7 +265,7 @@ export class LocalMapsManagerService {
|
||||
}
|
||||
|
||||
const zipUrl = map.versions.at(0).downloadURL;
|
||||
const mapFolderName = sanitize(`${map.id}-${map.name}`);
|
||||
const mapFolderName = sanitize(`${map.id} (${map.metadata.songName} - ${map.metadata.levelAuthorName})`);
|
||||
const mapsFolder = await this.getMapsFolderPath(version);
|
||||
|
||||
const mapPath = path.join(mapsFolder, mapFolderName);
|
||||
@@ -261,7 +274,7 @@ export class LocalMapsManagerService {
|
||||
if(!exists){ return null; }
|
||||
return this.loadMapInfoFromPath(mapPath);
|
||||
}).catch(() => null);
|
||||
|
||||
|
||||
if(map.versions.every(version => version.hash === installedMap?.hash)) {
|
||||
return installedMap;
|
||||
}
|
||||
|
||||
@@ -87,12 +87,14 @@ export class LocalModelsManagerService {
|
||||
|
||||
(async () => {
|
||||
const modelFolder = await this.getModelFolderPath(model.type, version);
|
||||
const modelDest = path.join(modelFolder, sanitize(path.basename(model.download)));
|
||||
|
||||
const url = model.download.split("/");
|
||||
url[url.length - 1] = encodeURIComponent(url[url.length - 1]);
|
||||
|
||||
const download$ = this.request.downloadFile(url.join("/"), modelDest);
|
||||
const download$ = this.request.downloadFile(url.join("/"), {
|
||||
destFolder: modelFolder,
|
||||
filename: sanitize(path.basename(model.download))
|
||||
});
|
||||
|
||||
subs.push(download$.subscribe({ next: value => subscriber.next({ ...value, data: undefined }), error: e => subscriber.error(e) }));
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { BeatSaverService } from "../thrid-party/beat-saver/beat-saver.service";
|
||||
import { copy, copyFile, pathExists, realpath } from "fs-extra";
|
||||
import { Progression, ensureFolderExist, pathExist } from "../../helpers/fs.helpers";
|
||||
import { IpcService } from "../ipc.service";
|
||||
import sanitize from "sanitize-filename";
|
||||
|
||||
export class LocalPlaylistsManagerService {
|
||||
private static instance: LocalPlaylistsManagerService;
|
||||
@@ -71,15 +72,20 @@ export class LocalPlaylistsManagerService {
|
||||
private async installBPListFile(bslistSource: string, version: BSVersion): Promise<string> {
|
||||
const playlistFolder = await this.getPlaylistsFolder(version);
|
||||
const isLocalFile = await pathExists(bslistSource).catch(e => { log.error(e); return false; });
|
||||
const filename = isLocalFile ? path.basename(bslistSource) : new URL(bslistSource).pathname.split('/').pop()
|
||||
const filename = isLocalFile ? path.basename(bslistSource) : sanitize(new URL(bslistSource).pathname.split('/').pop())
|
||||
const destFile = path.join(playlistFolder, filename);
|
||||
|
||||
if (isLocalFile) {
|
||||
return copyFile(bslistSource, destFile).then(() => destFile);
|
||||
}
|
||||
return lastValueFrom(this.request.downloadFile(bslistSource, destFile)).then(res => res.data);
|
||||
|
||||
return lastValueFrom(this.request.downloadFile(bslistSource, {
|
||||
destFolder: playlistFolder,
|
||||
filename,
|
||||
preferContentDisposition: true,
|
||||
})).then(res => res.data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async readPlaylistFile(path: string): Promise<BPList> {
|
||||
if (!(await pathExist(path))) {
|
||||
@@ -102,8 +108,8 @@ export class LocalPlaylistsManagerService {
|
||||
|
||||
const bpListFilePath = await this.installBPListFile(bpListUrl, version);
|
||||
const bpList = await this.readPlaylistFile(bpListFilePath);
|
||||
|
||||
const progress: Progression<DownloadPlaylistProgressionData> = {
|
||||
|
||||
const progress: Progression<DownloadPlaylistProgressionData> = {
|
||||
total: bpList.songs.length,
|
||||
current: 0,
|
||||
data: {
|
||||
|
||||
@@ -44,7 +44,7 @@ export class BsSteamDownloaderService {
|
||||
return path.join(this.utils.getAssetsScriptsPath(), "depot-downloader", `DepotDownloader.${process.platform === 'linux' ? 'dll' : 'exe'}`);
|
||||
}
|
||||
|
||||
public async isDotNet6Installed(): Promise<boolean> {
|
||||
public async isDotNetInstalled(): Promise<boolean> {
|
||||
try {
|
||||
const proc = process.platform === 'linux'
|
||||
? spawnSync('dotnet', [this.getDepotDownloaderExePath()])
|
||||
@@ -55,7 +55,7 @@ export class BsSteamDownloaderService {
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
log.error("Error while checking .NET 6", e);
|
||||
log.error("Error while checking .NET 8", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { deleteFolder, ensureFolderExist, moveFolderContent, pathExist, unlinkPa
|
||||
import { lstat, symlink } from "fs/promises";
|
||||
import path from "path";
|
||||
import { copy, readlink } from "fs-extra";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { noop } from "../../shared/helpers/function.helpers";
|
||||
|
||||
export class FolderLinkerService {
|
||||
private static instance: FolderLinkerService;
|
||||
@@ -72,7 +74,7 @@ export class FolderLinkerService {
|
||||
await ensureFolderExist(folderPath);
|
||||
|
||||
if (options?.keepContents !== false) {
|
||||
await moveFolderContent(folderPath, sharedPath).toPromise();
|
||||
await lastValueFrom(moveFolderContent(folderPath, sharedPath, { overwrite: true }));
|
||||
}
|
||||
|
||||
await deleteFolder(folderPath);
|
||||
@@ -95,9 +97,7 @@ export class FolderLinkerService {
|
||||
}
|
||||
|
||||
if (options.moveContents === true) {
|
||||
return moveFolderContent(sharedPath, folderPath)
|
||||
.toPromise()
|
||||
.then(() => {});
|
||||
return lastValueFrom(moveFolderContent(sharedPath, folderPath, { overwrite: true })).then(noop);
|
||||
}
|
||||
|
||||
if (options?.keepContents === false) {
|
||||
|
||||
@@ -14,8 +14,8 @@ import { lastValueFrom } from "rxjs";
|
||||
import JSZip from "jszip";
|
||||
import { extractZip } from "../../helpers/zip.helpers";
|
||||
import recursiveReadDir from "recursive-readdir";
|
||||
import { minToMs } from "../../../shared/helpers/time.helpers";
|
||||
import { ensureDir } from "fs-extra";
|
||||
import { sToMs } from "../../../shared/helpers/time.helpers";
|
||||
import { ensureDir, pathExistsSync } from "fs-extra";
|
||||
|
||||
export class BsModsManagerService {
|
||||
private static instance: BsModsManagerService;
|
||||
@@ -71,7 +71,7 @@ export class BsModsManagerService {
|
||||
const bsPath = await this.bsLocalService.getVersionPath(version);
|
||||
const modsPath = path.join(bsPath, modsDir);
|
||||
|
||||
if (!(await pathExist(modsPath))) {
|
||||
if (!pathExistsSync(modsPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -90,19 +90,22 @@ export class BsModsManagerService {
|
||||
if (!mod) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (ext === ".manifest") {
|
||||
this.manifestMatches.push(mod);
|
||||
return undefined;
|
||||
}
|
||||
if (filePath.includes("Libs")) {
|
||||
if (!this.manifestMatches.some(m => m.name === mod.name)) {
|
||||
|
||||
if (filePath.toLowerCase().includes("libs")) {
|
||||
const manifestIndex = this.manifestMatches.findIndex(m => m.name === mod.name);
|
||||
|
||||
if (manifestIndex < 0) {
|
||||
return undefined;
|
||||
}
|
||||
const modIndex = this.manifestMatches.indexOf(mod);
|
||||
if (modIndex > -1) {
|
||||
this.manifestMatches.splice(modIndex, 1);
|
||||
}
|
||||
|
||||
this.manifestMatches.splice(manifestIndex, 1);
|
||||
}
|
||||
|
||||
return mod;
|
||||
});
|
||||
|
||||
@@ -155,9 +158,26 @@ export class BsModsManagerService {
|
||||
}
|
||||
|
||||
return new Promise<boolean>(resolve => {
|
||||
log.info("START IPA PROCESS", `start /wait /min "" "${ipaPath}" ${args.join(" ")}`);
|
||||
const processIPA = spawn(`start /wait /min "" "${ipaPath}" ${args.join(" ")}`, { cwd: versionPath, detached: true, shell: true });
|
||||
|
||||
log.info("START IPA PROCESS", `"${ipaPath}"`, args);
|
||||
const processIPA = spawn(`"${ipaPath}"`, args, { cwd: versionPath, detached: true, shell: true });
|
||||
|
||||
const timemout = setTimeout(() => {
|
||||
log.info("Ipa process timeout");
|
||||
resolve(false)
|
||||
}, sToMs(30));
|
||||
|
||||
processIPA.on("error", e => {
|
||||
log.error("Ipa process error", e);
|
||||
resolve(false);
|
||||
})
|
||||
|
||||
processIPA.stderr.on("data", data => {
|
||||
log.error("Ipa process stderr", data.toString());
|
||||
});
|
||||
|
||||
processIPA.once("exit", code => {
|
||||
clearTimeout(timemout);
|
||||
if (code === 0) {
|
||||
log.info("Ipa process exist with code 0");
|
||||
return resolve(true);
|
||||
@@ -166,10 +186,7 @@ export class BsModsManagerService {
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
log.info("Ipa process timeout");
|
||||
resolve(false)
|
||||
}, minToMs(1));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -230,7 +247,7 @@ export class BsModsManagerService {
|
||||
log.error("Error while extracting mod zip", e);
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
log.info("Mod zip extraction end", mod.name, "to", destDir, "success:", extracted);
|
||||
|
||||
const res = isBSIPA
|
||||
@@ -317,22 +334,26 @@ export class BsModsManagerService {
|
||||
this.manifestMatches = [];
|
||||
await this.beatModsApi.getAllMods();
|
||||
const bsipa = await this.getBsipaInstalled(version);
|
||||
return Promise.all([this.getModsInDir(version, ModsInstallFolder.PLUGINS_PENDING), this.getModsInDir(version, ModsInstallFolder.LIBS_PENDING), this.getModsInDir(version, ModsInstallFolder.PLUGINS), this.getModsInDir(version, ModsInstallFolder.LIBS)]).then(dirMods => {
|
||||
const modsDict = new Map<string, Mod>();
|
||||
|
||||
if (bsipa) {
|
||||
modsDict.set(bsipa.name, bsipa);
|
||||
const pluginsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.PLUGINS), this.getModsInDir(version, ModsInstallFolder.PLUGINS_PENDING)]);
|
||||
const libsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.LIBS), this.getModsInDir(version, ModsInstallFolder.LIBS_PENDING)]);
|
||||
|
||||
const dirMods = pluginsMods.flat().concat(libsMods.flat());
|
||||
|
||||
const modsDict = new Map<string, Mod>();
|
||||
|
||||
if (bsipa) {
|
||||
modsDict.set(bsipa.name, bsipa);
|
||||
}
|
||||
|
||||
for (const mod of dirMods.flat()) {
|
||||
if (modsDict.has(mod.name)) {
|
||||
continue;
|
||||
}
|
||||
modsDict.set(mod.name, mod);
|
||||
}
|
||||
|
||||
for (const mod of dirMods.flat()) {
|
||||
if (modsDict.has(mod.name)) {
|
||||
continue;
|
||||
}
|
||||
modsDict.set(mod.name, mod);
|
||||
}
|
||||
|
||||
return Array.from(modsDict.values());
|
||||
});
|
||||
return Array.from(modsDict.values());
|
||||
}
|
||||
|
||||
public async installMods(mods: Mod[], version: BSVersion): Promise<InstallModsResult> {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Agent, RequestOptions, get } from "https";
|
||||
import { createWriteStream, unlink } from "fs";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
import { createWriteStream } from "fs";
|
||||
import { Progression, unlinkPath } from "../helpers/fs.helpers";
|
||||
import { Observable, shareReplay, tap } from "rxjs";
|
||||
import log from "electron-log";
|
||||
import fetch, { RequestInfo, RequestInit } from "node-fetch";
|
||||
import { app } from "electron";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { tryit } from "../../shared/helpers/error.helpers";
|
||||
import sanitize from "sanitize-filename";
|
||||
|
||||
export class RequestService {
|
||||
private static instance: RequestService;
|
||||
@@ -56,22 +59,65 @@ export class RequestService {
|
||||
}
|
||||
}
|
||||
|
||||
public downloadFile(url: string, dest: string): Observable<Progression<string>> {
|
||||
private getFilenameFromContentDisposition(disposition: string): string | null {
|
||||
|
||||
if(!disposition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)/i;
|
||||
const asciiFilenameRegex = /^filename=(["']?)(.*?[^\\])\1(?:; ?|$)/i;
|
||||
|
||||
const utf8Match = utf8FilenameRegex.exec(disposition);
|
||||
if (utf8Match) {
|
||||
return decodeURIComponent(utf8Match[1]);
|
||||
}
|
||||
|
||||
const filenameStart = disposition.toLowerCase().indexOf('filename=');
|
||||
if (filenameStart >= 0) {
|
||||
const partialDisposition = disposition.slice(filenameStart);
|
||||
const asciiMatch = asciiFilenameRegex.exec(partialDisposition);
|
||||
if (asciiMatch && asciiMatch[2]) {
|
||||
return asciiMatch[2];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public downloadFile(url: string, opt: { destFolder: string, filename: string, preferContentDisposition?: boolean }): Observable<Progression<string>> {
|
||||
return new Observable<Progression<string>>(subscriber => {
|
||||
const progress: Progression<string> = { current: 0, total: 0 };
|
||||
|
||||
const file = createWriteStream(dest);
|
||||
|
||||
file.on("close", () => {
|
||||
progress.data = dest;
|
||||
subscriber.next(progress);
|
||||
subscriber.complete();
|
||||
});
|
||||
file.on("error", err => unlink(dest, () => subscriber.error(err)));
|
||||
|
||||
const req = get(url, this.requestOptionsFromDefaultInit(), res => {
|
||||
|
||||
const filePath = (() => {
|
||||
if(opt.preferContentDisposition && res.headers["content-disposition"]) {
|
||||
const filename = this.getFilenameFromContentDisposition(res.headers["content-disposition"]);
|
||||
return path.join(opt.destFolder, sanitize(filename || opt.filename));
|
||||
}
|
||||
return path.join(opt.destFolder, opt.filename);
|
||||
})();
|
||||
|
||||
const file = createWriteStream(filePath);
|
||||
|
||||
file.on("close", () => {
|
||||
subscriber.next(progress);
|
||||
subscriber.complete();
|
||||
});
|
||||
file.on("error", async err => {
|
||||
log.error(err);
|
||||
const res = await tryit(() => unlinkPath(filePath));
|
||||
if(res.error) {
|
||||
log.error(res.error);
|
||||
}
|
||||
});
|
||||
|
||||
progress.data = filePath;
|
||||
progress.total = parseInt(res.headers?.["content-length"] || "0", 10);
|
||||
|
||||
subscriber.next(progress);
|
||||
|
||||
res.on("data", chunk => {
|
||||
progress.current += chunk.length;
|
||||
subscriber.next(progress);
|
||||
@@ -83,7 +129,7 @@ export class RequestService {
|
||||
req.on("error", err => {
|
||||
subscriber.error(err);
|
||||
});
|
||||
}).pipe(tap({ error: e => log.error(e, url, dest) }), shareReplay(1));
|
||||
}).pipe(tap({ error: e => log.error(e, url) }), shareReplay(1));
|
||||
}
|
||||
|
||||
public downloadBuffer(url: string): Observable<Progression<Buffer>> {
|
||||
|
||||
@@ -49,6 +49,9 @@ export class BeatSaverApiService {
|
||||
...(filter.maxNps && { maxNps: String(filter.maxNps) }),
|
||||
};
|
||||
|
||||
delete params.enabledTags;
|
||||
delete params.excludedTags;
|
||||
|
||||
return new URLSearchParams(params);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getFoldersInFolder } from "../helpers/fs.helpers";
|
||||
import path from "path";
|
||||
import { VersionLinkerAction, VersionLinkFolderAction, VersionUnlinkFolderAction } from "renderer/services/version-folder-linker.service";
|
||||
import { VersionLinkerAction, VersionUnlinkFolderAction } from "renderer/services/version-folder-linker.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { LocalMapsManagerService } from "./additional-content/local-maps-manager.service";
|
||||
import { BSLocalVersionService } from "./bs-local-version.service";
|
||||
@@ -58,17 +58,14 @@ export class VersionFolderLinkerService {
|
||||
return path.join(parentPath, relativePath);
|
||||
}
|
||||
|
||||
public async linkVersionFolder(action: VersionLinkerAction): Promise<boolean> {
|
||||
public async linkVersionFolder(action: VersionLinkerAction): Promise<void> {
|
||||
action.options = this.specialFolderOption(action.relativeFolder, action.options);
|
||||
const versionPath = await this.localVersion.getVersionPath(action.version);
|
||||
const folderPath = this.relativeToFullPath(versionPath, action.relativeFolder);
|
||||
return this.folderLinker
|
||||
.linkFolder(folderPath, action.options)
|
||||
.catch(() => false)
|
||||
.then(() => true);
|
||||
return this.folderLinker.linkFolder(folderPath, action.options)
|
||||
}
|
||||
|
||||
public async unlinkVersionFolder(action: VersionUnlinkFolderAction): Promise<boolean> {
|
||||
public async unlinkVersionFolder(action: VersionUnlinkFolderAction): Promise<void> {
|
||||
action.options = this.specialFolderOption(action.relativeFolder, action.options);
|
||||
|
||||
const versionPath = await this.localVersion.getVersionPath(action.version);
|
||||
@@ -76,13 +73,10 @@ export class VersionFolderLinkerService {
|
||||
|
||||
action.options.moveContents = !(await this.isOtherVersionHaveFolderLinked(action.relativeFolder, folderPath));
|
||||
|
||||
return this.folderLinker
|
||||
.unlinkFolder(folderPath, action.options)
|
||||
.catch(() => false)
|
||||
.then(() => true);
|
||||
return this.folderLinker.unlinkFolder(folderPath, action.options);
|
||||
}
|
||||
|
||||
public async doAction(action: VersionLinkerAction): Promise<boolean> {
|
||||
public doAction(action: VersionLinkerAction): Promise<void> {
|
||||
if (action.type === "link") {
|
||||
return this.linkVersionFolder(action);
|
||||
}
|
||||
@@ -123,7 +117,7 @@ export class VersionFolderLinkerService {
|
||||
|
||||
for (const version of versions) {
|
||||
const linkedFolders = await this.getLinkedFolders(version, { relative: true, ignoreSymlinkTargetError: true });
|
||||
const actions = linkedFolders.map(folder => ({ type: "link", version, relativeFolder: folder } as VersionLinkFolderAction));
|
||||
const actions = linkedFolders.map(folder => ({ type: "link", version, relativeFolder: folder } as VersionLinkerAction));
|
||||
await Promise.all(actions.map(action => this.doAction(action)));
|
||||
}
|
||||
}
|
||||
|
||||
+11
-12
@@ -1,17 +1,16 @@
|
||||
/* eslint import/prefer-default-export: off, import/no-mutable-exports: off */
|
||||
import { URL } from "url";
|
||||
import { pathToFileURL, URL } from "url";
|
||||
import path from "path";
|
||||
|
||||
export let resolveHtmlPath: (htmlFileName: string) => string;
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const port = process.env.PORT || 1212;
|
||||
resolveHtmlPath = (htmlFileName: string) => {
|
||||
export function resolveHtmlPath (htmlFileName: string) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const port = process.env.PORT || 1212;
|
||||
const url = new URL(`http://localhost:${port}/${htmlFileName}`);
|
||||
return url.toString();
|
||||
};
|
||||
} else {
|
||||
resolveHtmlPath = (htmlFileName: string) => {
|
||||
return `file://${path.resolve(__dirname, "../renderer/", htmlFileName)}`;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const filePath = path.resolve(__dirname, "..", "renderer");
|
||||
return pathToFileURL(filePath).toString().concat("/", htmlFileName);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { MapFilter, MapSpecificity, MapStyle, MapTag, MapType } from "shared/models/maps/beat-saver.model";
|
||||
import { MapFilter, MapRequirement, MapSpecificity, MapStyle, MapTag, MapType } from "shared/models/maps/beat-saver.model";
|
||||
import { motion } from "framer-motion";
|
||||
import { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
import { MAP_TYPES } from "renderer/partials/maps/map-tags/map-types";
|
||||
import { MAP_STYLES } from "renderer/partials/maps/map-tags/map-styles";
|
||||
import { BsmCheckbox } from "../shared/bsm-checkbox.component";
|
||||
import { minToS } from "../../../shared/helpers/time.helpers";
|
||||
import dateFormat from "dateformat";
|
||||
import { BsmRange } from "../shared/bsm-range.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { MAP_SPECIFICITIES } from "renderer/partials/maps/map-general/map-specificity";
|
||||
import { MAP_REQUIREMENTS } from "renderer/partials/maps/map-requirements/map-requirements";
|
||||
import { MAP_DIFFICULTIES_COLORS } from "renderer/partials/maps/map-difficulties/map-difficulties-colors";
|
||||
import { MapExclude, MAP_EXCLUDES } from "renderer/partials/maps/map-excludes/map-excludes";
|
||||
import { BsmButton } from "../shared/bsm-button.component";
|
||||
import equal from "fast-deep-equal/es6";
|
||||
import clone from "rfdc";
|
||||
@@ -149,10 +144,6 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat
|
||||
return t(`maps.map-specificities.${specificity}`);
|
||||
};
|
||||
|
||||
const translateMapExclude = (exclude: MapExclude): string => {
|
||||
return t(`maps.map-excludes.${exclude}`);
|
||||
};
|
||||
|
||||
type BooleanKeys<T> = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T];
|
||||
|
||||
const handleCheckbox = (key: BooleanKeys<MapFilter>) => {
|
||||
@@ -182,14 +173,14 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat
|
||||
<div className="w-full h-full flex gap-x-2">
|
||||
<section className="shrink-0">
|
||||
<h2 className="mb-1 uppercase text-sm">{t("maps.map-filter-panel.specificities")}</h2>
|
||||
{MAP_SPECIFICITIES.map(specificity => (
|
||||
{Object.values(MapSpecificity).map(specificity => (
|
||||
<div key={specificity} className="flex justify-start items-center h-[22px] z-20 relative py-0.5 cursor-pointer" onClick={() => handleCheckbox(specificity)}>
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1" checked={filter?.[specificity]} onChange={() => handleCheckbox(specificity)} />
|
||||
<span className="grow capitalize">{translateMapSpecificity(specificity)}</span>
|
||||
</div>
|
||||
))}
|
||||
<h2 className="my-1 uppercase text-sm">{t("maps.map-filter-panel.requirements")}</h2>
|
||||
{MAP_REQUIREMENTS.map(requirement => (
|
||||
{Object.values(MapRequirement).map(requirement => (
|
||||
<div key={requirement} className="flex justify-start items-center h-[22px] z-20 relative py-0.5 cursor-pointer" onClick={() => handleCheckbox(requirement)}>
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1" checked={filter?.[requirement]} onChange={() => handleCheckbox(requirement)} />
|
||||
<span className="grow capitalize">{requirement}</span>
|
||||
@@ -198,26 +189,24 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat
|
||||
{ !localData && (
|
||||
<>
|
||||
<h2 className="my-1 uppercase text-sm">{t("maps.map-filter-panel.exclude")}</h2>
|
||||
{MAP_EXCLUDES.map(exclude => (
|
||||
<div key={exclude} className="flex justify-start items-center h-[22px] z-20 relative py-0.5 cursor-pointer" onClick={() => handleCheckbox(exclude)}>
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1" checked={filter?.[exclude]} onChange={() => handleCheckbox(exclude)} />
|
||||
<span className="grow capitalize">{translateMapExclude(exclude)}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex justify-start items-center h-[22px] z-20 relative py-0.5 cursor-pointer" onClick={() => handleCheckbox("installed")}>
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1" checked={filter?.installed} onChange={() => handleCheckbox("installed")} />
|
||||
<span className="grow capitalize">{t("maps.map-excludes.installed")}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
<section className="grow capitalize">
|
||||
<h2 className="uppercase text-sm mb-1">{t("maps.map-filter-panel.tags")}</h2>
|
||||
<div className="w-full flex flex-row flex-wrap items-start justify-start content-start gap-1 mb-2">
|
||||
{MAP_TYPES.map(tag => (
|
||||
{Object.values(MapType).map(tag => (
|
||||
<span key={tag} onClick={() => handleTagClick(tag)} className={`text-[12.5px] text-black rounded-md px-1 font-bold cursor-pointer ${!isTagActivated(tag) && "opacity-40 hover:opacity-90"}`} style={{ backgroundColor: isTagExcluded(tag) ? MAP_DIFFICULTIES_COLORS.Expert : MAP_DIFFICULTIES_COLORS.Normal }}>
|
||||
{translateMapType(tag)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-full flex flex-row flex-wrap items-start justify-start content-start gap-1">
|
||||
{MAP_STYLES.map(tag => (
|
||||
{Object.values(MapStyle).map(tag => (
|
||||
<span key={tag} onClick={() => handleTagClick(tag)} className={`text-[12.5px] text-black rounded-md px-1 font-bold cursor-pointer ${!isTagActivated(tag) && "opacity-40 hover:opacity-90"}`} style={{ backgroundColor: isTagExcluded(tag) ? MAP_DIFFICULTIES_COLORS.Expert : MAP_DIFFICULTIES_COLORS.Easy }}>
|
||||
{translateMapStyle(tag)}
|
||||
</span>
|
||||
|
||||
+1
-1
@@ -358,7 +358,7 @@ export const LocalMapsListPanel = forwardRef(({ version, className, filter, sear
|
||||
}
|
||||
|
||||
const searchCheck = (() => {
|
||||
return ((map.rawInfo?._songName ?? map.bsaverInfo?.name) || "")?.toLowerCase().includes(search.toLowerCase()) || ((map.rawInfo?._songAuthorName ?? map.bsaverInfo?.metadata?.songAuthorName) || "")?.toLowerCase().includes(search.toLowerCase()) || ((map.rawInfo?._levelAuthorName ?? map.bsaverInfo?.metadata?.levelAuthorName) || "")?.toLowerCase().includes(search.toLowerCase());
|
||||
return ((map.mapInfo?.songName ?? map.bsaverInfo?.name) || "")?.toLowerCase().includes(search.toLowerCase()) || ((map.mapInfo?.songAuthorName ?? map.bsaverInfo?.metadata?.songAuthorName) || "")?.toLowerCase().includes(search.toLowerCase()) || ((map.mapInfo?.levelMappers.at(0) ?? map.bsaverInfo?.metadata?.levelAuthorName) || "")?.toLowerCase().includes(search.toLowerCase());
|
||||
})();
|
||||
|
||||
if (!searchCheck) {
|
||||
|
||||
@@ -26,43 +26,40 @@ export const MapsRow = memo(({ maps, style, selectedMaps$, onMapSelect, onMapDel
|
||||
if (map.bsaverInfo?.versions[0]?.diffs) {
|
||||
map.bsaverInfo.versions[0].diffs.forEach(diff => {
|
||||
const arr = res.get(diff.characteristic) || [];
|
||||
const diffName = map.rawInfo._difficultyBeatmapSets.find(set => set._beatmapCharacteristicName === diff.characteristic)._difficultyBeatmaps.find(rawDiff => rawDiff._difficulty === diff.difficulty)?._customData?._difficultyLabel || diff.difficulty;
|
||||
const diffName = map.mapInfo.difficulties.find(set => set.characteristic === diff.characteristic && set.difficulty === diff.difficulty)?.difficultyLabel || diff.difficulty;
|
||||
arr.push({ name: diffName, type: diff.difficulty, stars: diff.stars });
|
||||
res.set(diff.characteristic, arr);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
map.rawInfo._difficultyBeatmapSets.forEach(set => {
|
||||
set._difficultyBeatmaps.forEach(diff => {
|
||||
const arr = res.get(set._beatmapCharacteristicName) || [];
|
||||
arr.push({ name: diff._customData?._difficultyLabel || diff._difficulty, type: diff._difficulty, stars: null });
|
||||
res.set(set._beatmapCharacteristicName, arr);
|
||||
});
|
||||
map.mapInfo.difficulties.forEach(diff => {
|
||||
const arr = res.get(diff.characteristic) || [];
|
||||
arr.push({ name: diff?.difficultyLabel || diff.difficulty, type: diff.difficulty, stars: null });
|
||||
});
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
const renderMapItem = (map: BsmLocalMap) => {
|
||||
return <MapItem
|
||||
key={map.hash}
|
||||
hash={map.hash}
|
||||
title={map.rawInfo._songName}
|
||||
coverUrl={map.coverUrl}
|
||||
songUrl={map.songUrl}
|
||||
autor={map.rawInfo._levelAuthorName}
|
||||
songAutor={map.rawInfo._songAuthorName}
|
||||
bpm={map.rawInfo._beatsPerMinute}
|
||||
duration={map.bsaverInfo?.metadata?.duration}
|
||||
selected={selectedMaps.some(selected => selected.hash === map.hash)}
|
||||
diffs={extractMapDiffs(map)} mapId={map.bsaverInfo?.id}
|
||||
ranked={map.bsaverInfo?.ranked}
|
||||
autorId={map.bsaverInfo?.uploader?.id}
|
||||
likes={map.bsaverInfo?.stats?.upvotes}
|
||||
createdAt={map.bsaverInfo?.createdAt}
|
||||
onDelete={onMapDelete}
|
||||
onSelected={onMapSelect}
|
||||
return <MapItem
|
||||
key={map.hash}
|
||||
hash={map.hash}
|
||||
title={map.mapInfo.songName}
|
||||
coverUrl={map.coverUrl}
|
||||
songUrl={map.songUrl}
|
||||
autor={map.mapInfo.levelMappers.at(0)}
|
||||
songAutor={map.mapInfo.songAuthorName}
|
||||
bpm={map.mapInfo.beatsPerMinute}
|
||||
duration={map.bsaverInfo?.metadata?.duration}
|
||||
selected={selectedMaps.some(selected => selected.hash === map.hash)}
|
||||
diffs={extractMapDiffs(map)} mapId={map.bsaverInfo?.id}
|
||||
ranked={map.bsaverInfo?.ranked}
|
||||
autorId={map.bsaverInfo?.uploader?.id}
|
||||
likes={map.bsaverInfo?.stats?.upvotes}
|
||||
createdAt={map.bsaverInfo?.createdAt}
|
||||
onDelete={onMapDelete}
|
||||
onSelected={onMapSelect}
|
||||
callBackParam={map}
|
||||
/>;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
|
||||
export const DeleteMapsModal: ModalComponent<void, { linked: boolean; maps: BsmLocalMap[] }> = ({ resolver, data: { linked, maps } }) => {
|
||||
|
||||
|
||||
const config = useService(ConfigurationService);
|
||||
|
||||
const t = useTranslation();
|
||||
@@ -34,7 +34,7 @@ export const DeleteMapsModal: ModalComponent<void, { linked: boolean; maps: BsmL
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(titleText)}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict} />
|
||||
<p className="max-w-sm w-full">{t(descText, multiple ? { nb: maps.length.toString() } : { name: maps.at(0).rawInfo._songName })}</p>
|
||||
<p className="max-w-sm w-full">{t(descText, multiple ? { nb: maps.length.toString() } : { name: maps.at(0).mapInfo.songName })}</p>
|
||||
{linked && (
|
||||
<p className="text-sm italic mt-2 cursor-help w-fit" title={t(infoTitleText)}>
|
||||
{t(infoText)}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { lt } from "semver";
|
||||
import { lastValueFrom, of, take } from "rxjs";
|
||||
import { FolderLinkState } from "renderer/services/version-folder-linker.service";
|
||||
import { BsContentTabItemProps } from "../shared/bs-content-tab-panel/bs-content-tab-item.component";
|
||||
@@ -74,20 +73,6 @@ export function ModelsPanel({ version, isActive, goToMods }: { version?: BSVersi
|
||||
});
|
||||
}, [isActive]);
|
||||
|
||||
useOnUpdate(() => {
|
||||
if (!isActive || !version || lt(version.BSVersion, "1.29.4")) {
|
||||
return;
|
||||
}
|
||||
if (config.get("not-remind-models-breaks")) {
|
||||
return;
|
||||
}
|
||||
notification.notifyInfo({ title: "models.notifications.prevent-for-models-breaks.title", desc: "models.notifications.prevent-for-models-breaks.desc", actions: [{ id: "0", title: "models.notifications.prevent-for-mods.not-remind", cancel: true }], duration: 12_000 }).then(res => {
|
||||
if (res === "0") {
|
||||
config.set("not-remind-models-breaks", true);
|
||||
}
|
||||
});
|
||||
}, [isActive, version]);
|
||||
|
||||
const exportModels = () => {
|
||||
const selectedModels = modelsGridRefs.map(ref => ref.current?.getSelectedModels() as BsmLocalModel[]).flat();
|
||||
modelsManager.exportModels(selectedModels, version);
|
||||
|
||||
@@ -109,7 +109,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
lastValueFrom(modsManager.getAvailableMods(version)),
|
||||
lastValueFrom(modsManager.getInstalledMods(version))
|
||||
]).then(([available, installed]) => {
|
||||
const defaultMods = configService.get<string[]>("default_mods" as DefaultConfigKey);
|
||||
const defaultMods = installed?.length ? [] : configService.get<string[]>("default_mods" as DefaultConfigKey);
|
||||
setModsAvailable(modsToCategoryMap(available));
|
||||
setModsSelected(available.filter(m => m.required || defaultMods.some(d => m.name.toLowerCase() === d.toLowerCase()) || installed.some(i => m.name === i.name)));
|
||||
setModsInstalled(modsToCategoryMap(installed));
|
||||
@@ -120,7 +120,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
const subs: Subscription[] = [];
|
||||
|
||||
if (isVisible && isOnline) {
|
||||
|
||||
|
||||
(async () => {
|
||||
if (configService.get<boolean>(ACCEPTED_DISCLAIMER_KEY)) {
|
||||
return true;
|
||||
@@ -138,7 +138,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
if (!canLoad) {
|
||||
return onDisclamerDecline?.();
|
||||
}
|
||||
|
||||
|
||||
loadMods();
|
||||
|
||||
subs.push(
|
||||
|
||||
@@ -8,7 +8,6 @@ export const defaultConfiguration: { [key in DefaultConfigKey]: any } = {
|
||||
"default-shared-folders": [
|
||||
window.electron.path.join("Beat Saber_Data", "CustomLevels"),
|
||||
window.electron.path.join("Beat Saber_Data", "CustomWIPLevels"),
|
||||
"DLC"
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export type MapExclude = "installed";
|
||||
|
||||
export const MAP_EXCLUDES: MapExclude[] = ["installed"];
|
||||
@@ -1,3 +0,0 @@
|
||||
import { MapSpecificity } from "shared/models/maps/beat-saver.model";
|
||||
|
||||
export const MAP_SPECIFICITIES: MapSpecificity[] = ["automapper", "ranked", "curated", "verified", "fullSpread"];
|
||||
@@ -1,3 +0,0 @@
|
||||
import { MapRequirement } from "shared/models/maps/beat-saver.model";
|
||||
|
||||
export const MAP_REQUIREMENTS: MapRequirement[] = ["chroma", "noodle", "me", "cinema"];
|
||||
@@ -1,3 +0,0 @@
|
||||
import { MapStyle } from "shared/models/maps/beat-saver.model";
|
||||
|
||||
export const MAP_STYLES: MapStyle[] = ["dance", "swing", "nightcore", "folk", "family", "ambient", "funk", "jazz", "soul", "speedcore", "punk", "rb", "holiday", "vocaloid", "jrock", "trance", "drumbass", "comedy", "instrumental", "hardcore", "kpop", "indie", "techno", "house", "game", "film", "alt", "dubstep", "metal", "anime", "hiphop", "jpop", "rock", "pop", "electronic", "classical-orchestral"];
|
||||
@@ -1,3 +0,0 @@
|
||||
import { MapType } from "shared/models/maps/beat-saver.model";
|
||||
|
||||
export const MAP_TYPES: MapType[] = ["accuracy", "balanced", "challenge", "dancestyle", "fitness", "speed", "tech"];
|
||||
@@ -45,8 +45,8 @@ export class SteamDownloaderService extends AbstractBsDownloaderService implemen
|
||||
this.linkOpener = LinkOpenerService.getInstance();
|
||||
}
|
||||
|
||||
public isDotNet6Installed(): Promise<boolean> {
|
||||
return lastValueFrom(this.ipcService.sendV2<boolean>("is-dotnet-6-installed"));
|
||||
public isDotNetInstalled(): Promise<boolean> {
|
||||
return lastValueFrom(this.ipcService.sendV2<boolean>("is-dotnet-installed"));
|
||||
}
|
||||
|
||||
private setSteamSession(username: string): void { localStorage.setItem(this.STEAM_SESSION_USERNAME_KEY, username); }
|
||||
@@ -63,7 +63,7 @@ export class SteamDownloaderService extends AbstractBsDownloaderService implemen
|
||||
});
|
||||
|
||||
if (choice === "0") {
|
||||
this.linkOpener.open("https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-6.0.12-windows-x64-installer");
|
||||
this.linkOpener.open("https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.10-windows-x64-installer");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ export class SteamDownloaderService extends AbstractBsDownloaderService implemen
|
||||
|
||||
const downloadPromise = (async () => {
|
||||
|
||||
const haveDotNet = await this.isDotNet6Installed().catch(() => false);
|
||||
const haveDotNet = await this.isDotNetInstalled().catch(() => false);
|
||||
if(!haveDotNet){
|
||||
this.showDotNetNotInstalledError();
|
||||
return Promise.reject(new Error("DotNet not installed"));
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { LinkOptions, UnlinkOptions } from "main/services/folder-linker.service";
|
||||
import { map, distinctUntilChanged, filter, mergeMap, shareReplay } from "rxjs/operators";
|
||||
import { BehaviorSubject, Observable, of } from "rxjs";
|
||||
import { BehaviorSubject, lastValueFrom, Observable, of } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
import equal from "fast-deep-equal";
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
import { NotificationService } from "./notification.service";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
|
||||
|
||||
|
||||
export class VersionFolderLinkerService {
|
||||
private static instance: VersionFolderLinkerService;
|
||||
@@ -16,8 +21,11 @@ export class VersionFolderLinkerService {
|
||||
return VersionFolderLinkerService.instance;
|
||||
}
|
||||
|
||||
private readonly KNOWN_ERROR_CODES = ["EPERM", "EACCES", "ENOSPC"];
|
||||
|
||||
private readonly ipcService: IpcService;
|
||||
private readonly progress: ProgressBarService;
|
||||
private readonly notifications: NotificationService;
|
||||
|
||||
private readonly _queue$ = new BehaviorSubject<VersionLinkerAction[]>([]);
|
||||
|
||||
@@ -27,6 +35,7 @@ export class VersionFolderLinkerService {
|
||||
private constructor() {
|
||||
this.ipcService = IpcService.getInstance();
|
||||
this.progress = ProgressBarService.getInstance();
|
||||
this.notifications = NotificationService.getInstance();
|
||||
|
||||
this.currentAction$.pipe(filter(action => !!action)).subscribe(action => this.processAction(action));
|
||||
}
|
||||
@@ -39,14 +48,22 @@ export class VersionFolderLinkerService {
|
||||
progressOpened = true;
|
||||
}
|
||||
|
||||
const linked = await this.doAction(action).toPromise();
|
||||
const { error } = await tryit(() => lastValueFrom(this.doAction(action)));
|
||||
|
||||
// Spécial notification
|
||||
if(error){
|
||||
const { code } = (error as CustomError);
|
||||
const message = this.KNOWN_ERROR_CODES.includes(code) ? `notifications.shared-folder.linking-error.msg.${code}` : "notifications.shared-folder.linking-error.msg.UNKNOWN_ERROR";
|
||||
this.notifications.notifyError({
|
||||
title: "notifications.shared-folder.linking-error.title",
|
||||
desc: message
|
||||
})
|
||||
}
|
||||
|
||||
// Special notification
|
||||
if (action.type === VersionLinkerActionType.Link) {
|
||||
this.linkListeners.forEach(listener => listener(action, linked));
|
||||
this.linkListeners.forEach(listener => listener(action, !error));
|
||||
} else {
|
||||
this.unlinkListeners.forEach(listener => listener(action, linked));
|
||||
this.unlinkListeners.forEach(listener => listener(action, !error));
|
||||
}
|
||||
|
||||
if (progressOpened) {
|
||||
@@ -136,7 +153,7 @@ export class VersionFolderLinkerService {
|
||||
if(currentAction && equal(currentAction.version, version) && currentAction.relativeFolder === relativeFolder) {
|
||||
return of(FolderLinkState.Processing)
|
||||
}
|
||||
|
||||
|
||||
if(queue.some(action => equal(action.version, version) && action.relativeFolder === relativeFolder)) {
|
||||
return of(FolderLinkState.Pending);
|
||||
}
|
||||
@@ -146,7 +163,7 @@ export class VersionFolderLinkerService {
|
||||
);
|
||||
}),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1)
|
||||
shareReplay(1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -179,14 +196,8 @@ export interface VersionLinkerAction {
|
||||
options?: LinkOptions;
|
||||
}
|
||||
|
||||
export interface VersionLinkFolderAction extends VersionLinkerAction {
|
||||
type: VersionLinkerActionType.Link;
|
||||
}
|
||||
|
||||
export interface VersionUnlinkFolderAction extends VersionLinkerAction {
|
||||
type: VersionLinkerActionType.Unlink;
|
||||
options?: UnlinkOptions;
|
||||
}
|
||||
export type VersionLinkFolderAction = Omit<VersionLinkerAction, "type">;
|
||||
export type VersionUnlinkFolderAction = Omit<VersionLinkerAction, "type"> & { options: UnlinkOptions };
|
||||
|
||||
export type VersionLinkerActionListener = (action: VersionLinkerAction, linked: boolean) => void;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export function isFunction(value: any): value is Function {
|
||||
return !!(value && value.constructor && value.call && value.apply)
|
||||
}
|
||||
|
||||
export function noop() {}
|
||||
|
||||
@@ -143,12 +143,72 @@ export interface BsvMapParitySummary {
|
||||
export type BsvMapCharacteristic = "Standard" | "OneSaber" | "NoArrows" | "90Degree" | "360Degree" | "Lightshow" | "Lawless";
|
||||
export type BsvMapDifficultyType = "Easy" | "Normal" | "Hard" | "Expert" | "ExpertPlus";
|
||||
|
||||
export type MapStyle = "dance" | "swing" | "nightcore" | "folk" | "family" | "ambient" | "funk" | "jazz" | "soul" | "speedcore" | "punk" | "rb" | "holiday" | "vocaloid" | "jrock" | "trance" | "drumbass" | "comedy" | "instrumental" | "hardcore" | "kpop" | "indie" | "techno" | "house" | "game" | "film" | "alt" | "dubstep" | "metal" | "anime" | "hiphop" | "jpop" | "rock" | "pop" | "electronic" | "classical-orchestral";
|
||||
export type MapType = "accuracy" | "balanced" | "challenge" | "dancestyle" | "fitness" | "speed" | "tech";
|
||||
export enum MapStyle {
|
||||
Alt = "alternative",
|
||||
Ambient = "ambient",
|
||||
Anime = "anime",
|
||||
ClassicalOrchestral = "classical-orchestral",
|
||||
Comedy = "comedy-meme",
|
||||
Dance = "dance",
|
||||
DrumBass = "drum-and-bass",
|
||||
Dubstep = "dubstep",
|
||||
Electronic = "electronic",
|
||||
Folk = "folk-acoustic",
|
||||
Funk = "funk-disco",
|
||||
Hardcore = "hardcore",
|
||||
Hiphop = "hip-hop-rap",
|
||||
Holiday = "holiday",
|
||||
House = "house",
|
||||
Indie = "indie",
|
||||
Instrumental = "instrumental",
|
||||
JPop = "j-pop",
|
||||
JRock = "j-rock",
|
||||
Jazz = "jazz",
|
||||
KPop = "k-pop",
|
||||
Family = "kids-family",
|
||||
Metal = "metal",
|
||||
Nightcore = "nightcore",
|
||||
Pop = "pop",
|
||||
Punk = "punk",
|
||||
Rb = "rb",
|
||||
Rock = "rock",
|
||||
Soul = "soul",
|
||||
Speedcore = "speedcore",
|
||||
Swing = "swing",
|
||||
Film = "tv-movie-soundtrack",
|
||||
Techno = "techno",
|
||||
Trance = "trance",
|
||||
Game = "video-game-soundtrack",
|
||||
Vocaloid = "vocaloid",
|
||||
}
|
||||
|
||||
export enum MapType {
|
||||
Accuracy = "accuracy",
|
||||
Balanced = "balanced",
|
||||
Challenge = "challenge",
|
||||
Dancestyle = "dance-style",
|
||||
Fitness = "fitness",
|
||||
Speed = "speed",
|
||||
Tech = "tech"
|
||||
}
|
||||
|
||||
export type MapTag = MapStyle | MapType;
|
||||
|
||||
export type MapRequirement = "chroma" | "noodle" | "me" | "cinema";
|
||||
export type MapSpecificity = "automapper" | "ranked" | "curated" | "verified" | "fullSpread";
|
||||
export enum MapRequirement {
|
||||
Chroma = "chroma",
|
||||
Noodle = "noodle",
|
||||
Me = "me",
|
||||
Cinema = "cinema"
|
||||
|
||||
}
|
||||
|
||||
export enum MapSpecificity {
|
||||
Automapper = "automapper",
|
||||
Ranked = "ranked",
|
||||
Curated = "curated",
|
||||
Verified = "verified",
|
||||
FullSpread = "fullSpread"
|
||||
}
|
||||
|
||||
export interface MapFilter {
|
||||
automapper?: boolean;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BsvMapDetail } from "./beat-saver.model";
|
||||
import { RawMapInfoData } from "./raw-map.model";
|
||||
import { MapInfo } from "./info/map-info.model";
|
||||
|
||||
export interface BsmLocalMap {
|
||||
hash: string;
|
||||
coverUrl: string;
|
||||
songUrl: string;
|
||||
rawInfo: RawMapInfoData;
|
||||
mapInfo: MapInfo;
|
||||
bsaverInfo?: BsvMapDetail;
|
||||
path: string;
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export { RawMapInfoData, RawMapDifficulty, RawDifficultySet } from "./raw-map.model";
|
||||
export { BsvInstant, BsvMapDetail, BsvMapDetailMetadata, BsvMapDifficulty, BsvMapParitySummary, BsvMapStats, BsvMapTestplay, BsvMapVersion, BsvUserDetail } from "./beat-saver.model";
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { BsvMapCharacteristic, BsvMapDifficultyType } from "../beat-saver.model";
|
||||
import { RawMapInfoDataV2 } from "./raw-map-info-v2.model";
|
||||
import { RawMapInfoDataV3 } from "./raw-map-info-v3.model";
|
||||
import { RawMapInfoDataV4 } from "./raw-map-info-v4.model";
|
||||
|
||||
export type AnyRawMapInfo = RawMapInfoDataV2 | RawMapInfoDataV3 | RawMapInfoDataV4;
|
||||
|
||||
// interfaces/mapInfo.ts
|
||||
export interface MapInfo {
|
||||
version: string;
|
||||
songName: string;
|
||||
songSubName?: string;
|
||||
songAuthorName: string;
|
||||
levelMappers: string[];
|
||||
levelLighters: string[];
|
||||
beatsPerMinute: number;
|
||||
shuffle?: number;
|
||||
shufflePeriod?: number;
|
||||
previewStartTime: number;
|
||||
previewDuration: number;
|
||||
songFilename: string;
|
||||
songPreviewFilename: string;
|
||||
coverImageFilename: string;
|
||||
environmentNames: string[];
|
||||
difficulties: MapDifficulty[];
|
||||
}
|
||||
|
||||
export interface MapDifficulty {
|
||||
characteristic: BsvMapCharacteristic;
|
||||
difficulty: BsvMapDifficultyType;
|
||||
difficultyLabel?: string;
|
||||
beatmapFilename: string;
|
||||
noteJumpMovementSpeed: number;
|
||||
noteJumpStartBeatOffset: number;
|
||||
beatmapColorSchemeIdx?: number;
|
||||
environmentNameIdx?: number;
|
||||
// Additional fields from version 4.0.0
|
||||
beatmapAuthors?: {
|
||||
mappers: string[];
|
||||
lighters: string[];
|
||||
};
|
||||
lightshowDataFilename?: string;
|
||||
}
|
||||
+14
-14
@@ -1,7 +1,7 @@
|
||||
import { BsvMapCharacteristic, BsvMapDifficultyType } from "./beat-saver.model";
|
||||
import { BsvMapCharacteristic, BsvMapDifficultyType } from "../beat-saver.model";
|
||||
|
||||
export interface RawMapInfoData<T = unknown> {
|
||||
_version: string;
|
||||
export interface RawMapInfoDataV2 {
|
||||
_version: `2.${number}.${number}`;
|
||||
_songName: string;
|
||||
_songSubName: string;
|
||||
_songAuthorName: string;
|
||||
@@ -16,24 +16,24 @@ export interface RawMapInfoData<T = unknown> {
|
||||
_environmentName: string;
|
||||
_allDirectionsEnvironmentName: string;
|
||||
_songTimeOffset: number;
|
||||
_customData: T;
|
||||
_difficultyBeatmapSets: RawDifficultySet[];
|
||||
_difficultyBeatmapSets: RawDifficultySetV2[];
|
||||
// Additional fields for 2.1.0
|
||||
_environmentNames?: string[];
|
||||
_colorSchemes?: unknown[];
|
||||
}
|
||||
|
||||
export interface RawDifficultySet {
|
||||
interface RawDifficultySetV2 {
|
||||
_beatmapCharacteristicName: BsvMapCharacteristic;
|
||||
_difficultyBeatmaps: RawMapDifficulty[];
|
||||
_difficultyBeatmaps: RawMapDifficultyV2[];
|
||||
}
|
||||
|
||||
export interface RawMapDifficulty {
|
||||
interface RawMapDifficultyV2 {
|
||||
_difficulty: BsvMapDifficultyType;
|
||||
_difficultyRank: string;
|
||||
_difficultyRank: number;
|
||||
_beatmapFilename: string;
|
||||
_noteJumpMovementSpeed: number;
|
||||
_noteJumpStartBeatOffset: number;
|
||||
_customData?: RawMapDifficultyCustomData;
|
||||
}
|
||||
|
||||
export interface RawMapDifficultyCustomData {
|
||||
_difficultyLabel?: string;
|
||||
_beatmapColorSchemeIdx?: number;
|
||||
_environmentNameIdx?: number;
|
||||
_customData?: { _difficultyLabel?: string; };
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { SongDetailDiffCharactertistic, SongDiffName } from "../song-details-cache/song-details-cache.model";
|
||||
|
||||
export interface RawMapInfoDataV3 {
|
||||
_version: `3.${number}.${number}`;
|
||||
_songName: string;
|
||||
_songSubName: string;
|
||||
_songAuthorName: string;
|
||||
_levelAuthorName: string;
|
||||
_beatsPerMinute: number;
|
||||
_songTimeOffset: number;
|
||||
_shuffle: number;
|
||||
_shufflePeriod: number;
|
||||
_previewStartTime: number;
|
||||
_previewDuration: number;
|
||||
_songFilename: string;
|
||||
_coverImageFilename: string;
|
||||
_environmentName: string;
|
||||
_environmentNames?: string[];
|
||||
_allDirectionsEnvironmentName: string;
|
||||
_difficultyBeatmapSets: RawDifficultySetV3[];
|
||||
}
|
||||
|
||||
interface RawDifficultySetV3 {
|
||||
_beatmapCharacteristicName: SongDetailDiffCharactertistic;
|
||||
_difficultyBeatmaps: RawMapDifficultyV3[];
|
||||
}
|
||||
|
||||
interface RawMapDifficultyV3 {
|
||||
_difficulty: SongDiffName;
|
||||
_difficultyRank: number;
|
||||
_beatmapFilename: string;
|
||||
_noteJumpMovementSpeed: number;
|
||||
_noteJumpStartBeatOffset: number;
|
||||
_beatmapColorSchemeIdx?: number;
|
||||
_environmentNameIdx?: number;
|
||||
_customData?: {
|
||||
_difficultyLabel?: string;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { BsvMapCharacteristic, BsvMapDifficultyType } from "../beat-saver.model";
|
||||
|
||||
// interfaces/version4.ts
|
||||
export interface RawMapInfoDataV4 {
|
||||
version: `4.${number}.${number}`;
|
||||
song: {
|
||||
title: string;
|
||||
subTitle: string;
|
||||
author: string;
|
||||
};
|
||||
audio: {
|
||||
songFilename: string;
|
||||
songDuration: number;
|
||||
audioDataFilename: string;
|
||||
bpm: number;
|
||||
lufs: number;
|
||||
previewStartTime: number;
|
||||
previewDuration: number;
|
||||
};
|
||||
songPreviewFilename: string;
|
||||
coverImageFilename: string;
|
||||
environmentNames: string[];
|
||||
colorSchemes: unknown[];
|
||||
difficultyBeatmaps: RawMapDifficultyV4[];
|
||||
}
|
||||
|
||||
interface RawMapDifficultyV4 {
|
||||
characteristic: BsvMapCharacteristic;
|
||||
difficulty: BsvMapDifficultyType;
|
||||
beatmapAuthors: {
|
||||
mappers: string[];
|
||||
lighters: string[];
|
||||
};
|
||||
environmentNameIdx: number;
|
||||
beatmapColorSchemeIdx: number;
|
||||
noteJumpMovementSpeed: number;
|
||||
noteJumpStartBeatOffset: number;
|
||||
beatmapDataFilename: string;
|
||||
lightshowDataFilename: string;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { MapDifficulty, MapInfo, AnyRawMapInfo } from "shared/models/maps/info/map-info.model";
|
||||
import { RawMapInfoDataV2 } from "shared/models/maps/info/raw-map-info-v2.model";
|
||||
import { RawMapInfoDataV3 } from "shared/models/maps/info/raw-map-info-v3.model";
|
||||
import { RawMapInfoDataV4 } from "shared/models/maps/info/raw-map-info-v4.model";
|
||||
|
||||
function parseVersion2(data: RawMapInfoDataV2): MapInfo {
|
||||
return {
|
||||
version: data._version,
|
||||
songName: data._songName,
|
||||
songSubName: data._songSubName,
|
||||
songAuthorName: data._songAuthorName,
|
||||
levelMappers: [data._levelAuthorName],
|
||||
levelLighters: [],
|
||||
beatsPerMinute: data._beatsPerMinute,
|
||||
shuffle: data._shuffle,
|
||||
shufflePeriod: data._shufflePeriod,
|
||||
previewStartTime: data._previewStartTime,
|
||||
previewDuration: data._previewDuration,
|
||||
songFilename: data._songFilename,
|
||||
songPreviewFilename: data._songFilename,
|
||||
coverImageFilename: data._coverImageFilename,
|
||||
environmentNames: data._environmentNames || [data._environmentName],
|
||||
difficulties: data._difficultyBeatmapSets.flatMap(set =>
|
||||
set._difficultyBeatmaps.map<MapDifficulty>((diff) => ({
|
||||
characteristic: set._beatmapCharacteristicName,
|
||||
difficulty: diff._difficulty,
|
||||
difficultyLabel: diff._customData?._difficultyLabel,
|
||||
beatmapFilename: diff._beatmapFilename,
|
||||
noteJumpMovementSpeed: diff._noteJumpMovementSpeed,
|
||||
noteJumpStartBeatOffset: diff._noteJumpStartBeatOffset,
|
||||
beatmapColorSchemeIdx: diff._beatmapColorSchemeIdx,
|
||||
environmentNameIdx: diff._environmentNameIdx,
|
||||
}))
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function parseVersion3(data: RawMapInfoDataV3): MapInfo {
|
||||
// It's the same treatment as the version 2
|
||||
return parseVersion2(data as unknown as RawMapInfoDataV2)
|
||||
}
|
||||
|
||||
function parseVersion4(data: RawMapInfoDataV4): MapInfo {
|
||||
return {
|
||||
version: data.version,
|
||||
songName: data.song.title,
|
||||
songSubName: data.song.subTitle,
|
||||
songAuthorName: data.song.author,
|
||||
levelMappers: data.difficultyBeatmaps.flatMap(diff => diff.beatmapAuthors.mappers),
|
||||
levelLighters: data.difficultyBeatmaps.flatMap(diff => diff.beatmapAuthors.lighters),
|
||||
beatsPerMinute: data.audio.bpm,
|
||||
previewStartTime: data.audio.previewStartTime,
|
||||
previewDuration: data.audio.previewDuration,
|
||||
songFilename: data.audio.songFilename,
|
||||
songPreviewFilename: data.songPreviewFilename,
|
||||
coverImageFilename: data.coverImageFilename,
|
||||
environmentNames: data.environmentNames,
|
||||
difficulties: data.difficultyBeatmaps.map<MapDifficulty>(diff => ({
|
||||
characteristic: diff.characteristic,
|
||||
difficulty: diff.difficulty,
|
||||
beatmapFilename: diff.beatmapDataFilename,
|
||||
noteJumpMovementSpeed: diff.noteJumpMovementSpeed,
|
||||
noteJumpStartBeatOffset: diff.noteJumpStartBeatOffset,
|
||||
beatmapColorSchemeIdx: diff.beatmapColorSchemeIdx,
|
||||
environmentNameIdx: diff.environmentNameIdx,
|
||||
beatmapAuthors: diff.beatmapAuthors,
|
||||
lightshowDataFilename: diff.lightshowDataFilename,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseMapInfoDat(info: AnyRawMapInfo): MapInfo | never {
|
||||
const version = (info as RawMapInfoDataV2)?._version || (info as RawMapInfoDataV3)?._version || (info as RawMapInfoDataV4)?.version;
|
||||
|
||||
if(!version) {
|
||||
throw new Error('Cannot determine info.dat version');
|
||||
}
|
||||
|
||||
if (version.startsWith('2.')) {
|
||||
return parseVersion2(info as RawMapInfoDataV2);
|
||||
}
|
||||
|
||||
if (version.startsWith('3.')) {
|
||||
return parseVersion3(info as RawMapInfoDataV3);
|
||||
}
|
||||
|
||||
if (version.startsWith('4.')) {
|
||||
return parseVersion4(info as RawMapInfoDataV4);
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported info.dat version: ${version}`);
|
||||
}
|
||||
Reference in New Issue
Block a user