Compare commits

...

240 commits

Author SHA1 Message Date
Nikolay Kuznetsov f905428238
Merge pull request #23 from DubyaDude/master
Move to api/v1 + additional QOL changes
2022-06-16 20:37:22 +02:00
Usman Shafiq b74c421bbb Added IsRetired (ApprovalStatus 3) 2022-06-15 18:15:32 -04:00
Usman Shafiq 71efc3990f Fixed download file name 2022-06-12 02:35:56 -04:00
Usman Shafiq d57e730baa Version bump 2022-06-12 00:57:50 -04:00
Usman Shafiq 2adb976b21 On Agree Removed Windows Notif + Move To Mods. Search bar enabled by default + made bigger 2022-06-12 00:22:23 -04:00
Usman Shafiq 59cd085130 Moved to api/v1 + Moved Broken to bottom 2022-06-12 00:04:03 -04:00
Nikolay Kuznetsov 992480facc Handle missing directories for deleting all mods 2021-07-16 01:59:08 +02:00
Nikolay Kuznetsov 8160521d10 Add a bit of exception handling to headpats/hugs buttons 2021-07-16 01:59:08 +02:00
Nikolay Kuznetsov a6321a9ecc Let SemVer handle four-digit version numbers 2021-07-16 01:59:08 +02:00
Nikolay Kuznetsov 88c987d714 Bump version again 2021-07-16 01:59:08 +02:00
Nikolay Kuznetsov 610098dcbf
Fix formatting 2021-07-15 22:45:05 +02:00
Nikolay Kuznetsov ec8cd210f7 Bump version 2021-07-15 19:43:24 +02:00
Nikolay Kuznetsov 66d2b41de8 Update default mods 2021-07-15 19:42:36 +02:00
Nikolay Kuznetsov ed27510ccc Update hardcoded categories for new mods 2021-07-15 19:42:15 +02:00
Nikolay Kuznetsov 0f60e68c02 Use latest ML installer instead of 0.3.0 2021-07-15 19:41:57 +02:00
Nikolay Kuznetsov f250bb893c Fix hyperlinks in mod info page 2021-05-29 22:02:39 +02:00
Nikolay Kuznetsov b1e2b3510b Don't ILRepack the debug build 2021-05-26 22:46:17 +02:00
Nikolay Kuznetsov 29ebdd82f9 Add mod author column to the mods list, add a few hardcoded author names 2021-05-26 22:46:04 +02:00
Nikolay Kuznetsov 6cc5510c5d Fix update check 2021-05-26 00:59:35 +02:00
Nikolay Kuznetsov e1b7320449 Use correct nuget restore 2021-05-26 00:45:04 +02:00
Nikolay Kuznetsov 8f93c766ac Bump version 2021-05-26 00:40:15 +02:00
Nikolay Kuznetsov 75a3cc8392 Make mods list a bit faster 2021-05-26 00:33:55 +02:00
Nikolay Kuznetsov ea59c178e6 Add more hardcoded categories for new mods 2021-05-26 00:33:28 +02:00
Nikolay Kuznetsov 9d453e7419 Fix mod list failing to load if mods directory has non-mod dlls 2021-05-17 23:16:09 +02:00
Nikolay Kuznetsov 3a9b80afdb Properly download stuff to memory 2021-05-17 23:15:55 +02:00
Nikolay Kuznetsov 4b69295fda Handle failing tasks that are not awaited 2021-05-17 23:15:38 +02:00
Nikolay Kuznetsov f755fc8b4a Add hardcoded categories for a few pending mods 2021-05-17 22:52:07 +02:00
Nikolay Kuznetsov c9b6c594cc Fix NRE on startup 2021-05-17 22:51:25 +02:00
Nikolay Kuznetsov 3cebca05b6 Add issue reporting warning to intro page 2021-05-17 22:51:16 +02:00
Nikolay Kuznetsov 5b4a6a9c1b Fork initial: VRCMelonManager based on ModAssistant 1.1.18 2021-05-17 22:27:27 +02:00
Vyolex 7788c2573c
Fix rate limit issues for playlist downloading (#326)
* Fix inconsistent use of UTC and local time, changed display to local time.
2021-04-14 23:09:55 +01:00
Jack Baron ab775ea496
update readme logo 2021-03-27 14:40:54 +00:00
Jack Baron 2d2477d19d
Updated version to 1.1.18 2021-03-27 11:39:01 +00:00
Jack Baron 37375b1026
Merge pull request #293 from Stevoisiak/oneclick-registry-description
Add description for OneClick registry keys
2021-03-27 11:33:11 +00:00
wgzeyu ee06f36ab9
Update zh.xaml (#311)
* Update zh.xaml

* Update zh.xaml
2021-03-27 11:26:10 +00:00
Robert Reichel 84c879c16f
add missing German translations (#312) 2021-03-27 11:26:01 +00:00
Jack Baron 2c32da0ed5
run CI in PRs 2021-03-27 11:24:05 +00:00
Jack Baron b9e58c0b8c
decode uri components in playlist filenames
closes #317
2021-03-22 06:50:15 +00:00
Assistant 6b4450156e
Merge pull request #314 from ErisApps/feature/name_consistency
Fixed inconsistent naming ModAssistant vs Mod Assistant
2021-03-16 07:37:36 -06:00
Eris 6d7e02aaa4 Fixed inconsistent naming ModAssistant vs Mod Assistant
Melopod ƸӜƷ — Today at 22:42
@Assistant hey, would you prefer it being ModAssistant or Mod Assistant
Space or no space

Assistant — Today at 22:43
Mod Assistant
2021-03-15 23:05:39 +01:00
Jack Baron 7a824f8230
Updated version to 1.1.17 2021-03-13 22:39:12 +00:00
Jack Baron 260441282a
cleanup redundant comments 2021-03-13 22:36:00 +00:00
Uncouth 5198383b2f
Update sv.xaml (#308) 2021-03-13 22:34:55 +00:00
larynai bc001f5852
Update ru.xaml (#303)
Minor updates of localization
2021-03-13 22:34:29 +00:00
Migush 3bf63e3122
Updated nl.xaml (#304)
* Update nl.xaml
2021-03-13 22:29:18 +00:00
Assistant 8fe1e5494e
Merge pull request #294 from Stevoisiak/read-only-language-var
Make availableLanguageCodes readonly
2021-01-24 14:17:09 -07:00
Stevoisiak 177820b954 Shift arguments by 3
Addresses https://github.com/Assistant/ModAssistant/pull/293#discussion_r560598751
2021-01-19 19:55:59 -05:00
Stevoisiak 02c30d5158 Make availableLanguageCodes readonly
Resolves IDE0044 style rule
2021-01-19 14:08:06 -05:00
Stevoisiak 140dde19fd Add description for OneClick registry keys
Adds a description to protocol handler registry keys when setting up OneClick install.
2021-01-19 12:34:54 -05:00
Jack Baron 3b9dc222d0
Merge pull request #289 from SieR-VR/master
Update ko.xaml
2021-01-07 07:04:16 +00:00
SieR c33baacc9e
Update ko.xaml
I'm stupid
2021-01-06 16:46:47 +09:00
Jack Baron 2c7018a9d1
Merge pull request #272 from simeng/feature/nb-translation
Norwegian (bokmål) translation
2020-12-23 04:17:59 +00:00
Kim Jaejun a298d226ec
Added missing translations for Korean (#281) 2020-12-23 04:17:23 +00:00
Jack Baron df1cd91358
Merge pull request #278 from ErisApps/feature/Added_missing_translations_for_Dutch_and_French
Added missing translations for Dutch and French
2020-11-26 09:24:08 +00:00
Eris d5a2884390 Added missing translations for Dutch and French 2020-11-26 00:02:10 +01:00
Simen Graaten 118352b5f3
Fixed some translation strings 2020-11-16 14:31:46 +00:00
Simen Graaten ed8170c5e0
Update ModAssistant.csproj 2020-11-16 14:31:46 +00:00
Simen Graaten c592e0ccbc
Add a norwegian (bokmål) translation. 2020-11-16 14:31:46 +00:00
Simen Graaten 71819281a5
Update Languages.cs 2020-11-16 14:31:25 +00:00
Jack Baron 89bdd100bb
format all files 2020-11-16 14:19:59 +00:00
Jack Baron 688857aea9
ignore unreachable code warnings on autoupdater 2020-11-16 14:11:01 +00:00
Jack Baron 5901ffb5de
use pattern matching and interpolation 2020-11-16 14:10:11 +00:00
Jack Baron d3ad20d082
remove seemingly unused param 2020-11-16 14:09:03 +00:00
Jack Baron 1884b7f9ec
disable naming warnings for JSON types 2020-11-16 14:08:18 +00:00
Jack Baron c10f9b88f6
use simple types 2020-11-16 14:06:24 +00:00
Jack Baron cbdc019b1c
simplify ratelimit init 2020-11-16 14:04:32 +00:00
Jack Baron 1c0f6dcaba
simplify object initialisers 2020-11-16 14:03:46 +00:00
Jack Baron d4753910f4
add readonly modifiers where appropriate 2020-11-16 14:03:01 +00:00
Jack Baron cfe66c84e1
simplify all 2020-11-16 14:02:28 +00:00
Jack Baron 2f0375bb86
fix unlocalised dialog title 2020-11-16 13:57:05 +00:00
Jack Baron 57822f3721
fixed incorrect language code for dutch 2020-11-16 13:37:59 +00:00
Jack Baron ac027753ad
add missing swedish localisations
thanks RangeValley#0237
2020-11-16 13:37:16 +00:00
Jack Baron 47602291a0
remove and sort usings 2020-11-16 13:22:30 +00:00
Jack Baron b5223f36a9
Merge pull request #266 from DomDom3333/master
Added check and Warning for Uninstalling BSIPA
2020-11-05 16:35:40 +00:00
Jack Baron 0ff1c5164c
add missing translations 2020-11-05 16:31:35 +00:00
Jack Baron be06ee7a99
localise bsipa uninstall fail box 2020-11-05 16:31:24 +00:00
Jack Baron 8659cbb7f7
improve readability 2020-11-05 16:25:56 +00:00
DomDom3333 7ba79ecd41 Did a stupid and forgot some brackets.
Just forgot some brackets and blindly commited before checking. Fixed now.
2020-10-27 21:58:13 +01:00
DomDom3333 5f814ab58e Added check and Warning for BSIPA
Simple check if IPA.exe and IPA Directory exists before trying to delete it.
2020-10-27 16:34:14 +01:00
Jack Baron 1c1332f1b5
Merge pull request #264 from ErisApps/feature/Renamed_#support_to_#pc-help
Renamed the #support channelname to #pc-help
2020-10-26 21:01:19 +00:00
Eris 0b702e8840 Renamed the #support channelname to #pc-help 2020-10-26 20:05:19 +01:00
Jack Baron 503aad4af2
Updated version to 1.1.16 2020-10-25 01:13:42 +00:00
Jack Baron 1c14aba0d6
Merge pull request #262 from wgzeyu/patch-23
Update Chinese localization
2020-10-25 01:12:52 +00:00
wgzeyu e4a6fe0335
Update Chinese localization 2020-10-25 08:55:14 +08:00
Jack Baron f41b7a7b23
Merge pull request #235 from wgzeyu/patch-22
Add chinese guide
2020-10-25 01:29:28 +01:00
Jack Baron b268d472bf
don't check for updates when running debug 2020-10-25 01:15:21 +01:00
Jack Baron ad62d5ecbd
Merge pull request #240 from TheJmJ/master
Issue #239: Quick hotfix for error that comes if not up-to-date
2020-10-25 01:13:01 +01:00
Jack Baron 223da16d7f
minor cleanup to alias resolution 2020-10-25 01:11:52 +01:00
Jack Baron e3726cbc63
Merge pull request #257 from Assistant/fix/spam-pats-hugs
[Fix] Spamming pat/hug buttons causes crashes
2020-10-22 19:14:53 +01:00
Jack Baron d9d9c9e5d0
only reenable pat/hug buttons when popups are open
closes #253
2020-10-22 19:12:35 +01:00
Jack Baron 69f3f0556a
Merge pull request #256 from ErisApps/feature/Added_revised_Swedish_translations
Added/revised Swedish translations
2020-10-22 16:53:30 +01:00
Eris bb5729ef9b Added/revised Swedish translations
All credits for those translations go to Davy#0013 ( as I don't speak nor understand a single word of what's written here 😅 )
2020-10-22 17:45:23 +02:00
Jack Baron 24918e6079
Merge pull request #209 from Parapass/patch-5
Check if Playlists folder exists before downloading
2020-10-22 15:36:58 +01:00
Jack Baron 8a05d0e4a0
only create playlists dir when needed 2020-10-22 15:34:34 +01:00
Jack Baron 3039f744a5
rewrite create function
CreateDirectory does nothing if the dir exists
2020-10-22 15:32:09 +01:00
Jack Baron 60893fd40c
Merge pull request #255 from Awagi/patch-3
🇫🇷 Update translation
2020-10-22 15:23:05 +01:00
Awagi ea5b271954
🇫🇷 Update translation 2020-10-22 12:18:50 +02:00
Jack Baron 271f649cb5
Merge pull request #251 from paulvandenburg/feature/nl-translation-fixes
Add missing NL translations, fix typos and suboptimal NL translations
2020-10-21 11:24:47 +01:00
Jack Baron 9eda65c5bc
Merge pull request #250 from mhombach/feature/issue-249-missing-german-translation
feat(249): added missing german translation
2020-10-21 11:24:39 +01:00
Paul van den Burg 9e92464ceb Add missing NL translations, fix typos and suboptimal NL translations 2020-10-21 00:41:23 +02:00
Jack Baron 4832649002
one day I'll get CI right 2020-10-20 21:29:07 +01:00
mhombach 3f9cf7c2cd feat(249): german translation
- added all missing translation
- changed "About:List:Header" because it sounded awkward in german (it's still not 100% but I did't want to change the whole structure of the sentence)
2020-10-20 22:28:00 +02:00
Jack Baron f105e5336f
Updated version to 1.1.15 2020-10-20 21:25:46 +01:00
Jack Baron 84382c48b2
properly resolve version for release 2020-10-20 21:24:53 +01:00
Jack Baron 7f94b2bf33
i left debug code in oops 2020-10-20 21:22:29 +01:00
Jack Baron 1b1c209df4
Updated version to 1.1.14 2020-10-20 20:52:53 +01:00
Jack Baron 5294455fdd
unfuck actions yml 2020-10-20 20:50:00 +01:00
Jack Baron 18c35bb70f
Merge pull request #245 from Assistant/feat/translation-stubs
Translation Stubs Tool
2020-10-20 20:46:15 +01:00
Jack Baron b0f9e8b6db
Merge pull request #237 from RedBrumbler/patch-6
Improved some phrases
2020-10-19 14:54:17 +01:00
Jack Baron db9c241d26
"Tik op hoofd" sounds like :owoHit:
Co-authored-by: Eris <erisapps@outlook.be>
2020-10-19 14:53:04 +01:00
Jack Baron 98181a50df
Merge pull request #246 from ErisApps/feature/Added_Swedish_translation_for_NoMods_key
Added Swedish translation for NoMods loc key
2020-10-19 14:41:46 +01:00
Eris f905e0d172 Added Swedish translation for NoMods loc key 2020-10-19 15:40:28 +02:00
Jack Baron a3ded0c9bf
Merge pull request #244 from ErisApps/feature/Added_translations_for_NoMods_key
Feature/added translations for no mods key
2020-10-19 14:29:38 +01:00
Jack Baron 4878bac01e
generate translation stubs 2020-10-19 14:28:20 +01:00
Eris def19229f5 Added Dutch translation for NoMods loc key 2020-10-19 15:27:17 +02:00
Eris e852ad3516 Added French translation for NoMods loc key 2020-10-19 15:26:57 +02:00
Jack Baron a0314af766
add translation stubs tool 2020-10-19 14:23:49 +01:00
Jack Baron 6651df67ee
oops forgot this 2020-10-19 14:19:02 +01:00
Jack Baron addf3e858f
optimise CI 2020-10-19 14:12:43 +01:00
Jack Baron ca87635473
Merge pull request #241 from PulseLane/master
Add informative text for versions with no updated mods
2020-10-19 14:05:47 +01:00
Jack Baron 272251f79b
move warning to center of mods panel 2020-10-19 14:01:57 +01:00
Jack Baron 5a36a2f4db
tweak wording and add stub translations 2020-10-19 13:35:41 +01:00
PulseLane b850b928db Add informative text for versions with no updated mods 2020-10-15 23:43:31 -07:00
Jere Tuohino 1c8ae4e604 Issue #239: Quick hotfix for error that comes if older version of BS is installed 2020-10-14 02:14:33 +03:00
RedBrumbler 5245c7838c
Improved some phrases 2020-09-19 00:02:38 +02:00
wgzeyu 10c90ef9ec
Add chinese guide 2020-09-13 18:00:54 +08:00
Assistant 215bc0f081
Merge pull request #220 from wgzeyu/patch-20
Update Chinese localization
2020-08-19 20:42:06 -06:00
Assistant 068647f8f5
Merge pull request #226 from DeadlyKitten/bugfix/desktop.ini
Add exclusion for desktop.ini to anti-piracy
2020-08-19 20:41:19 -06:00
Steven ff357cbc03 Add exclusion for desktop.ini to anti-piracy 2020-08-18 22:12:50 -07:00
wgzeyu 9f013d3af6
Update Chinese localization 2020-07-30 23:08:35 +08:00
Megalon 58de249f1d Create Playlists folder if it doesn't exist 2020-06-29 10:56:01 -07:00
Parapass c170d91d26
Check if Playlists folder exists before downloading
strings 3head
2020-06-29 08:29:59 -04:00
Assistant 2d53be9009 Updated version to 1.1.13 2020-06-24 00:48:22 -06:00
Assistant e10cd23a91
Merge pull request #205 from wgzeyu/patch-17
Update Chinese localization
2020-06-24 00:46:57 -06:00
Assistant 29322a4807 Removed delay from OCI close 2020-06-24 00:32:14 -06:00
wgzeyu d9594ff6d8
Update Chinese localization 2020-06-22 14:41:11 +08:00
Assistant c92da4c012 Improved OCI UX 2020-06-22 00:17:30 -06:00
Assistant 7ff93ec823 Added options for OCI window 2020-06-21 22:05:53 -06:00
Assistant eead2780e9
Merge pull request #194 from Rychard/try-catch-clipboard
Suppress exceptions when setting clipboard data
2020-06-21 18:58:08 -06:00
Assistant 57496ff873
Merge pull request #195 from RedBrumbler/patch-5
Update nl.xaml
2020-06-21 18:57:35 -06:00
Assistant e01cb6b968
Merge pull request #198 from larynai/patch-1
Update ru.xaml
2020-06-21 18:57:21 -06:00
Assistant 8ecba1e076
Merge pull request #201 from DrTexx/master
README.md typo fix
2020-06-21 18:57:09 -06:00
Assistant bc3b5b6748
Merge pull request #203 from wgzeyu/patch-15
Update Chinese localization
2020-06-21 18:56:42 -06:00
Assistant 9fefe75735 Fix property instancing order issue 2020-06-21 18:52:55 -06:00
wgzeyu 3a459a262a
Update Chinese localization 2020-06-21 02:04:56 +08:00
Denver P 05890fec66
README.md typo fix 2020-06-18 18:55:18 +10:00
larynai 7e17946202
Update ru.xaml 2020-06-16 16:05:46 +03:00
RedBrumbler 44e87bf3ec
Update nl.xaml 2020-06-12 12:41:05 +02:00
megalon adaca10ddb
Merge pull request #145 from UncouthMedia/patch-1
Create sv.xaml
2020-06-09 16:32:27 -07:00
Megalon 86977c68eb Add "sv" to available language codes 2020-06-09 16:27:13 -07:00
megalon 4fc3417360
Merge branch 'master' into patch-1 2020-06-09 16:23:22 -07:00
Joshua Shearer 8d8bf90088 Suppress exceptions when setting clipboard data 2020-06-09 09:29:49 -04:00
Assistant 6c2894e5d4 Updated version to 1.1.12 2020-06-08 21:59:07 -06:00
Assistant 2301eb3303
Merge pull request #192 from wgzeyu/patch-13
Add description for 围城 event
2020-06-08 21:55:57 -06:00
wgzeyu 98d1549fcb
Add description for 围城 event
“我们发现 围城 的网站未经作者同意擅自打包贩卖歌曲、模型,并抹黑社区中的成员。
请各位玩家引以为鉴,不要攻击其他玩家或支持这种行为,谢谢配合。”
2020-06-09 11:49:17 +08:00
Assistant 4cbf2c244f Updated version to 1.1.11 2020-06-08 21:00:03 -06:00
Assistant de39c64ddd
Merge pull request #179 from wgzeyu/patch-11
Update chinese localisation
2020-06-08 20:46:23 -06:00
Assistant 436593a509
Merge pull request #187 from RedBrumbler/patch-4
Update nl.xaml
2020-06-08 20:46:06 -06:00
Assistant 474c51d165
Merge pull request #185 from Awagi/patch-2
FR localisation update
2020-06-08 20:43:56 -06:00
megalon 3072ded757
Merge pull request #190 from megalon/feature/oneclick-menu
Move OneClick options into a menu
2020-06-08 19:43:15 -07:00
Megalon c54165ec72 why this 2020-06-08 19:42:20 -07:00
Megalon f78e26ac2f Add sv.xaml to project 2020-06-08 19:37:27 -07:00
Assistant 1fd55d9df6
Merge pull request #189 from Rychard/verify-resource-before-access
Don't attempt to access non-existent manifest resources
2020-06-08 20:29:45 -06:00
megalon 869802f59d
Merge pull request #182 from megalon/fix/text
Fix text not fitting inside buttons
2020-06-08 19:25:05 -07:00
Megalon 359d77a96b Remove "Close window" option 2020-06-08 19:16:57 -07:00
Megalon eb246cf7d0 Update margins 2020-06-08 19:15:02 -07:00
Megalon 8aa828739c Better styling for OneClick menu 2020-06-08 19:04:45 -07:00
Megalon 4e70f55bf8 Move OneClick buttons to menu 2020-06-08 18:38:13 -07:00
Joshua Shearer affa4aa6ff Don't attempt to access non-existent manifest resources 2020-06-05 10:20:17 -04:00
RedBrumbler cbc84472a7
Update nl.xaml 2020-06-05 12:54:50 +02:00
Awagi e91fc54a90
Update fr.xaml 2020-06-04 21:26:35 +02:00
Megalon fa9dec967c Fix hugs / headpats buttons 2020-06-02 16:41:04 -07:00
Megalon 2c7d6fc349 Scale side button text 2020-06-02 16:41:04 -07:00
Megalon e785ccea3e Fix size of Mod Info and Install buttons
- Removed extra stackpanel
- Fixed columns not rezising
- Added padding 
- Added min width
2020-06-02 16:41:04 -07:00
Megalon 2c2a44b3c3 Fix Intro button padding 2020-06-02 16:41:04 -07:00
Assistant b9319f9990 -e 2020-06-02 16:48:30 -06:00
Assistant 6327c197fb Fix'd localizations not loading correctly 2020-06-02 16:47:59 -06:00
wgzeyu a120caf001
Update zh.xaml 2020-06-02 11:51:50 +08:00
Assistant fe2fcfb7f9 Updated version to 1.1.10 2020-06-01 21:15:14 -06:00
megalon d1203ac255
Merge pull request #175 from megalon/feature/language-select
Language select
2020-06-01 19:52:01 -07:00
megalon 53dd2115ef
Merge pull request #168 from wgzeyu/patch-10
Update chinese localisation
2020-06-01 19:37:22 -07:00
megalon d96cb8bb95
Merge pull request #170 from larynai/master
russian localization small update
2020-06-01 19:36:57 -07:00
Megalon 11664afdf4 Label for language combobox 2020-06-01 19:28:50 -07:00
Megalon 3dc29753f1 Save selected language and load on startup 2020-06-01 19:28:50 -07:00
Megalon 97ef2c1285 Add LanguageCode setting 2020-06-01 19:28:50 -07:00
Megalon e3f8ab9696 Move language combobox 2020-06-01 19:28:50 -07:00
Megalon 79936c79f3 Show NativeName instead of EnglishName 2020-06-01 19:28:50 -07:00
Megalon ff327ecffc Language select and refactor 2020-06-01 19:28:50 -07:00
Megalon 3fe99bb5e2 Move LoadLanguage to Utils 2020-06-01 19:28:50 -07:00
Megalon 83760c6a0a Temp combobox 2020-06-01 19:28:50 -07:00
megalon 6495cab513
Merge pull request #178 from megalon/fix/not-closing
Force shutdown app when main window closed
2020-06-01 19:28:29 -07:00
Megalon ef197d1611 Shutdown app when main window closed 2020-06-01 19:25:35 -07:00
larynai a6b6c79515
Update ru.xaml 2020-05-30 18:32:42 +03:00
larynai c16568dfd2
Update ru.xaml 2020-05-30 18:29:13 +03:00
Uncouth 9319d0dbc0
Update sv.xaml
Basically finished? Need someone to take a look and see if I've mistaken any translatable parts for code. Unsure if to translate lines 11-13, might need some context.
2020-05-28 22:20:08 +02:00
Uncouth 28e1624b64
Update sv.xaml
Translated some more. Forgot about this to be honest.  Finishing it tomorrow.
2020-05-28 00:49:13 +02:00
Assistant 425d96f83d Remove accidental debug message 2020-05-24 17:51:00 -06:00
larynai b81e264709
russian localization update 2020-05-23 16:44:40 +03:00
wgzeyu 626881b730
Update zh.xaml 2020-05-21 01:26:06 +08:00
wgzeyu cf4d0c4c51
Update chinese localisation 2020-05-20 21:15:45 +08:00
Assistant 56d21f1432 Placeholder localizations 2020-05-19 19:17:00 -06:00
Assistant 145c350c14 improved filetype detection 2020-05-19 19:09:03 -06:00
Assistant 9622cef810 Fixed log uploading 2020-05-19 18:38:08 -06:00
Assistant 0576d293b8 Remove debugging message 2020-05-19 17:19:32 -06:00
Assistant c9bceb8a21 Fixes #165 2020-05-19 17:13:55 -06:00
Assistant ec03c8127b Added option to have OCI window close after its finished 2020-05-19 16:48:35 -06:00
Assistant dba0a7d951 Merge branch 'master' of https://github.com/Assistant/ModAssistant 2020-05-19 15:55:45 -06:00
Assistant d880ae4024 Added missing placeholders 2020-05-19 15:54:18 -06:00
Assistant 3a146cb8f4
Merge pull request #164 from mischiminator/master
Fixed typos
2020-05-19 15:41:12 -06:00
Kariko 932dd2f0f3
Fixed typos 2020-05-19 23:37:44 +02:00
Assistant 23135b0725
Merge pull request #163 from mischiminator/master
Updated German Translation
2020-05-19 15:28:57 -06:00
Kariko 86818d44b4
Update de.xaml
Added new translations
2020-05-19 23:18:25 +02:00
Kariko cf8cec9a65
Merge pull request #1 from Assistant/master
fork update
2020-05-19 23:14:20 +02:00
Assistant c59105b91c Updated version to 1.1.9 2020-05-19 12:13:45 -06:00
Assistant 16dd4ba72f
Merge pull request #162 from Assistant/OCI-overhaul
OCI overhaul
2020-05-19 11:59:00 -06:00
Assistant 05dc6b502a
Merge pull request #161 from Parapass/patch-4
Add the OneClick UI for Playlists
2020-05-19 11:57:05 -06:00
Assistant a2dd0f71b1 why did this not commit what the fuck 2020-05-19 11:48:25 -06:00
Assistant d353ce7f4b Big commits will make me hate myself eventually
but I'll hate myself anyways so lets go
2020-05-19 11:47:45 -06:00
Parapass ff2a63429c
do localization thing - part 2 2020-05-19 07:49:37 -04:00
Parapass 62abdfa0b5
do localization thing - part 1 2020-05-19 07:49:06 -04:00
Parapass 0ed8aafd99
Copy+paste mo-- I mean add the OneClick to Options CS File 2020-05-19 07:46:55 -04:00
Parapass 1d65e3ad13
Add the OneClick UI 2020-05-19 07:44:09 -04:00
Assistant 7d60f3f4a4 bug fixes 2020-05-19 02:10:44 -06:00
Assistant 80a9339227 made rate limits nullable 2020-05-18 19:10:54 -06:00
Assistant 9009ea4a87 added Russian localization 2020-05-18 19:08:59 -06:00
Assistant 1a55d70928 Some error checking 2020-05-18 19:08:21 -06:00
Assistant 4a71c5cfe1
Merge pull request #157 from larynai/master
Russian Localization | Update
2020-05-18 18:38:08 -06:00
Assistant 3bb30e57f3
Merge pull request #158 from claudiodemarzo/master
Italian localisation | updated
2020-05-18 18:36:51 -06:00
Assistant a556b56b75
Merge branch 'master' into master 2020-05-18 18:36:40 -06:00
Assistant e40a791beb
Merge pull request #156 from RedBrumbler/patch-2
fixed some typos
2020-05-18 18:35:04 -06:00
Assistant 2a8f2c3a47
Merge pull request #155 from Awagi/patch-1
French localisation update
2020-05-18 18:34:45 -06:00
Assistant 57c358b0c8
Merge pull request #153 from wgzeyu/patch-9
Update Chinese translation
2020-05-18 18:34:23 -06:00
Claudio De Marzo 290a6072e3
Update it.xaml
updated with new strings
2020-05-18 22:59:43 +02:00
larynai 0eae0730a1
Russian Localization | Update 2020-05-18 22:48:10 +03:00
RedBrumbler ed2249cd7a
fixed some typos 2020-05-18 20:52:42 +02:00
Awagi 4084f7b803
French localisation update
Translated 6 new strings
2020-05-18 20:01:46 +02:00
wgzeyu 9a6e0aa08c
Update zh.xaml 2020-05-19 00:34:10 +08:00
Assistant ff3b45e031 Updated version to 1.1.8 2020-05-18 10:25:32 -06:00
Assistant 70e8116611 Fixed playlist not being copied 2020-05-18 10:24:58 -06:00
UncouthMedia caeb2557ea
Create sv.xaml
Work in progress, about half-way done.
2020-05-13 16:07:33 +02:00
91 changed files with 2498 additions and 4755 deletions

View file

@ -14,3 +14,6 @@ dotnet_separate_import_directive_groups = false
[*.md]
trim_trailing_whitespace = false
[*.yml]
indent_size = 2

5
.github/FUNDING.yml vendored
View file

@ -1,4 +1 @@
patreon: BeatSaberMods
ko_fi: N4N8JX7B
liberapay: Assistant
custom: ['https://paypal.me/AssistantMoe', 'https://bs.assistant.moe/Donate/']
custom: ['https://github.com/Assistant/ModAssistant']

View file

@ -1,52 +1,44 @@
name: .NET Build
on: [push]
on:
push:
pull_request:
branches:
- master
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
# Set Path workaround for https://github.com/actions/virtual-environments/issues/263
- name: "Temp step to Set Path for Windows"
run: |
echo "::add-path::C:\Program Files\Git\mingw64\bin"
echo "::add-path::C:\Program Files\Git\usr\bin"
echo "::add-path::C:\Program Files\Git\bin"
- name: Setup MSBuild
uses: warrenbuckley/Setup-MSBuild@v1
- name: Install dependencies
run: msbuild -t:restore
- name: Checkout
uses: actions/checkout@v2
- name: Setup msbuild
uses: microsoft/setup-msbuild@v1.0.2
- name: Restore NuGet packages
working-directory: ${{env.GITHUB_WORKSPACE}}
run: nuget restore VRCMelonAssistant.sln
- name: Build project
run: msbuild ModAssistant/ModAssistant.csproj /t:Build /p:Configuration=Release
run: msbuild VRCMelonAssistant/VRCMelonAssistant.csproj /t:Build /p:Configuration=Release
- name: Cleanup release
shell: bash
run: |
find "ModAssistant/bin/Release" -type f ! -name "ModAssistant.exe" -delete
cp "LICENSE" "ModAssistant/bin/Release/LICENSE.ModAssistant.txt"
find "VRCMelonAssistant/bin/Release" -type f ! -name "VRCMelonAssistant.exe" -delete
cp "LICENSE" "VRCMelonAssistant/bin/Release/LICENSE.VRCMelonAssistant.txt"
- name: Upload Build
if: contains(github.ref, 'refs/tags/') == false
uses: actions/upload-artifact@v1
if: startsWith(github.ref, 'refs/tags/') == false
uses: actions/upload-artifact@v2
with:
name: ModAssistant-${{ github.sha }}
path: ./ModAssistant/bin/Release
- name: Create Release
if: contains(github.ref, 'refs/tags/') == true
id: create_release
uses: actions/create-release@v1.0.0
name: VRCMelonAssistant-${{ github.sha }}
path: ./VRCMelonAssistant/bin/Release/
- name: Extract Release Version
if: startsWith(github.ref, 'refs/tags/')
id: get_version
shell: bash
run: echo ::set-output name=version::${GITHUB_REF#refs/tags/v}
- name: Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Mod Assistant ${{ github.ref }}
draft: false
prerelease: false
- name: Upload Release Asset
if: contains(github.ref, 'refs/tags/') == true
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./ModAssistant/bin/Release/ModAssistant.exe
asset_name: ModAssistant.exe
asset_content_type: application/vnd.microsoft.portable-executable
name: VRChat Melon Assistant v${{ steps.get_version.outputs.version }}
files: ./VRCMelonAssistant/bin/Release/VRCMelonAssistant.exe

View file

@ -1,63 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="ModAssistant.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
<section name="ModAssistant.Settings1" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<userSettings>
<ModAssistant.Properties.Settings>
<setting name="InstallFolder" serializeAs="String">
<value />
</setting>
<setting name="StoreType" serializeAs="String">
<value />
</setting>
<setting name="SaveSelected" serializeAs="String">
<value>False</value>
</setting>
<setting name="CheckInstalled" serializeAs="String">
<value>True</value>
</setting>
<setting name="SavedMods" serializeAs="String">
<value>False</value>
</setting>
<setting name="Agreed" serializeAs="String">
<value>False</value>
</setting>
<setting name="SelectInstalled" serializeAs="String">
<value>False</value>
</setting>
<setting name="GameVersion" serializeAs="String">
<value />
</setting>
<setting name="AllGameVersions" serializeAs="String">
<value />
</setting>
<setting name="UpgradeRequired" serializeAs="String">
<value>True</value>
</setting>
<setting name="LastTab" serializeAs="String">
<value />
</setting>
<setting name="SelectedTheme" serializeAs="String">
<value />
</setting>
<setting name="ReinstallInstalled" serializeAs="String">
<value>True</value>
</setting>
</ModAssistant.Properties.Settings>
<ModAssistant.Settings1>
<setting name="InstallFolder" serializeAs="String">
<value />
</setting>
<setting name="StoreVariant" serializeAs="String">
<value />
</setting>
</ModAssistant.Settings1>
</userSettings>
</configuration>

View file

@ -1,288 +0,0 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Windows;
using static ModAssistant.Http;
namespace ModAssistant.API
{
public class BeatSaver
{
private const string BeatSaverURLPrefix = "https://beatsaver.com";
private static readonly string CustomSongsFolder = Path.Combine("Beat Saber_Data", "CustomLevels");
private const bool BypassDownloadCounter = false;
public static async Task<BeatSaverMap> GetFromKey(string Key, bool showNotification = true)
{
return await GetMap(Key, "key", showNotification);
}
public static async Task<BeatSaverMap> GetFromHash(string Hash, bool showNotification = true)
{
return await GetMap(Hash, "hash", showNotification);
}
private static async Task<BeatSaverMap> GetMap(string id, string type, bool showNotification)
{
string urlSegment;
switch (type)
{
case "hash":
urlSegment = "/api/maps/by-hash/";
break;
case "key":
urlSegment = "/api/maps/detail/";
break;
default:
return null;
}
BeatSaverMap map = new BeatSaverMap();
map.Success = false;
try
{
BeatSaverApiResponse beatsaver = await GetResponse(BeatSaverURLPrefix + urlSegment + id);
map.Name = await InstallMap(beatsaver.map, showNotification);
map.Success = true;
}
catch (Exception e)
{
ModAssistant.Utils.Log($"Failed downloading BeatSaver map: {id} | Error: {e}", "ERROR");
}
return map;
}
private static async Task<BeatSaverApiResponse> GetResponse(string url, bool showNotification = true)
{
BeatSaverApiResponse response = new BeatSaverApiResponse();
try
{
var resp = await HttpClient.GetAsync(url);
response.statusCode = resp.StatusCode;
response.ratelimit = GetRatelimit(resp.Headers);
if (response.statusCode == HttpStatusCode.OK)
{
if (response.ratelimit.IsSafe)
{
string body = await resp.Content.ReadAsStringAsync();
response.map = JsonSerializer.Deserialize<BeatSaverApiResponseMap>(body);
return response;
}
else
{
return response;
}
}
else
{
return response;
}
}
catch (Exception e)
{
if (showNotification)
{
MessageBox.Show($"{Application.Current.FindResource("OneClick:MapDownloadFailed")}\n\n" + e);
}
return null;
}
}
private static BeatSaverRatelimit GetRatelimit(HttpResponseHeaders headers)
{
BeatSaverRatelimit ratelimit = new BeatSaverRatelimit();
var _rateLimitRemaining = headers.GetValues("Rate-Limit-Remaining").GetEnumerator();
var _rateLimitReset = headers.GetValues("Rate-Limit-Reset").GetEnumerator();
var _rateLimitTotal = headers.GetValues("Rate-Limit-Total").GetEnumerator();
_rateLimitRemaining.MoveNext();
_rateLimitReset.MoveNext();
_rateLimitTotal.MoveNext();
ratelimit.Remaining = Int32.Parse(_rateLimitRemaining.Current);
ratelimit.Reset = Int32.Parse(_rateLimitReset.Current);
ratelimit.Total = Int32.Parse(_rateLimitTotal.Current);
ratelimit.ResetTime = UnixTimestampToDateTime((long)ratelimit.Reset);
return ratelimit;
}
public static DateTime UnixTimestampToDateTime(double unixTime)
{
DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond);
return new DateTime(unixStart.Ticks + unixTimeStampInTicks, System.DateTimeKind.Utc);
}
public static async Task<string> InstallMap(BeatSaverApiResponseMap Map, bool showNotification = true)
{
string zip = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, Map.hash) + ".zip";
string mapName = string.Concat(($"{Map.key} ({Map.metadata.songName} - {Map.metadata.levelAuthorName})")
.Split(ModAssistant.Utils.Constants.IllegalCharacters));
string directory = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, mapName);
#pragma warning disable CS0162 // Unreachable code detected
if (BypassDownloadCounter)
{
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.directDownload, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification);
}
else
{
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.downloadURL, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification);
}
#pragma warning restore CS0162 // Unreachable code detected
if (File.Exists(zip))
{
using (FileStream stream = new FileStream(zip, FileMode.Open))
using (ZipArchive archive = new ZipArchive(stream))
{
foreach (ZipArchiveEntry file in archive.Entries)
{
string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName));
if (!Directory.Exists(fileDirectory))
{
Directory.CreateDirectory(fileDirectory);
}
if (!string.IsNullOrEmpty(file.Name))
{
file.ExtractToFile(Path.Combine(directory, file.FullName), true);
}
}
}
File.Delete(zip);
}
else
{
if (showNotification)
{
string line1 = (string)Application.Current.FindResource("OneClick:SongDownload:Failed");
string line2 = (string)Application.Current.FindResource("OneClick:SongDownload:NetworkIssues");
string title = (string)Application.Current.FindResource("OneClick:SongDownload:FailedTitle");
MessageBox.Show($"{line1}\n{line2}", title);
}
return null;
}
return mapName;
}
public class BeatSaverMap
{
public BeatSaverApiResponse response { get; set; }
public bool Success { get; set; }
public string Name { get; set; }
}
public class BeatSaverApiResponse
{
public HttpStatusCode statusCode { get; set; }
public BeatSaverRatelimit ratelimit { get; set;}
public BeatSaverApiResponseMap map { get; set; }
}
public class BeatSaverRatelimit
{
public int Remaining { get; set; }
public int Total { get; set; }
public int Reset { get; set; }
public DateTime ResetTime { get; set; }
public bool IsSafe
{
get
{
if (Remaining > 3) return true;
else return false;
}
}
public async Task Wait()
{
await Task.Delay(new TimeSpan(ResetTime.Ticks - DateTime.Now.Ticks));
}
}
public class BeatSaverApiResponseMap
{
public Metadata metadata { get; set; }
public Stats stats { get; set; }
public string description { get; set; }
public DateTime? deletedAt { get; set; }
public string _id { get; set; }
public string key { get; set; }
public string name { get; set; }
public Uploader uploader { get; set; }
public DateTime uploaded { get; set; }
public string hash { get; set; }
public string directDownload { get; set; }
public string downloadURL { get; set; }
public string coverURL { get; set; }
public class Difficulties
{
public bool easy { get; set; }
public bool normal { get; set; }
public bool hard { get; set; }
public bool expert { get; set; }
public bool expertPlus { get; set; }
}
public class Metadata
{
public Difficulties difficulties { get; set; }
public Characteristic[] characteristics { get; set; }
public double duration { get; set; }
public string songName { get; set; }
public string songSubName { get; set; }
public string songAuthorName { get; set; }
public string levelAuthorName { get; set; }
public double bpm { get; set; }
}
public class Characteristic
{
public string name { get; set; }
public CharacteristicDifficulties difficulties { get; set; }
}
public class CharacteristicDifficulties
{
public Difficulty easy { get; set; }
public Difficulty normal { get; set; }
public Difficulty hard { get; set; }
public Difficulty expert { get; set; }
public Difficulty expertPlus { get; set; }
}
public class Difficulty
{
public double? duration { get; set; }
public double? length { get; set; }
public double bombs { get; set; }
public double notes { get; set; }
public double obstacles { get; set; }
public double njs { get; set; }
public double njsOffset { get; set; }
}
public class Stats
{
public int downloads { get; set; }
public int plays { get; set; }
public int downVotes { get; set; }
public int upVotes { get; set; }
public double heat { get; set; }
public double rating { get; set; }
}
public class Uploader
{
public string _id { get; set; }
public string username { get; set; }
}
}
}
}

View file

@ -1,34 +0,0 @@
using System;
using System.Threading.Tasks;
namespace ModAssistant.API
{
class ModelSaber
{
private const string ModelSaberURLPrefix = "https://modelsaber.com/files/";
private const string CustomAvatarsFolder = "CustomAvatars";
private const string CustomSabersFolder = "CustomSabers";
private const string CustomPlatformsFolder = "CustomPlatforms";
private const string CustomBloqsFolder = "CustomNotes";
public static async Task GetModel(Uri uri)
{
switch (uri.Host)
{
case "avatar":
await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomAvatarsFolder);
break;
case "saber":
await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomSabersFolder);
break;
case "platform":
await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomPlatformsFolder);
break;
case "bloq":
await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomBloqsFolder);
break;
}
}
}
}

View file

@ -1,120 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static ModAssistant.Http;
using System.Windows;
namespace ModAssistant.API
{
public class Playlists
{
private const string BSaberURLPrefix = "https://bsaber.com/PlaylistAPI/";
private const string PlaylistsFolder = "Playlists";
private static readonly string BeatSaberPath = Utils.BeatSaberPath;
public static async Task DownloadAll(Uri uri)
{
switch (uri.Host)
{
case "playlist":
Uri url = new Uri($"{uri.LocalPath.Trim('/')}");
string filename = await Get(url);
await DownloadFrom(filename);
break;
}
}
public static async Task<string> Get(Uri url)
{
string filename = url.Segments.Last();
string absolutePath = Path.Combine(BeatSaberPath, PlaylistsFolder, filename);
try
{
await Utils.DownloadAsset(url.ToString(), PlaylistsFolder, filename);
return absolutePath;
}
catch
{
return null;
}
}
public static async Task DownloadFrom(string file, bool gui = false, System.Windows.Controls.ProgressBar progress = null)
{
int Errors = 0;
int Minimum = 0;
int Value = 0;
if (progress != null)
{
progress.Minimum = 0;
progress.Maximum = 1;
progress.Value = 0;
}
Playlist playlist = JsonSerializer.Deserialize<Playlist>(File.ReadAllText(file));
int Maximum = playlist.songs.Length;
if (progress != null) progress.Maximum = playlist.songs.Length;
foreach (Playlist.Song song in playlist.songs)
{
API.BeatSaver.BeatSaverMap response = new BeatSaver.BeatSaverMap();
if (!string.IsNullOrEmpty(song.hash))
{
response = await BeatSaver.GetFromHash(song.hash, false);
}
else if (!string.IsNullOrEmpty(song.key))
{
response = await BeatSaver.GetFromKey(song.key, false);
}
Value++;
if (progress != null) progress.Value++;
if (gui)
{
if (response.Success)
{
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:InstallingPlaylist"), TextProgress(Minimum, Maximum, Value))}";
}
else
{
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:FailedPlaylistSong"), song.songName)}";
ModAssistant.Utils.Log($"Failed installing BeatSaver map: {song.songName} | {song.key} | {song.hash}");
await Task.Delay(3 * 1000);
Errors++;
}
}
}
if (gui)
{
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:FinishedPlaylist"), Errors, playlist.playlistTitle)}";
}
}
private static string TextProgress(int min, int max, int value)
{
if (max == value)
{
return $" {string.Concat(Enumerable.Repeat("", 10))} [{value}/{max}]";
}
int interval = (int)Math.Floor((double)value / ( ((double)max - (double)min ) / (double)10));
return $" {string.Concat(Enumerable.Repeat("", interval))}{string.Concat(Enumerable.Repeat("", 10 - interval))} [{value}/{max}]";
}
class Playlist
{
public string playlistTitle { get; set; }
public string playlistAuthor { get; set; }
public string image { get; set; }
public Song[] songs { get; set; }
public class Song
{
public string key { get; set; }
public string hash { get; set; }
public string songName { get; set; }
public string uploader { get; set; }
}
}
}
}

View file

@ -1,59 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
namespace ModAssistant.API
{
public class Utils
{
public static readonly string BeatSaberPath = App.BeatSaberInstallDirectory;
public static async Task DownloadAsset(string link, string folder, bool showNotifcation, string fileName = null)
{
await DownloadAsset(link, folder, fileName, null, showNotifcation);
}
public static async Task DownloadAsset(string link, string folder, string fileName = null, string displayName = null)
{
await DownloadAsset(link, folder, fileName, displayName, true);
}
public static async Task DownloadAsset(string link, string folder, string fileName, string displayName, bool showNotification)
{
if (string.IsNullOrEmpty(BeatSaberPath))
{
ModAssistant.Utils.SendNotify((string)Application.Current.FindResource("OneClick:InstallDirNotFound"));
}
try
{
Directory.CreateDirectory(Path.Combine(BeatSaberPath, folder));
if (string.IsNullOrEmpty(fileName))
{
fileName = WebUtility.UrlDecode(Path.Combine(BeatSaberPath, folder, new Uri(link).Segments.Last()));
}
else
{
fileName = WebUtility.UrlDecode(Path.Combine(BeatSaberPath, folder, fileName));
}
if (string.IsNullOrEmpty(displayName))
{
displayName = Path.GetFileNameWithoutExtension(fileName);
}
await ModAssistant.Utils.Download(link, fileName);
if (showNotification)
{
ModAssistant.Utils.SendNotify(string.Format((string)Application.Current.FindResource("OneClick:InstalledAsset"), displayName));
}
}
catch
{
ModAssistant.Utils.SendNotify((string)Application.Current.FindResource("OneClick:AssetInstallFailed"));
}
}
}
}

View file

@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using ModAssistant.Pages;
namespace ModAssistant
{
public class Mod
{
public string name;
public string version;
public string gameVersion;
public string _id;
public string status;
public string authorId;
public string uploadedDate;
public string updatedDate;
public Author author;
public string description;
public string link;
public string category;
public DownloadLink[] downloads;
public bool required;
public Dependency[] dependencies;
public List<Mod> Dependents = new List<Mod>();
public Mods.ModListItem ListItem;
public class Author
{
public string _id;
public string username;
public string lastLogin;
}
public class DownloadLink
{
public string type;
public string url;
public FileHashes[] hashMd5;
}
public class FileHashes
{
public string hash;
public string file;
}
public class Dependency
{
public string name;
public string _id;
public Mod Mod;
}
}
}

View file

@ -1,134 +0,0 @@
using Microsoft.Win32;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace ModAssistant
{
class OneClickInstaller
{
private static readonly string[] Protocols = new[] { "modelsaber", "beatsaver", "bsplaylist" };
public static async Task InstallAsset(string link)
{
Uri uri = new Uri(link);
if (!Protocols.Contains(uri.Scheme)) return;
switch (uri.Scheme)
{
case "modelsaber":
await ModelSaber(uri);
break;
case "beatsaver":
await BeatSaver(uri);
break;
case "bsplaylist":
await Playlist(uri);
break;
}
}
private static async Task BeatSaver(Uri uri)
{
string Key = uri.Host;
await API.BeatSaver.GetFromKey(Key);
}
private static async Task ModelSaber(Uri uri)
{
await API.ModelSaber.GetModel(uri);
}
private static async Task Playlist(Uri uri)
{
await API.Playlists.DownloadAll(uri);
}
public static void Register(string Protocol, bool Background = false)
{
if (IsRegistered(Protocol) == true)
return;
try
{
if (Utils.IsAdmin)
{
RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol, true);
if (ProtocolKey == null)
ProtocolKey = Registry.ClassesRoot.CreateSubKey(Protocol, true);
RegistryKey CommandKey = ProtocolKey.CreateSubKey(@"shell\open\command", true);
if (CommandKey == null)
CommandKey = Registry.ClassesRoot.CreateSubKey(@"shell\open\command", true);
if (ProtocolKey.GetValue("OneClick-Provider", "").ToString() != "ModAssistant")
{
ProtocolKey.SetValue("URL Protocol", "", RegistryValueKind.String);
ProtocolKey.SetValue("OneClick-Provider", "ModAssistant", RegistryValueKind.String);
CommandKey.SetValue("", $"\"{Utils.ExePath}\" \"--install\" \"%1\"");
}
Utils.SendNotify(string.Format((string)Application.Current.FindResource("OneClick:ProtocolHandler:Registered"), Protocol));
}
else
{
Utils.StartAsAdmin($"\"--register\" \"{Protocol}\"");
}
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
if (Background)
Application.Current.Shutdown();
else
Pages.Options.Instance.UpdateHandlerStatus();
}
public static void Unregister(string Protocol, bool Background = false)
{
if (IsRegistered(Protocol) == false)
return;
try
{
if (Utils.IsAdmin)
{
using (RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol, true))
{
if (ProtocolKey != null
&& ProtocolKey.GetValue("OneClick-Provider", "").ToString() == "ModAssistant")
{
Registry.ClassesRoot.DeleteSubKeyTree(Protocol);
}
}
Utils.SendNotify(string.Format((string)Application.Current.FindResource("OneClick:ProtocolHandler:Unregistered"), Protocol));
}
else
{
Utils.StartAsAdmin($"\"--unregister\" \"{Protocol}\"");
}
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
if (Background)
Application.Current.Shutdown();
else
Pages.Options.Instance.UpdateHandlerStatus();
}
public static bool IsRegistered(string Protocol)
{
RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol);
if (ProtocolKey != null
&& ProtocolKey.GetValue("OneClick-Provider", "").ToString() == "ModAssistant")
return true;
else
return false;
}
}
}

View file

@ -1,238 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModAssistant"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ResourceDictionaryName">i18n:en-US</sys:String>
<!-- App -->
<sys:String x:Key="App:InstallDirDialog:Title">Der Beat Saber Installationsordner konnte nicht gefunden werden!</sys:String>
<sys:String x:Key="App:InstallDirDialog:OkCancel">Drücke OK um es erneut zu versuchen, oder Abbrechen um das Programm zu beenden.</sys:String>
<sys:String x:Key="App:InvalidArgument">Ungültiges Argument! '{0}' benötigt eine Option.</sys:String>
<sys:String x:Key="App:UnrecognizedArgument">Unbekanntes Argument. Beende Mod Assistant.</sys:String>
<sys:String x:Key="App:UnhandledException">Eine nicht behandelte Ausnahme ist aufgetreten</sys:String>
<sys:String x:Key="App:Exception">Ausnahme</sys:String>
<!-- Main Window -->
<sys:String x:Key="MainWindow:WindowTitle">ModAssistant</sys:String>
<sys:String x:Key="MainWindow:IntroButton">Intro</sys:String>
<sys:String x:Key="MainWindow:ModsButton">Mods</sys:String>
<sys:String x:Key="MainWindow:AboutButton">Über</sys:String>
<sys:String x:Key="MainWindow:OptionsButton">Optionen</sys:String>
<sys:String x:Key="MainWindow:GameVersionLabel">Spiel Version</sys:String>
<sys:String x:Key="MainWindow:VersionLabel">Version</sys:String>
<sys:String x:Key="MainWindow:ModInfoButton">Mod Info</sys:String>
<sys:String x:Key="MainWindow:InstallButtonTop">Installieren/</sys:String>
<sys:String x:Key="MainWindow:InstallButtonBottom">Aktualisieren</sys:String>
<sys:String x:Key="MainWindow:GameVersionLoadFailed">Spielversion konnte nicht geladen werden, der Mods Tab wird nicht verfügbar sein.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Title">Neue Spielversion gefunden!</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line1">Es scheint ein Spiel Update gegeben zu haben.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line2">Bitte prüfe ob unten links die richtige Version ausgewählt ist!</sys:String>
<sys:String x:Key="MainWindow:NoModSelected">Kein Mod ausgewählt!</sys:String>
<sys:String x:Key="MainWindow:NoModInfoPage">{0} hat keine Informationsseite.</sys:String>
<!-- Intro Page -->
<sys:String x:Key="Intro:Title">Intro</sys:String>
<sys:String x:Key="Intro:PageTitle">Willkommen bei Mod Assistant</sys:String>
<sys:String x:Key="Intro:Terms:Header">Bitte lies diese Seite vollständig und aufmerksam!</sys:String>
<Span x:Key="Intro:Terms:Line1">
Durch Nutzung des Programms wird bestätigt, dass folgende Bedinungen gelesen und akzeptiert wurden:
</Span>
<Span x:Key="Intro:Terms:Line2">
Beat Saber
unterstützt normalerweise <Bold>keine</Bold> Mods. Das heißt:
</Span>
<Span x:Key="Intro:Terms:Term1">
Mods
werden nach jedem Update <Bold>nicht mehr funktionieren</Bold>. Dies ist normal, und
die Schuld liegt <Bold>nicht</Bold> bei Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term2">
Mods
<Bold>werden</Bold> Fehler und Leistungsprobleme verursachen. Die Schuld
liegt <Bold>nicht</Bold> bei Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term3">
Mods werden
<Bold>kostenlos</Bold> von Leuten in deren
<Bold>Freizeit</Bold> erstellt. Bitte sei geduldig und verständnissvoll.
</Span>
<Span x:Key="Intro:ReviewsBeatGamesFault">
Bitte gib <Bold>KEINE</Bold> schlechten Bewertungen weil die Mods nicht funktionieren. Die Schuld
liegt <Bold>nicht</Bold> bei Beat Games.
<LineBreak/> Sie versuchen nicht die Mods zu unterbinden.
</Span>
<Span x:Key="Intro:ReviewsRustySpoon">
Wenn ich weiterhin schlecht Bewertungen
<Italic>wegen</Italic> nicht funktionierenden Mods sehe,
<LineBreak/>
<Bold>Werde ich persönlich die Mods mit einem rostigen Löffel töten</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
Bitte lies den Einsteiger Leitfaden im
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/pc-modding.html">
Wiki
</Hyperlink>.
</Span>
<sys:String x:Key="Intro:AgreeButton">Annehmen</sys:String>
<sys:String x:Key="Intro:DisagreeButton">Ablehnen</sys:String>
<sys:String x:Key="Intro:ClosingApp">Programm wird beendet: Du hast den Bedingunen nicht zugestimmt.</sys:String>
<sys:String x:Key="Intro:VersionDownloadFailed">Versionsliste konnte nicht geladen werden</sys:String>
<sys:String x:Key="Intro:ModsTabDisabled">Mods Tab deaktiviert. Bitte Programm neu starten um es nochmal zu versuchen.</sys:String>
<sys:String x:Key="Intro:ModsTabEnabled">Du kannst jetzt den Mods Tab benutzen!</sys:String>
<!-- Mods Page -->
<sys:String x:Key="Mods:Title">Mods</sys:String>
<sys:String x:Key="Mods:Header:Name">Name</sys:String>
<sys:String x:Key="Mods:Header:Installed">Installiert</sys:String>
<sys:String x:Key="Mods:Header:Latest">Neuste</sys:String>
<sys:String x:Key="Mods:Header:Description">Beschreibung</sys:String>
<sys:String x:Key="Mods:Header:Uninstall">Entfernen</sys:String>
<sys:String x:Key="Mods:UninstallButton">Entfernen</sys:String>
<sys:String x:Key="Mods:LoadFailed">Modliste konnte nicht geladen werden</sys:String>
<sys:String x:Key="Mods:CheckingInstalledMods">Prüfe installierte Mods</sys:String>
<sys:String x:Key="Mods:LoadingMods">Lade Mods</sys:String>
<sys:String x:Key="Mods:FinishedLoadingMods">Laden der Mods abgeschlossen</sys:String>
<sys:String x:Key="Mods:InstallingMod">Installiere {0}</sys:String>
<sys:String x:Key="Mods:InstalledMod">{0} installiert</sys:String>
<sys:String x:Key="Mods:FinishedInstallingMods">Mod Installation abgeschlossen</sys:String>
<sys:String x:Key="Mods:ModDownloadLinkMissing">Downloadlink für {0} konnte nicht gefunden werden</sys:String>
<sys:String x:Key="Mods:UninstallBox:Title">{0} entfernen?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1">Bist du dir sicher das du {0} entfernen möchtest?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body2">Dies kann die anderen Mods unbrauchbar machen</sys:String>
<sys:String x:Key="Mods:FailedExtract">Fehler beim Extrahieren von {0}, neuer Versuch in {1} Sekunden. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">Fehler beim Extrahieren von {0} nach {1} Versuchen, wird übersprungen. Dieser Mod funktioniert möglicherweise nicht richtig, also gehe auf eigenes Risiko vor</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String> <!-- NEEDS TRANSLATING -->
<!-- About Page -->
<sys:String x:Key="About:Title">Über</sys:String>
<sys:String x:Key="About:PageTitle">Über Mod Assistant</sys:String>
<sys:String x:Key="About:List:Header">Ich bin Assistant und ich habe Mod Assistant als Assistent für Mods mit ein paar Prinzipen im Auge gemacht:</sys:String>
<sys:String x:Key="About:List:Item1">Einfachheit</sys:String>
<sys:String x:Key="About:List:Item2">Portabilität</sys:String>
<sys:String x:Key="About:List:Item3">Nur eine Datei</sys:String>
<sys:String x:Key="About:List:Item4">Verantwortungsbewusster Umgang</sys:String>
<Span x:Key="About:SupportAssistant">
Wenn dir das Programm gefällt und du mich unterstützen möchtest, dann besuche meine
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs.assistant.moe/Donate/">
Spendenseite
</Hyperlink>
oder mein
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://www.patreon.com/AssistantMoe">
Patreon
</Hyperlink>
</Span>
<sys:String x:Key="About:SpecialThanks">Besonder Dank ♥</sys:String>
<sys:String x:Key="About:Donate">Spenden</sys:String>
<sys:String x:Key="About:HeadpatsButton">Kopftätscheln</sys:String>
<sys:String x:Key="About:HugsButton">Umarmungen</sys:String>
<!-- Options Page -->
<sys:String x:Key="Options:Title">Optionen</sys:String>
<sys:String x:Key="Options:PageTitle">Einstellungen</sys:String>
<sys:String x:Key="Options:InstallFolder">Installations Ordner</sys:String>
<sys:String x:Key="Options:SelectFolderButton">Ordner wählen</sys:String>
<sys:String x:Key="Options:OpenFolderButton">Order öffnen</sys:String>
<sys:String x:Key="Options:SaveSelectedMods">Ausgewählte Mods speichern</sys:String>
<sys:String x:Key="Options:CheckInstalledMods">Installierte Mods prüfen</sys:String>
<sys:String x:Key="Options:SelectInstalledMods">Installierte Mods auswählen</sys:String>
<sys:String x:Key="Options:ReinstallInstalledMods">Installierte Mods neu installieren</sys:String>
<sys:String x:Key="Options:EnableOneClickInstalls">OneClick™ Installation aktivieren</sys:String>
<sys:String x:Key="Options:BeatSaver">BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">ModelSaber</sys:String>
<sys:String x:Key="Options:GameType">Spiel Typ</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Diagnostics">Diagnose</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Log öffnen</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">AppData öffnen</sys:String>
<sys:String x:Key="Options:UninstallBSIPAButton">BSIPA entfernen</sys:String>
<sys:String x:Key="Options:RemoveAllModsButton">Mods entfernen</sys:String>
<sys:String x:Key="Options:ApplicationTheme">Design</sys:String>
<sys:String x:Key="Options:ExportTemplateButton">Exportieren</sys:String>
<sys:String x:Key="Options:UploadingLog">Log wird hochgeladen</sys:String>
<sys:String x:Key="Options:LogUrlCopied">Log URL in die Zwischenablage kopiert!</sys:String>
<sys:String x:Key="Options:LogUploadFailed">Log Hochladen fehlgeschlagen!</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Title">Log Hochladen fehlgeschlagen!</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Body">Log Datei konnte nicht zu Teknik hochgeladen werden, bitte nochmal versuchen oder die Datei manuell senden.</sys:String>
<sys:String x:Key="Options:GettingModList">Lade Liste der Mods</sys:String>
<sys:String x:Key="Options:FindingBSIPAVersion">Suche BSIPA Version</sys:String>
<sys:String x:Key="Options:BSIPAUninstalled">BSIPA entfernt</sys:String>
<sys:String x:Key="Options:YeetModsBox:Title">Alle Mods entfernen?</sys:String>
<sys:String x:Key="Options:YeetModsBox:RemoveAllMods">Bist du dir sicher das du ALLE Mods entfernen möchtest?</sys:String>
<sys:String x:Key="Options:YeetModsBox:CannotBeUndone">Dies kann nicht rückgängig gemacht werden.</sys:String>
<sys:String x:Key="Options:AllModsUninstalled">Alle Mods entfernt</sys:String>
<sys:String x:Key="Options:CurrentThemeRemoved">Aktuelles Design wurde entfernt, gehe zurück zum Standart...</sys:String>
<sys:String x:Key="Options:ThemeFolderNotFound">Designs Ordner nicht gefunden! Versuche die Vorlage zu exportieren...</sys:String>
<sys:String x:Key="Options:AppDataNotFound">AppData Ordner nicht gefunden! Versuche dein Spiel zu starten.</sys:String>
<!-- Loading Page -->
<sys:String x:Key="Loading:Loading">Lade Mods</sys:String>
<!-- Invalid Page -->
<sys:String x:Key="Invalid:Title">Ungültig</sys:String>
<sys:String x:Key="Invalid:PageTitle">Ungültige Installation erkannt</sys:String>
<sys:String x:Key="Invalid:PageSubtitle">Die SPielinstallation ist beschädigt oder anderwärtig ungültig</sys:String>
<sys:String x:Key="Invalid:List:Header">Dies kann passieren wenn dein Spiel eine Raubkopie ist oder eine Raubkopie über eine legitime Version kopiert wurde</sys:String>
<Span x:Key="Invalid:List:Line1">
Falls dein Spiel eine Raubkopie ist,
<Bold>bitte kaufe das Spiel
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
HIER
</Hyperlink>
</Bold>.
</Span>
<Span x:Key="Invalid:List:Line2">
Wenn dein Spiel
<Bold>keine</Bold> Raubkopie ist, bitte
<Hyperlink NavigateUri="https://bsmg.wiki/support#clean-installation" local:HyperlinkExtensions.IsExternal="True">
mach eine saubere Neuinstallation
</Hyperlink>.
</Span>
<Span x:Key="Invalid:List:Line3">
Falls das nicht hilft, frage im
<Span Foreground="Blue">#support</Span> Kanal in der
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
BSMG
</Hyperlink>.
</Span>
<sys:String x:Key="Invalid:BoughtGame1">Falls du eine Raubkopie hattest aber das Spiel jetzt gekauft hast</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">Select Folder</sys:String>
<sys:String x:Key="Invalid:BoughtGame2">Muss Mod Assisstant neu gestartet werden wenn eine legitime Version installiert wurde</sys:String>
<!-- OneClick Class -->
<sys:String x:Key="OneClick:MapDownloadFailed">Map Details konnten nicht geladen werden.</sys:String>
<sys:String x:Key="OneClick:SongDownloadFailed">Titel konnte nicht geladen werden.</sys:String>
<sys:String x:Key="OneClick:SongDownload:Failed">Titel konnte nicht geladen werden.</sys:String>
<sys:String x:Key="OneClick:SongDownload:NetworkIssues">Möglicherweise gibt es Probleme mit BeatSaver oder deiner Internetverbindung.</sys:String>
<sys:String x:Key="OneClick:SongDownload:FailedTitle">Herunterladen der Titel ZIP fehlgeschlagen</sys:String>
<sys:String x:Key="OneClick:InstallDirNotFound">Beat Saber Installations Pfad nicht gefunden.</sys:String>
<sys:String x:Key="OneClick:InstalledAsset">Installiert: {0}</sys:String>
<sys:String x:Key="OneClick:AssetInstallFailed">Installation fehlgeschlagen.</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick™ Installion Handler registriert!</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick™ Installion Handler entfernt!</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">Design nicht gefunden, gehe zurück zum Standart Design...</sys:String>
<sys:String x:Key="Themes:ThemeSet">Design gesetzt auf: {0}.</sys:String>
<sys:String x:Key="Themes:ThemeMissing">{0} existiert nicht.</sys:String>
<sys:String x:Key="Themes:SavedTemplateTheme">Designvorlage &quot;{0}&quot; in Design Ordner gespeichert.</sys:String>
<sys:String x:Key="Themes:TemplateThemeExists">Designvorlage existiert bereits!</sys:String>
<sys:String x:Key="Themes:FailedToLoadXaml">Fehler beim Laden der .xaml Datei von Design {0}: {1}</sys:String>
<!-- Updater Class -->
<sys:String x:Key="Updater:CheckFailed">Konnte nicht auf Aktualisierungen prüfen.</sys:String>
<sys:String x:Key="Updater:DownloadFailed">Konnte Aktualisierung nicht herunterladen.</sys:String>
<!-- Utils Class -->
<sys:String x:Key="Utils:NotificationTitle">Mod Assistant</sys:String>
<sys:String x:Key="Utils:NoInstallFolder">Beat Saber Installationsordner konnte nicht erkannt werden. Bitte manuell auswählen.</sys:String>
<sys:String x:Key="Utils:RunAsAdmin">Mod Assistant muss diese Aufgabe mit Administrator Rechten ausführen. Bitte nochmal versuchen.</sys:String>
<sys:String x:Key="Utils:InstallDir:DialogTitle">Wähle den Beat Saber Installationsordner aus</sys:String>
<sys:String x:Key="Utils:CannotOpenFolder">Ordner konnte nicht geöffnet werden: {0}</sys:String>
</ResourceDictionary>

View file

@ -1,245 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModAssistant"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ResourceDictionaryName">i18n:fr-FR</sys:String>
<!-- App -->
<sys:String x:Key="App:InstallDirDialog:Title">Impossible de trouver le dossier d'installation de Beat Saber !</sys:String>
<sys:String x:Key="App:InstallDirDialog:OkCancel">Appuyez sur OK pour réessayer, ou Annuler pour fermer l'application.</sys:String>
<sys:String x:Key="App:InvalidArgument">Argument invalide ! '{0}' nécessite une option.</sys:String>
<sys:String x:Key="App:UnrecognizedArgument">Argument non reconnu. Fermeture de Mod Assistant.</sys:String>
<sys:String x:Key="App:UnhandledException">Une exception non gérée est survenue</sys:String>
<sys:String x:Key="App:Exception">Exception</sys:String>
<!-- Main Window -->
<sys:String x:Key="MainWindow:WindowTitle">ModAssistant</sys:String>
<sys:String x:Key="MainWindow:IntroButton">Intro</sys:String>
<sys:String x:Key="MainWindow:ModsButton">Mods</sys:String>
<sys:String x:Key="MainWindow:AboutButton">À propos</sys:String>
<sys:String x:Key="MainWindow:OptionsButton">Options</sys:String>
<sys:String x:Key="MainWindow:GameVersionLabel">Version du jeu</sys:String>
<sys:String x:Key="MainWindow:VersionLabel">Version</sys:String>
<sys:String x:Key="MainWindow:ModInfoButton">Info sur le mod</sys:String>
<sys:String x:Key="MainWindow:InstallButtonTop">Installer</sys:String>
<sys:String x:Key="MainWindow:InstallButtonBottom">ou Mettre à jour</sys:String>
<sys:String x:Key="MainWindow:GameVersionLoadFailed">Impossible de charger les versions du jeu, l'onglet Mods sera indisponible.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Title">Nouvelle version du jeu détectée !</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line1">Il semble que le jeu a été mis à jour.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line2">Veuillez vous assurer que la bonne version soit sélectionnée en bas à gauche !</sys:String>
<sys:String x:Key="MainWindow:NoModSelected">Aucun mod sélectionné !</sys:String>
<sys:String x:Key="MainWindow:NoModInfoPage">{0} n'a pas de page informative.</sys:String>
<!-- Intro Page -->
<sys:String x:Key="Intro:Title">Intro</sys:String>
<sys:String x:Key="Intro:PageTitle">Bienvenue sur Mod Assistant</sys:String>
<sys:String x:Key="Intro:Terms:Header">Veuillez lire cette page entièrement et avec attention</sys:String>
<Span x:Key="Intro:Terms:Line1">
En utilisant ce programme, vous attestez que vous avez lu et accepté les modalités suivantes :
</Span>
<Span x:Key="Intro:Terms:Line2">
Beat Saber
<Bold>ne</Bold> supporte
<Bold>pas</Bold> nativement les mods. Cela signifie que :
</Span>
<Span x:Key="Intro:Terms:Term1">
Les mods
<Bold>dysfonctionneront</Bold> à chaque mise à jour. C'est normal, et ce
<Bold>n'</Bold>est
<Bold>pas</Bold> la faute de Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term2">
Les mods
<Bold>causeront</Bold> des bugs et des problèmes de performance. Ce
<Bold>n'</Bold>est
<Bold>pas</Bold> la faute de Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term3">
Les mods sont créés
<Bold>gratuitement</Bold> par des gens sur leur
<Bold>temps libre.</Bold> Veuillez être patient et compréhensif.
</Span>
<Span x:Key="Intro:ReviewsBeatGamesFault">
<Bold>NE</Bold> laissez
<Bold>PAS</Bold> de commentaires négatifs parce que les mods ne fonctionnent plus. Ce
<Bold>n'</Bold>est
<Bold>pas</Bold> la faute de Beat Games.
<LineBreak/> Ils n'essaient pas de faire disparaître les mods.
</Span>
<Span x:Key="Intro:ReviewsRustySpoon">
Si je continue de voir des gens laisser des commentaires négatifs
<Italic>parce que</Italic> les mods ne fonctionnent plus,
<LineBreak/>
<Bold>J'irai personnellement liquider les mods avec une cuillère rouillée</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
Veuillez lire le Guide du Débutant sur le
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/fr/pc-modding.html">
Wiki
</Hyperlink>.
</Span>
<sys:String x:Key="Intro:AgreeButton">J'accepte</sys:String>
<sys:String x:Key="Intro:DisagreeButton">Je refuse</sys:String>
<sys:String x:Key="Intro:ClosingApp">Fermeture de l'application : vous n'avez pas accepté les modalités et conditions.</sys:String>
<sys:String x:Key="Intro:VersionDownloadFailed">Impossible de télécharger la liste des versions</sys:String>
<sys:String x:Key="Intro:ModsTabDisabled">Onglet Mods désactivé. Veuillez relancer pour réessayer.</sys:String>
<sys:String x:Key="Intro:ModsTabEnabled">Vous pouvez désormais utiliser l'onglet Mods !</sys:String>
<!-- Mods Page -->
<sys:String x:Key="Mods:Title">Mods</sys:String>
<sys:String x:Key="Mods:Header:Name">Nom</sys:String>
<sys:String x:Key="Mods:Header:Installed">Installé</sys:String>
<sys:String x:Key="Mods:Header:Latest">Récent</sys:String>
<sys:String x:Key="Mods:Header:Description">Description</sys:String>
<sys:String x:Key="Mods:Header:Uninstall">Désinstaller</sys:String>
<sys:String x:Key="Mods:UninstallButton">Désinstaller</sys:String>
<sys:String x:Key="Mods:LoadFailed">Impossible de charger la liste des mods</sys:String>
<sys:String x:Key="Mods:CheckingInstalledMods">Vérification des mods installés</sys:String>
<sys:String x:Key="Mods:LoadingMods">Chargement des mods</sys:String>
<sys:String x:Key="Mods:FinishedLoadingMods">Fin : mods chargés</sys:String>
<sys:String x:Key="Mods:InstallingMod">Installation de {0}</sys:String>
<sys:String x:Key="Mods:InstalledMod">{0} installé</sys:String>
<sys:String x:Key="Mods:FinishedInstallingMods">Fin : mods installés</sys:String>
<sys:String x:Key="Mods:ModDownloadLinkMissing">Impossible de trouver le lien de téléchargement de {0}</sys:String>
<sys:String x:Key="Mods:UninstallBox:Title">Désinstaller {0} ?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1">Êtes-vous sûr de vouloir supprimer {0} ?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body2">Cela pourrait faire dysfonctionner d'autres mods installés</sys:String>
<sys:String x:Key="Mods:FailedExtract">Échec de l'extraction de {0}, nouvelle tentative dans {1} secondes. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">Échec de l'extraction de {0} après le maximum de tentatives ({1}), abandon. Ce mod pourrait ne pas fonctionner correctement, continuez à vos propres risques</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String> <!-- NEEDS TRANSLATING -->
<!-- About Page -->
<sys:String x:Key="About:Title">À propos</sys:String>
<sys:String x:Key="About:PageTitle">À propos de Mod Assistant</sys:String>
<sys:String x:Key="About:List:Header">Je suis Assistant, et je réalise Mod Assistant pour l'assistance aux mods, avec quelques principes en tête :</sys:String>
<sys:String x:Key="About:List:Item1">Simplicité</sys:String>
<sys:String x:Key="About:List:Item2">Portabilité</sys:String>
<sys:String x:Key="About:List:Item3">Exécutable unique</sys:String>
<sys:String x:Key="About:List:Item4">Utilisation responsable</sys:String>
<Span x:Key="About:SupportAssistant">
Si vous aimez ce programme et souhaitez me soutenir, veuillez visiter ma
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs.assistant.moe/Donate/">
page de don
</Hyperlink>
ou mon
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://www.patreon.com/AssistantMoe">
Patreon
</Hyperlink>
</Span>
<sys:String x:Key="About:SpecialThanks">Remerciements particuliers ♥</sys:String>
<sys:String x:Key="About:Donate">Faire un don</sys:String>
<sys:String x:Key="About:HeadpatsButton">Caresses-tête</sys:String>
<sys:String x:Key="About:HugsButton">Câlins</sys:String>
<!-- Options Page -->
<sys:String x:Key="Options:Title">Options</sys:String>
<sys:String x:Key="Options:PageTitle">Paramètres</sys:String>
<sys:String x:Key="Options:InstallFolder">Dossier d'installation</sys:String>
<sys:String x:Key="Options:SelectFolderButton">Sélectionner un dossier</sys:String>
<sys:String x:Key="Options:OpenFolderButton">Ouvrir le dossier</sys:String>
<sys:String x:Key="Options:SaveSelectedMods">Sauvegarder les mods sélectionnés</sys:String>
<sys:String x:Key="Options:CheckInstalledMods">Détecter les mods installés</sys:String>
<sys:String x:Key="Options:SelectInstalledMods">Sélectionner les mods installés</sys:String>
<sys:String x:Key="Options:ReinstallInstalledMods">Réinstaller les mods installés</sys:String>
<sys:String x:Key="Options:EnableOneClickInstalls">Activer les installations OneClick™</sys:String>
<sys:String x:Key="Options:BeatSaver">BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">ModelSaber</sys:String>
<sys:String x:Key="Options:GameType">Type du jeu</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Diagnostics">Diagnostic</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Ouvrir les logs</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Ouvrir AppData</sys:String>
<sys:String x:Key="Options:UninstallBSIPAButton">Désinstaller BSIPA</sys:String>
<sys:String x:Key="Options:RemoveAllModsButton">Supprimer tous les mods</sys:String>
<sys:String x:Key="Options:ApplicationTheme">Thème de l'application</sys:String>
<sys:String x:Key="Options:ExportTemplateButton">Exporter le modèle</sys:String>
<sys:String x:Key="Options:UploadingLog">Envoi des logs</sys:String>
<sys:String x:Key="Options:LogUrlCopied">URL des logs copiée dans le presse-papier !</sys:String>
<sys:String x:Key="Options:LogUploadFailed">L'envoi des logs a échoué</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Title">L'envoi des logs a échoué !</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Body">Impossible d'envoyer le fichier de logs à Teknik, veuillez réessayer ou envoyer le fichier manuellement.</sys:String>
<sys:String x:Key="Options:GettingModList">Récupération de la liste des mods</sys:String>
<sys:String x:Key="Options:FindingBSIPAVersion">Découverte de la version de BSIPA</sys:String>
<sys:String x:Key="Options:BSIPAUninstalled">BSIPA désinstallé</sys:String>
<sys:String x:Key="Options:YeetModsBox:Title">Désinstaller les mods ?</sys:String>
<sys:String x:Key="Options:YeetModsBox:RemoveAllMods">Êtes-vous sûr de vouloir supprimer TOUS les mods ?</sys:String>
<sys:String x:Key="Options:YeetModsBox:CannotBeUndone">Cela ne peut pas être annulé.</sys:String>
<sys:String x:Key="Options:AllModsUninstalled">Tous les mods ont été désinstallés</sys:String>
<sys:String x:Key="Options:CurrentThemeRemoved">Le thème actuel a été supprimé, passage au thème par défaut...</sys:String>
<sys:String x:Key="Options:ThemeFolderNotFound">Dossier Themes non trouvé ! Essayez d'exporter le modèle...</sys:String>
<sys:String x:Key="Options:AppDataNotFound">Dossier AppData non trouvé ! Essayez de lancer votre jeu.</sys:String>
<!-- Loading Page -->
<sys:String x:Key="Loading:Loading">Chargement des mods</sys:String>
<!-- Invalid Page -->
<sys:String x:Key="Invalid:Title">Invalide</sys:String>
<sys:String x:Key="Invalid:PageTitle">Installation invalide détectée</sys:String>
<sys:String x:Key="Invalid:PageSubtitle">Votre installation du jeu est corrompue sinon invalide</sys:String>
<sys:String x:Key="Invalid:List:Header">Cela peut survenir si votre copie du jeu est piratée, ou si vous copiez une copie piratée sur votre installation légitime</sys:String>
<Span x:Key="Invalid:List:Line1">
Si votre copie du jeu est piratée,
<Bold>veuillez acheter le jeu
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
ICI
</Hyperlink>
</Bold>.
</Span>
<Span x:Key="Invalid:List:Line2">
Si votre copie du jeu
<Bold>n'</Bold>est
<Bold>pas</Bold> piratée, veuillez
<Hyperlink NavigateUri="https://bsmg.wiki/fr/support/#installation-propre" local:HyperlinkExtensions.IsExternal="True">
faire une installation propre
</Hyperlink>.
</Span>
<Span x:Key="Invalid:List:Line3">
Si cela n'a pas fonctionné, demandez de l'aide au support dans le canal textuel
<Span Foreground="Blue">#support</Span> dans
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
BSMG
</Hyperlink>.
</Span>
<sys:String x:Key="Invalid:BoughtGame1">Si vous utilisiez une version piratée mais avez acheté le jeu depuis</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">Sélectionner un dossier</sys:String>
<sys:String x:Key="Invalid:BoughtGame2">Vous devez relancer Mod Assistant après avoir choisi l'installation légitime</sys:String>
<!-- OneClick Class -->
<sys:String x:Key="OneClick:MapDownloadFailed">Impossible de récupérer les détails de la map.</sys:String>
<sys:String x:Key="OneClick:SongDownloadFailed">Impossible de télécharger la musique.</sys:String>
<sys:String x:Key="OneClick:SongDownload:Failed">Impossible de télécharger la musique.</sys:String>
<sys:String x:Key="OneClick:SongDownload:NetworkIssues">Il pourrait y avoir des problèmes avec BeatSaver ou votre connexion Internet.</sys:String>
<sys:String x:Key="OneClick:SongDownload:FailedTitle">Échec du téléchargement du ZIP de la musique</sys:String>
<sys:String x:Key="OneClick:InstallDirNotFound">Chemin de l'installation de Beat Saber non trouvé.</sys:String>
<sys:String x:Key="OneClick:InstalledAsset">Installé : {0}</sys:String>
<sys:String x:Key="OneClick:AssetInstallFailed">Échec de l'installation.</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} : gestionnaires d'installation OneClick™ inscrits !</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} : gestionnaires d'installation OneClick™ désinscrits !</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">Thème non trouvé, passage au thème par défaut...</sys:String>
<sys:String x:Key="Themes:ThemeSet">Thème défini sur {0}.</sys:String>
<sys:String x:Key="Themes:ThemeMissing">{0} n'existe pas.</sys:String>
<sys:String x:Key="Themes:SavedTemplateTheme">Modèle du thème &quot;{0}&quot; sauvegardé dans le dossier Themes.</sys:String>
<sys:String x:Key="Themes:TemplateThemeExists">Le modèle du thème existe déjà !</sys:String>
<sys:String x:Key="Themes:FailedToLoadXaml">Échec du chargement du fichier .xaml pour le thème {0} : {1}</sys:String>
<!-- Updater Class -->
<sys:String x:Key="Updater:CheckFailed">Impossible de vérifier les mises à jour.</sys:String>
<sys:String x:Key="Updater:DownloadFailed">Impossible de télécharger la mise à jour.</sys:String>
<!-- Utils Class -->
<sys:String x:Key="Utils:NotificationTitle">Mod Assistant</sys:String>
<sys:String x:Key="Utils:NoInstallFolder">Impossible de détecter le dossier d'installation de Beat Saber. Veuillez le sélectionner manuellement.</sys:String>
<sys:String x:Key="Utils:RunAsAdmin">Mod Assistant a besoin de lancer cette tâche en administrateur. Veuillez réessayer.</sys:String>
<sys:String x:Key="Utils:InstallDir:DialogTitle">Sélectionnez le dossier d'installation de Beat Saber</sys:String>
<sys:String x:Key="Utils:CannotOpenFolder">Impossible d'ouvrir le dossier : {0}</sys:String>
</ResourceDictionary>

View file

@ -1,238 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModAssistant"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ResourceDictionaryName">i18n:it-IT</sys:String>
<!-- App -->
<sys:String x:Key="App:InstallDirDialog:Title">Impossibile trovare la directory d'installazione di Beat Saber</sys:String>
<sys:String x:Key="App:InstallDirDialog:OkCancel">Premi OK per riprovare, oppure Cancel per chiudere l'applicazione.</sys:String>
<sys:String x:Key="App:InvalidArgument">Argomento non valido! '{0}' ha bisogno di un'opzione.</sys:String>
<sys:String x:Key="App:UnrecognizedArgument">Argomento non riconosciuto. Chiusura di Mod Assistant.</sys:String>
<sys:String x:Key="App:UnhandledException">Si è appena verificata un'eccezione non gestita</sys:String>
<sys:String x:Key="App:Exception">Eccezione</sys:String>
<!-- Main Window -->
<sys:String x:Key="MainWindow:WindowTitle">ModAssistant</sys:String>
<sys:String x:Key="MainWindow:IntroButton">Introduzione</sys:String>
<sys:String x:Key="MainWindow:ModsButton">Mod</sys:String>
<sys:String x:Key="MainWindow:AboutButton">Info</sys:String>
<sys:String x:Key="MainWindow:OptionsButton">Opzioni</sys:String>
<sys:String x:Key="MainWindow:GameVersionLabel">Versione del gioco</sys:String>
<sys:String x:Key="MainWindow:VersionLabel">Versione</sys:String>
<sys:String x:Key="MainWindow:ModInfoButton">Info sulla mod</sys:String>
<sys:String x:Key="MainWindow:InstallButtonTop">Installa</sys:String>
<sys:String x:Key="MainWindow:InstallButtonBottom">o Aggiorna</sys:String>
<sys:String x:Key="MainWindow:GameVersionLoadFailed">Non è stato possibile caricare le versioni del gioco, il menù Mod non sarà disponibile.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Title">Rilevata nuova versione del gioco!</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line1">Sembra che ci sia stato un aggiornamento del gioco.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line2">Per piacere verifica che sia selezionata la versione corretta nel menù in basso a sinistra</sys:String>
<sys:String x:Key="MainWindow:NoModSelected">Nessuna mod selezionata!</sys:String>
<sys:String x:Key="MainWindow:NoModInfoPage">{0} non ha una pagina di informazioni.</sys:String>
<!-- Intro Page -->
<sys:String x:Key="Intro:Title">Introduzione</sys:String>
<sys:String x:Key="Intro:PageTitle">Benvenuto/a in Mod Assistant</sys:String>
<sys:String x:Key="Intro:Terms:Header">Ti invitiamo a leggere questa pagina fino alla fine e con cautela</sys:String>
<Span x:Key="Intro:Terms:Line1">
Utilizzando questo programma, attesti di accettare i seguenti termini:
</Span>
<Span x:Key="Intro:Terms:Line2">
Beat Saber
<Bold>non</Bold> supporta nativamente le mod. Ciò significa che:
</Span>
<Span x:Key="Intro:Terms:Term1">
Le mod
<Bold>smetteranno di funzionare</Bold> ad ogni aggiornamento. È assolutamente normale, e
<Bold>non è</Bold> colpa di Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term2">
Le mod
<Bold>causeranno</Bold> bug e problemi di prestazioni. Questa
<Bold>non è</Bold> colpa di Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term3">
Le mod sono fatte
<Bold>gratuitamente</Bold> da sviluppatori nel loro
<Bold>tempo libero.</Bold> Ti invitiamo ad essere paziente e di comprendere la situazione.
</Span>
<Span x:Key="Intro:ReviewsBeatGamesFault">
<Bold>NON</Bold> lasciare feedback negativi solo perche le mod smettono di funzionare. Questa
<Bold>non è</Bold> colpa Beat Games.
<LineBreak/> Non è loro intenzione "rompere" le mod.
</Span>
<Span x:Key="Intro:ReviewsRustySpoon">
Se continuerò a trovare persone che lasciano feedback negativi
<Italic>perchè</Italic> le mod smettono di funzionare,
<LineBreak/>
<Bold>Mi assicurerò di farle sparire dalla circolazione</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
Ti invitiamo a leggere la guida introduttiva sulla
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/pc-modding.html">
Wiki
</Hyperlink>.
</Span>
<sys:String x:Key="Intro:AgreeButton">Accetto</sys:String>
<sys:String x:Key="Intro:DisagreeButton">Non accetto</sys:String>
<sys:String x:Key="Intro:ClosingApp">Chiusura dell'app: Non hai accettato i termini e condizioni.</sys:String>
<sys:String x:Key="Intro:VersionDownloadFailed">Non sono riuscito a scaricare la lista delle versioni</sys:String>
<sys:String x:Key="Intro:ModsTabDisabled">Menù delle mod disattivato. Ti invitiamo a riprovare riavviando il programma.</sys:String>
<sys:String x:Key="Intro:ModsTabEnabled">Ora puoi utilizzare il menù Mod!</sys:String>
<!-- Mods Page -->
<sys:String x:Key="Mods:Title">Mod</sys:String>
<sys:String x:Key="Mods:Header:Name">Nome</sys:String>
<sys:String x:Key="Mods:Header:Installed">Versione installata</sys:String>
<sys:String x:Key="Mods:Header:Latest">Ultima versione</sys:String>
<sys:String x:Key="Mods:Header:Description">Descrizione</sys:String>
<sys:String x:Key="Mods:Header:Uninstall">Disinstalla</sys:String>
<sys:String x:Key="Mods:UninstallButton">Disinstalla</sys:String>
<sys:String x:Key="Mods:LoadFailed">Impossibile caricare la lista delle mod</sys:String>
<sys:String x:Key="Mods:CheckingInstalledMods">Controllo le mod installate</sys:String>
<sys:String x:Key="Mods:LoadingMods">Carico le mod</sys:String>
<sys:String x:Key="Mods:FinishedLoadingMods">Caricamento delle mod completato</sys:String>
<sys:String x:Key="Mods:InstallingMod">Installazione di {0} in corso</sys:String>
<sys:String x:Key="Mods:InstalledMod">{0} installato</sys:String>
<sys:String x:Key="Mods:FinishedInstallingMods">Installazione delle mod completata</sys:String>
<sys:String x:Key="Mods:ModDownloadLinkMissing">Impossibile trovare il link di download per {0}</sys:String>
<sys:String x:Key="Mods:UninstallBox:Title">Vuoi disinstallare {0}?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1">Sei sicuro di voler disinstallare {0}?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body2">Continuando, altre mod potrebbero smettere di funzionare</sys:String>
<sys:String x:Key="Mods:FailedExtract">Impossibile estrarre {0}, prossimo tentativo in {1} secondi. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">Non sono riuscito ad estrarre {0} dopo aver raggiunto il numero massimo di tentativi ({1}), salto questa mod. Questa protrebbe anche non funzionare correttamente, quindi procedi a tuo rischio e pericolo</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String> <!-- NEEDS TRANSLATING -->
<!-- About Page -->
<sys:String x:Key="About:Title">Info</sys:String>
<sys:String x:Key="About:PageTitle">Info su Mod Assistant</sys:String>
<sys:String x:Key="About:List:Header">Ciao, sono Assistant, ed ho creato Mod Assistant per aiutarmi con le mod, con alcuni principi in testa:</sys:String>
<sys:String x:Key="About:List:Item1">Semplicità d'uso</sys:String>
<sys:String x:Key="About:List:Item2">Portabilità</sys:String>
<sys:String x:Key="About:List:Item3">Un solo eseguibile</sys:String>
<sys:String x:Key="About:List:Item4">Uso responsabile</sys:String>
<Span x:Key="About:SupportAssistant">
Se ti piace questo programma, e volessi supportarmi, puoi visitare la mia
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs.assistant.moe/Donate/">
pagina delle donazioni
</Hyperlink>
oppure il mio
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://www.patreon.com/AssistantMoe">
Patreon
</Hyperlink>
</Span>
<sys:String x:Key="About:SpecialThanks">Ringraziamenti speciali ♥</sys:String>
<sys:String x:Key="About:Donate">Donazioni</sys:String>
<sys:String x:Key="About:HeadpatsButton">Carezze</sys:String>
<sys:String x:Key="About:HugsButton">Abbracci</sys:String>
<!-- Options Page -->
<sys:String x:Key="Options:Title">Opzioni</sys:String>
<sys:String x:Key="Options:PageTitle">Impostazioni</sys:String>
<sys:String x:Key="Options:InstallFolder">Directory d'Installazione</sys:String>
<sys:String x:Key="Options:SelectFolderButton">Seleziona Cartella</sys:String>
<sys:String x:Key="Options:OpenFolderButton">Apri Cartella</sys:String>
<sys:String x:Key="Options:SaveSelectedMods">Salva le Mod Selezionate</sys:String>
<sys:String x:Key="Options:CheckInstalledMods">Rileva le Mod Installate</sys:String>
<sys:String x:Key="Options:SelectInstalledMods">Seleziona le Mod Installate</sys:String>
<sys:String x:Key="Options:ReinstallInstalledMods">Reinstalla le Mod</sys:String>
<sys:String x:Key="Options:EnableOneClickInstalls">Attiva OneClick™</sys:String>
<sys:String x:Key="Options:BeatSaver">BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">ModelSaber</sys:String>
<sys:String x:Key="Options:GameType">Tipo di Installazione</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Diagnostics">Diagnostica</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Apri il Log</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Apri AppData</sys:String>
<sys:String x:Key="Options:UninstallBSIPAButton">Disinstalla BSIPA</sys:String>
<sys:String x:Key="Options:RemoveAllModsButton">Rimuovi Tutte le Mod</sys:String>
<sys:String x:Key="Options:ApplicationTheme">Tema dell'app</sys:String>
<sys:String x:Key="Options:ExportTemplateButton">Esporta Template</sys:String>
<sys:String x:Key="Options:UploadingLog">Caricamento del Log</sys:String>
<sys:String x:Key="Options:LogUrlCopied">URL del Log copiato negli Appunti!</sys:String>
<sys:String x:Key="Options:LogUploadFailed">Caricamento del Log Fallito</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Title">Caricamento del Log Fallito!</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Body">Non sono riuscito a caricare il Log su Teknik, ti invitiamo a riprovare oppure puoi caricare il file manualmente.</sys:String>
<sys:String x:Key="Options:GettingModList">Prendo la lista delle Mod</sys:String>
<sys:String x:Key="Options:FindingBSIPAVersion">Cerco la versione di BSIPA</sys:String>
<sys:String x:Key="Options:BSIPAUninstalled">BSIPA Disinstallato</sys:String>
<sys:String x:Key="Options:YeetModsBox:Title">Disinstallare tutte le Mod?</sys:String>
<sys:String x:Key="Options:YeetModsBox:RemoveAllMods">Sei sicuro di voler rimuovere TUTTE le mod?</sys:String>
<sys:String x:Key="Options:YeetModsBox:CannotBeUndone">Questa azione non può essere annullata.</sys:String>
<sys:String x:Key="Options:AllModsUninstalled">Tutte le Mod sono state Disinstallate</sys:String>
<sys:String x:Key="Options:CurrentThemeRemoved">Il tema corrente è stato rimosso, torno al principale...</sys:String>
<sys:String x:Key="Options:ThemeFolderNotFound">Cartella del tema non trovata! Prova ad esportare il template...</sys:String>
<sys:String x:Key="Options:AppDataNotFound">Cartella AppData non trovata! Prova ad avviare il gioco.</sys:String>
<!-- Loading Page -->
<sys:String x:Key="Loading:Loading">Caricamento delle Mod</sys:String>
<!-- Invalid Page -->
<sys:String x:Key="Invalid:Title">Non Valida</sys:String>
<sys:String x:Key="Invalid:PageTitle">Installazione non valida rilevata</sys:String>
<sys:String x:Key="Invalid:PageSubtitle">La tua installazione di BeatSaber è corrotta o invalida</sys:String>
<sys:String x:Key="Invalid:List:Header">Ciò accade se hai una copia piratata, oppure se hai installato una versione originale senza rimuovere quella piratata</sys:String>
<Span x:Key="Invalid:List:Line1">
Se la tua copia è piratata,
<Bold>ti invitiamo a compare il gioco
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
QUI
</Hyperlink>
</Bold>.
</Span>
<Span x:Key="Invalid:List:Line2">
Se la tua copia del gioco
<Bold>non è</Bold> piratata, ti invitiamo a
<Hyperlink NavigateUri="https://bsmg.wiki/support#clean-installation" local:HyperlinkExtensions.IsExternal="True">
reinstallare il gioco
</Hyperlink>.
</Span>
<Span x:Key="Invalid:List:Line3">
Se questo non aiuta, puoi sempre chiedere aiuto nel canale
<Span Foreground="Blue">#support</Span> all'interno del
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
Server Discord di BSMG
</Hyperlink>.
</Span>
<sys:String x:Key="Invalid:BoughtGame1">Se avevi la versione piratata, ma hai comprato il gioco</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">Seleziona cartella</sys:String>
<sys:String x:Key="Invalid:BoughtGame2">Dovrai riavviare ModAssistant dopo aver cambiato la directory d'Installazione</sys:String>
<!-- OneClick Class -->
<sys:String x:Key="OneClick:MapDownloadFailed">Impossibile estrarre i dettagli della mappa.</sys:String>
<sys:String x:Key="OneClick:SongDownloadFailed">Impossibile scaricare il brano.</sys:String>
<sys:String x:Key="OneClick:SongDownload:Failed">Non sono riuscito a scaricare il brano.</sys:String>
<sys:String x:Key="OneClick:SongDownload:NetworkIssues">Ci possono essere problemi con BeatSaver e/o la tua connessione ad Internet.</sys:String>
<sys:String x:Key="OneClick:SongDownload:FailedTitle">Download del file ZIP della mappa fallito</sys:String>
<sys:String x:Key="OneClick:InstallDirNotFound">Directory d'installazione di Beat Saber non trovata.</sys:String>
<sys:String x:Key="OneClick:InstalledAsset">Installato: {0}</sys:String>
<sys:String x:Key="OneClick:AssetInstallFailed">Non sono riuscito ad installare la mappa.</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} Registrazione dei gestori OneClick™ riuscita!</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} De-Regitrazione dei gestori OneClick™ riuscita!</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">Tema non trovato, ritorno al tema predefinito...</sys:String>
<sys:String x:Key="Themes:ThemeSet">Tema impostato su {0}.</sys:String>
<sys:String x:Key="Themes:ThemeMissing">{0} non esiste.</sys:String>
<sys:String x:Key="Themes:SavedTemplateTheme">Template del tema &quot;{0}&quot; salvato nella cartella Themes.</sys:String>
<sys:String x:Key="Themes:TemplateThemeExists">Template del tema già esistente!</sys:String>
<sys:String x:Key="Themes:FailedToLoadXaml">Impossibile caricare il file .xaml per il tema {0}: {1}</sys:String>
<!-- Updater Class -->
<sys:String x:Key="Updater:CheckFailed">Impossibile controllare gli aggiornamenti.</sys:String>
<sys:String x:Key="Updater:DownloadFailed">Impossibile scaricare l'aggiornamento.</sys:String>
<!-- Utils Class -->
<sys:String x:Key="Utils:NotificationTitle">Mod Assistant</sys:String>
<sys:String x:Key="Utils:NoInstallFolder">Impossibile determinare automaticamente la directory d'installazione di Beat Saber. Ti invitiamo a selezionarla manualmente.</sys:String>
<sys:String x:Key="Utils:RunAsAdmin">Mod Assistant ha bisogno di eseguire questa azione come Amministratore. Ti invitiamo a riprovare.</sys:String>
<sys:String x:Key="Utils:InstallDir:DialogTitle">Seleziona la directory d'installazione di Beat Saber</sys:String>
<sys:String x:Key="Utils:CannotOpenFolder">Impossibile aprire la seguente cartella: {0}</sys:String>
</ResourceDictionary>

View file

@ -1,237 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModAssistant"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ResourceDictionaryName">i18n:ko-KR</sys:String>
<!-- App -->
<sys:String x:Key="App:InstallDirDialog:Title">비트세이버 설치 폴더를 찾을 수 없습니다!</sys:String>
<sys:String x:Key="App:InstallDirDialog:OkCancel">OK를 눌러 재시도하거나, Cancel을 눌러 프로그램을 종료할 수 있습니다.</sys:String>
<sys:String x:Key="App:InvalidArgument">유효하지 않은 인수입니다! '{0}'은 옵션을 필요로 합니다.</sys:String>
<sys:String x:Key="App:UnrecognizedArgument">인식할 수 없는 인수입니다. 모드 어시스턴트를 종료합니다.</sys:String>
<sys:String x:Key="App:UnhandledException">처리되지 않은 예외가 발생했습니다.</sys:String>
<sys:String x:Key="App:Exception">예외</sys:String>
<!-- Main Window -->
<sys:String x:Key="MainWindow:WindowTitle">모드 어시스턴트</sys:String>
<sys:String x:Key="MainWindow:IntroButton">인트로</sys:String>
<sys:String x:Key="MainWindow:ModsButton">모드</sys:String>
<sys:String x:Key="MainWindow:AboutButton">정보</sys:String>
<sys:String x:Key="MainWindow:OptionsButton">옵션</sys:String>
<sys:String x:Key="MainWindow:GameVersionLabel">게임 버전</sys:String>
<sys:String x:Key="MainWindow:VersionLabel">버전</sys:String>
<sys:String x:Key="MainWindow:ModInfoButton">모드 설명</sys:String>
<sys:String x:Key="MainWindow:InstallButtonTop">설치</sys:String>
<sys:String x:Key="MainWindow:InstallButtonBottom">또는 업데이트</sys:String>
<sys:String x:Key="MainWindow:GameVersionLoadFailed">게임 버전을 로드할 수 없었기 때문에 모드 탭이 비활성화됩니다.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Title">새로운 게임 버전이 감지되었습니다!</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line1">게임 업데이트가 있었던 것 같습니다.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line2">왼쪽 아래에 선택된 버전이 맞는 버전인지 다시 한번 확인해주세요!</sys:String>
<sys:String x:Key="MainWindow:NoModSelected">아무런 모드도 선택되지 않았습니다!</sys:String>
<sys:String x:Key="MainWindow:NoModInfoPage">{0}는 설명 페이지를 가지고 있지 않습니다.</sys:String>
<!-- Intro Page -->
<sys:String x:Key="Intro:Title">인트로</sys:String>
<sys:String x:Key="Intro:PageTitle">모드 어시스턴트에 어서오세요</sys:String>
<sys:String x:Key="Intro:Terms:Header">이 페이지를 정독해주세요</sys:String>
<Span x:Key="Intro:Terms:Line1">
이 프로그램을 사용하려면 다음 약관을 읽고 동의해야 합니다:
</Span>
<Span x:Key="Intro:Terms:Line2">
비트세이버는 공식적으로 모드를
<Bold>지원하지 않습니다.</Bold>
</Span>
<Span x:Key="Intro:Terms:Term1">
이것은 모드들이 매 업데이트마다
<Bold>망가진다는 것을</Bold> 의미합니다. 이것은 일반적이며, Beat Games'의 탓이
<Bold>아닙니다.</Bold>
</Span>
<Span x:Key="Intro:Terms:Term2">
모드들은 버그와 성능 문제를
<Bold>발생시킵니다.</Bold> 이것은 Beat Games'의 탓이
<Bold>아닙니다.</Bold>
</Span>
<Span x:Key="Intro:Terms:Term3">
모드들은
<Bold>무료로</Bold> 만들어졌으며 모더들의
<Bold>소중한 시간의 결과물입니다.</Bold>기다림을 갖고 이해해주세요.
</Span>
<Span x:Key="Intro:ReviewsBeatGamesFault">
모드가 망가진 것 때문에 게임에 대한 부정적인 의견을<Bold>남기지 마세요.</Bold> 이것은 Beat Games'의 탓이
<Bold>아닙니다.</Bold>
<LineBreak/> Beat Games'는 모드를 죽이려 하지 않습니다.
</Span>
<Span x:Key="Intro:ReviewsRustySpoon">
만일 사람들이 모드가
<Italic>망가진 것을 이유로</Italic> 부정적인 의견을 남기는 것을 계속 보게 된다면,
<LineBreak/>
<Bold>제가 개인적으로 모드를 터트릴지도요..?</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/pc-modding.html">
위키
</Hyperlink>에 있는 초보자 가이드를 읽어주세요.
</Span>
<sys:String x:Key="Intro:AgreeButton">동의합니다</sys:String>
<sys:String x:Key="Intro:DisagreeButton">거부합니다</sys:String>
<sys:String x:Key="Intro:ClosingApp">어플리케이션을 종료합니다: 당신은 약관에 동의하지 않았습니다.</sys:String>
<sys:String x:Key="Intro:VersionDownloadFailed">버전 리스트를 다운로드할 수 없었습니다</sys:String>
<sys:String x:Key="Intro:ModsTabDisabled">모드 탭이 비활성화되었습니다. 어시스턴트를 재시작해주세요.</sys:String>
<sys:String x:Key="Intro:ModsTabEnabled">이제 모드 탭을 사용할 수 있습니다!</sys:String>
<!-- Mods Page -->
<sys:String x:Key="Mods:Title">모드</sys:String>
<sys:String x:Key="Mods:Header:Name">이름</sys:String>
<sys:String x:Key="Mods:Header:Installed">설치 버전</sys:String>
<sys:String x:Key="Mods:Header:Latest">최신 버전</sys:String>
<sys:String x:Key="Mods:Header:Description">설명</sys:String>
<sys:String x:Key="Mods:Header:Uninstall">제거</sys:String>
<sys:String x:Key="Mods:UninstallButton">제거</sys:String>
<sys:String x:Key="Mods:LoadFailed">모드 목록을 불러올 수 없었습니다</sys:String>
<sys:String x:Key="Mods:CheckingInstalledMods">설치된 모드들을 확인하고 있습니다</sys:String>
<sys:String x:Key="Mods:LoadingMods">모드들을 불러오고 있습니다</sys:String>
<sys:String x:Key="Mods:FinishedLoadingMods">모드들을 불러왔습니다</sys:String>
<sys:String x:Key="Mods:InstallingMod">Installing {0}</sys:String>
<sys:String x:Key="Mods:InstalledMod">Installed {0}</sys:String>
<sys:String x:Key="Mods:FinishedInstallingMods">모드 설치를 마쳤습니다</sys:String>
<sys:String x:Key="Mods:ModDownloadLinkMissing">{0}를 위한 다운로드 링크를 찾을 수 없었습니다</sys:String>
<sys:String x:Key="Mods:UninstallBox:Title">{0}를 제거하시겠습니까?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1">정말로 {0}를 제거하시겠습니까?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body2">다른 모드를 사용 못하게 만들 수도 있습니다.</sys:String>
<sys:String x:Key="Mods:FailedExtract">{0}를 추출하는데 실패했습니다. {1}초 안에 재시도합니다. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">({1})회동안 {0}를 추출하는데 실패했습니다. 이 모드가 제대로 작동하지 않을지도 모릅니다</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String> <!-- NEEDS TRANSLATING -->
<!-- About Page -->
<sys:String x:Key="About:Title">정보</sys:String>
<sys:String x:Key="About:PageTitle">모드 어시스턴트에 대하여</sys:String>
<sys:String x:Key="About:List:Header">I'm Assistant, and I made Mod Assistant for mod assistance, with a few principles in mind:</sys:String>
<sys:String x:Key="About:List:Item1">Simplicity</sys:String>
<sys:String x:Key="About:List:Item2">Portability</sys:String>
<sys:String x:Key="About:List:Item3">Single Executable</sys:String>
<sys:String x:Key="About:List:Item4">Responsible use</sys:String>
<Span x:Key="About:SupportAssistant">
If you enjoy this program and would like to support me, please visit my
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs.assistant.moe/Donate/">
donation page
</Hyperlink>
or my
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://www.patreon.com/AssistantMoe">
Patreon
</Hyperlink>
</Span>
<sys:String x:Key="About:SpecialThanks">Special Thanks ♥</sys:String>
<sys:String x:Key="About:Donate">Donate</sys:String>
<sys:String x:Key="About:HeadpatsButton">Headpats</sys:String>
<sys:String x:Key="About:HugsButton">Hugs</sys:String>
<!-- Options Page -->
<sys:String x:Key="Options:Title">옵션</sys:String>
<sys:String x:Key="Options:PageTitle">설정</sys:String>
<sys:String x:Key="Options:InstallFolder">설치 폴더</sys:String>
<sys:String x:Key="Options:SelectFolderButton">폴더 선택</sys:String>
<sys:String x:Key="Options:OpenFolderButton">폴더 열기</sys:String>
<sys:String x:Key="Options:SaveSelectedMods">선택된 모드 저장</sys:String>
<sys:String x:Key="Options:CheckInstalledMods">설치된 모드 감지</sys:String>
<sys:String x:Key="Options:SelectInstalledMods">설치된 모드 선택</sys:String>
<sys:String x:Key="Options:ReinstallInstalledMods">설치된 모드 재설치</sys:String>
<sys:String x:Key="Options:EnableOneClickInstalls">OneClick™ 설치 활성화</sys:String>
<sys:String x:Key="Options:BeatSaver">BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">ModelSaber</sys:String>
<sys:String x:Key="Options:GameType">게임 유형</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Diagnostics">진단</sys:String>
<sys:String x:Key="Options:OpenLogsButton">로그 열기</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">앱데이터 열기</sys:String>
<sys:String x:Key="Options:UninstallBSIPAButton">BSIPA 삭제</sys:String>
<sys:String x:Key="Options:RemoveAllModsButton">모든 모드 삭제</sys:String>
<sys:String x:Key="Options:ApplicationTheme">어플리케이션 테마</sys:String>
<sys:String x:Key="Options:ExportTemplateButton">템플릿 추출</sys:String>
<sys:String x:Key="Options:UploadingLog">로그 제출</sys:String>
<sys:String x:Key="Options:LogUrlCopied">로그 URL이 클립보드에 복사되었습니다!</sys:String>
<sys:String x:Key="Options:LogUploadFailed">로그 제출에 실패하였습니다</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Title">로그 제출에 실패하였습니다!</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Body">Teknik에게 로그 파일을 제출하는데에 실패하였습니다. 재시도하거나 수동으로 파일을 보내주세요.</sys:String>
<sys:String x:Key="Options:GettingModList">모드 목록을 얻는중입니다</sys:String>
<sys:String x:Key="Options:FindingBSIPAVersion">BSIPA 버전을 찾는중입니다</sys:String>
<sys:String x:Key="Options:BSIPAUninstalled">BSIPA가 제거되었습니다</sys:String>
<sys:String x:Key="Options:YeetModsBox:Title">모든 모드를 제거할까요?</sys:String>
<sys:String x:Key="Options:YeetModsBox:RemoveAllMods">정말로 모든 모드를 제거할까요?</sys:String>
<sys:String x:Key="Options:YeetModsBox:CannotBeUndone">이것은 취소할 수 없습니다.</sys:String>
<sys:String x:Key="Options:AllModsUninstalled">모든 모드가 제거되었습니다</sys:String>
<sys:String x:Key="Options:CurrentThemeRemoved">현재 테마를 제거중입니다. 원래 테마로 돌아갑니다...</sys:String>
<sys:String x:Key="Options:ThemeFolderNotFound">테마 폴더를 찾을 수 없습니다! 템플릿으로 내보냅니다...</sys:String>
<sys:String x:Key="Options:AppDataNotFound">AppData 폴더를 찾을 수 없습니다! 게임을 실행해보세요.</sys:String>
<!-- Loading Page -->
<sys:String x:Key="Loading:Loading">모드 로딩중</sys:String>
<!-- Invalid Page -->
<sys:String x:Key="Invalid:Title">잘못된 프로그램</sys:String>
<sys:String x:Key="Invalid:PageTitle">잘못된 프로그램 설치가 감지되었습니다</sys:String>
<sys:String x:Key="Invalid:PageSubtitle">게임이 손상되었거나 다른 이유로 잘못된 것 같습니다</sys:String>
<sys:String x:Key="Invalid:List:Header">이 오류는 게임이 불법적인 경로로 받아졌거나, 불법적인 경로로 받아진 게임을 당신의 정상적인 게임에 덮어씌워졌을 때에 발생합니다</sys:String>
<Span x:Key="Invalid:List:Line1">
만일 당신이 게임을 불법적인 경로로 받았다면,
<Bold>
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
여기서
</Hyperlink>
</Bold>게임을 구매해주세요.
</Span>
<Span x:Key="Invalid:List:Line2">
만일 당신의 게임이 불법적인 경로로 받아진게
<Bold>아니라면</Bold>,
<Hyperlink NavigateUri="https://bsmg.wiki/support#clean-installation" local:HyperlinkExtensions.IsExternal="True">
클린재설치
</Hyperlink>를 해주세요.
</Span>
<Span x:Key="Invalid:List:Line3">
만일 그것들이 도움되지 않았다면,
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
BSMG
</Hyperlink>의
<Span Foreground="Blue">#support</Span> 채널에서 도움을 구하세요.
</Span>
<sys:String x:Key="Invalid:BoughtGame1">만일 불법적인 경로로 받아진 게임을 가지고 있었다가 게임을 구매했다면,</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">폴더를 선택해주세요</sys:String>
<sys:String x:Key="Invalid:BoughtGame2">정상적인 설치 이후 모드 어시스턴트를 재시작할 필요가 있습니다</sys:String>
<!-- OneClick Class -->
<sys:String x:Key="OneClick:MapDownloadFailed">맵의 세부정보를 얻어올 수 없었습니다.</sys:String>
<sys:String x:Key="OneClick:SongDownloadFailed">곡을 받아올 수 없었습니다.</sys:String>
<sys:String x:Key="OneClick:SongDownload:Failed">곡을 받아올 수 없었습니다.</sys:String>
<sys:String x:Key="OneClick:SongDownload:NetworkIssues">BeatSaver 또는 당신의 인터넷 연결에 문제가 있는 것 같습니다.</sys:String>
<sys:String x:Key="OneClick:SongDownload:FailedTitle">노래 ZIP 파일을 받는 데에 실패했습니다.</sys:String>
<sys:String x:Key="OneClick:InstallDirNotFound">비트세이버 설치 폴더를 찾을 수 없었습니다.</sys:String>
<sys:String x:Key="OneClick:InstalledAsset">설치됨: {0}</sys:String>
<sys:String x:Key="OneClick:AssetInstallFailed">설치에 실패하였습니다.</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick™ 설치 관리자가 등록되었습니다!</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick™ 설치 관리자가 등록 취소되었습니다!</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">테마를 찾을 수 없어, 기본 테마로 돌아갑니다...</sys:String>
<sys:String x:Key="Themes:ThemeSet">{0} 테마로 설정합니다.</sys:String>
<sys:String x:Key="Themes:ThemeMissing">{0}는 존재하지 않습니다.</sys:String>
<sys:String x:Key="Themes:SavedTemplateTheme">템플릿 테마 &quot;{0}&quot;가 템플릿 폴더에 저장되었습니다.</sys:String>
<sys:String x:Key="Themes:TemplateThemeExists">템플릿 테마가 이미 존재합니다!</sys:String>
<sys:String x:Key="Themes:FailedToLoadXaml">테마를 위한 .xaml 파일을 불러오지 못했습니다 {0}: {1}</sys:String>
<!-- Updater Class -->
<sys:String x:Key="Updater:CheckFailed">업데이트를 확인할 수 없습니다.</sys:String>
<sys:String x:Key="Updater:DownloadFailed">업데이트를 다운로드할 수 없습니다.</sys:String>
<!-- Utils Class -->
<sys:String x:Key="Utils:NotificationTitle">모드 어시스턴트</sys:String>
<sys:String x:Key="Utils:NoInstallFolder">비트세이버 설치 폴더를 찾을 수 없습니다. 수동으로 선택해주세요.</sys:String>
<sys:String x:Key="Utils:RunAsAdmin">모드 어시스턴트는 이 작업을 관리자 권한으로 실행하는 것을 필요로 합니다. 다시 시도해주세요.</sys:String>
<sys:String x:Key="Utils:InstallDir:DialogTitle">비트세이버 설치 폴더를 선택해주세요</sys:String>
<sys:String x:Key="Utils:CannotOpenFolder">폴더를 열 수 없습니다: {0}</sys:String>
</ResourceDictionary>

View file

@ -1,236 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModAssistant"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ResourceDictionaryName">i18n:en-US</sys:String>
<!-- App -->
<sys:String x:Key="App:InstallDirDialog:Title">Beat Saber installatie map niet gevonden!</sys:String>
<sys:String x:Key="App:InstallDirDialog:OkCancel">Klik OK om opnieuw te proberen, of Annuleren om de applicatie af te sluiten.</sys:String>
<sys:String x:Key="App:InvalidArgument">Ongeldig argument! '{0}' heeft een optie nodig.</sys:String>
<sys:String x:Key="App:UnrecognizedArgument">Niet herkend argument. Mod Assistant sluit af.</sys:String>
<sys:String x:Key="App:UnhandledException">Een onverwerkte foutcode is zojuist opgetreden</sys:String>
<sys:String x:Key="App:Exception">Foutcode</sys:String>
<!-- Main Window -->
<sys:String x:Key="MainWindow:WindowTitle">ModAssistant</sys:String>
<sys:String x:Key="MainWindow:IntroButton">Introductie</sys:String>
<sys:String x:Key="MainWindow:ModsButton">Mods</sys:String>
<sys:String x:Key="MainWindow:AboutButton">Over</sys:String>
<sys:String x:Key="MainWindow:OptionsButton">Opties</sys:String>
<sys:String x:Key="MainWindow:GameVersionLabel">Game Versie</sys:String>
<sys:String x:Key="MainWindow:VersionLabel">Versie</sys:String>
<sys:String x:Key="MainWindow:ModInfoButton">Mod Info</sys:String>
<sys:String x:Key="MainWindow:InstallButtonTop">Installeren</sys:String>
<sys:String x:Key="MainWindow:InstallButtonBottom">of Update</sys:String>
<sys:String x:Key="MainWindow:GameVersionLoadFailed">Kan de game versie niet laden, Mods tabblad zal onbeschikbaar zijn.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Title">Nieuwe Game Versie Gedetecteerd!</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line1">Het lijkt erop dat er een game update is geweest.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line2">Controleer alstublieft of de correcte versie geselecteerd is linksonder in de hoek</sys:String>
<sys:String x:Key="MainWindow:NoModSelected">Geen mod geselecteerd!</sys:String>
<sys:String x:Key="MainWindow:NoModInfoPage">{0} heeft geen info pagina</sys:String>
<!-- Introductie Pagina -->
<sys:String x:Key="Intro:Title">Introductie</sys:String>
<sys:String x:Key="Intro:PageTitle">Welkom bij Mod Assistant</sys:String>
<sys:String x:Key="Intro:Terms:Header">Lees deze pagina alstublieft volledig en aandachtig</sys:String>
<Span x:Key="Intro:Terms:Line1">
Door het gebruiken van dit programma verlkaar ik de volgende voorwaarden te hebben gelezen en hier mee akkoord te gaan:
</Span>
<Span x:Key="Intro:Terms:Line2">
Beat Saber
Ondersteund mods <Bold>niet</Bold> van zichzelf, dit betekent dat:
</Span>
<Span x:Key="Intro:Terms:Term1">
Mods
Elke update <Bold>niet meer werken</Bold>, dit is normaal en <Bold>niet</Bold> de fout van Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term2">
Mods
<Bold>zullen</Bold> bugs en prestatievermindering veroorzaken. Dit is <Bold>niet</Bold> een fout van Beat Games.
</Span>
<Span x:Key="Intro:Terms:Term3">
Mods worden
<Bold>gratis</Bold> gemaakt door mensen in hun
<Bold>vrije tijd.</Bold> Wees alstublieft geduldig en begripvol.
</Span>
<Span x:Key="Intro:ReviewsBeatGamesFault">
Laat <Bold>GEEN</Bold> negatieve beoordelingen achter op beat saber omdat mods niet meer werken. Dit is
<Bold>niet</Bold> de fout van Beat Games.
<LineBreak/> Ze proberen niet mods ontoegankelijk te maken.
</Span>
<Span x:Key="Intro:ReviewsRustySpoon">
Als ik blijf zien dat mensen negatieve reviews achterlaten
<Italic>omdat</Italic> mods niet meer werken,
<LineBreak/>
<Bold>zal ik persoonlijk met een roestige lepel mods niet meer laten werken</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
Lees alstublieft de 'Beginners Guide' op de wiki
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/pc-modding.html">
Wiki
</Hyperlink>. (engels)
</Span>
<sys:String x:Key="Intro:AgreeButton">Accepteer</sys:String>
<sys:String x:Key="Intro:DisagreeButton">Accepteer niet</sys:String>
<sys:String x:Key="Intro:ClosingApp">Sluit applicatie af: U accepteerde de voorwaarden niet.</sys:String>
<sys:String x:Key="Intro:VersionDownloadFailed">Kon de versie lijst niet downloaden</sys:String>
<sys:String x:Key="Intro:ModsTabDisabled">Mods tabblad uitgeschakeld. Herstart het programma om opnieuw te proberen.</sys:String>
<sys:String x:Key="Intro:ModsTabEnabled">U kan nu het mods tabblad gebruiken!</sys:String>
<!-- Mods Page -->
<sys:String x:Key="Mods:Title">Mods</sys:String>
<sys:String x:Key="Mods:Header:Name">Naam</sys:String>
<sys:String x:Key="Mods:Header:Installed">Geïnstalleerd</sys:String>
<sys:String x:Key="Mods:Header:Latest">Recentst</sys:String>
<sys:String x:Key="Mods:Header:Description">Beschrijving</sys:String>
<sys:String x:Key="Mods:Header:Uninstall">Deïnstalleer</sys:String>
<sys:String x:Key="Mods:UninstallButton">Deïnstalleer</sys:String>
<sys:String x:Key="Mods:LoadFailed">Kon de mod lijst niet laden</sys:String>
<sys:String x:Key="Mods:CheckingInstalledMods">Geïnstalleerde mods controleren</sys:String>
<sys:String x:Key="Mods:LoadingMods">Mods Laden</sys:String>
<sys:String x:Key="Mods:FinishedLoadingMods">Klaar met mods laden</sys:String>
<sys:String x:Key="Mods:InstallingMod">{0} wordt geïnstalleerd</sys:String>
<sys:String x:Key="Mods:InstalledMod">{0} is geïnstalleerd</sys:String>
<sys:String x:Key="Mods:FinishedInstallingMods">Klaar met mods installeren</sys:String>
<sys:String x:Key="Mods:ModDownloadLinkMissing">Kon de download link voor {0} niet vinden</sys:String>
<sys:String x:Key="Mods:UninstallBox:Title">{0} deïnstalleren?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1"> Weet U zeker dat U {0} wilt verwijderen?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body2">Dit zou uw andere mods niet meer kunnen laten werken</sys:String>
<sys:String x:Key="Mods:FailedExtract">Kon {0} niet extracten, probeer opniew over {1} seconden. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">Kon {0} niet extracten na maximaal aantal pogingen ({1}), Overslaan. Deze mod werkt msischien niet goed dus ga verder op eigen risico</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String> <!-- NEEDS TRANSLATING -->
<!-- About Page -->
<sys:String x:Key="About:Title">Over</sys:String>
<sys:String x:Key="About:PageTitle">Over Mod Assistant</sys:String>
<sys:String x:Key="About:List:Header">Ik ben Assistant, en ik heb Mod Assistant gemaakt om te assisteren met mods, met een aantal principes als basis:</sys:String>
<sys:String x:Key="About:List:Item1">Eenvoud</sys:String>
<sys:String x:Key="About:List:Item2">Draagbaarheid</sys:String>
<sys:String x:Key="About:List:Item3">Één enkel uitvoerbaar bestand</sys:String>
<sys:String x:Key="About:List:Item4">Verantwoordelijk gebruik</sys:String>
<Span x:Key="About:SupportAssistant">
Als U dit programma nuttig vind en mij graag wilt steunen, ga dan naar mijn
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs.assistant.moe/Donate/">
donatie pagina
</Hyperlink>
of mijn
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://www.patreon.com/AssistantMoe">
Patreon
</Hyperlink>
</Span>
<sys:String x:Key="About:SpecialThanks">Bijzondere dank ♥</sys:String>
<sys:String x:Key="About:Donate">Doneer</sys:String>
<sys:String x:Key="About:HeadpatsButton">Tik op hoofd</sys:String>
<sys:String x:Key="About:HugsButton">Knuffels</sys:String>
<!-- Options Page -->
<sys:String x:Key="Options:Title">Opties</sys:String>
<sys:String x:Key="Options:PageTitle">Instellingen</sys:String>
<sys:String x:Key="Options:InstallFolder">Installatie map</sys:String>
<sys:String x:Key="Options:SelectFolderButton">Selecteer map</sys:String>
<sys:String x:Key="Options:OpenFolderButton">Open map</sys:String>
<sys:String x:Key="Options:SaveSelectedMods">Sla geselecteerde mods op</sys:String>
<sys:String x:Key="Options:CheckInstalledMods">Detecteer geïnstalleerde mods</sys:String>
<sys:String x:Key="Options:SelectInstalledMods">Selecteer geïnstalleerde mods</sys:String>
<sys:String x:Key="Options:ReinstallInstalledMods">Geïnstalleerde mods herinstalleren</sys:String>
<sys:String x:Key="Options:EnableOneClickInstalls">Activeer OneClick™ Installaties</sys:String>
<sys:String x:Key="Options:BeatSaver">BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">ModelSaber</sys:String>
<sys:String x:Key="Options:GameType">Game Type</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Diagnostics">Diagnostiek</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Open Logs</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Open AppData</sys:String>
<sys:String x:Key="Options:UninstallBSIPAButton">Deïnstalleer BSIPA</sys:String>
<sys:String x:Key="Options:RemoveAllModsButton">Verwijder Alle Mods</sys:String>
<sys:String x:Key="Options:ApplicationTheme">Applicatie thema</sys:String>
<sys:String x:Key="Options:ExportTemplateButton">Exporteer sjabloon</sys:String>
<sys:String x:Key="Options:UploadingLog">Log aan het uploaden</sys:String>
<sys:String x:Key="Options:LogUrlCopied">Log URL gekopieerd naar klembord!</sys:String>
<sys:String x:Key="Options:LogUploadFailed">Log Upload Mislukt</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Title">Log upload mislukt!</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Body">Kon het log bestand niet uploaden naar teknik, probeer alstublieft opnieuw of upload handmatig.</sys:String>
<sys:String x:Key="Options:GettingModList">Mod Lijst Ophalen</sys:String>
<sys:String x:Key="Options:FindingBSIPAVersion">BSIPA versie vinden</sys:String>
<sys:String x:Key="Options:BSIPAUninstalled">BSIPA Gedeïnstalleerd</sys:String>
<sys:String x:Key="Options:YeetModsBox:Title">Deïnstalleer ALLE mods?</sys:String>
<sys:String x:Key="Options:YeetModsBox:RemoveAllMods">Weet U zeker dat U ALLE mods wilt deïnstalleren?</sys:String>
<sys:String x:Key="Options:YeetModsBox:CannotBeUndone">Dit kan niet ongedaan gemaakt worden.</sys:String>
<sys:String x:Key="Options:AllModsUninstalled">Alle mods gedeïnstalleerd</sys:String>
<sys:String x:Key="Options:CurrentThemeRemoved">Huidig thema is verwijderd, terugvallen op standaard...</sys:String>
<sys:String x:Key="Options:ThemeFolderNotFound">Thema map niet gevonden! probeer het sjabloon te exporteren...</sys:String>
<sys:String x:Key="Options:AppDataNotFound">Appdata map niet gevonden! probeer Uw spel te starten.</sys:String>
<!-- Loading Page -->
<sys:String x:Key="Loading:Loading">Mods Laden</sys:String>
<!-- Invalid Page -->
<sys:String x:Key="Invalid:Title">Ongeldig</sys:String>
<sys:String x:Key="Invalid:PageTitle">Ongeldige Installatie Gedetecteerd</sys:String>
<sys:String x:Key="Invalid:PageSubtitle">Uw game installatie is corrupt of ongeldig</sys:String>
<sys:String x:Key="Invalid:List:Header">Dit kan gebeuren als U een gepirateerde versie heeft, of een gepirateerde versie over de legitieme versie heeft gekopieerd</sys:String>
<Span x:Key="Invalid:List:Line1">
Als Uw game versie is gepirateerd,
<Bold>Koop alstublieft de game
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
HIER
</Hyperlink>
</Bold>.
</Span>
<Span x:Key="Invalid:List:Line2">
Als Uw game versie
<Bold>niet</Bold> gepirateerd is, doe dan alstublieft
<Hyperlink NavigateUri="https://bsmg.wiki/support#clean-installation" local:HyperlinkExtensions.IsExternal="True">
een "schone" installatie
</Hyperlink>.
</Span>
<Span x:Key="Invalid:List:Line3">
Als dat allebei niet helpt, vraag om hulp in de
<Span Foreground="Blue">#support</Span> channel in
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
BSMG
</Hyperlink>. (Engels)
</Span>
<sys:String x:Key="Invalid:BoughtGame1">Als U een gepirateerde versie van het spel had maar nu het spel hebt gekocht</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">Selecteer map</sys:String>
<sys:String x:Key="Invalid:BoughtGame2">Dan moet U Mod Assistant opnieuw starten na het wisselen naar de legitieme installatie</sys:String>
<!-- OneClick Class -->
<sys:String x:Key="OneClick:MapDownloadFailed">Kon de Map details niet vinden.</sys:String>
<sys:String x:Key="OneClick:SongDownloadFailed">Kon het nummer niet downloaden.</sys:String>
<sys:String x:Key="OneClick:SongDownload:Failed">Kon het nummer niet downlaoden.</sys:String>
<sys:String x:Key="OneClick:SongDownload:NetworkIssues">Er kunnen problemen zijn met BeatSaver of Uw internet verbinding.</sys:String>
<sys:String x:Key="OneClick:SongDownload:FailedTitle">Kon de ZIP van het nummer niet downloaden</sys:String>
<sys:String x:Key="OneClick:InstallDirNotFound">Kon het Beat Saber installatie pad niet vinden</sys:String>
<sys:String x:Key="OneClick:InstalledAsset">{0} Geïnstalleerd</sys:String>
<sys:String x:Key="OneClick:AssetInstallFailed">Installatie mislukt</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick™ Installeer afhandelingen geregistreerd!</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick™ Install afhandelingen uitgeregistreerd!</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">Thema niet gevonden, terugvallen op standaard thema...</sys:String>
<sys:String x:Key="Themes:ThemeSet">Theme ingesteld op {0}.</sys:String>
<sys:String x:Key="Themes:ThemeMissing">{0} bestaat niet.</sys:String>
<sys:String x:Key="Themes:SavedTemplateTheme">Thema sjabloon &quot;{0}&quot; opgeslagen in de thema map.</sys:String>
<sys:String x:Key="Themes:TemplateThemeExists">Thema sjabloon bestaat al!</sys:String>
<sys:String x:Key="Themes:FailedToLoadXaml">.xaml bestand laden voor {0} mislukt: {1}</sys:String>
<!-- Updater Class -->
<sys:String x:Key="Updater:CheckFailed">Kon niet controleren voor updates</sys:String>
<sys:String x:Key="Updater:DownloadFailed">Kon update niet downloaden</sys:String>
<!-- Utils Class -->
<sys:String x:Key="Utils:NotificationTitle">Mod Assistant</sys:String>
<sys:String x:Key="Utils:NoInstallFolder">Kon Uw Beat Saber installatie map niet vinden. Selecteer deze alstublieft handmatig.</sys:String>
<sys:String x:Key="Utils:RunAsAdmin">Mod Assistant moet deze taak als adminstrator uitvoeren. Probeer alstublieft opnieuw.</sys:String>
<sys:String x:Key="Utils:InstallDir:DialogTitle">Selecteer Uw Beat Saber installatie map</sys:String>
<sys:String x:Key="Utils:CannotOpenFolder">Kan map niet openen: {0}</sys:String>
</ResourceDictionary>

View file

@ -1,229 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModAssistant"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ResourceDictionaryName">i18n:zh-Hans</sys:String>
<!-- App -->
<sys:String x:Key="App:InstallDirDialog:Title">找不到您的Beat Saber安装路径</sys:String>
<sys:String x:Key="App:InstallDirDialog:OkCancel">点确定重试,或点取消关闭软件。</sys:String>
<sys:String x:Key="App:InvalidArgument">无效参数!'{0}'需要一个选项。</sys:String>
<sys:String x:Key="App:UnrecognizedArgument">无法识别的参数。关闭Mod Assistant。</sys:String>
<sys:String x:Key="App:UnhandledException">刚刚发生了一个未处理的异常</sys:String>
<sys:String x:Key="App:Exception">例外</sys:String>
<!-- Main Window -->
<sys:String x:Key="MainWindow:WindowTitle">ModAssistant</sys:String>
<sys:String x:Key="MainWindow:IntroButton">简介</sys:String>
<sys:String x:Key="MainWindow:ModsButton">Mod</sys:String>
<sys:String x:Key="MainWindow:AboutButton">关于</sys:String>
<sys:String x:Key="MainWindow:OptionsButton">选项</sys:String>
<sys:String x:Key="MainWindow:GameVersionLabel">游戏版本</sys:String>
<sys:String x:Key="MainWindow:VersionLabel">软件版本</sys:String>
<sys:String x:Key="MainWindow:ModInfoButton">Mod信息</sys:String>
<sys:String x:Key="MainWindow:InstallButtonTop">安装</sys:String>
<sys:String x:Key="MainWindow:InstallButtonBottom">或更新</sys:String>
<sys:String x:Key="MainWindow:GameVersionLoadFailed">无法获取游戏版本Mod选项卡将不可用。</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Title">检测到新的游戏版本!</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line1">似乎游戏更新了,</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line2">请仔细检查左下角是否选择了正确的游戏版本!</sys:String>
<sys:String x:Key="MainWindow:NoModSelected">没有选择Mod</sys:String>
<sys:String x:Key="MainWindow:NoModInfoPage">{0}没有信息页。</sys:String>
<!-- Intro Page -->
<sys:String x:Key="Intro:Title">简介</sys:String>
<sys:String x:Key="Intro:PageTitle">欢迎使用Mod Assistant</sys:String>
<sys:String x:Key="Intro:Terms:Header">请仔细阅读本页</sys:String>
<Span x:Key="Intro:Terms:Line1">
通过使用此程序,证明您已阅读并同意以下条款:
</Span>
<Span x:Key="Intro:Terms:Line2">
Beat Saber<Bold>并不能原生支持Mod</Bold>。 这意味着:
</Span>
<Span x:Key="Intro:Terms:Term1">
<Bold>Mod将会在每次游戏更新后无法使用。这很正常并不是Beat Games的错。</Bold>
</Span>
<Span x:Key="Intro:Terms:Term2">
Mod会导致出现BUG或者性能问题。这并不是Beat Games的错。
</Span>
<Span x:Key="Intro:Terms:Term3">
爱好者在空闲时间<Bold>用爱发电</Bold>制作了这些Mod请保持耐心等待Mod更新。
</Span>
<Span x:Key="Intro:ReviewsBeatGamesFault">
<Bold>请勿由于Mod不可用而发表差评Beat Games不会封杀Mod。</Bold>
</Span>
<Span x:Key="Intro:ReviewsRustySpoon">
如果我继续看到因为Mod不可用而留下的差评<Bold>我会亲自干掉Mod。</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
<Bold><Span Foreground="Red">请务必阅读</Span>
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs-wgzeyu.gtxcn.com/">
BS中文教程及常见问题解答
</Hyperlink>
</Bold>,以及
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/pc-modding.html">
新手指南(英文)
</Hyperlink>。
</Span>
<sys:String x:Key="Intro:AgreeButton">同意</sys:String>
<sys:String x:Key="Intro:DisagreeButton">拒绝</sys:String>
<sys:String x:Key="Intro:ClosingApp">关闭软件:您不同意此条款。</sys:String>
<sys:String x:Key="Intro:VersionDownloadFailed">无法下载版本列表</sys:String>
<sys:String x:Key="Intro:ModsTabDisabled">已禁用Mod选项卡请尝试重新打开软件。</sys:String>
<sys:String x:Key="Intro:ModsTabEnabled">你现在可以使用Mod选项卡了</sys:String>
<!-- Mods Page -->
<sys:String x:Key="Mods:Title">Mod</sys:String>
<sys:String x:Key="Mods:Header:Name">名称</sys:String>
<sys:String x:Key="Mods:Header:Installed">已安装</sys:String>
<sys:String x:Key="Mods:Header:Latest">最新</sys:String>
<sys:String x:Key="Mods:Header:Description">介绍</sys:String>
<sys:String x:Key="Mods:Header:Uninstall">卸载</sys:String>
<sys:String x:Key="Mods:UninstallButton">卸载</sys:String>
<sys:String x:Key="Mods:LoadFailed">无法加载Mod列表</sys:String>
<sys:String x:Key="Mods:CheckingInstalledMods">正在检测已安装的Mod</sys:String>
<sys:String x:Key="Mods:LoadingMods">正在加载Mod列表</sys:String>
<sys:String x:Key="Mods:FinishedLoadingMods">Mod列表加载完成</sys:String>
<sys:String x:Key="Mods:InstallingMod">正在安装{0}</sys:String>
<sys:String x:Key="Mods:InstalledMod">已安装{0}</sys:String>
<sys:String x:Key="Mods:FinishedInstallingMods">Mod安装完成</sys:String>
<sys:String x:Key="Mods:ModDownloadLinkMissing">{0}找不到下载地址!</sys:String>
<sys:String x:Key="Mods:UninstallBox:Title">卸载{0}</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1">你确定要移除{0}</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body2">这可能会导致其他Mod不可用。</sys:String>
<sys:String x:Key="Mods:FailedExtract">{0}解压失败,将在{1}秒后重试。({2}/{3}</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">{0}在重试{1}次后仍然无法解压将被跳过。注意这个Mod可能无法使用。</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String> <!-- NEEDS TRANSLATING -->
<!-- About Page -->
<sys:String x:Key="About:Title">关于</sys:String>
<sys:String x:Key="About:PageTitle">关于Mod Assistant</sys:String>
<sys:String x:Key="About:List:Header">我是Assistant为了方便安装MOD我制作了Mod Assistant。有以下特性</sys:String>
<sys:String x:Key="About:List:Item1">简单易用</sys:String>
<sys:String x:Key="About:List:Item2">可移植性</sys:String>
<sys:String x:Key="About:List:Item3">单一文件</sys:String>
<sys:String x:Key="About:List:Item4">负责任地使用</sys:String>
<Span x:Key="About:SupportAssistant">
如果你喜欢这个项目并且想支持我,请访问我的
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs.assistant.moe/Donate/">
捐助页面
</Hyperlink>
或我的
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://www.patreon.com/AssistantMoe">
Patreon
</Hyperlink>
</Span>
<sys:String x:Key="About:SpecialThanks">特别感谢 ♥</sys:String>
<sys:String x:Key="About:Donate">捐助</sys:String>
<sys:String x:Key="About:HeadpatsButton">摸摸头</sys:String>
<sys:String x:Key="About:HugsButton">抱抱</sys:String>
<!-- Options Page -->
<sys:String x:Key="Options:Title">选项</sys:String>
<sys:String x:Key="Options:PageTitle">设置</sys:String>
<sys:String x:Key="Options:InstallFolder">安装路径</sys:String>
<sys:String x:Key="Options:SelectFolderButton">选择路径</sys:String>
<sys:String x:Key="Options:OpenFolderButton">打开路径</sys:String>
<sys:String x:Key="Options:SaveSelectedMods">保存选中的Mod</sys:String>
<sys:String x:Key="Options:CheckInstalledMods">检查已安装的Mod</sys:String>
<sys:String x:Key="Options:SelectInstalledMods">选中已安装的Mod</sys:String>
<sys:String x:Key="Options:ReinstallInstalledMods">重新安装已有Mod</sys:String>
<sys:String x:Key="Options:EnableOneClickInstalls">在以下站点启用OneClick™一键安装</sys:String>
<sys:String x:Key="Options:BeatSaver">BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">ModelSaber</sys:String>
<sys:String x:Key="Options:GameType">游戏类型</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String> <!-- NEEDS TRANSLATING -->
<sys:String x:Key="Options:Diagnostics">诊断工具</sys:String>
<sys:String x:Key="Options:OpenLogsButton">打开日志</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">打开游戏存档</sys:String>
<sys:String x:Key="Options:UninstallBSIPAButton">卸载BSIPA</sys:String>
<sys:String x:Key="Options:RemoveAllModsButton">移除所有Mod</sys:String>
<sys:String x:Key="Options:ApplicationTheme">软件主题</sys:String>
<sys:String x:Key="Options:ExportTemplateButton">导出模板</sys:String>
<sys:String x:Key="Options:UploadingLog">正在上传日志</sys:String>
<sys:String x:Key="Options:LogUrlCopied">日志网址已复制到剪贴板!</sys:String>
<sys:String x:Key="Options:LogUploadFailed">日志上传完成!</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Title">日志上传失败!</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Body">无法将日志文件上传到Teknik请重试或手动发送文件。</sys:String>
<sys:String x:Key="Options:GettingModList">正在获取Mod列表</sys:String>
<sys:String x:Key="Options:FindingBSIPAVersion">正在检查BSIPA版本</sys:String>
<sys:String x:Key="Options:BSIPAUninstalled">已卸载BSIPA</sys:String>
<sys:String x:Key="Options:YeetModsBox:Title">卸载所有Mod</sys:String>
<sys:String x:Key="Options:YeetModsBox:RemoveAllMods">你确定要移除所有Mod</sys:String>
<sys:String x:Key="Options:YeetModsBox:CannotBeUndone">这将无法撤销。</sys:String>
<sys:String x:Key="Options:AllModsUninstalled">已卸载所有Mod</sys:String>
<sys:String x:Key="Options:CurrentThemeRemoved">当前主题已被删除,恢复为默认...</sys:String>
<sys:String x:Key="Options:ThemeFolderNotFound">找不到主题文件夹!请尝试导出模板</sys:String>
<sys:String x:Key="Options:AppDataNotFound">找不到游戏存档路径!请尝试启动游戏后重试。</sys:String>
<!-- Loading Page -->
<sys:String x:Key="Loading:Loading">正在加载Mod...</sys:String>
<!-- Invalid Page -->
<sys:String x:Key="Invalid:Title">无效</sys:String>
<sys:String x:Key="Invalid:PageTitle">检测到无效的安装</sys:String>
<sys:String x:Key="Invalid:PageSubtitle">您的游戏安装已损坏或无效</sys:String>
<sys:String x:Key="Invalid:List:Header">如果您的游戏是盗版的,或者您使用盗版游戏替换了正版,则可能会发生这种情况。</sys:String>
<Span x:Key="Invalid:List:Line1">
如果您的游戏是盗版的,
<Bold>请在
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
这里
</Hyperlink>
</Bold>购买游戏。
</Span>
<Span x:Key="Invalid:List:Line2">
如果您的游戏
<Bold>不是盗版</Bold>,请
<Hyperlink NavigateUri="https://bsmg.wiki/support#clean-installation" local:HyperlinkExtensions.IsExternal="True">
清洁安装
</Hyperlink>。
</Span>
<Span x:Key="Invalid:List:Line3">
如果这些方法没有帮助请在BSMG的
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
Discord
</Hyperlink><Span Foreground="Blue">#support</Span>频道寻求帮助。
</Span>
<sys:String x:Key="Invalid:BoughtGame1">如果您曾经使用盗版,但之后购买了正版游戏</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">选择路径</sys:String>
<sys:String x:Key="Invalid:BoughtGame2">更改为正版游戏之后您需要重新启动Mod Assistant</sys:String>
<!-- OneClick Class -->
<sys:String x:Key="OneClick:MapDownloadFailed">无法获取地图详情。</sys:String>
<sys:String x:Key="OneClick:SongDownloadFailed">无法下载歌曲。</sys:String>
<sys:String x:Key="OneClick:SongDownload:Failed">无法下载歌曲。</sys:String>
<sys:String x:Key="OneClick:SongDownload:NetworkIssues">可能您的互联网连接或BeatSaver存在问题。</sys:String>
<sys:String x:Key="OneClick:SongDownload:FailedTitle">下载歌曲压缩包失败。</sys:String>
<sys:String x:Key="OneClick:InstallDirNotFound">找不到Beat Saber安装路径。</sys:String>
<sys:String x:Key="OneClick:InstalledAsset">已安装:{0}</sys:String>
<sys:String x:Key="OneClick:AssetInstallFailed">安装失败。</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick™ 一键安装处理程序已注册!</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick™ 一键安装处理程序已移除!</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">找不到主题,恢复为默认主题...</sys:String>
<sys:String x:Key="Themes:ThemeSet">主题设置为{0}.</sys:String>
<sys:String x:Key="Themes:ThemeMissing">{0}不存在</sys:String>
<sys:String x:Key="Themes:SavedTemplateTheme">模板主题&quot;{0}&quot;保存到主题文件夹。</sys:String>
<sys:String x:Key="Themes:TemplateThemeExists">模板主题已存在!</sys:String>
<sys:String x:Key="Themes:FailedToLoadXaml">无法加载主题的.xaml文件 {0}: {1}</sys:String>
<!-- Updater Class -->
<sys:String x:Key="Updater:CheckFailed">无法检查更新。</sys:String>
<sys:String x:Key="Updater:DownloadFailed">无法下载更新。</sys:String>
<!-- Utils Class -->
<sys:String x:Key="Utils:NotificationTitle">Mod Assistant</sys:String>
<sys:String x:Key="Utils:NoInstallFolder">检测不到您的Beat Saber安装路径请手动选择。</sys:String>
<sys:String x:Key="Utils:RunAsAdmin">Mod Assistant需要以管理员身份运行此任务请重试。</sys:String>
<sys:String x:Key="Utils:InstallDir:DialogTitle">选择您的Beat Saber安装路径</sys:String>
<sys:String x:Key="Utils:CannotOpenFolder">无法打开路径:{0}</sys:String>
</ResourceDictionary>

View file

@ -1,323 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using ModAssistant.Pages;
using static ModAssistant.Http;
namespace ModAssistant
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public static MainWindow Instance;
public static bool ModsOpened = false;
public static bool ModsLoading = false;
public static string GameVersion;
public static string GameVersionOverride;
public TaskCompletionSource<bool> VersionLoadStatus = new TaskCompletionSource<bool>();
public string MainText
{
get
{
return MainTextBlock.Text;
}
set
{
Dispatcher.Invoke(new Action(() => { MainWindow.Instance.MainTextBlock.Text = value; }));
}
}
public MainWindow()
{
InitializeComponent();
Instance = this;
const int ContentWidth = 1280;
const int ContentHeight = 720;
double ChromeWidth = SystemParameters.WindowNonClientFrameThickness.Left + SystemParameters.WindowNonClientFrameThickness.Right;
double ChromeHeight = SystemParameters.WindowNonClientFrameThickness.Top + SystemParameters.WindowNonClientFrameThickness.Bottom;
double ResizeBorder = SystemParameters.ResizeFrameVerticalBorderWidth;
Width = ChromeWidth + ContentWidth + 2 * ResizeBorder;
Height = ChromeHeight + ContentHeight + 2 * ResizeBorder;
VersionText.Text = App.Version;
if (Utils.IsVoid())
{
Main.Content = Invalid.Instance;
MainWindow.Instance.ModsButton.IsEnabled = false;
MainWindow.Instance.OptionsButton.IsEnabled = false;
MainWindow.Instance.IntroButton.IsEnabled = false;
MainWindow.Instance.AboutButton.IsEnabled = false;
MainWindow.Instance.GameVersionsBox.IsEnabled = false;
return;
}
Themes.LoadThemes();
Themes.FirstLoad(Properties.Settings.Default.SelectedTheme);
Task.Run(() => LoadVersionsAsync());
if (!Properties.Settings.Default.Agreed || string.IsNullOrEmpty(Properties.Settings.Default.LastTab))
{
Main.Content = Intro.Instance;
}
else
{
switch (Properties.Settings.Default.LastTab)
{
case "Intro":
Main.Content = Intro.Instance;
break;
case "Mods":
_ = ShowModsPage();
break;
case "About":
Main.Content = About.Instance;
break;
case "Options":
Main.Content = Options.Instance;
Themes.LoadThemes();
break;
default:
Main.Content = Intro.Instance;
break;
}
}
}
private async void LoadVersionsAsync()
{
try
{
var resp = await HttpClient.GetAsync(Utils.Constants.BeatModsVersions);
var body = await resp.Content.ReadAsStringAsync();
List<string> versions = JsonSerializer.Deserialize<string[]>(body).ToList();
resp = await HttpClient.GetAsync(Utils.Constants.BeatModsAlias);
body = await resp.Content.ReadAsStringAsync();
object jsonObject = JsonSerializer.DeserializeObject(body);
Dispatcher.Invoke(() =>
{
GameVersion = GetGameVersion(versions, jsonObject);
GameVersionsBox.ItemsSource = versions;
GameVersionsBox.SelectedValue = GameVersion;
if (!string.IsNullOrEmpty(GameVersionOverride))
{
GameVersionsBox.Visibility = Visibility.Collapsed;
GameVersionsBoxOverride.Visibility = Visibility.Visible;
GameVersionsBoxOverride.Text = GameVersionOverride;
GameVersionsBoxOverride.IsEnabled = false;
}
if (!string.IsNullOrEmpty(GameVersion) && Properties.Settings.Default.Agreed)
{
MainWindow.Instance.ModsButton.IsEnabled = true;
}
});
VersionLoadStatus.SetResult(true);
}
catch (Exception e)
{
Dispatcher.Invoke(() =>
{
GameVersionsBox.IsEnabled = false;
MessageBox.Show($"{Application.Current.FindResource("MainWindow:GameVersionLoadFailed")}\n{e}");
});
VersionLoadStatus.SetResult(false);
}
}
private string GetGameVersion(List<string> versions, object aliases)
{
string version = Utils.GetVersion();
if (!string.IsNullOrEmpty(version) && versions.Contains(version))
{
return version;
}
string aliasOf = CheckAliases(versions, aliases, version);
if (!string.IsNullOrEmpty(aliasOf))
{
return aliasOf;
}
string versionsString = String.Join(",", versions.ToArray());
if (Properties.Settings.Default.AllGameVersions != versionsString)
{
Properties.Settings.Default.AllGameVersions = versionsString;
Properties.Settings.Default.Save();
string title = (string)Application.Current.FindResource("MainWindow:GameUpdateDialog:Title");
string line1 = (string)Application.Current.FindResource("MainWindow:GameUpdateDialog:Line1");
string line2 = (string)Application.Current.FindResource("MainWindow:GameUpdateDialog:Line2");
Utils.ShowMessageBoxAsync($"{line1}\n\n{line2}", title);
return versions[0];
}
if (!string.IsNullOrEmpty(Properties.Settings.Default.GameVersion) && versions.Contains(Properties.Settings.Default.GameVersion))
return Properties.Settings.Default.GameVersion;
return versions[0];
}
private string CheckAliases(List<string> versions, object aliases, string detectedVersion)
{
Dictionary<string, object> Objects = (Dictionary<string, object>)aliases;
foreach (string version in versions)
{
object[] aliasArray = (object[])Objects[version];
foreach (object alias in aliasArray)
{
if (alias.ToString() == detectedVersion)
{
GameVersionOverride = detectedVersion;
return version;
}
}
}
return string.Empty;
}
private async Task ShowModsPage()
{
void OpenModsPage()
{
Main.Content = Mods.Instance;
Properties.Settings.Default.LastTab = "Mods";
Properties.Settings.Default.Save();
Mods.Instance.RefreshColumns();
}
if (ModsOpened == true && Mods.Instance.PendingChanges == false)
{
OpenModsPage();
return;
}
Main.Content = Loading.Instance;
if (ModsLoading) return;
ModsLoading = true;
await Mods.Instance.LoadMods();
ModsLoading = false;
if (ModsOpened == false) ModsOpened = true;
if (Mods.Instance.PendingChanges == true) Mods.Instance.PendingChanges = false;
if (Main.Content == Loading.Instance)
{
OpenModsPage();
}
}
private void ModsButton_Click(object sender, RoutedEventArgs e)
{
_ = ShowModsPage();
}
private void IntroButton_Click(object sender, RoutedEventArgs e)
{
Main.Content = Intro.Instance;
Properties.Settings.Default.LastTab = "Intro";
Properties.Settings.Default.Save();
}
private void AboutButton_Click(object sender, RoutedEventArgs e)
{
Main.Content = About.Instance;
Properties.Settings.Default.LastTab = "About";
Properties.Settings.Default.Save();
}
private void OptionsButton_Click(object sender, RoutedEventArgs e)
{
Main.Content = Options.Instance;
Themes.LoadThemes();
Properties.Settings.Default.LastTab = "Options";
Properties.Settings.Default.Save();
}
private void InstallButton_Click(object sender, RoutedEventArgs e)
{
Mods.Instance.InstallMods();
}
private void InfoButton_Click(object sender, RoutedEventArgs e)
{
if ((Mods.ModListItem)Mods.Instance.ModsListView.SelectedItem == null)
{
MessageBox.Show((string)Application.Current.FindResource("MainWindow:NoModSelected"));
return;
}
Mods.ModListItem mod = ((Mods.ModListItem)Mods.Instance.ModsListView.SelectedItem);
string infoUrl = mod.ModInfo.link;
if (string.IsNullOrEmpty(infoUrl))
{
MessageBox.Show(string.Format((string)Application.Current.FindResource("MainWindow:NoModInfoPage"), mod.ModName));
}
else
{
System.Diagnostics.Process.Start(infoUrl);
}
}
private async void GameVersionsBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string oldGameVersion = GameVersion;
GameVersion = (sender as ComboBox).SelectedItem.ToString();
if (string.IsNullOrEmpty(oldGameVersion)) return;
Properties.Settings.Default.GameVersion = GameVersion;
Properties.Settings.Default.Save();
if (ModsOpened)
{
var prevPage = Main.Content;
Mods.Instance.PendingChanges = true;
await ShowModsPage();
Main.Content = prevPage;
}
}
private void Window_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
About.Instance.PatUp.IsOpen = false;
About.Instance.PatButton.IsEnabled = true;
About.Instance.HugUp.IsOpen = false;
About.Instance.HugButton.IsEnabled = true;
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (Main.Content == Mods.Instance)
{
Mods.Instance.RefreshColumns();
}
}
private void BackgroundVideo_MediaEnded(object sender, RoutedEventArgs e)
{
BackgroundVideo.Position = TimeSpan.Zero;
BackgroundVideo.Play();
}
}
}

View file

@ -1,137 +0,0 @@
<Page
x:Class="ModAssistant.Pages.Mods"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ModAssistant"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{DynamicResource Mods:Title}"
d:DesignHeight="629"
d:DesignWidth="1182"
Loaded="Page_Loaded"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock
Name="SearchText"
Padding="5,0,0,0"
Height="0"
Grid.Row="0"
Text="{DynamicResource Mods:SearchLabel}"
Panel.ZIndex="1"
Foreground="{DynamicResource TextColor}"
Background="{DynamicResource BottomStatusBarBackground}" />
<TextBox
Name="SearchBar"
Margin="0,-1,0,0"
Padding="3,1,0,0"
Height="0"
Grid.Row="0"
Panel.ZIndex="2"
Background="#00000000"
Foreground="{DynamicResource TextColor}"
TextChanged="SearchBar_TextChanged"
BorderThickness="0" />
<ListView
Name="ModsListView"
Grid.Column="1"
Grid.Row="1"
SelectionChanged="ModsListView_SelectionChanged"
SelectionMode="Single">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Width="30">
<GridViewColumn.Header>
<Button
Name="SearchButton"
Background="#00000000"
BorderThickness="0"
Click="SearchButton_Click"
Padding="9,-1,9,0"
Margin="-5"
Content="🔍"
FontSize="11" />
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
Name="ModCheckBox"
Checked="ModCheckBox_Checked"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
IsEnabled="{Binding IsEnabled}"
Tag="{Binding ModInfo}"
Unchecked="ModCheckBox_Unchecked" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding ModName}" Header="{DynamicResource Mods:Header:Name}" />
<GridViewColumn x:Name="InstalledColumn" Header="{DynamicResource Mods:Header:Installed}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
Foreground="{Binding GetVersionColor}"
Text="{Binding InstalledVersion}"
TextDecorations="{Binding GetVersionDecoration}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding ModVersion}" Header="{DynamicResource Mods:Header:Latest}" />
<GridViewColumn x:Name="DescriptionColumn" Header="{DynamicResource Mods:Header:Description}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="{Binding PromotionMargin}">
<Hyperlink NavigateUri="{Binding PromotionLink, TargetNullValue=about:blank}" RequestNavigate="Hyperlink_RequestNavigate">
<Run Text="{Binding PromotionText}" />
</Hyperlink>
</TextBlock>
<TextBlock Text="{Binding ModDescription}" MouseLeftButtonDown="CopyText" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn
x:Name="UninstallColumn"
Width="70"
Header="{DynamicResource Mods:Header:Uninstall}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Name="Uninstall"
Click="Uninstall_Click"
Content="{DynamicResource Mods:UninstallButton}"
Foreground="Red"
IsEnabled="{Binding CanDelete}"
Tag="{Binding ModInfo}"
Visibility="{Binding CanSeeDelete}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
Padding="6,0,0,0"
FontSize="14"
FontWeight="Bold"
Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
</Page>

View file

@ -1,777 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Forms;
using System.Windows.Navigation;
using static ModAssistant.Http;
using ModAssistant.Libs;
using System.Windows.Media.Animation;
using TextBox = System.Windows.Controls.TextBox;
namespace ModAssistant.Pages
{
/// <summary>
/// Interaction logic for Mods.xaml
/// </summary>
public sealed partial class Mods : Page
{
public static Mods Instance = new Mods();
public List<string> DefaultMods = new List<string>() { "SongCore", "ScoreSaber", "BeatSaverDownloader", "BeatSaverVoting", "PlaylistCore", "Survey" };
public Mod[] ModsList;
public Mod[] AllModsList;
public static List<Mod> InstalledMods = new List<Mod>();
public List<string> CategoryNames = new List<string>();
public CollectionView view;
public bool PendingChanges;
private readonly SemaphoreSlim _modsLoadSem = new SemaphoreSlim(1, 1);
public List<ModListItem> ModList { get; set; }
public Mods()
{
InitializeComponent();
}
private void RefreshModsList()
{
if (view != null)
{
view.Refresh();
}
}
public void RefreshColumns()
{
if (MainWindow.Instance.Main.Content != Mods.Instance) return;
double viewWidth = ModsListView.ActualWidth;
double totalSize = 0;
GridViewColumn description = null;
GridView grid = ModsListView.View as GridView;
if (grid != null)
{
foreach (var column in grid.Columns)
{
if (column.Header?.ToString() == FindResource("Mods:Header:Description").ToString())
{
description = column;
}
else
{
totalSize += column.ActualWidth;
}
if (double.IsNaN(column.Width))
{
column.Width = column.ActualWidth;
column.Width = double.NaN;
}
}
double descriptionNewWidth = viewWidth - totalSize - 35;
description.Width = descriptionNewWidth > 200 ? descriptionNewWidth : 200;
}
}
public async Task LoadMods()
{
var versionLoadSuccess = await MainWindow.Instance.VersionLoadStatus.Task;
if (versionLoadSuccess == false) return;
await _modsLoadSem.WaitAsync();
try
{
MainWindow.Instance.InstallButton.IsEnabled = false;
MainWindow.Instance.GameVersionsBox.IsEnabled = false;
MainWindow.Instance.InfoButton.IsEnabled = false;
if (ModsList != null)
{
Array.Clear(ModsList, 0, ModsList.Length);
}
if (AllModsList != null)
{
Array.Clear(AllModsList, 0, AllModsList.Length);
}
InstalledMods = new List<Mod>();
CategoryNames = new List<string>();
ModList = new List<ModListItem>();
ModsListView.Visibility = Visibility.Hidden;
if (App.CheckInstalledMods)
{
MainWindow.Instance.MainText = $"{FindResource("Mods:CheckingInstalledMods")}...";
await Task.Run(async () => await CheckInstalledMods());
InstalledColumn.Width = double.NaN;
UninstallColumn.Width = 70;
DescriptionColumn.Width = 750;
}
else
{
InstalledColumn.Width = 0;
UninstallColumn.Width = 0;
DescriptionColumn.Width = 800;
}
MainWindow.Instance.MainText = $"{FindResource("Mods:LoadingMods")}...";
await Task.Run(async () => await PopulateModsList());
ModsListView.ItemsSource = ModList;
view = (CollectionView)CollectionViewSource.GetDefaultView(ModsListView.ItemsSource);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("Category");
view.GroupDescriptions.Add(groupDescription);
this.DataContext = this;
RefreshModsList();
ModsListView.Visibility = Visibility.Visible;
MainWindow.Instance.MainText = $"{FindResource("Mods:FinishedLoadingMods")}.";
MainWindow.Instance.InstallButton.IsEnabled = true;
MainWindow.Instance.GameVersionsBox.IsEnabled = true;
}
finally
{
_modsLoadSem.Release();
}
}
public async Task CheckInstalledMods()
{
await GetAllMods();
List<string> empty = new List<string>();
GetBSIPAVersion();
CheckInstallDir("IPA/Pending/Plugins", empty);
CheckInstallDir("IPA/Pending/Libs", empty);
CheckInstallDir("Plugins", empty);
CheckInstallDir("Libs", empty);
}
public async Task GetAllMods()
{
var resp = await HttpClient.GetAsync(Utils.Constants.BeatModsAPIUrl + "mod");
var body = await resp.Content.ReadAsStringAsync();
AllModsList = JsonSerializer.Deserialize<Mod[]>(body);
}
private void CheckInstallDir(string directory, List<string> blacklist)
{
if (!Directory.Exists(Path.Combine(App.BeatSaberInstallDirectory, directory)))
{
return;
}
foreach (string file in Directory.GetFileSystemEntries(Path.Combine(App.BeatSaberInstallDirectory, directory)))
{
if (File.Exists(file) && Path.GetExtension(file) == ".dll" || Path.GetExtension(file) == ".manifest")
{
Mod mod = GetModFromHash(Utils.CalculateMD5(file));
if (mod != null)
{
AddDetectedMod(mod);
}
}
}
}
public void GetBSIPAVersion()
{
string InjectorPath = Path.Combine(App.BeatSaberInstallDirectory, "Beat Saber_Data", "Managed", "IPA.Injector.dll");
if (!File.Exists(InjectorPath)) return;
string InjectorHash = Utils.CalculateMD5(InjectorPath);
foreach (Mod mod in AllModsList)
{
if (mod.name.ToLower() == "bsipa")
{
foreach (Mod.DownloadLink download in mod.downloads)
{
foreach (Mod.FileHashes fileHash in download.hashMd5)
{
if (fileHash.hash == InjectorHash)
{
AddDetectedMod(mod);
}
}
}
}
}
}
private void AddDetectedMod(Mod mod)
{
if (!InstalledMods.Contains(mod))
{
InstalledMods.Add(mod);
if (App.SelectInstalledMods && !DefaultMods.Contains(mod.name))
{
DefaultMods.Add(mod.name);
}
}
}
private Mod GetModFromHash(string hash)
{
foreach (Mod mod in AllModsList)
{
if (mod.name.ToLower() != "bsipa" && mod.status != "declined")
{
foreach (Mod.DownloadLink download in mod.downloads)
{
foreach (Mod.FileHashes fileHash in download.hashMd5)
{
if (fileHash.hash == hash)
return mod;
}
}
}
}
return null;
}
public async Task PopulateModsList()
{
try
{
var resp = await HttpClient.GetAsync(Utils.Constants.BeatModsAPIUrl + Utils.Constants.BeatModsModsOptions + "&gameVersion=" + MainWindow.GameVersion);
var body = await resp.Content.ReadAsStringAsync();
ModsList = JsonSerializer.Deserialize<Mod[]>(body);
}
catch (Exception e)
{
System.Windows.MessageBox.Show($"{FindResource("Mods:LoadFailed")}.\n\n" + e);
return;
}
foreach (Mod mod in ModsList)
{
bool preSelected = mod.required;
if (DefaultMods.Contains(mod.name) || (App.SaveModSelection && App.SavedMods.Contains(mod.name)))
{
preSelected = true;
if (!App.SavedMods.Contains(mod.name))
{
App.SavedMods.Add(mod.name);
}
}
RegisterDependencies(mod);
ModListItem ListItem = new ModListItem()
{
IsSelected = preSelected,
IsEnabled = !mod.required,
ModName = mod.name,
ModVersion = mod.version,
ModDescription = mod.description.Replace("\r\n", " ").Replace("\n", " "),
ModInfo = mod,
Category = mod.category
};
foreach (Promotion promo in Promotions.ActivePromotions)
{
if (mod.name == promo.ModName)
{
ListItem.PromotionText = promo.Text;
ListItem.PromotionLink = promo.Link;
}
}
foreach (Mod installedMod in InstalledMods)
{
if (mod.name == installedMod.name)
{
ListItem.InstalledModInfo = installedMod;
ListItem.IsInstalled = true;
ListItem.InstalledVersion = installedMod.version;
break;
}
}
mod.ListItem = ListItem;
ModList.Add(ListItem);
}
foreach (Mod mod in ModsList)
{
ResolveDependencies(mod);
}
}
public async void InstallMods()
{
MainWindow.Instance.InstallButton.IsEnabled = false;
string installDirectory = App.BeatSaberInstallDirectory;
foreach (Mod mod in ModsList)
{
// Ignore mods that are newer than installed version
if (mod.ListItem.GetVersionComparison > 0) continue;
// Ignore mods that are on current version if we aren't reinstalling mods
if (mod.ListItem.GetVersionComparison == 0 && !App.ReinstallInstalledMods) continue;
if (mod.name.ToLower() == "bsipa")
{
MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstallingMod"), mod.name)}...";
await Task.Run(async () => await InstallMod(mod, installDirectory));
MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstalledMod"), mod.name)}.";
if (!File.Exists(Path.Combine(installDirectory, "winhttp.dll")))
{
await Task.Run(() =>
Process.Start(new ProcessStartInfo
{
FileName = Path.Combine(installDirectory, "IPA.exe"),
WorkingDirectory = installDirectory,
Arguments = "-n"
}).WaitForExit()
);
}
Pages.Options.Instance.YeetBSIPA.IsEnabled = true;
}
else if (mod.ListItem.IsSelected)
{
MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstallingMod"), mod.name)}...";
await Task.Run(async () => await InstallMod(mod, Path.Combine(installDirectory, @"IPA\Pending")));
MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstalledMod"), mod.name)}.";
}
}
MainWindow.Instance.MainText = $"{FindResource("Mods:FinishedInstallingMods")}.";
MainWindow.Instance.InstallButton.IsEnabled = true;
RefreshModsList();
}
private async Task InstallMod(Mod mod, string directory)
{
string downloadLink = null;
foreach (Mod.DownloadLink link in mod.downloads)
{
if (link.type == "universal")
{
downloadLink = link.url;
break;
}
else if (link.type.ToLower() == App.BeatSaberInstallType.ToLower())
{
downloadLink = link.url;
break;
}
}
if (string.IsNullOrEmpty(downloadLink))
{
System.Windows.MessageBox.Show(string.Format((string)FindResource("Mods:ModDownloadLinkMissing"), mod.name));
return;
}
using (Stream stream = await DownloadMod(Utils.Constants.BeatModsURL + downloadLink))
using (ZipArchive archive = new ZipArchive(stream))
{
foreach (ZipArchiveEntry file in archive.Entries)
{
string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName));
if (!Directory.Exists(fileDirectory))
{
Directory.CreateDirectory(fileDirectory);
}
if (!string.IsNullOrEmpty(file.Name))
{
await ExtractFile(file, Path.Combine(directory, file.FullName), 3.0, mod.name, 10);
}
}
}
if (App.CheckInstalledMods)
{
mod.ListItem.IsInstalled = true;
mod.ListItem.InstalledVersion = mod.version;
mod.ListItem.InstalledModInfo = mod;
}
}
private async Task ExtractFile(ZipArchiveEntry file, string path, double seconds, string name, int maxTries, int tryNumber = 0)
{
if (tryNumber < maxTries)
{
try
{
file.ExtractToFile(path, true);
}
catch
{
MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:FailedExtract"), name, seconds, tryNumber + 1, maxTries)}";
await Task.Delay((int)(seconds * 1000));
await ExtractFile(file, path, seconds, name, maxTries, tryNumber + 1);
}
}
else
{
System.Windows.MessageBox.Show($"{string.Format((string)FindResource("Mods:FailedExtractMaxReached"), name, maxTries)}.", "Failed to install " + name);
}
}
private async Task<Stream> DownloadMod(string link)
{
var resp = await HttpClient.GetAsync(link);
return await resp.Content.ReadAsStreamAsync();
}
private void RegisterDependencies(Mod dependent)
{
if (dependent.dependencies.Length == 0)
return;
foreach (Mod mod in ModsList)
{
foreach (Mod.Dependency dep in dependent.dependencies)
{
if (dep.name == mod.name)
{
dep.Mod = mod;
mod.Dependents.Add(dependent);
}
}
}
}
private void ResolveDependencies(Mod dependent)
{
if (dependent.ListItem.IsSelected && dependent.dependencies.Length > 0)
{
foreach (Mod.Dependency dependency in dependent.dependencies)
{
if (dependency.Mod.ListItem.IsEnabled)
{
dependency.Mod.ListItem.PreviousState = dependency.Mod.ListItem.IsSelected;
dependency.Mod.ListItem.IsSelected = true;
dependency.Mod.ListItem.IsEnabled = false;
ResolveDependencies(dependency.Mod);
}
}
}
}
private void UnresolveDependencies(Mod dependent)
{
if (!dependent.ListItem.IsSelected && dependent.dependencies.Length > 0)
{
foreach (Mod.Dependency dependency in dependent.dependencies)
{
if (!dependency.Mod.ListItem.IsEnabled)
{
bool needed = false;
foreach (Mod dep in dependency.Mod.Dependents)
{
if (dep.ListItem.IsSelected)
{
needed = true;
break;
}
}
if (!needed && !dependency.Mod.required)
{
dependency.Mod.ListItem.IsSelected = dependency.Mod.ListItem.PreviousState;
dependency.Mod.ListItem.IsEnabled = true;
UnresolveDependencies(dependency.Mod);
}
}
}
}
}
private void ModCheckBox_Checked(object sender, RoutedEventArgs e)
{
Mod mod = ((sender as System.Windows.Controls.CheckBox).Tag as Mod);
mod.ListItem.IsSelected = true;
ResolveDependencies(mod);
App.SavedMods.Add(mod.name);
Properties.Settings.Default.SavedMods = string.Join(",", App.SavedMods.ToArray());
Properties.Settings.Default.Save();
RefreshModsList();
}
private void ModCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
Mod mod = ((sender as System.Windows.Controls.CheckBox).Tag as Mod);
mod.ListItem.IsSelected = false;
UnresolveDependencies(mod);
App.SavedMods.Remove(mod.name);
Properties.Settings.Default.SavedMods = string.Join(",", App.SavedMods.ToArray());
Properties.Settings.Default.Save();
RefreshModsList();
}
public class Category
{
public string CategoryName { get; set; }
public List<ModListItem> Mods = new List<ModListItem>();
}
public class ModListItem
{
public string ModName { get; set; }
public string ModVersion { get; set; }
public string ModDescription { get; set; }
public bool PreviousState { get; set; }
public bool IsEnabled { get; set; }
public bool IsSelected { get; set; }
public Mod ModInfo { get; set; }
public string Category { get; set; }
public Mod InstalledModInfo { get; set; }
public bool IsInstalled { get; set; }
private SemVersion _installedVersion { get; set; }
public string InstalledVersion
{
get
{
if (!IsInstalled || _installedVersion == null) return "-";
return _installedVersion.ToString();
}
set
{
if (SemVersion.TryParse(value, out SemVersion tempInstalledVersion))
{
_installedVersion = tempInstalledVersion;
} else
{
_installedVersion = null;
}
}
}
public string GetVersionColor
{
get
{
if (!IsInstalled) return "Black";
return _installedVersion >= ModVersion ? "Green" : "Red";
}
}
public string GetVersionDecoration
{
get
{
if (!IsInstalled) return "None";
return _installedVersion >= ModVersion ? "None" : "Strikethrough";
}
}
public int GetVersionComparison
{
get
{
if (!IsInstalled || _installedVersion < ModVersion) return -1;
if (_installedVersion > ModVersion) return 1;
return 0;
}
}
public bool CanDelete
{
get
{
return (!ModInfo.required && IsInstalled);
}
}
public string CanSeeDelete
{
get
{
if (!ModInfo.required && IsInstalled)
return "Visible";
else
return "Hidden";
}
}
public string PromotionText { get; set; }
public string PromotionLink { get; set; }
public string PromotionMargin
{
get
{
if (string.IsNullOrEmpty(PromotionText)) return "0";
return "0,0,5,0";
}
}
}
private void ModsListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((Mods.ModListItem)Mods.Instance.ModsListView.SelectedItem == null)
{
MainWindow.Instance.InfoButton.IsEnabled = false;
}
else
{
MainWindow.Instance.InfoButton.IsEnabled = true;
}
}
public void UninstallBSIPA(Mod.DownloadLink links)
{
Process.Start(new ProcessStartInfo
{
FileName = Path.Combine(App.BeatSaberInstallDirectory, "IPA.exe"),
WorkingDirectory = App.BeatSaberInstallDirectory,
Arguments = "--revert -n"
}).WaitForExit();
foreach (Mod.FileHashes files in links.hashMd5)
{
string file = files.file.Replace("IPA/", "").Replace("Data", "Beat Saber_Data");
if (File.Exists(Path.Combine(App.BeatSaberInstallDirectory, file)))
File.Delete(Path.Combine(App.BeatSaberInstallDirectory, file));
}
Pages.Options.Instance.YeetBSIPA.IsEnabled = false;
}
private void Uninstall_Click(object sender, RoutedEventArgs e)
{
Mod mod = ((sender as System.Windows.Controls.Button).Tag as Mod);
string title = string.Format((string)FindResource("Mods:UninstallBox:Title"), mod.name);
string body1 = string.Format((string)FindResource("Mods:UninstallBox:Body1"), mod.name);
string body2 = string.Format((string)FindResource("Mods:UninstallBox:Body2"), mod.name);
var result = System.Windows.Forms.MessageBox.Show($"{body1}\n{body2}", title, MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
UninstallModFromList(mod);
}
}
private void UninstallModFromList(Mod mod)
{
UninstallMod(mod.ListItem.InstalledModInfo);
mod.ListItem.IsInstalled = false;
mod.ListItem.InstalledVersion = null;
if (App.SelectInstalledMods)
{
mod.ListItem.IsSelected = false;
UnresolveDependencies(mod);
App.SavedMods.Remove(mod.name);
Properties.Settings.Default.SavedMods = string.Join(",", App.SavedMods.ToArray());
Properties.Settings.Default.Save();
RefreshModsList();
}
view.Refresh();
}
public void UninstallMod(Mod mod)
{
Mod.DownloadLink links = null;
foreach (Mod.DownloadLink link in mod.downloads)
{
if (link.type.ToLower() == "universal" || link.type.ToLower() == App.BeatSaberInstallType.ToLower())
{
links = link;
break;
}
}
if (mod.name.ToLower() == "bsipa")
UninstallBSIPA(links);
foreach (Mod.FileHashes files in links.hashMd5)
{
if (File.Exists(Path.Combine(App.BeatSaberInstallDirectory, files.file)))
File.Delete(Path.Combine(App.BeatSaberInstallDirectory, files.file));
if (File.Exists(Path.Combine(App.BeatSaberInstallDirectory, "IPA", "Pending", files.file)))
File.Delete(Path.Combine(App.BeatSaberInstallDirectory, "IPA", "Pending", files.file));
}
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
e.Handled = true;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
RefreshColumns();
}
private void CopyText(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
System.Windows.Clipboard.SetText(((TextBlock)sender).Text);
Utils.SendNotify("Copied text to clipboard");
}
private void SearchButton_Click(object sender, RoutedEventArgs e)
{
if (SearchBar.Height == 0)
{
SearchBar.Focus();
Animate(SearchBar, 0, 16, new TimeSpan(0, 0, 0, 0, 300));
Animate(SearchText, 0, 16, new TimeSpan(0, 0, 0, 0, 300));
ModsListView.Items.Filter = new Predicate<object>(SearchFilter);
}
else
{
Animate(SearchBar, 16, 0, new TimeSpan(0, 0, 0, 0, 300));
Animate(SearchText, 16, 0, new TimeSpan(0, 0, 0, 0, 300));
ModsListView.Items.Filter = null;
}
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
ModsListView.Items.Filter = new Predicate<object>(SearchFilter);
if (SearchBar.Text.Length > 0)
{
SearchText.Text = null;
}
else
{
SearchText.Text = (string)FindResource("Mods:SearchLabel");
}
}
private bool SearchFilter(object mod)
{
ModListItem item = mod as ModListItem;
if (item.ModName.ToLower().Contains(SearchBar.Text.ToLower())) return true;
if (item.ModDescription.ToLower().Contains(SearchBar.Text.ToLower())) return true;
if (item.ModName.ToLower().Replace(" ", string.Empty).Contains(SearchBar.Text.ToLower().Replace(" ", string.Empty))) return true;
if (item.ModDescription.ToLower().Replace(" ", string.Empty).Contains(SearchBar.Text.ToLower().Replace(" ", string.Empty))) return true;
return false;
}
private void Animate(TextBlock target, double oldHeight, double newHeight, TimeSpan duration)
{
target.Height = oldHeight;
DoubleAnimation animation = new DoubleAnimation(newHeight, duration);
target.BeginAnimation(TextBlock.HeightProperty, animation);
}
private void Animate(TextBox target, double oldHeight, double newHeight, TimeSpan duration)
{
target.Height = oldHeight;
DoubleAnimation animation = new DoubleAnimation(newHeight, duration);
target.BeginAnimation(TextBox.HeightProperty, animation);
}
}
}

View file

@ -1,321 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Path = System.IO.Path;
namespace ModAssistant.Pages
{
/// <summary>
/// Interaction logic for Options.xaml
/// </summary>
public partial class Options : Page
{
public static Options Instance = new Options();
public string InstallDirectory { get; set; }
public string InstallType { get; set; }
public bool SaveSelection { get; set; }
public bool CheckInstalledMods { get; set; }
public bool SelectInstalledMods { get; set; }
public bool ReinstallInstalledMods { get; set; }
public bool ModelSaberProtocolHandlerEnabled { get; set; }
public bool BeatSaverProtocolHandlerEnabled { get; set; }
public string LogURL { get; private set; }
public Options()
{
InitializeComponent();
InstallDirectory = App.BeatSaberInstallDirectory;
InstallType = App.BeatSaberInstallType;
SaveSelection = App.SaveModSelection;
CheckInstalledMods = App.CheckInstalledMods;
SelectInstalledMods = App.SelectInstalledMods;
ReinstallInstalledMods = App.ReinstallInstalledMods;
if (!CheckInstalledMods)
{
SelectInstalled.IsEnabled = false;
ReinstallInstalled.IsEnabled = false;
}
UpdateHandlerStatus();
this.DataContext = this;
}
public void UpdateHandlerStatus()
{
ModelSaberProtocolHandlerEnabled = OneClickInstaller.IsRegistered("modelsaber");
BeatSaverProtocolHandlerEnabled = OneClickInstaller.IsRegistered("beatsaver");
}
private void SelectDirButton_Click(object sender, RoutedEventArgs e)
{
Utils.GetManualDir();
DirectoryTextBlock.Text = InstallDirectory;
GameTypeTextBlock.Text = InstallType;
}
private void OpenDirButton_Click(object sender, RoutedEventArgs e)
{
Utils.OpenFolder(InstallDirectory);
}
private void Test_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Utils.GetSteamDir());
}
private void SaveSelected_Checked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.SaveSelected = true;
App.SaveModSelection = true;
Properties.Settings.Default.Save();
}
private void SaveSelected_Unchecked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.SaveSelected = false;
App.SaveModSelection = false;
Properties.Settings.Default.Save();
}
private void CheckInstalled_Checked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.CheckInstalled = true;
App.CheckInstalledMods = true;
CheckInstalledMods = true;
Properties.Settings.Default.Save();
SelectInstalled.IsEnabled = true;
ReinstallInstalled.IsEnabled = true;
if (MainWindow.ModsOpened)
{
Mods.Instance.PendingChanges = true;
}
}
private void CheckInstalled_Unchecked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.CheckInstalled = false;
App.CheckInstalledMods = false;
CheckInstalledMods = false;
Properties.Settings.Default.Save();
SelectInstalled.IsEnabled = false;
ReinstallInstalled.IsEnabled = false;
if (MainWindow.ModsOpened)
{
Mods.Instance.PendingChanges = true;
}
}
public void ModelSaberProtocolHandler_Checked(object sender, RoutedEventArgs e)
{
OneClickInstaller.Register("modelsaber");
}
public void ModelSaberProtocolHandler_Unchecked(object sender, RoutedEventArgs e)
{
OneClickInstaller.Unregister("modelsaber");
}
public void BeatSaverProtocolHandler_Checked(object sender, RoutedEventArgs e)
{
OneClickInstaller.Register("beatsaver");
}
public void BeatSaverProtocolHandler_Unchecked(object sender, RoutedEventArgs e)
{
OneClickInstaller.Unregister("beatsaver");
}
private void SelectInstalled_Checked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.SelectInstalled = true;
App.SelectInstalledMods = true;
SelectInstalledMods = true;
Properties.Settings.Default.Save();
}
private void SelectInstalled_Unchecked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.SelectInstalled = false;
App.SelectInstalledMods = false;
SelectInstalledMods = false;
Properties.Settings.Default.Save();
}
private void ReinstallInstalled_Checked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.ReinstallInstalled = true;
App.ReinstallInstalledMods = true;
ReinstallInstalledMods = true;
Properties.Settings.Default.Save();
}
private void ReinstallInstalled_Unchecked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.ReinstallInstalled = false;
App.ReinstallInstalledMods = false;
ReinstallInstalledMods = false;
Properties.Settings.Default.Save();
}
private async void OpenLogsDirButton_Click(object sender, RoutedEventArgs e)
{
try
{
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:UploadingLog")}...";
await Task.Run(async () => await UploadLog());
System.Diagnostics.Process.Start(LogURL);
Clipboard.SetText(LogURL);
MainWindow.Instance.MainText = (string)Application.Current.FindResource("Options:LogUrlCopied");
}
catch (Exception exception)
{
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:LogUploadFailed")}.";
string title = (string)Application.Current.FindResource("Options:LogUploadFailed:Title");
string body = (string)Application.Current.FindResource("Options:LogUploadFailed:Body");
MessageBox.Show($"{body}\n ================= \n" + exception, title);
Utils.OpenFolder(Path.Combine(InstallDirectory, "Logs"));
}
}
private async Task UploadLog()
{
const string DateFormat = "yyyy-mm-dd HH:mm:ss";
DateTime now = DateTime.Now;
var nvc = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("title", $"_latest.log ({now.ToString(DateFormat)})"),
new KeyValuePair<string, string>("expireUnit", "hour"),
new KeyValuePair<string, string>("expireLength", "5"),
new KeyValuePair<string, string>("code", File.ReadAllText(Path.Combine(InstallDirectory, "Logs", "_latest.log"))),
};
var req = new HttpRequestMessage(HttpMethod.Post, Utils.Constants.TeknikAPIUrl + "Paste")
{
Content = new FormUrlEncodedContent(nvc),
};
var resp = await Http.HttpClient.SendAsync(req);
var body = await resp.Content.ReadAsStringAsync();
var TeknikResponse = Http.JsonSerializer.Deserialize<Utils.TeknikPasteResponse>(body);
LogURL = TeknikResponse.result.url;
}
private void OpenAppDataButton_Click(object sender, RoutedEventArgs e)
{
string location = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"AppData", "LocalLow", "Hyperbolic Magnetism");
if (Directory.Exists(location))
{
Utils.OpenFolder(location);
}
else
{
MessageBox.Show((string)Application.Current.FindResource("Options:AppDataNotFound"));
}
}
private async void YeetBSIPAButton_Click(object sender, RoutedEventArgs e)
{
if (Mods.Instance.AllModsList == null)
{
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:GettingModList")}...";
await Task.Run(async () => await Mods.Instance.GetAllMods());
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:FindingBSIPAVersion")}...";
await Task.Run(() => Mods.Instance.GetBSIPAVersion());
}
foreach (Mod mod in Mods.InstalledMods)
{
if (mod.name.ToLower() == "bsipa")
{
Mods.Instance.UninstallMod(mod);
break;
}
}
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:BSIPAUninstalled")}...";
}
private async void YeetModsButton_Click(object sender, RoutedEventArgs e)
{
string title = (string)Application.Current.FindResource("Options:YeetModsBox:Title");
string line1 = (string)Application.Current.FindResource("Options:YeetModsBox:RemoveAllMods");
string line2 = (string)Application.Current.FindResource("Options:YeetModsBox:CannotBeUndone");
var resp = System.Windows.Forms.MessageBox.Show($"{line1}\n{line2}", title, System.Windows.Forms.MessageBoxButtons.YesNo);
if (resp == System.Windows.Forms.DialogResult.Yes)
{
if (Mods.Instance.AllModsList == null)
{
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:GettingModList")}...";
await Task.Run(async () => await Mods.Instance.CheckInstalledMods());
}
foreach (Mod mod in Mods.InstalledMods)
{
Mods.Instance.UninstallMod(mod);
}
if (Directory.Exists(Path.Combine(App.BeatSaberInstallDirectory, "Plugins")))
Directory.Delete(Path.Combine(App.BeatSaberInstallDirectory, "Plugins"), true);
if (Directory.Exists(Path.Combine(App.BeatSaberInstallDirectory, "Libs")))
Directory.Delete(Path.Combine(App.BeatSaberInstallDirectory, "Libs"), true);
if (Directory.Exists(Path.Combine(App.BeatSaberInstallDirectory, "IPA")))
Directory.Delete(Path.Combine(App.BeatSaberInstallDirectory, "IPA"), true);
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:AllModsUninstalled")}...";
}
}
private void ApplicationThemeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((sender as ComboBox).SelectedItem == null)
{
Themes.ApplyWindowsTheme();
MainWindow.Instance.MainText = (string)Application.Current.FindResource("Options:CurrentThemeRemoved");
}
else
{
Themes.ApplyTheme((sender as ComboBox).SelectedItem.ToString());
}
}
private void ApplicationThemeExportTemplate_Click(object sender, RoutedEventArgs e)
{
Themes.WriteThemeToDisk("Ugly Kulu-Ya-Ku");
Themes.LoadThemes();
}
private void ApplicationThemeOpenThemesFolder_Click(object sender, RoutedEventArgs e)
{
if (Directory.Exists(Themes.ThemeDirectory))
{
Utils.OpenFolder(Themes.ThemeDirectory);
}
else
{
MessageBox.Show((string)Application.Current.FindResource("Options:ThemeFolderNotFound"));
}
}
private void InstallPlaylistButton_Click(object sender, RoutedEventArgs e)
{
string playlistFile = Utils.GetManualFile();
if (File.Exists(playlistFile))
{
Task.Run(() => { API.Playlists.DownloadFrom(playlistFile, true).Wait(); });
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

View file

@ -1,103 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--
ModAssistant BSMG theme by Megalon2D
https://twitter.com/megalon2D
-->
<!-- Standard Styles -->
<Color x:Key="StandardContent">#DCDDDE</Color>
<Color x:Key="StandardPrimary">#36393F</Color>
<Color x:Key="StandardSecondary">#2F3136</Color>
<Color x:Key="StandardBackground">#202225</Color>
<Color x:Key="StandardBorder">#202225</Color>
<Color x:Key="StandardHighlight">#7289DA</Color>
<Color x:Key="StandardActive">#44347C</Color>
<Color x:Key="StandardIcon">#DCDDDE</Color>
<Color x:Key="DiscordTextBox">#40444B</Color>
<Color x:Key="DiscordHighlight">#32353B</Color>
<!-- Default text -->
<SolidColorBrush x:Key="TextColor" Color="{StaticResource ResourceKey=StandardContent}" />
<SolidColorBrush x:Key="TextHighlighted" Color="Black" />
<!-- Buttons (Info/Mods/About/Options as well as More Info and Install/Update) -->
<SolidColorBrush x:Key="ButtonBackground" Color="#303237" />
<SolidColorBrush x:Key="ButtonOutline" Color="#202225" />
<SolidColorBrush x:Key="ButtonHighlightedBackground" Color="{DynamicResource ResourceKey=StandardHighlight}" />
<SolidColorBrush x:Key="ButtonClickedBackground" Color="{DynamicResource ResourceKey=StandardActive}" />
<SolidColorBrush x:Key="ButtonDisabledBackground" Color="{DynamicResource ResourceKey=StandardSecondary}" />
<SolidColorBrush x:Key="ButtonDangerBackground" Color="{DynamicResource ResourceKey=StandardActive}" />
<!-- Page Buttons (Side of Main Page) -->
<SolidColorBrush x:Key="PageButtonBackground" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="PageButtonOutline" Color="{DynamicResource ResourceKey=StandardBorder}" />
<SolidColorBrush x:Key="PageButtonHighlightedBackground" Color="{DynamicResource ResourceKey=StandardHighlight}" />
<SolidColorBrush x:Key="PageButtonClickedBackground" Color="{DynamicResource ResourceKey=StandardActive}" />
<SolidColorBrush x:Key="PageButtonDisabledBackground" Color="{DynamicResource ResourceKey=StandardSecondary}" />
<!-- Mod List -->
<SolidColorBrush x:Key="ModColumnBackground" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="ModColumnBorderBrush" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="ModColumnHeaderHighlighted" Color="{DynamicResource ResourceKey=StandardHighlight}" />
<SolidColorBrush x:Key="ModListBackground" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="ModListItemHighlighted" Color="{DynamicResource ResourceKey=DiscordHighlight}" />
<SolidColorBrush x:Key="ModListItemHighlightedOutline" Color="Transparent" />
<SolidColorBrush x:Key="ModListItemSelected" Color="{DynamicResource ResourceKey=StandardHighlight}" />
<SolidColorBrush x:Key="ModListItemSelectedOutline" Color="Transparent" />
<SolidColorBrush x:Key="ModListBorderBrush" Color="Transparent" />
<!-- Combo Box (Version select) -->
<SolidColorBrush x:Key="ComboBoxBackground" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="ComboBoxOutline" Color="{DynamicResource ResourceKey=StandardBorder}" />
<SolidColorBrush x:Key="ComboBoxHighlighted" Color="{DynamicResource ResourceKey=StandardHighlight}" />
<SolidColorBrush x:Key="ComboBoxSelected" Color="{DynamicResource ResourceKey=StandardActive}" />
<SolidColorBrush x:Key="ComboBoxClicked" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="ComboBoxArrow" Color="{DynamicResource ResourceKey=StandardBorder}" />
<!-- Checkboxes (Mod List and Options) -->
<SolidColorBrush x:Key="CheckboxDefaultBackground" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="CheckboxDefaultOutlineColor" Color="{DynamicResource ResourceKey=StandardBorder}" />
<SolidColorBrush x:Key="CheckboxDisabledBackground" Color="{DynamicResource ResourceKey=StandardSecondary}" />
<SolidColorBrush x:Key="CheckboxDisabledOutlineColor" Color="{DynamicResource ResourceKey=StandardBorder}" />
<SolidColorBrush x:Key="CheckboxDisabledTickColor" Color="{DynamicResource ResourceKey=StandardBorder}" />
<SolidColorBrush x:Key="CheckboxHoveredBackground" Color="{DynamicResource ResourceKey=StandardHighlight}" />
<SolidColorBrush x:Key="CheckboxHoveredTickColor" Color="{DynamicResource ResourceKey=StandardIcon}" />
<SolidColorBrush x:Key="CheckboxTickColor" Color="{DynamicResource ResourceKey=StandardIcon}" />
<SolidColorBrush x:Key="CheckboxPressedBackground" Color="{DynamicResource ResourceKey=StandardActive}" />
<!-- Scroll Bars -->
<SolidColorBrush x:Key="ScrollBarBackground" Color="{DynamicResource ResourceKey=StandardSecondary}" />
<SolidColorBrush x:Key="ScrollBarBorder" Color="{DynamicResource ResourceKey=Transparent}" />
<SolidColorBrush x:Key="ScrollBarTextColor" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="ScrollBarDisabled" Color="{DynamicResource ResourceKey=StandardSecondary}" />
<SolidColorBrush x:Key="ScrollBarArrowColor" Color="{DynamicResource ResourceKey=StandardActive}" />
<SolidColorBrush x:Key="ScrollBarArrowClicked" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="ScrollBarArrowHovered" Color="{DynamicResource ResourceKey=StandardHighlight}" />
<SolidColorBrush x:Key="ScrollBarHandle" Color="{DynamicResource ResourceKey=StandardBackground}" />
<SolidColorBrush x:Key="ScrollBarHandleHovered" Color="{DynamicResource ResourceKey=StandardBackground}" />
<SolidColorBrush x:Key="ScrollBarHandleClick" Color="{DynamicResource ResourceKey=StandardBackground}" />
<GridLength x:Key="ScrollBarButtonHeight">0</GridLength>
<!-- Various important elements that need to be controlled independently -->
<SolidColorBrush x:Key="ModAssistantBackground" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="FrameBackgroundColor" Color="#00000000" />
<SolidColorBrush x:Key="BottomStatusBarBackground" Color="{DynamicResource ResourceKey=DiscordTextBox}" />
<SolidColorBrush x:Key="BottomStatusBarOutline" Color="{DynamicResource ResourceKey=DiscordTextBox}" />
<SolidColorBrush x:Key="DirectoryBackground" Color="{DynamicResource ResourceKey=DiscordTextBox}" />
<SolidColorBrush x:Key="DirectoryOutline" Color="{DynamicResource ResourceKey=DiscordTextBox}" />
<!-- Colors for the corresponding icons. -->
<SolidColorBrush x:Key="InfoIconColor" Color="{DynamicResource ResourceKey=StandardIcon}" />
<SolidColorBrush x:Key="ModsIconColor" Color="{DynamicResource ResourceKey=StandardIcon}" />
<SolidColorBrush x:Key="AboutIconColor" Color="{DynamicResource ResourceKey=StandardIcon}" />
<SolidColorBrush x:Key="OptionsIconColor" Color="{DynamicResource ResourceKey=StandardIcon}" />
<SolidColorBrush x:Key="LoadingIconColor" Color="{DynamicResource ResourceKey=StandardIcon}" />
<!-- Background and Side image settings. -->
<!-- Fill, None, Uniform, UniformToFill -->
<Stretch x:Key="BackgroundImageStretch">UniformToFill</Stretch>
<!-- Bottom, Center, Top -->
<VerticalAlignment x:Key="SideImageYPosition">Top</VerticalAlignment>
</ResourceDictionary>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

136
README.md
View file

@ -1,141 +1,51 @@
[![ModAssistant](https://cdn.assistant.moe/images/ModAssistant/Icons/Banner.svg)](https://github.com/Assistant/ModAssistant/releases/latest)
[![Download here!](https://cdn.assistant.moe/images/ModAssistant/Icons/Download.svg)](https://github.com/Assistant/ModAssistant/releases/latest)
# [Download here!](https://github.com/knah/VRCMelonAssistant/releases/latest)
ModAssistant is a PC mod installer for Beat Saber. It uses mods from [BeatMods](https://beatmods.com/).
VRChat Melon Assistant is a PC mod installer for VRChat. It uses mods published in [VRChat Modding Group Discord](https://discord.gg/rCqKSvR).
It's a (very stripped down) fork of [Assistant's Mod Assistant](https://github.com/Assistant/ModAssistant), a mod manager for Beat Saber.
**Modifying the VRChat client is not allowed by VRChat Terms of Service and can lead to your account being banned.** Mods available via this installer are manually checked to minimize the chance of that happening, but the risk is always there.
VRChat Melon Assistant is not affiliated with and/or endorsed by VRChat Inc.
* [Features](#Features)
* [Usage](#Usage)
* [Themes](#Themes)
* [Custom Themes](#Custom-Themes)
* [Built In](#Built-In)
* [Packaged `.mat` Files](#Packaged-mat-Files)
* [Loose Folder Themes](#Loose-Folder-Themes)
* [Overriding Themes](#Overriding-Themes)
* [Common Issues](#Common-Issues)
## Features
ModAssistant boasts a rich feature set, some of which include:
* Dependency resolution
VRChat Melon Assistant boasts a rich feature set, some of which include:
* Installed mod detection
* Mod uninstallation
* OneClick&trade; Install support
* Broken mod move-aside (temporarily uninstalls them until a fix is available)
* Complex theming engine
* Localization support
* Headpats and Hugs
## Usage
Download the newest installer from the release section and run it. This application auto-updates when launched, there is no need to download a new release each time.
1. **Run the game at least once before trying to mod the game!** This applies to reinstalling your game too. All mods are moved into an `Old X.X.X Plugins` folder on first launch to avoid version mismatches, so make sure to do this before installing mods on a fresh version.
2. Once that's done, simply check off the mods that you wish to install and click the <kbd>Install or Update</kbd> button. Likewise, click the <kbd>Uninstall</kbd> button to remove any mods.
3. Mods are installed to `IPA/Pending` until the game is run. Boot the game to complete mod installation.
Download the newest build from the release section and run it. This application auto-updates when launched, there is no need to download a new release each time.
Then, simply check off the mods that you wish to install or update and click the <kbd>Install or Update</kbd> button. Likewise, click the <kbd>Uninstall</kbd> button to remove any mods.
## Themes
<details>
<summary><b>Light</b></summary>
<div>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Light/Intro.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Light/Mods.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Light/About.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Light/Options.png" /></p>
</div>
</details>
<details>
<summary><b>Dark</b></summary>
<div>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Dark/Intro.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Dark/Mods.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Dark/About.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Dark/Options.png" /></p>
</div>
</details>
<details>
<summary><b>BSMG</b></summary>
<div>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/BSMG/Intro.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/BSMG/Mods.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/BSMG/About.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/BSMG/Options.png" /></p>
</div>
</details>
<details>
<summary><b>Light Pink</b></summary>
<div>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Light Pink/Intro.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Light Pink/Mods.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Light Pink/About.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Light Pink/Options.png" /></p>
</div>
</details>
<details>
<summary><b>Your own!</b></summary>
<div>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Custom/Intro.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Custom/Mods.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Custom/About.png" /></p>
<p><img src="https://cdn.assistant.moe/images/ModAssistant/Themes/Custom/Options.png" /></p>
</div>
</details>
### Custom Themes
Custom themes are located in a folder called `Themes` in the same folder as `ModAssistant.exe`. ModAssistant can load themes from one of three sources.
### Built In
These come with the program and you can't change them, however you can overwrite them by creating one of the other two theme types with the same name.
If you have a particularly popular theme, you can submit a [Pull Request](https://github.com/Assistant/ModAssistant/pulls) to add your theme as a built-in theme.
### Packaged `.mat` Files
These are pre-packaged theme files. Under the hood they are renamed`.zip` files, and the file structure is the same as that of `Folders` themes. These will be overwritten by `Folders` themes with the same name.
To create one follow the instructions on `Folders` themes, and zip the files up into a zip archive, and rename it to `<themeName>.mat`.
### Loose Folder Themes
These will overwrite all other themes, and are loaded from a folder named after the theme. There are 4 types of files you can include:
* `Theme.xaml`
* This file determines the colors and styling of the theme.
* The filename isn't important, but the `.xaml` file extension is.
* To see an example of this file press the <kbd>Export Template</kbd> button in the `Options` page. It will create a folder in `Themes` called `Ugly Kulu-Ya-Ku`. You can open that file to use as a template for your own themes, or just use it.
* `Waifu.png`
* This will be loaded as the background image.
* It will be centered, and you can pick how to stretch it in the associated `.xaml` file.
* The filename isn't important, but the `.png` file extension is.
* `Waifu.side.png`
* This will be loaded as the left side image.<br />It will be left aligned, and you can pick its vertical alignment in the associated `.xaml` file.
* The filename isn't important, but the `.side.png` file extension is.
* `Video.{mp4, webm, mkv, avi, m2ts}`
* This will be loaded as a background video, with sound.
* The filename isn't important, but the file extension must be supported (`.mp4`, `.webm`, `.mkv`, `.avi`, `.m2ts`)
* Whether the file works or not will depend on what codecs the file has, and whether those are available on your machine.
### Overriding Themes
You can mix and match parts from different themes by giving them the same name.
The priority in which they will be used is `Loose Folder Themes` > `Packaged .mat files` > `Built in`. Overriding themes will only change the files that are included.
Examples:
* Adding `/Themes/Dark.mat` which includes `.png` and `.xaml` files will override both those aspects of the `Dark` theme.
* Adding `/Themes/Dark/image.png` will use that image as the background for the `Dark` theme, overriding both the built in theme and `Dark.mat` if it exists.
VRChat Melon Assistant should support themes for Mod Assistant. Check [its README](https://github.com/Assistant/ModAssistant#themes) for more info on theming.
However, this is not a supported feature. If something doesn't work, don't complain to either Assistant or knah.
## Common Issues
**I hit install but I don't see anything in game!**
Double check that you followed the [Usage](#usage) instructions correctly.
Make sure you're looking in the right place. Sometimes mod menus move as modding libraries/practices change.
Double check that you followed the [Usage](#usage) instructions correctly.
Make sure you're looking in the right place. Sometimes mod menus move as modding libraries/practices change.
Additionally, make sure the proper VRChat installation directory is selected in option tab.
**I don't see a certain mod in the mods list!**
Mod Assistant uses mods from [BeatMods](https://beatmods.com/) and shows the whatever is available there. If you need to install a mod manually, please refer to the [Beat Saber Modding Group Wiki](https://bsmg.wiki/pc-modding.html#manual-installation).
VRChat Melon Assistant uses mods from VRChat Modding Group and shows whatever is available for download. It's recommended to avoid non-VRCMG mods due to rampant spread of malware disguised as mods.
**I hit install but now my game won't launch, I can't click any buttons, I only see a black screen, etc**
Please visit the [Beat Saber Modding Group](https://discord.gg/beatsabermods) `#pc-help` channels. Check the pinned messages or ask for help and see if you can work out things out.
Please visit the [VRChat Modding Group](https://discord.gg/rCqKSvR) `#help-and-support` channel. Check the pinned messages or ask for help and see if you can work out things out.
## Credits
semver by Max Hauser
Lemon icon from Twitter Emoji
https://github.com/twitter/twemoji
Original Mod Assistant is made by Assistant and used under the terms of MIT License.
https://github.com/Assistant/ModAssistant
semver by Max Hauser
https://github.com/maxhauser/semver

View file

@ -3,7 +3,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModAssistant", "ModAssistant\ModAssistant.csproj", "{6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRCMelonAssistant", "VRCMelonAssistant\VRCMelonAssistant.csproj", "{6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8DBEE4E3-1033-433D-B1F3-F7D789808141}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
.github\FUNDING.yml = .github\FUNDING.yml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="VRCMelonAssistant.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<userSettings>
<VRCMelonAssistant.Properties.Settings>
<setting name="InstallFolder" serializeAs="String">
<value />
</setting>
<setting name="StoreType" serializeAs="String">
<value />
</setting>
<setting name="Agreed" serializeAs="String">
<value>False</value>
</setting>
<setting name="UpgradeRequired" serializeAs="String">
<value>True</value>
</setting>
<setting name="LastTab" serializeAs="String">
<value />
</setting>
<setting name="SelectedTheme" serializeAs="String">
<value />
</setting>
<setting name="CloseWindowOnFinish" serializeAs="String">
<value>False</value>
</setting>
<setting name="LanguageCode" serializeAs="String">
<value />
</setting>
</VRCMelonAssistant.Properties.Settings>
</userSettings>
</configuration>

View file

@ -1,5 +1,5 @@
<Application
x:Class="ModAssistant.App"
x:Class="VRCMelonAssistant.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DispatcherUnhandledException="Application_DispatcherUnhandledException"
@ -21,6 +21,8 @@
<!-- Load SVG icons, courtesy of lolPants. -->
<ResourceDictionary x:Name="Icons" Source="Resources/Icons.xaml" />
<ResourceDictionary x:Name="AppResources" Source="Resources/AppResources.xaml" />
<!-- Load Styles needed for the Theme engine to work. -->
<ResourceDictionary x:Name="Button_Style" Source="Styles/Button.xaml" />
<ResourceDictionary x:Name="Label_Style" Source="Styles/Label.xaml" />

View file

@ -1,61 +1,54 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
namespace ModAssistant
namespace VRCMelonAssistant
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public static string BeatSaberInstallDirectory;
public static string BeatSaberInstallType;
public static bool SaveModSelection;
public static bool CheckInstalledMods;
public static bool SelectInstalledMods;
public static bool ReinstallInstalledMods;
public static string VRChatInstallDirectory;
public static string VRChatInstallType;
public static bool CloseWindowOnFinish;
public static string Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
public static List<string> SavedMods = ModAssistant.Properties.Settings.Default.SavedMods.Split(',').ToList();
public static MainWindow window;
public static string Arguments;
public static bool Update = true;
public static bool GUI = true;
private async void Application_Startup(object sender, StartupEventArgs e)
{
// Set SecurityProtocol to prevent crash with TLS
System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
// Load localisation languages
LoadLanguage(CultureInfo.CurrentCulture.Name);
// Uncomment the next line to debug localisation
// LoadLanguage("en-DEBUG");
if (ModAssistant.Properties.Settings.Default.UpgradeRequired)
if (VRCMelonAssistant.Properties.Settings.Default.UpgradeRequired)
{
ModAssistant.Properties.Settings.Default.Upgrade();
ModAssistant.Properties.Settings.Default.UpgradeRequired = false;
ModAssistant.Properties.Settings.Default.Save();
VRCMelonAssistant.Properties.Settings.Default.Upgrade();
VRCMelonAssistant.Properties.Settings.Default.UpgradeRequired = false;
VRCMelonAssistant.Properties.Settings.Default.Save();
}
Version = Version.Substring(0, Version.Length - 2);
BeatSaberInstallDirectory = Utils.GetInstallDir();
Pages.Options options = Pages.Options.Instance;
options.InstallDirectory =
VRChatInstallDirectory = Utils.GetInstallDir();
while (string.IsNullOrEmpty(App.BeatSaberInstallDirectory))
Languages.LoadLanguages();
while (string.IsNullOrEmpty(VRChatInstallDirectory))
{
string title = (string)Current.FindResource("App:InstallDirDialog:Title");
string body = (string)Current.FindResource("App:InstallDirDialog:OkCancel");
if (System.Windows.Forms.MessageBox.Show(body, title, System.Windows.Forms.MessageBoxButtons.OKCancel) == System.Windows.Forms.DialogResult.OK)
{
App.BeatSaberInstallDirectory = Utils.GetManualDir();
VRChatInstallDirectory = Utils.GetManualDir();
}
else
{
@ -63,32 +56,43 @@ namespace ModAssistant
}
}
BeatSaberInstallType = ModAssistant.Properties.Settings.Default.StoreType;
SaveModSelection = ModAssistant.Properties.Settings.Default.SaveSelected;
CheckInstalledMods = ModAssistant.Properties.Settings.Default.CheckInstalled;
SelectInstalledMods = ModAssistant.Properties.Settings.Default.SelectInstalled;
ReinstallInstalledMods = ModAssistant.Properties.Settings.Default.ReinstallInstalled;
options.InstallType =
VRChatInstallType = VRCMelonAssistant.Properties.Settings.Default.StoreType;
options.CloseWindowOnFinish =
CloseWindowOnFinish = VRCMelonAssistant.Properties.Settings.Default.CloseWindowOnFinish;
await ArgumentHandler(e.Args);
await Init(Update, GUI);
await Init();
}
private async Task Init(bool Update, bool GUI)
private async Task Init()
{
if (Update)
{
await Task.Run(async () => await Updater.Run());
try
{
await Task.Run(async () => await Updater.Run());
}
catch (UnauthorizedAccessException)
{
Utils.StartAsAdmin(Arguments, true);
}
}
if (GUI)
{
MainWindow window = new MainWindow();
window = new MainWindow();
window.Show();
}
else
{
//Application.Current.Shutdown();
}
}
private async Task ArgumentHandler(string[] args)
{
Arguments = string.Join(" ", args);
while (args.Length > 0)
{
switch (args[0])
@ -98,9 +102,11 @@ namespace ModAssistant
{
Utils.SendNotify(string.Format((string)Current.FindResource("App:InvalidArgument"), "--install"));
}
else
if (CloseWindowOnFinish)
{
await OneClickInstaller.InstallAsset(args[1]);
await Task.Delay(5 * 1000);
Current.Shutdown();
}
Update = false;
@ -120,25 +126,26 @@ namespace ModAssistant
}
else
{
LoadLanguage(args[1]);
if (Languages.LoadLanguage(args[1]))
{
VRCMelonAssistant.Properties.Settings.Default.LanguageCode = args[1];
VRCMelonAssistant.Properties.Settings.Default.Save();
Languages.UpdateUI(args[1]);
}
}
args = Shift(args, 2);
break;
case "--register":
if (args.Length < 2 || string.IsNullOrEmpty(args[1]))
if (args.Length < 3 || string.IsNullOrEmpty(args[1]))
{
Utils.SendNotify(string.Format((string)Current.FindResource("App:InvalidArgument"), "--register"));
}
else
{
OneClickInstaller.Register(args[1], true);
}
Update = false;
GUI = false;
args = Shift(args, 2);
args = Shift(args, 3);
break;
case "--unregister":
@ -146,16 +153,18 @@ namespace ModAssistant
{
Utils.SendNotify(string.Format((string)Current.FindResource("App:InvalidArgument"), "--unregister"));
}
else
{
OneClickInstaller.Unregister(args[1], true);
}
Update = false;
GUI = false;
args = Shift(args, 2);
break;
case "--runforever":
while (true)
{
}
default:
Utils.SendNotify((string)Current.FindResource("App:UnrecognizedArgument"));
args = Shift(args);
@ -168,7 +177,7 @@ namespace ModAssistant
{
if (places >= array.Length) return Array.Empty<string>();
string[] newArray = new string[array.Length - places];
for(int i = places; i < array.Length; i++)
for (int i = places; i < array.Length; i++)
{
newArray[i - places] = array[i];
}
@ -180,34 +189,10 @@ namespace ModAssistant
{
string title = (string)Current.FindResource("App:Exception");
string body = (string)Current.FindResource("App:UnhandledException");
MessageBox.Show($"{body}: {e.Exception}", "Exception", MessageBoxButton.OK, MessageBoxImage.Warning);
MessageBox.Show($"{body}: {e.Exception}", title, MessageBoxButton.OK, MessageBoxImage.Warning);
e.Handled = true;
Application.Current.Shutdown();
}
private ResourceDictionary LanguagesDict
{
get
{
return Resources.MergedDictionaries[1];
}
}
private void LoadLanguage(string culture)
{
try
{
LanguagesDict.Source = new Uri($"Localisation/{culture}.xaml", UriKind.Relative);
}
catch (IOException)
{
if (culture.Contains("-"))
{
LoadLanguage(culture.Split('-').First());
}
// Can't load language file
}
Current.Shutdown();
}
}
}

View file

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
namespace ModAssistant
namespace VRCMelonAssistant
{
class Diagnostics
{
@ -37,11 +36,11 @@ namespace ModAssistant
{
MessageBox.Show("! " + file);
}
}
if (entries.Count > 0)
{
entries[entries.Count - 1] = entries[entries.Count - 1].Replace("├", "└");
}
return entries.ToArray();
}

View file

@ -0,0 +1,125 @@
using System.Collections.Generic;
namespace VRCMelonAssistant
{
public static class HardcodedCategories
{
private static readonly Dictionary<string, List<string>> CategoryContents = new()
{
{"Safety & Security", new() {"Advanced Safety", "Finitizer", "True Shader Anticrash", "Safety-Presets", "Final IK Sanity"}},
{"Core mods and libraries", new() {"UI Expansion Kit", "ActionMenuApi", "VRCModUpdater.Loader", "VRChatUtilityKit"}},
{"All-in-one mods", new() {"emmVRCLoader"}},
{"Camera mods", new() {
"CameraMinus", "DesktopCamera", "BetterSteadycam", "ITR's Melon Cameras", "CameraResChanger",
"LocalCameraMod", "Lag Free Screenshots"
}},
{"Performance & Fidelity", new() {
"Core Limiter", "MirrorResolutionUnlimiter", "AvatarHider", "Runtime Graphics Settings",
"GamePriority", "FrameFocus", "ClearVRAM", "NoPerformanceStats", "Turbones"
}},
{"Utilities & Tweaks", new() {
"ReloadAvatars", "KeyboardPaste", "No Outlines", "UnmuteSound", "SparkleBeGone",
"BTKSAImmersiveHud", "OGTrustRanks", "ToggleMicIcon", "Friends+ home",
"MicSensitivity", "CloningBeGone", "ToggleFullScreen", "View Point Tweaker",
"SettingsRestart", "SmallUserVolume", "TeleportCameraToYou",
"BetterPortalPlacement", "BetterDirections", "ChairExitController",
"Panic Button Rework", "SelectYourself", "Trust Color Changer", "Voice Falloff Override"
}},
{"Hardware support", new() {"LeapMotionExtension", "ThumbParams", "VRCFaceTracking", "VRCPimaxEyeTracker", "VRCBhapticsIntegration"}},
{"Dynamic bones", new() {
"ImmersiveTouch", "Dynamic Bones Safety", "MultiplayerDynamicBonesMod", "Multiplayer Dynamic Bones",
}},
{"World tweaks", new() {
"PostProcessing", "NearClipPlaneAdj", "RemoveChairs", "ComponentToggle", "No Grabby Hands", "AOOverride"
}},
{"Fixes", new() {"Invite+ fix", "CursorLockFix", "DownloadFix", "ProneUiFix"}},
{"New features & Overhauls", new() {
"IKTweaks", "JoinNotifier", "FBT Saver", "BTKSANameplateMod", "AdvancedInvites", "VRCVideoLibrary",
"BTKSASelfPortrait", "OldMate", "BetterLoadingScreen", "Loading Screen Pictures", "FavCat",
"ActionMenuUtils", "WorldPredownload", "AskToPortal", "Headlight", "ITR's Player Tracer",
"InstanceHistory", "PortableMirrorMod", "VRCBonesController", "CalibrationLinesVisualizer",
"ITR's Collider Mod", "RememberMe", "TriggerESP"
}},
{"UI mods", new() {
"Particle and DynBone limiter settings UI", "CalibrateConfirm", "Emoji Page Buttons",
"UserInfoExtensions", "MLConsoleViewer", "OwO Mod", "ActiveBackground", "PlayerList", "ComfyVRMenu",
"DiscordMute", "MicToggle", "VRCPlusPet", "AMMusic", "Friend Notes", "GestureIndicator",
"NameplateStats", "PreviewScroller", "ProPlates", "QuickMenuVolume", "ToggleUIStickers"
}},
{"Movement", new() {
"TeleporterVR", "ImmobilizePlayerMod", "TrackingRotator", "OculusPlayspaceMover",
"ITR's Gravity Controller", "QMFreeze", "Double-Tap Runner", "Player Rotater",
"HeadTurn"
}},
{"Very Niche Mods", new() {"HWIDPatch", "No Steam. At all.", "Vertex Animation Remover", "LocalPlayerPrefs", "BTKSAGestureMod",}}
};
private static readonly Dictionary<string, string> CategoryDescriptions = new()
{
{"Safety & Security", "Crash less, block annoyances"},
{"Core mods and libraries", "Other mods might require these"},
{"All-in-one mods", "It does a lot of stuff"},
{"Camera mods", "For all your screenshot or streaming needs"},
{"Performance & Fidelity", "Improve performance or make the game look better"},
{"Utilities & Tweaks", "Small mods that address specific issues"},
{"Hardware support", "For all exotic hardware out there"},
{"Dynamic bones", "Mods that affect jiggly bits"},
{"World tweaks", "Change aspects of the world you're in"},
{"Fixes", "It's not a bug, it's a feature"},
{"New features & Overhauls", "Mods that introduce new features or significantly change existing ones"},
{"UI mods", "Modify the user interface or introduce new functionality to it"},
{"Movement", "Move in new exciting ways"},
{"Very Niche Mods", "Only use these if you're really sure you need them"}
};
private static readonly Dictionary<string, string> ModNameToCategory = new();
static HardcodedCategories()
{
foreach (var keyValuePair in CategoryContents)
foreach (var s in keyValuePair.Value)
ModNameToCategory.Add(s.ToLowerInvariant(), keyValuePair.Key);
}
public static string GetCategoryFor(Mod mod)
{
foreach (var alias in mod.aliases)
{
if (ModNameToCategory.TryGetValue(alias.ToLowerInvariant(), out var result)) return result;
}
foreach (var version in mod.versions)
{
if (ModNameToCategory.TryGetValue(version.name.ToLowerInvariant(), out var result)) return result;
}
return null;
}
public static string GetCategoryDescription(string category)
{
return CategoryDescriptions.TryGetValue(category, out var result) ? result : "";
}
private static List<(string Original, string Replace)> ourAuthorReplaces =
new()
{
("<@!170953680718266369>", "ImTiara"),
("<@!286669951987613706>", "Rafa"),
("<@!168795588366696450>", "Grummus"),
("<@!167335587488071682>", "KortyBoi/Lily"),
("<@!127978642981650432>", "tetra"),
("<@!155396491853168640>", "Dawn/arion")
};
public static string FixupAuthor(string authorName)
{
if (string.IsNullOrEmpty(authorName) || !authorName.Contains("@")) return authorName;
foreach (var authorReplace in ourAuthorReplaces)
authorName = authorName.Replace(authorReplace.Original, authorReplace.Replace);
return authorName;
}
}
}

View file

@ -3,7 +3,7 @@ using System.Net;
using System.Net.Http;
using System.Web.Script.Serialization;
namespace ModAssistant
namespace VRCMelonAssistant
{
static class Http
{
@ -26,7 +26,7 @@ namespace ModAssistant
};
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
_client.DefaultRequestHeaders.Add("User-Agent", "ModAssistant/" + App.Version);
_client.DefaultRequestHeaders.Add("User-Agent", "VRCMelonAssistant/" + App.Version);
return _client;
}

View file

@ -1,9 +1,9 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
namespace ModAssistant
namespace VRCMelonAssistant
{
public static class HyperlinkExtensions
{
@ -29,10 +29,21 @@ namespace ModAssistant
hyperlink.RequestNavigate -= Hyperlink_RequestNavigate;
}
private static void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
public static void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
e.Handled = true;
}
public static void NoAwait(this Task task)
{
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
Utils.ShowErrorMessageBox("Exception in free-floating task", t.Exception);
}
});
}
}
}

View file

@ -0,0 +1,118 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using VRCMelonAssistant.Pages;
namespace VRCMelonAssistant
{
public static class InstallHandlers
{
public static bool IsMelonLoaderInstalled()
{
return File.Exists(Path.Combine(App.VRChatInstallDirectory, "version.dll")) && File.Exists(Path.Combine(App.VRChatInstallDirectory, "MelonLoader", "Dependencies", "Bootstrap.dll"));
}
public static bool RemoveMelonLoader()
{
MainWindow.Instance.MainText = $"{(string)App.Current.FindResource("Mods:UnInstallingMelonLoader")}...";
try
{
var versionDllPath = Path.Combine(App.VRChatInstallDirectory, "version.dll");
var melonLoaderDirPath = Path.Combine(App.VRChatInstallDirectory, "MelonLoader");
if (File.Exists(versionDllPath))
File.Delete(versionDllPath);
if (Directory.Exists(melonLoaderDirPath))
Directory.Delete(melonLoaderDirPath, true);
}
catch (Exception ex)
{
MessageBox.Show($"{App.Current.FindResource("Mods:UninstallMLFailed")}.\n\n" + ex);
return false;
}
return true;
}
public static async Task InstallMelonLoader()
{
if (!RemoveMelonLoader()) return;
try
{
MainWindow.Instance.MainText = $"{(string) App.Current.FindResource("Mods:DownloadingMelonLoader")}...";
using var installerZip = await DownloadFileToMemory("https://github.com/LavaGang/MelonLoader/releases/latest/download/MelonLoader.x64.zip");
using var zipReader = new ZipArchive(installerZip, ZipArchiveMode.Read);
MainWindow.Instance.MainText = $"{(string) App.Current.FindResource("Mods:UnpackingMelonLoader")}...";
foreach (var zipArchiveEntry in zipReader.Entries)
{
var targetFileName = Path.Combine(App.VRChatInstallDirectory, zipArchiveEntry.FullName);
Directory.CreateDirectory(Path.GetDirectoryName(targetFileName));
using var targetFile = File.OpenWrite(targetFileName);
using var entryStream = zipArchiveEntry.Open();
await entryStream.CopyToAsync(targetFile);
}
Directory.CreateDirectory(Path.Combine(App.VRChatInstallDirectory, "Mods"));
Directory.CreateDirectory(Path.Combine(App.VRChatInstallDirectory, "Plugins"));
}
catch (Exception ex)
{
MessageBox.Show($"{App.Current.FindResource("Mods:InstallMLFailed")}.\n\n" + ex);
}
}
internal static async Task<Stream> DownloadFileToMemory(string link)
{
using var resp = await Http.HttpClient.GetAsync(link);
var newStream = new MemoryStream();
await resp.Content.CopyToAsync(newStream);
newStream.Position = 0;
return newStream;
}
public static async Task InstallMod(Mod mod)
{
string downloadLink = mod.versions[0].downloadLink;
if (string.IsNullOrEmpty(downloadLink))
{
MessageBox.Show(string.Format((string)App.Current.FindResource("Mods:ModDownloadLinkMissing"), mod.versions[0].name));
return;
}
if (mod.installedFilePath != null)
File.Delete(mod.installedFilePath);
string targetFilePath = "";
using (var resp = await Http.HttpClient.GetAsync(downloadLink))
{
var stream = new MemoryStream();
await resp.Content.CopyToAsync(stream);
stream.Position = 0;
targetFilePath = Path.Combine(App.VRChatInstallDirectory, mod.versions[0].IsPlugin ? "Plugins" : "Mods",
mod.versions[0].IsBroken ? "Broken" : (mod.versions[0].IsRetired ? "Retired" : ""), resp.RequestMessage.RequestUri.Segments.Last());
Directory.CreateDirectory(Path.GetDirectoryName(targetFilePath));
using var targetFile = File.OpenWrite(targetFilePath);
await stream.CopyToAsync(targetFile);
}
mod.ListItem.IsInstalled = true;
mod.installedFilePath = targetFilePath;
mod.ListItem.InstalledVersion = mod.versions[0].modVersion;
mod.ListItem.InstalledModInfo = mod;
}
}
}

View file

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using VRCMelonAssistant.Pages;
namespace VRCMelonAssistant
{
class Languages
{
public static string LoadedLanguage { get; private set; }
public static List<CultureInfo> LoadedLanguages { get => availableCultures.ToList(); }
public static bool FirstRun = true;
private static readonly string[] availableLanguageCodes = { "en" };
private static IEnumerable<CultureInfo> availableCultures;
public static void LoadLanguages()
{
var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
// Get CultureInfo for any of the available translations
availableCultures = allCultures.Where(cultureInfo => availableLanguageCodes.Any(code => code.Equals(cultureInfo.Name)));
string savedLanguageCode = Properties.Settings.Default.LanguageCode;
if (!LoadLanguage(savedLanguageCode))
{
// If no language code was saved, load system language
if (!LoadLanguage(CultureInfo.CurrentUICulture.Name))
{
LoadLanguage("en");
}
}
UpdateUI(LoadedLanguage);
}
public static void UpdateUI(string languageCode)
{
if (Options.Instance != null && Options.Instance.LanguageSelectComboBox != null)
{
Options.Instance.LanguageSelectComboBox.ItemsSource = availableCultures.Select(cultureInfo => cultureInfo.NativeName).ToList();
Options.Instance.LanguageSelectComboBox.SelectedIndex = LoadedLanguages.FindIndex(cultureInfo => cultureInfo.Name.Equals(languageCode));
}
}
public static ResourceDictionary LanguagesDict
{
get
{
return Application.Current.Resources.MergedDictionaries[1];
}
}
public static bool LoadLanguage(string languageCode)
{
if (string.IsNullOrEmpty(languageCode)) return false;
try
{
LanguagesDict.Source = new Uri($"Localisation/{languageCode}.xaml", UriKind.Relative);
LoadedLanguage = languageCode;
return true;
}
catch (IOException)
{
if (languageCode.Contains("-"))
{
return LoadLanguage(languageCode.Split('-').First());
}
return false;
}
}
}
}

View file

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using VRCMelonAssistant.Pages;
namespace VRCMelonAssistant
{
public class Mod
{
public int _id;
public string uploadDate;
public string category;
public string[] aliases;
public ModVersion[] versions;
public Mods.ModListItem ListItem;
public string installedFilePath;
public string installedVersion;
public bool installedInBrokenDir;
public bool installedInRetiredDir;
public class ModVersion
{
public int _version;
public string name;
public string modVersion;
public string modType;
public string author;
public string description;
public string downloadLink;
public string sourceLink;
public string hash;
public string updateDate;
public string vrchatVersion;
public string loaderVersion;
public int approvalStatus;
public bool IsBroken => approvalStatus == 2;
public bool IsRetired => approvalStatus == 3;
public bool IsPlugin => modType.Equals("plugin", StringComparison.InvariantCultureIgnoreCase);
}
}
}

View file

@ -1,6 +1,4 @@
using System;
namespace ModAssistant
namespace VRCMelonAssistant
{
class Promotions
{
@ -8,9 +6,9 @@ namespace ModAssistant
{
new Promotion
{
ModName = "YUR Fit Calorie Tracker",
ModName = "emmVRCLoader",
Text = "Join our Discord!",
Link = "https://yur.chat"
Link = "https://discord.gg/emmvrc"
}
};
}

View file

@ -1,17 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.IO;
using System.Windows.Media;
using ModAssistant.Pages;
using System.Reflection;
using Microsoft.Win32;
using System.Windows.Media.Imaging;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
using VRCMelonAssistant.Pages;
namespace ModAssistant
namespace VRCMelonAssistant
{
public class Themes
{
@ -22,15 +22,15 @@ namespace ModAssistant
/// <summary>
/// Local dictionary of Resource Dictionaries mapped by their names.
/// </summary>
private static Dictionary<string, Theme> loadedThemes = new Dictionary<string, Theme>();
private static List<string> preInstalledThemes = new List<string> { "Light", "Dark", "BSMG", "Light Pink" };
private static readonly Dictionary<string, Theme> loadedThemes = new Dictionary<string, Theme>();
private static readonly List<string> preInstalledThemes = new List<string> { "Light", "Dark", "Light Pink" };
/// <summary>
/// Index of "LoadedTheme" in App.xaml
/// </summary>
private static readonly int LOADED_THEME_INDEX = 3;
private static List<string> supportedVideoExtensions = new List<string>() { ".mp4", ".webm", ".mkv", ".avi", ".m2ts" };
private static readonly List<string> supportedVideoExtensions = new List<string>() { ".mp4", ".webm", ".mkv", ".avi", ".m2ts" };
/// <summary>
/// Load all themes from local Themes subfolder and from embedded resources.
@ -49,20 +49,27 @@ namespace ModAssistant
string location = $"Themes/{localTheme}.xaml";
Uri local = new Uri(location, UriKind.Relative);
ResourceDictionary localDictionary = new ResourceDictionary();
localDictionary.Source = local;
ResourceDictionary localDictionary = new ResourceDictionary
{
Source = local
};
/*
* Load any Waifus that come with these built-in themes, too.
* The format must be: Background.png and Sidebar.png as a subfolder with the same name as the theme name.
* For example: "Themes/Dark/Background.png", or "Themes/Ugly Kulu-Ya-Ku/Sidebar.png"
*/
Waifus waifus = new Waifus();
waifus.Background = GetImageFromEmbeddedResources(localTheme, "Background");
waifus.Sidebar = GetImageFromEmbeddedResources(localTheme, "Sidebar");
Waifus waifus = new Waifus
{
Background = GetImageFromEmbeddedResources(localTheme, "Background"),
Sidebar = GetImageFromEmbeddedResources(localTheme, "Sidebar")
};
Theme theme = new Theme(localTheme, localDictionary)
{
Waifus = waifus
};
Theme theme = new Theme(localTheme, localDictionary);
theme.Waifus = waifus;
loadedThemes.Add(localTheme, theme);
}
@ -112,28 +119,28 @@ namespace ModAssistant
{
try
{
Themes.ApplyWindowsTheme();
ApplyWindowsTheme();
}
catch
{
Themes.ApplyTheme("Light", false);
ApplyTheme("Light", false);
}
return;
}
try
{
Themes.ApplyTheme(savedTheme, false);
ApplyTheme(savedTheme, false);
}
catch (ArgumentException)
{
Themes.ApplyWindowsTheme();
ApplyWindowsTheme();
MainWindow.Instance.MainText = (string)Application.Current.FindResource("Themes:ThemeNotFound");
}
}
/// <summary>
/// Applies a loaded theme to ModAssistant.
/// Applies a loaded theme to VRCMelonAssistant.
/// </summary>
/// <param name="theme">Name of the theme.</param>
/// <param name="sendMessage">Send message to MainText (default: true).</param>
@ -202,7 +209,7 @@ namespace ModAssistant
* Writing it as is instead of using XAMLWriter keeps the source as is with comments, spacing, and organization.
* Using XAMLWriter would compress it into an unorganized mess.
*/
using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream($"ModAssistant.Themes.{themeName}.xaml"))
using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream($"VRCMelonAssistant.Themes.{themeName}.xaml"))
using (FileStream writer = new FileStream($@"{ThemeDirectory}\\{themeName}\\{themeName}.xaml", FileMode.Create))
{
byte[] buffer = new byte[s.Length];
@ -250,8 +257,10 @@ namespace ModAssistant
/// <returns></returns>
private static Theme LoadTheme(string directory, string name)
{
Theme theme = new Theme(name, null);
theme.Waifus = new Waifus();
Theme theme = new Theme(name, null)
{
Waifus = new Waifus()
};
foreach (string file in Directory.EnumerateFiles(directory).OrderBy(x => x))
{
@ -275,8 +284,11 @@ namespace ModAssistant
try
{
Uri resourceSource = new Uri(info.FullName);
ResourceDictionary dictionary = new ResourceDictionary();
dictionary.Source = resourceSource;
ResourceDictionary dictionary = new ResourceDictionary
{
Source = resourceSource
};
theme.ThemeDictionary = dictionary;
}
catch (Exception ex)
@ -380,7 +392,7 @@ namespace ModAssistant
/*
* Check to see if the lengths of each file are different. If they are, overwrite what currently exists.
* The reason we are also checking LoadedTheme against the name variable is to prevent overwriting a file that's
* already being used by ModAssistant and causing a System.IO.IOException.
* already being used by VRCMelonAssistant and causing a System.IO.IOException.
*/
FileInfo existingInfo = new FileInfo(videoName);
if (existingInfo.Length != file.Length && LoadedTheme != name)
@ -405,8 +417,10 @@ namespace ModAssistant
}
}
Theme theme = new Theme(name, dictionary);
theme.Waifus = waifus;
Theme theme = new Theme(name, dictionary)
{
Waifus = waifus
};
return theme;
}
@ -433,9 +447,19 @@ namespace ModAssistant
private static BitmapImage GetImageFromEmbeddedResources(string themeName, string imageName)
{
var assembly = Assembly.GetExecutingAssembly();
var resourceNames = assembly.GetManifestResourceNames();
var desiredResourceName = $"VRCMelonAssistant.Themes.{themeName}.{imageName}.png";
// Don't attempt to access non-existent manifest resources
if (!resourceNames.Contains(desiredResourceName))
{
return null;
}
try
{
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"ModAssistant.Themes.{themeName}.{imageName}.png"))
using (Stream stream = assembly.GetManifestResourceStream(desiredResourceName))
{
byte[] imageBytes = new byte[stream.Length];
stream.Read(imageBytes, 0, (int)stream.Length);
@ -484,8 +508,6 @@ namespace ModAssistant
ChangeColor(icons, "InfoIconColor", "info_circleDrawingGroup");
ChangeColor(icons, "OptionsIconColor", "cogDrawingGroup");
ChangeColor(icons, "ModsIconColor", "microchipDrawingGroup");
ChangeColor(icons, "LoadingIconColor", "loadingInnerDrawingGroup");
ChangeColor(icons, "LoadingIconColor", "loadingMiddleDrawingGroup");
ChangeColor(icons, "LoadingIconColor", "loadingOuterDrawingGroup");
}

View file

@ -3,22 +3,28 @@ using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using static ModAssistant.Http;
using static VRCMelonAssistant.Http;
namespace ModAssistant
namespace VRCMelonAssistant
{
class Updater
{
private static readonly string APILatestURL = "https://api.github.com/repos/Assistant/ModAssistant/releases/latest";
private static readonly string APILatestURL = "https://api.github.com/repos/knah/VRCMelonAssistant/releases/latest";
private static Update LatestUpdate;
private static Version CurrentVersion;
private static Version LatestVersion;
private static bool NeedsUpdate = false;
private static string NewExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "ModAssistant.exe");
private static readonly string NewExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "VRCMelonAssistant.exe");
private static readonly string Arguments = App.Arguments;
#pragma warning disable CS0162 // Unreachable code detected
public static async Task<bool> CheckForUpdate()
{
#if DEBUG
return false;
#endif
var resp = await HttpClient.GetAsync(APILatestURL);
var body = await resp.Content.ReadAsStringAsync();
LatestUpdate = JsonSerializer.Deserialize<Update>(body);
@ -28,10 +34,11 @@ namespace ModAssistant
return (LatestVersion > CurrentVersion);
}
#pragma warning restore CS0162 // Unreachable code detected
public static async Task Run()
{
if (Path.GetFileName(Utils.ExePath).Equals("ModAssistant.old.exe")) RunNew();
if (Path.GetFileName(Utils.ExePath).Equals("VRCMelonAssistant.old.exe")) RunNew();
try
{
NeedsUpdate = await CheckForUpdate();
@ -46,12 +53,12 @@ namespace ModAssistant
public static async Task StartUpdate()
{
string OldExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "ModAssistant.old.exe");
string OldExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "VRCMelonAssistant.old.exe");
string DownloadLink = null;
foreach (Update.Asset asset in LatestUpdate.assets)
{
if (asset.name == "ModAssistant.exe")
if (asset.name == "VRCMelonAssistant.exe")
{
DownloadLink = asset.browser_download_url;
}
@ -77,7 +84,7 @@ namespace ModAssistant
private static void RunNew()
{
Process.Start(NewExe);
Process.Start(NewExe, Arguments);
Application.Current.Dispatcher.Invoke(() => { Application.Current.Shutdown(); });
}
}

View file

@ -1,4 +1,3 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Configuration;
@ -12,9 +11,11 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using static ModAssistant.Http;
using Microsoft.Win32;
using VRCMelonAssistant.Pages;
using static VRCMelonAssistant.Http;
namespace ModAssistant
namespace VRCMelonAssistant
{
public class Utils
{
@ -23,14 +24,9 @@ namespace ModAssistant
public class Constants
{
public const string BeatSaberAPPID = "620980";
public const string BeatModsAPIUrl = "https://beatmods.com/api/v1/";
public const string TeknikAPIUrl = "https://api.teknik.io/v1/";
public const string BeatModsURL = "https://beatmods.com";
public const string BeatModsVersions = "https://versions.beatmods.com/versions.json";
public const string BeatModsAlias = "https://alias.beatmods.com/aliases.json";
public const string VRChatAppId = "438100";
public const string VRCMGModsJson = "https://api.vrcmg.com/v1/mods";
public const string WeebCDNAPIURL = "https://pat.assistant.moe/api/v1.0/";
public const string BeatModsModsOptions = "mod?status=approved";
public const string MD5Spacer = " ";
public static readonly char[] IllegalCharacters = new char[]
{
@ -42,20 +38,6 @@ namespace ModAssistant
};
}
public class TeknikPasteResponse
{
public Result result;
public class Result
{
public string id;
public string url;
public string title;
public string syntax;
public DateTime? expiration;
public string password;
}
}
public class WeebCDNRandomResponse
{
public int index;
@ -125,8 +107,8 @@ namespace ModAssistant
if (!string.IsNullOrEmpty(InstallDir)
&& Directory.Exists(InstallDir)
&& Directory.Exists(Path.Combine(InstallDir, "Beat Saber_Data", "Plugins"))
&& File.Exists(Path.Combine(InstallDir, "Beat Saber.exe")))
&& Directory.Exists(Path.Combine(InstallDir, "VRChat_Data", "Plugins"))
&& File.Exists(Path.Combine(InstallDir, "VRChat.exe")))
{
return InstallDir;
}
@ -164,13 +146,14 @@ namespace ModAssistant
public static string SetDir(string directory, string store)
{
App.BeatSaberInstallDirectory = directory;
App.BeatSaberInstallType = store;
App.VRChatInstallDirectory = directory;
App.VRChatInstallType = store;
Pages.Options.Instance.InstallDirectory = directory;
Pages.Options.Instance.InstallType = store;
Properties.Settings.Default.InstallFolder = directory;
Properties.Settings.Default.StoreType = store;
Properties.Settings.Default.Save();
MainWindow.Instance?.MarkModsPageForRefresh();
return directory;
}
@ -210,9 +193,9 @@ namespace ModAssistant
regex = new Regex("\\s\"installdir\"\\s+\"(.+)\"");
foreach (string path in SteamPaths)
{
if (File.Exists(Path.Combine(@path, @"appmanifest_" + Constants.BeatSaberAPPID + ".acf")))
if (File.Exists(Path.Combine(@path, @"appmanifest_" + Constants.VRChatAppId + ".acf")))
{
using (StreamReader reader = new StreamReader(Path.Combine(@path, @"appmanifest_" + Constants.BeatSaberAPPID + ".acf")))
using (StreamReader reader = new StreamReader(Path.Combine(@path, @"appmanifest_" + Constants.VRChatAppId + ".acf")))
{
string line;
while ((line = reader.ReadLine()) != null)
@ -220,7 +203,7 @@ namespace ModAssistant
Match match = regex.Match(line);
if (match.Success)
{
if (File.Exists(Path.Combine(@path, @"common", match.Groups[1].Value, "Beat Saber.exe")))
if (File.Exists(Path.Combine(@path, @"common", match.Groups[1].Value, "VRChat.exe")))
{
return SetDir(Path.Combine(@path, @"common", match.Groups[1].Value), "Steam");
}
@ -234,7 +217,7 @@ namespace ModAssistant
public static string GetVersion()
{
string filename = Path.Combine(App.BeatSaberInstallDirectory, "Beat Saber_Data", "globalgamemanagers");
string filename = Path.Combine(App.VRChatInstallDirectory, "VRChat_Data", "globalgamemanagers");
using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
byte[] file = File.ReadAllBytes(filename);
@ -245,7 +228,7 @@ namespace ModAssistant
int index = Encoding.UTF8.GetString(file).IndexOf("public.app-category.games") + 136;
Array.Copy(file, index, bytes, 0, 32);
string version = Encoding.UTF8.GetString(bytes).Trim(Utils.Constants.IllegalCharacters);
string version = Encoding.UTF8.GetString(bytes).Trim(Constants.IllegalCharacters);
return version;
}
@ -258,9 +241,9 @@ namespace ModAssistant
if (!string.IsNullOrEmpty(OculusInstall))
{
if (File.Exists(Path.Combine(OculusInstall, "Software", "hyperbolic-magnetism-beat-saber", "Beat Saber.exe")))
if (File.Exists(Path.Combine(OculusInstall, "Software", "vrchat-vrchat", "VRChat.exe")))
{
return SetDir(Path.Combine(OculusInstall, "Software", "hyperbolic-magnetism-beat-saber"), "Oculus");
return SetDir(Path.Combine(OculusInstall, "Software", "vrchat-vrchat"), "Oculus");
}
}
@ -294,8 +277,8 @@ namespace ModAssistant
string GUIDLetter = guidLetterVolumes.FirstOrDefault(x => libraryPath.Contains(x.Key)).Value;
if (!string.IsNullOrEmpty(GUIDLetter))
{
string finalPath = Path.Combine(GUIDLetter, libraryPath.Substring(49), @"Software\hyperbolic-magnetism-beat-saber");
if (File.Exists(Path.Combine(finalPath, "Beat Saber.exe")))
string finalPath = Path.Combine(GUIDLetter, libraryPath.Substring(49), @"Software\vrchat-vrchat");
if (File.Exists(Path.Combine(finalPath, "VRChat.exe")))
{
return SetDir(finalPath, "Oculus");
}
@ -323,10 +306,10 @@ namespace ModAssistant
path = path.Replace("\\select.this.directory", "");
path = path.Replace(".this.directory", "");
path = path.Replace("\\select.directory", "");
if (File.Exists(Path.Combine(path, "Beat Saber.exe")))
if (File.Exists(Path.Combine(path, "VRChat.exe")))
{
string store;
if (File.Exists(Path.Combine(path, "Beat Saber_Data", "Plugins", "steam_api64.dll")))
if (File.Exists(Path.Combine(path, "VRChat_Data", "Plugins", "steam_api64.dll")))
{
store = "Steam";
}
@ -356,20 +339,6 @@ namespace ModAssistant
return null;
}
public static bool IsVoid()
{
string directory = App.BeatSaberInstallDirectory;
if (File.Exists(Path.Combine(directory, "IGG-GAMES.COM.url")) ||
File.Exists(Path.Combine(directory, "SmartSteamEmu.ini")) ||
File.Exists(Path.Combine(directory, "GAMESTORRENT.CO.url")) ||
File.Exists(Path.Combine(directory, "Beat Saber_Data", "Plugins", "BSteam crack.dll")) ||
File.Exists(Path.Combine(directory, "Beat Saber_Data", "Plugins", "HUHUVR_steam_api64.dll")) ||
Directory.GetFiles(Path.Combine(directory, "Beat Saber_Data", "Plugins"), "*.ini", SearchOption.TopDirectoryOnly).Length > 0)
return true;
return false;
}
public static byte[] StreamToArray(Stream input)
{
byte[] buffer = new byte[16 * 1024];
@ -391,7 +360,7 @@ namespace ModAssistant
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo()
Process.Start(new System.Diagnostics.ProcessStartInfo()
{
FileName = location,
UseShellExecute = true,
@ -408,7 +377,7 @@ namespace ModAssistant
{
string path = Path.GetDirectoryName(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath);
string logFile = $"{path}{Path.DirectorySeparatorChar}log.log";
File.AppendAllText(logFile, $"[{DateTime.UtcNow.ToString("yyyy-mm-dd HH:mm:ss.ffffff")}][{severity.ToUpper()}] {message}\n");
File.AppendAllText(logFile, $"[{DateTime.UtcNow:yyyy-mm-dd HH:mm:ss.ffffff}][{severity.ToUpper()}] {message}\n");
}
public static async Task Download(string link, string output)
@ -439,5 +408,36 @@ namespace ModAssistant
ShowMessageBoxDelegate caller = new ShowMessageBoxDelegate(ShowMessageBox);
caller.BeginInvoke(Message, null, null, null);
}
public static void ShowErrorMessageBox(string title, Exception ex)
{
MessageBox.Show(MainWindow.Instance, $"{title}\n{ex}", "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
}
/// <summary>
/// Attempts to write the specified string to the <see cref="System.Windows.Clipboard"/>.
/// </summary>
/// <param name="text">The string to be written</param>
public static void SetClipboard(string text)
{
bool success = false;
try
{
Clipboard.SetText(text);
success = true;
}
catch (Exception)
{
// Swallow exceptions relating to writing data to clipboard.
}
// This could be placed in the try/catch block but we don't
// want to suppress exceptions for non-clipboard operations
if (success)
{
SendNotify($"Copied text to clipboard");
}
}
}
}

View file

@ -22,7 +22,7 @@ THE SOFTWARE.
using System.Text;
namespace ModAssistant.Libs
namespace VRCMelonAssistant.Libs
{
internal static class IntExtensions
{

View file

@ -29,7 +29,7 @@ using System.Security.Permissions;
#endif
using System.Text.RegularExpressions;
namespace ModAssistant.Libs
namespace VRCMelonAssistant.Libs
{
/// <summary>
/// A semantic version implementation.
@ -46,6 +46,7 @@ namespace ModAssistant.Libs
new Regex(@"^(?<major>\d+)" +
@"(?>\.(?<minor>\d+))?" +
@"(?>\.(?<patch>\d+))?" +
@"(?>\.(?<extra>\d+))?" +
@"(?>\-(?<pre>[0-9A-Za-z\-\.]+))?" +
@"(?>\+(?<build>[0-9A-Za-z\-\.]+))?$",
#if NETSTANDARD
@ -82,11 +83,12 @@ namespace ModAssistant.Libs
/// <param name="patch">The patch version.</param>
/// <param name="prerelease">The prerelease version (e.g. "alpha").</param>
/// <param name="build">The build metadata (e.g. "nightly.232").</param>
public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
public SemVersion(int major, int minor = 0, int patch = 0, int extra = 0, string prerelease = "", string build = "")
{
Major = major;
Minor = minor;
Patch = patch;
Extra = extra;
Prerelease = prerelease ?? "";
Build = build ?? "";
@ -151,10 +153,17 @@ namespace ModAssistant.Libs
else if (strict)
throw new InvalidOperationException("Invalid version (no patch version given in strict mode)");
var extraMatch = match.Groups["extra"];
int extra = 0;
if (extraMatch.Success)
extra = int.Parse(extraMatch.Value, CultureInfo.InvariantCulture);
var prerelease = match.Groups["pre"].Value;
var build = match.Groups["build"].Value;
return new SemVersion(major, minor, patch, prerelease, build);
return new SemVersion(major, minor, patch, extra, prerelease, build);
}
/// <summary>
@ -197,10 +206,18 @@ namespace ModAssistant.Libs
}
else if (strict) return false;
var extraMatch = match.Groups["extra"];
int extra = 0;
if (extraMatch.Success)
{
if (!int.TryParse(extraMatch.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out extra))
return false;
}
var prerelease = match.Groups["pre"].Value;
var build = match.Groups["build"].Value;
semver = new SemVersion(major, minor, patch, prerelease, build);
semver = new SemVersion(major, minor, patch, extra, prerelease, build);
return true;
}
@ -248,13 +265,14 @@ namespace ModAssistant.Libs
/// To change only the patch version:
/// <code>version.Change(patch: 4)</code>
/// </example>
public SemVersion Change(int? major = null, int? minor = null, int? patch = null,
public SemVersion Change(int? major = null, int? minor = null, int? patch = null, int? extra = null,
string prerelease = null, string build = null)
{
return new SemVersion(
major ?? Major,
minor ?? Minor,
patch ?? Patch,
extra ?? Extra,
prerelease ?? Prerelease,
build ?? Build);
}
@ -283,6 +301,11 @@ namespace ModAssistant.Libs
/// </value>
public int Patch { get; }
/// <summary>
/// Handles the fourth number present in assembly versions
/// </summary>
public int Extra { get; }
/// <summary>
/// Gets the prerelease version.
/// </summary>
@ -316,6 +339,12 @@ namespace ModAssistant.Libs
version.Append(Minor);
version.Append('.');
version.Append(Patch);
if (Extra != 0)
{
version.Append('.');
version.Append(Extra);
}
if (Prerelease.Length > 0)
{
version.Append('-');
@ -409,6 +438,9 @@ namespace ModAssistant.Libs
r = Patch.CompareTo(other.Patch);
if (r != 0) return r;
r = Extra.CompareTo(other.Extra);
if (r != 0) return r;
return CompareComponent(Prerelease, other.Prerelease, true);
}

View file

@ -1,7 +1,7 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModAssistant"
xmlns:local="clr-namespace:VRCMelonAssistant"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ResourceDictionaryName">i18n:en-DEBUG</sys:String>
@ -62,6 +62,7 @@
<sys:String x:Key="Mods:CheckingInstalledMods">Mods:CheckingInstalledMods</sys:String>
<sys:String x:Key="Mods:LoadingMods">Mods:LoadingMods</sys:String>
<sys:String x:Key="Mods:FinishedLoadingMods">Mods:FinishedLoadingMods</sys:String>
<sys:String x:Key="Mods:NoMods">Mods:NoMods</sys:String>
<sys:String x:Key="Mods:InstallingMod">{0} Mods:InstallingMod</sys:String>
<sys:String x:Key="Mods:InstalledMod">{0} Mods:InstalledMod</sys:String>
<sys:String x:Key="Mods:FinishedInstallingMods">Mods:FinishedInstallingMods</sys:String>
@ -72,6 +73,8 @@
<sys:String x:Key="Mods:FailedExtract">{0} {1} {2} {3} Mods:FailedExtract</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">{0} {1} Mods:FailedExtractMaxReached</sys:String>
<sys:String x:Key="Mods:SearchLabel">Mods:SearchLabel</sys:String>
<sys:String x:Key="Mods:UninstallBSIPANotFound:Title">Mods:UninstallBSIPANotFound:Title</sys:String>
<sys:String x:Key="Mods:UninstallBSIPANotFound:Body">Mods:UninstallBSIPANotFound:Body</sys:String>
<!-- About Page -->
<sys:String x:Key="About:Title">About:Title</sys:String>
@ -98,8 +101,7 @@
<sys:String x:Key="Options:SelectInstalledMods">Options:SelectInstalledMods</sys:String>
<sys:String x:Key="Options:ReinstallInstalledMods">Options:Reinstall Installed Mods</sys:String>
<sys:String x:Key="Options:EnableOneClickInstalls">Options:EnableOneClickInstalls</sys:String>
<sys:String x:Key="Options:BeatSaver">Options:BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">Options:ModelSaber</sys:String>
<sys:String x:Key="Options:CloseWindow">Options:CloseWindow</sys:String>
<sys:String x:Key="Options:GameType">Options:GameType</sys:String>
<sys:String x:Key="Options:GameType:Steam">Options:GameType:Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Options:GameType:Oculus</sys:String>
@ -108,6 +110,10 @@
<sys:String x:Key="Options:InstallingPlaylist">{0} Options:InstallingPlaylist</sys:String>
<sys:String x:Key="Options:FailedPlaylistSong">{0} Options:FailedPlaylistSong</sys:String>
<sys:String x:Key="Options:FinishedPlaylist">{0} {1} Options:FinishedPlaylist</sys:String>
<sys:String x:Key="Options:ShowOCIWindow">Options:ShowOCIWindow</sys:String>
<sys:String x:Key="Options:OCIWindowYes">Options:OCIWindowYes</sys:String>
<sys:String x:Key="Options:OCIWindowClose">Options:OCIWindowClose</sys:String>
<sys:String x:Key="Options:OCIWindowNo">Options:OCIWindowNo</sys:String>
<sys:String x:Key="Options:Diagnostics">Options:Diagnostics</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Options:OpenLogsButton</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Options:OpenAppDataButton</sys:String>
@ -157,6 +163,11 @@
<sys:String x:Key="OneClick:AssetInstallFailed">OneClick:AssetInstallFailed</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick:ProtocolHandler:Registered</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick:ProtocolHandler:Unregistered</sys:String>
<sys:String x:Key="OneClick:Installing">{0} OneClick:Installing</sys:String>
<sys:String x:Key="OneClick:RatelimitSkip">{0} OneClick:RatelimitSkip</sys:String>
<sys:String x:Key="OneClick:RatelimitHit">{0} OneClick:RatelimitHit</sys:String>
<sys:String x:Key="OneClick:Failed">{0} OneClick:Failed</sys:String>
<sys:String x:Key="OneClick:Done">OneClick:Done</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">Themes:ThemeNotFound</sys:String>

View file

@ -1,20 +1,20 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModAssistant"
xmlns:local="clr-namespace:VRCMelonAssistant"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ResourceDictionaryName">i18n:en-US</sys:String>
<!-- App -->
<sys:String x:Key="App:InstallDirDialog:Title">Couldn't find your Beat Saber install folder!</sys:String>
<sys:String x:Key="App:InstallDirDialog:Title">Couldn't find your VRChat install folder!</sys:String>
<sys:String x:Key="App:InstallDirDialog:OkCancel">Press OK to try again, or Cancel to close application.</sys:String>
<sys:String x:Key="App:InvalidArgument">Invalid argument! '{0}' requires an option.</sys:String>
<sys:String x:Key="App:UnrecognizedArgument">Unrecognized argument. Closing Mod Assistant.</sys:String>
<sys:String x:Key="App:UnrecognizedArgument">Unrecognized argument. Closing VRChat Melon Assistant.</sys:String>
<sys:String x:Key="App:UnhandledException">An unhandled exception just occurred</sys:String>
<sys:String x:Key="App:Exception">Exception</sys:String>
<!-- Main Window -->
<sys:String x:Key="MainWindow:WindowTitle">ModAssistant</sys:String>
<sys:String x:Key="MainWindow:WindowTitle">VRChat Melon Assistant</sys:String>
<sys:String x:Key="MainWindow:IntroButton">Intro</sys:String>
<sys:String x:Key="MainWindow:ModsButton">Mods</sys:String>
<sys:String x:Key="MainWindow:AboutButton">About</sys:String>
@ -24,33 +24,33 @@
<sys:String x:Key="MainWindow:ModInfoButton">Mod Info</sys:String>
<sys:String x:Key="MainWindow:InstallButtonTop">Install</sys:String>
<sys:String x:Key="MainWindow:InstallButtonBottom">or Update</sys:String>
<sys:String x:Key="MainWindow:GameVersionLoadFailed">Could not load game versions, Mods tab will be unavailable.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Title">New Game Version Detected!</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line1">It looks like there's been a game update.</sys:String>
<sys:String x:Key="MainWindow:GameUpdateDialog:Line2">Please double check that the correct version is selected at the bottom left corner!</sys:String>
<sys:String x:Key="MainWindow:NoModSelected">No mod selected!</sys:String>
<sys:String x:Key="MainWindow:NoModInfoPage">{0} does not have an info page.</sys:String>
<!-- Intro Page -->
<sys:String x:Key="Intro:Title">Intro</sys:String>
<sys:String x:Key="Intro:PageTitle">Welcome to Mod Assistant</sys:String>
<sys:String x:Key="Intro:PageTitle">Welcome to VRChat Melon Assistant</sys:String>
<sys:String x:Key="Intro:Terms:Header">Please read this page entirely and carefully</sys:String>
<Span x:Key="Intro:Terms:Line1">
By using this program attest to have read and agree to the following terms:
</Span>
<Span x:Key="Intro:Terms:Line2">
Beat Saber
<Bold>does not</Bold> natively support mods. This means:
VRChat
<Bold>explicitly forbids</Bold> modding the game client in their Terms of Service. This means:
</Span>
<Span x:Key="Intro:Terms:Term0">
Modding the client
<Bold>can</Bold> lead to bans. Mods available via this installer are manually checked to minimize the chance of that happening, but <Bold>the risk is always there</Bold>.
</Span>
<Span x:Key="Intro:Terms:Term1">
Mods
<Bold>will break</Bold> every update. This is normal, and
<Bold>not</Bold> Beat Games' fault.
<Bold>not</Bold> VRChat's fault.
</Span>
<Span x:Key="Intro:Terms:Term2">
Mods
<Bold>will</Bold> cause bugs and performance issues. This is
<Bold>not</Bold> Beat Games' fault.
<Bold>not</Bold> VRChat's fault.
</Span>
<Span x:Key="Intro:Terms:Term3">
Mods are made for
@ -58,21 +58,25 @@
<Bold>free time.</Bold> Please be patient and understanding.
</Span>
<Span x:Key="Intro:ReviewsBeatGamesFault">
<Bold>DO NOT</Bold> leave negative reviews because mods broke. This is
<Bold>not</Bold> Beat Games' fault.
<LineBreak/> They are not trying to kill mods.
<Bold>DO NOT</Bold> complain to VRChat Team because mods broke. This is
<Bold>not</Bold> VRChat's fault.
<LineBreak/> They are not trying to kill mods. Most likely.
</Span>
<Span x:Key="Intro:ReportIssuesNoMods">
If you want to report an issue with the game to VRChat Team, <Bold>make sure the issue is not caused by mods.</Bold>
<LineBreak />To do so, remove all mods and/or MelonLoader and check if the issue still happens.
</Span>
<Span x:Key="Intro:ReviewsRustySpoon">
If I keep seeing people leave negative reviews
If I keep seeing people complain to the devs
<Italic>because</Italic> mods broke,
<LineBreak/>
<Bold>I will personally kill mods with a rusty spoon</Bold>
</Span>
<Span x:Key="Intro:WikiGuide">
Please read the Beginners Guide on the
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bsmg.wiki/pc-modding.html">
Wiki
</Hyperlink>.
Feel free to join the
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://discord.gg/rCqKSvR">
VRChat Modding Group discord
</Hyperlink> for more info, help, and support.
</Span>
<sys:String x:Key="Intro:AgreeButton">I Agree</sys:String>
<sys:String x:Key="Intro:DisagreeButton">Disagree</sys:String>
@ -84,6 +88,7 @@
<!-- Mods Page -->
<sys:String x:Key="Mods:Title">Mods</sys:String>
<sys:String x:Key="Mods:Header:Name">Name</sys:String>
<sys:String x:Key="Mods:Header:Author">Author</sys:String>
<sys:String x:Key="Mods:Header:Installed">Installed</sys:String>
<sys:String x:Key="Mods:Header:Latest">Latest</sys:String>
<sys:String x:Key="Mods:Header:Description">Description</sys:String>
@ -93,34 +98,34 @@
<sys:String x:Key="Mods:CheckingInstalledMods">Checking installed mods</sys:String>
<sys:String x:Key="Mods:LoadingMods">Loading Mods</sys:String>
<sys:String x:Key="Mods:FinishedLoadingMods">Finished loading mods</sys:String>
<sys:String x:Key="Mods:NoMods">No mods available</sys:String>
<sys:String x:Key="Mods:InstallingMod">Installing {0}</sys:String>
<sys:String x:Key="Mods:InstalledMod">Installed {0}</sys:String>
<sys:String x:Key="Mods:UnInstallingMelonLoader">Uninstalling MelonLoader</sys:String>
<sys:String x:Key="Mods:DownloadingMelonLoader">Downloading MelonLoader</sys:String>
<sys:String x:Key="Mods:UnpackingMelonLoader">Unpacking MelonLoader</sys:String>
<sys:String x:Key="Mods:FinishedInstallingMods">Finished installing mods</sys:String>
<sys:String x:Key="Mods:ModDownloadLinkMissing">Could not find download link for {0}</sys:String>
<sys:String x:Key="Mods:UninstallBox:Title">Uninstall {0}?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body1">Are you sure you want to remove {0}?</sys:String>
<sys:String x:Key="Mods:UninstallBox:Body2">This could break your other mods</sys:String>
<sys:String x:Key="Mods:UninstallSingleFailed">Error uninstalling mod. Make sure VRChat is not running.</sys:String>
<sys:String x:Key="Mods:UninstallMLFailed">Error uninstalling MelonLoader. Make sure VRChat is not running.</sys:String>
<sys:String x:Key="Mods:InstallMLFailed">Error installing MelonLoader. Try using the standalone installer.</sys:String>
<sys:String x:Key="Mods:FailedExtract">Failed to extract {0}, trying again in {1} seconds. ({2}/{3})</sys:String>
<sys:String x:Key="Mods:FailedExtractMaxReached">Failed to extract {0} after max attempts ({1}), skipping. This mod might not work properly so proceed at your own risk</sys:String>
<sys:String x:Key="Mods:SearchLabel">Search...</sys:String>
<!-- About Page -->
<sys:String x:Key="About:Title">About</sys:String>
<sys:String x:Key="About:PageTitle">About Mod Assistant</sys:String>
<sys:String x:Key="About:List:Header">I'm Assistant, and I made Mod Assistant for mod assistance, with a few principles in mind:</sys:String>
<sys:String x:Key="About:List:Item1">Simplicity</sys:String>
<sys:String x:Key="About:List:Item2">Portability</sys:String>
<sys:String x:Key="About:List:Item3">Single Executable</sys:String>
<sys:String x:Key="About:List:Item4">Responsible use</sys:String>
<sys:String x:Key="About:PageTitle">About VRChat Melon Assistant</sys:String>
<sys:String x:Key="About:List:Header">VRChat Melon Assistant is a fork of Assistant's Mod Assistant for Beat Saber.</sys:String>
<Span x:Key="About:SupportAssistant">
If you enjoy this program and would like to support me, please visit my
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://bs.assistant.moe/Donate/">
donation page
</Hyperlink>
or my
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://www.patreon.com/AssistantMoe">
Patreon
Visit
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://github.com/Assistant/ModAssistant">
Mod Assistant GitHub repo
</Hyperlink>
to appreciate their hard work and/or throw some money at them!
</Span>
<sys:String x:Key="About:SpecialThanks">Special Thanks ♥</sys:String>
<sys:String x:Key="About:Donate">Donate</sys:String>
@ -137,9 +142,7 @@
<sys:String x:Key="Options:CheckInstalledMods">Detect Installed Mods</sys:String>
<sys:String x:Key="Options:SelectInstalledMods">Select Installed Mods</sys:String>
<sys:String x:Key="Options:ReinstallInstalledMods">Reinstall Installed Mods</sys:String>
<sys:String x:Key="Options:EnableOneClickInstalls">Enable OneClick™ Installs</sys:String>
<sys:String x:Key="Options:BeatSaver">BeatSaver</sys:String>
<sys:String x:Key="Options:ModelSaber">ModelSaber</sys:String>
<sys:String x:Key="Options:CloseWindow">Close window when finished</sys:String>
<sys:String x:Key="Options:GameType">Game Type</sys:String>
<sys:String x:Key="Options:GameType:Steam">Steam</sys:String>
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
@ -151,22 +154,21 @@
<sys:String x:Key="Options:Diagnostics">Diagnostics</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Open Logs</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Open AppData</sys:String>
<sys:String x:Key="Options:UninstallBSIPAButton">Uninstall BSIPA</sys:String>
<sys:String x:Key="Options:RemoveAllModsButton">Remove All Mods</sys:String>
<sys:String x:Key="Options:RemoveMLButton">Remove MelonLoader</sys:String>
<sys:String x:Key="Options:InstallMLButton">Install MelonLoader</sys:String>
<sys:String x:Key="Options:ApplicationTheme">Application Theme</sys:String>
<sys:String x:Key="Options:ExportTemplateButton">Export Template</sys:String>
<sys:String x:Key="Options:UploadingLog">Uploading Log</sys:String>
<sys:String x:Key="Options:LogUrlCopied">Log URL Copied To Clipboard!</sys:String>
<sys:String x:Key="Options:LogUploadFailed">Uploading Log Failed</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Title">Uploading log failed!</sys:String>
<sys:String x:Key="Options:LogUploadFailed:Body">Could not upload log file to Teknik, please try again or send the file manually.</sys:String>
<sys:String x:Key="Options:GettingModList">Getting Mod List</sys:String>
<sys:String x:Key="Options:FindingBSIPAVersion">Finding BSIPA Version</sys:String>
<sys:String x:Key="Options:BSIPAUninstalled">BSIPA Uninstalled</sys:String>
<sys:String x:Key="Options:YeetModsBox:Title">Uninstall All Mods?</sys:String>
<sys:String x:Key="Options:YeetModsBox:RemoveAllMods">Are you sure you want to remove ALL mods?</sys:String>
<sys:String x:Key="Options:YeetModsBox:CannotBeUndone">This cannot be undone.</sys:String>
<sys:String x:Key="Options:AllModsUninstalled">All Mods Uninstalled</sys:String>
<sys:String x:Key="Options:YeetMLBox:Title">Uninstall MelonLoader?</sys:String>
<sys:String x:Key="Options:YeetMLBox:RemoveAllMods">Are you sure you want to uninstall MelonLoader?</sys:String>
<sys:String x:Key="Options:YeetMLBox:CannotBeUndone">All your mods and mod data will stay intact.</sys:String>
<sys:String x:Key="Options:MLUninstalled">MelonLoader Uninstalled</sys:String>
<sys:String x:Key="Options:MLInstalled">MelonLoader Installed</sys:String>
<sys:String x:Key="Options:CurrentThemeRemoved">Current theme has been removed, reverting to default...</sys:String>
<sys:String x:Key="Options:ThemeFolderNotFound">Themes folder not found! Try exporting the template...</sys:String>
<sys:String x:Key="Options:AppDataNotFound">AppData folder not found! Try launching your game.</sys:String>
@ -174,49 +176,6 @@
<!-- Loading Page -->
<sys:String x:Key="Loading:Loading">Loading Mods</sys:String>
<!-- Invalid Page -->
<sys:String x:Key="Invalid:Title">Invalid</sys:String>
<sys:String x:Key="Invalid:PageTitle">Invalid Installation Detected</sys:String>
<sys:String x:Key="Invalid:PageSubtitle">Your game installation is corrupted or otherwise invalid</sys:String>
<sys:String x:Key="Invalid:List:Header">This can happen if your copy of the game is pirated, or if you copied a pirated copy over your legit install</sys:String>
<Span x:Key="Invalid:List:Line1">
If your copy of the game is pirated,
<Bold>please purchase the game
<Hyperlink NavigateUri="https://beatgames.com/" local:HyperlinkExtensions.IsExternal="True">
HERE
</Hyperlink>
</Bold>.
</Span>
<Span x:Key="Invalid:List:Line2">
If your copy of the game is
<Bold>not</Bold> pirated, please
<Hyperlink NavigateUri="https://bsmg.wiki/support#clean-installation" local:HyperlinkExtensions.IsExternal="True">
do a clean install
</Hyperlink>.
</Span>
<Span x:Key="Invalid:List:Line3">
If those don't help, ask for support in the
<Span Foreground="Blue">#support</Span> channel in
<Hyperlink NavigateUri="https://discord.gg/beatsabermods" local:HyperlinkExtensions.IsExternal="True">
BSMG
</Hyperlink>.
</Span>
<sys:String x:Key="Invalid:BoughtGame1">If you used to have a pirated version but have since bought the game</sys:String>
<sys:String x:Key="Invalid:SelectFolderButton">Select Folder</sys:String>
<sys:String x:Key="Invalid:BoughtGame2">You will need to restart Mod Assistant after changing to the legit install</sys:String>
<!-- OneClick Class -->
<sys:String x:Key="OneClick:MapDownloadFailed">Could not get map details.</sys:String>
<sys:String x:Key="OneClick:SongDownloadFailed">Could not download the song.</sys:String>
<sys:String x:Key="OneClick:SongDownload:Failed">Could not download the song.</sys:String>
<sys:String x:Key="OneClick:SongDownload:NetworkIssues">There might be issues with BeatSaver or your internet connection.</sys:String>
<sys:String x:Key="OneClick:SongDownload:FailedTitle">Failed to download song ZIP</sys:String>
<sys:String x:Key="OneClick:InstallDirNotFound">Beat Saber installation path not found.</sys:String>
<sys:String x:Key="OneClick:InstalledAsset">Installed: {0}</sys:String>
<sys:String x:Key="OneClick:AssetInstallFailed">Failed to install.</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick™ Install handlers registered!</sys:String>
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick™ Install handlers unregistered!</sys:String>
<!-- Themes Class -->
<sys:String x:Key="Themes:ThemeNotFound">Theme not found, reverting to default theme...</sys:String>
<sys:String x:Key="Themes:ThemeSet">Theme set to {0}.</sys:String>
@ -230,9 +189,17 @@
<sys:String x:Key="Updater:DownloadFailed">Couldn't download update.</sys:String>
<!-- Utils Class -->
<sys:String x:Key="Utils:NotificationTitle">Mod Assistant</sys:String>
<sys:String x:Key="Utils:NoInstallFolder">Could not detect your Beat Saber install folder. Please select it manually.</sys:String>
<sys:String x:Key="Utils:RunAsAdmin">Mod Assistant needs to run this task as Admin. Please try again.</sys:String>
<sys:String x:Key="Utils:InstallDir:DialogTitle">Select your Beat Saber install folder</sys:String>
<sys:String x:Key="Utils:NotificationTitle">VRChat Melon Assistant</sys:String>
<sys:String x:Key="Utils:NoInstallFolder">Could not detect your VRChat install folder. Please select it manually.</sys:String>
<sys:String x:Key="Utils:RunAsAdmin">VRChat Melon Assistant needs to run this task as Admin. Please try again.</sys:String>
<sys:String x:Key="Utils:InstallDir:DialogTitle">Select your VRChat install folder</sys:String>
<sys:String x:Key="Utils:CannotOpenFolder">Can't open folder: {0}</sys:String>
<sys:String x:Key="ModInfoWindow:Title">Mod info for {0}</sys:String>
<sys:String x:Key="ModInfoWindow:Author">Author: {0}</sys:String>
<sys:String x:Key="ModInfoWindow:NoAuthor">Unknown author</sys:String>
<sys:String x:Key="ModInfoWindow:NoDescription">No description available</sys:String>
<sys:String x:Key="ModInfoWindow:DownloadLink">Download link: </sys:String>
<sys:String x:Key="ModInfoWindow:SourceCodeLink">Source code: </sys:String>
<sys:String x:Key="ModInfoWindow:InternalIds">Internal ID: {0} {1}</sys:String>
</ResourceDictionary>

View file

@ -1,9 +1,9 @@
<Window
x:Class="ModAssistant.MainWindow"
x:Class="VRCMelonAssistant.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ModAssistant"
xmlns:local="clr-namespace:VRCMelonAssistant"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{DynamicResource MainWindow:WindowTitle}"
Icon="Resources/icon.ico"
@ -11,7 +11,7 @@
UIElement.PreviewMouseDown="Window_PreviewMouseDown"
mc:Ignorable="d">
<Grid>
<Rectangle Fill="{DynamicResource ModAssistantBackground}" />
<Rectangle Fill="{DynamicResource VRCMelonAssistantBackground}" />
<Rectangle>
<Rectangle.Fill>
<ImageBrush x:Name="BackgroundImage" Stretch="{DynamicResource BackgroundImageStretch}" />
@ -58,16 +58,17 @@
Margin="0,0,10,5"
Click="IntroButton_Click"
Style="{DynamicResource MainPageButton}">
<StackPanel Margin="0,8,0,0">
<StackPanel Margin="0,6,0,0">
<Image
Height="30"
VerticalAlignment="Bottom"
Source="{StaticResource info_circleDrawingImage}" />
<TextBlock
Margin="0,0,0,5"
<Viewbox Stretch="Uniform" Height="16">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Padding="2,0,2,0"
Text="{DynamicResource MainWindow:IntroButton}" />
</Viewbox>
</StackPanel>
</Button>
@ -84,11 +85,12 @@
Height="30"
VerticalAlignment="Bottom"
Source="{StaticResource microchipDrawingImage}" />
<TextBlock
Margin="0,0,0,5"
<Viewbox Stretch="Uniform" Height="16">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Padding="2,0,2,0"
Text="{DynamicResource MainWindow:ModsButton}" />
</Viewbox>
</StackPanel>
</Button>
@ -104,11 +106,12 @@
Height="30"
VerticalAlignment="Bottom"
Source="{StaticResource heartDrawingImage}" />
<TextBlock
Margin="0,0,0,5"
<Viewbox Stretch="Uniform" Height="16">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Padding="2,0,2,0"
Text="{DynamicResource MainWindow:AboutButton}" />
</Viewbox>
</StackPanel>
</Button>
@ -124,32 +127,14 @@
Height="30"
VerticalAlignment="Bottom"
Source="{StaticResource cogDrawingImage}" />
<TextBlock
Margin="0,0,0,5"
<Viewbox Stretch="Uniform" Height="16">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Padding="2,0,2,0"
Text="{DynamicResource MainWindow:OptionsButton}" />
</Viewbox>
</StackPanel>
</Button>
<StackPanel
Name="GameVersions"
Grid.Row="5"
VerticalAlignment="Bottom">
<TextBlock FontSize="10">
<TextBlock Text="{DynamicResource MainWindow:GameVersionLabel}" />
:
</TextBlock>
<ComboBox
Name="GameVersionsBox"
Margin="0,5,5,0"
SelectionChanged="GameVersionsBox_SelectionChanged" />
<ComboBox
Name="GameVersionsBoxOverride"
Margin="0,5,5,0"
Visibility="Collapsed" />
</StackPanel>
</Grid>
<StackPanel Grid.Row="1" VerticalAlignment="Center">
@ -166,8 +151,8 @@
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="115" />
<ColumnDefinition Width="115" />
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Border
@ -184,9 +169,10 @@
<Button
Name="InfoButton"
Grid.Column="1"
Width="100"
Height="40"
Margin="0,10,0,0"
MinWidth="115"
Margin="10,10,0,0"
Padding="20,0,20,0"
HorizontalAlignment="Right"
Click="InfoButton_Click"
IsEnabled="False">
@ -197,32 +183,27 @@
Text="{DynamicResource MainWindow:ModInfoButton}" />
</StackPanel>
</Button>
<StackPanel
Grid.Column="2" Orientation="Horizontal"
<Button
Name="InstallButton"
Grid.Column="2"
Height="40"
Width="100"
Margin="0,10,0,0"
HorizontalAlignment="Right">
<Button
Name="InstallButton"
Width="100"
Height="40"
HorizontalAlignment="Right"
Click="InstallButton_Click"
IsEnabled="False">
<StackPanel>
<TextBlock
MinWidth="115"
Margin="10,10,0,0"
Padding="20,0,20,0"
HorizontalAlignment="Right"
Click="InstallButton_Click"
IsEnabled="False">
<StackPanel>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Text="{DynamicResource MainWindow:InstallButtonTop}" />
<TextBlock
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Text="{DynamicResource MainWindow:InstallButtonBottom}" />
</StackPanel>
</Button>
</StackPanel>
</StackPanel>
</Button>
</Grid>
</Grid>
</Grid>

View file

@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using VRCMelonAssistant.Pages;
using static VRCMelonAssistant.Http;
namespace VRCMelonAssistant
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public static MainWindow Instance;
public static bool ModsOpened = false;
public static bool ModsLoading = false;
public string MainText
{
get
{
return MainTextBlock.Text;
}
set
{
Dispatcher.Invoke(new Action(() => { Instance.MainTextBlock.Text = value; }));
}
}
public MainWindow()
{
InitializeComponent();
Instance = this;
const int ContentWidth = 1280;
const int ContentHeight = 720;
double ChromeWidth = SystemParameters.WindowNonClientFrameThickness.Left + SystemParameters.WindowNonClientFrameThickness.Right;
double ChromeHeight = SystemParameters.WindowNonClientFrameThickness.Top + SystemParameters.WindowNonClientFrameThickness.Bottom;
double ResizeBorder = SystemParameters.ResizeFrameVerticalBorderWidth;
Width = ChromeWidth + ContentWidth + 2 * ResizeBorder;
Height = ChromeHeight + ContentHeight + 2 * ResizeBorder;
VersionText.Text = App.Version;
Themes.LoadThemes();
Themes.FirstLoad(Properties.Settings.Default.SelectedTheme);
if (Properties.Settings.Default.Agreed)
Instance.ModsButton.IsEnabled = true;
if (!Properties.Settings.Default.Agreed || string.IsNullOrEmpty(Properties.Settings.Default.LastTab))
{
Main.Content = Intro.Instance;
}
else
{
switch (Properties.Settings.Default.LastTab)
{
case "Intro":
Main.Content = Intro.Instance;
break;
case "Mods":
ShowModsPage().NoAwait();
break;
case "About":
Main.Content = About.Instance;
break;
case "Options":
Main.Content = Options.Instance;
Themes.LoadThemes();
break;
default:
Main.Content = Intro.Instance;
break;
}
}
}
/* Force the app to shutdown when The main window is closed.
*
* Explaination:
* OneClickStatus is initialized as a static object,
* so the window will exist, even if it is unused.
* This would cause VRChat Melon Assistant to not shutdown,
* because technically a window was still open.
*/
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
Application.Current.Shutdown();
}
internal void MarkModsPageForRefresh()
{
ModsOpened = false;
InstallButton.IsEnabled = false;
}
public async Task ShowModsPage()
{
void OpenModsPage()
{
Main.Content = Mods.Instance;
Properties.Settings.Default.LastTab = "Mods";
Properties.Settings.Default.Save();
Mods.Instance.RefreshColumns();
}
if (ModsOpened && !Mods.Instance.PendingChanges)
{
OpenModsPage();
return;
}
Main.Content = Loading.Instance;
if (ModsLoading) return;
ModsLoading = true;
await Mods.Instance.LoadMods();
ModsLoading = false;
if (ModsOpened == false) ModsOpened = true;
if (Mods.Instance.PendingChanges == true) Mods.Instance.PendingChanges = false;
if (Main.Content == Loading.Instance)
{
OpenModsPage();
}
}
private void ModsButton_Click(object sender, RoutedEventArgs e)
{
ShowModsPage().NoAwait();
}
private void IntroButton_Click(object sender, RoutedEventArgs e)
{
Main.Content = Intro.Instance;
Properties.Settings.Default.LastTab = "Intro";
Properties.Settings.Default.Save();
}
private void AboutButton_Click(object sender, RoutedEventArgs e)
{
Main.Content = About.Instance;
Properties.Settings.Default.LastTab = "About";
Properties.Settings.Default.Save();
}
private void OptionsButton_Click(object sender, RoutedEventArgs e)
{
Main.Content = Options.Instance;
Themes.LoadThemes();
Properties.Settings.Default.LastTab = "Options";
Properties.Settings.Default.Save();
}
private void InstallButton_Click(object sender, RoutedEventArgs e)
{
Mods.Instance.InstallMods();
}
private void InfoButton_Click(object sender, RoutedEventArgs e)
{
if ((Mods.ModListItem)Mods.Instance.ModsListView.SelectedItem == null)
{
MessageBox.Show((string)Application.Current.FindResource("MainWindow:NoModSelected"));
return;
}
Mods.ModListItem mod = ((Mods.ModListItem)Mods.Instance.ModsListView.SelectedItem);
ShowModInfoWindow(mod.ModInfo);
}
internal static void ShowModInfoWindow(Mod mod)
{
var infoWindow = new ModInfoWindow();
infoWindow.SetMod(mod);
infoWindow.Owner = Instance;
infoWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
infoWindow.ShowDialog();
}
private void Window_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (About.Instance.PatUp.IsOpen)
{
About.Instance.PatUp.IsOpen = false;
About.Instance.PatButton.IsEnabled = true;
}
if (About.Instance.HugUp.IsOpen)
{
About.Instance.HugUp.IsOpen = false;
About.Instance.HugButton.IsEnabled = true;
}
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (Main.Content == Mods.Instance)
{
Mods.Instance.RefreshColumns();
}
}
private void BackgroundVideo_MediaEnded(object sender, RoutedEventArgs e)
{
BackgroundVideo.Position = TimeSpan.Zero;
BackgroundVideo.Play();
}
}
}

View file

@ -0,0 +1,26 @@
<Window x:Class="VRCMelonAssistant.ModInfoWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:VRCMelonAssistant"
mc:Ignorable="d"
Height="450" Width="800">
<Grid>
<Rectangle Fill="{DynamicResource VRCMelonAssistantBackground}" />
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock FontSize="16" Name="ModName" />
<TextBlock Name="ModVersion"/>
<TextBlock Name="ModAuthor" />
<TextBlock />
<TextBlock Name="ModDescription" TextWrapping="Wrap" />
<TextBlock />
<TextBlock Name="SourceCodeLink" />
<TextBlock Name="DownloadLink" />
<TextBlock Name="InternalIds" />
</StackPanel>
</ScrollViewer>
</Grid>
</Window>

View file

@ -0,0 +1,50 @@
using System;
using System.Windows;
using System.Windows.Documents;
namespace VRCMelonAssistant
{
public partial class ModInfoWindow : Window
{
public ModInfoWindow()
{
InitializeComponent();
}
public void SetMod(Mod mod)
{
Title = string.Format((string) FindResource("ModInfoWindow:Title"), mod.versions[0].name);
ModDescription.Text = mod.versions[0].description ?? (string) FindResource("ModInfoWindow:NoDescription");
ModName.Text = mod.versions[0].name;
ModAuthor.Text = string.Format((string) FindResource("ModInfoWindow:Author"), mod.versions[0].author ?? FindResource("ModInfoWindow:NoAuthor"));
ModVersion.Text = mod.versions[0].modVersion;
var dlLink = mod.versions[0].downloadLink;
DownloadLink.Text = (string) FindResource("ModInfoWindow:DownloadLink");
DownloadLink.Inlines.Add(new Run(" "));
if (dlLink?.StartsWith("http") == true)
DownloadLink.Inlines.Add(CreateHyperlink(dlLink));
else
DownloadLink.Inlines.Add(new Run(dlLink));
var srcLink = mod.versions[0].sourceLink;
SourceCodeLink.Text = (string) FindResource("ModInfoWindow:SourceCodeLink");
SourceCodeLink.Inlines.Add(new Run(" "));
if (srcLink?.StartsWith("http") == true)
SourceCodeLink.Inlines.Add(CreateHyperlink(srcLink));
else
SourceCodeLink.Inlines.Add(new Run(srcLink));
InternalIds.Text = string.Format((string) FindResource("ModInfoWindow:InternalIds"), mod._id, mod.versions[0]._version);
}
private static Hyperlink CreateHyperlink(string uri)
{
var link = new Hyperlink(new Run(uri)) {NavigateUri = new Uri(uri)};
link.RequestNavigate += HyperlinkExtensions.Hyperlink_RequestNavigate;
return link;
}
}
}

View file

@ -1,9 +1,9 @@
<Page
x:Class="ModAssistant.Pages.About"
x:Class="VRCMelonAssistant.Pages.About"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ModAssistant"
xmlns:local="clr-namespace:VRCMelonAssistant"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wfi="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
xmlns:winForms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
@ -41,34 +41,7 @@
FontSize="16"
Text="{DynamicResource About:List:Header}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="2"
Margin="15,5"
FontSize="16">
<TextBlock Text="{DynamicResource About:List:Item1}" />
</TextBlock>
<TextBlock
Grid.Row="3"
Margin="15,5"
FontSize="16">
<TextBlock Text="{DynamicResource About:List:Item2}" />
</TextBlock>
<TextBlock
Grid.Row="4"
Margin="15,5"
FontSize="16">
<TextBlock Text="{DynamicResource About:List:Item3}" />
</TextBlock>
<TextBlock
Grid.Row="5"
Margin="15,5"
FontSize="16">
<TextBlock Text="{DynamicResource About:List:Item4}" />
</TextBlock>
<TextBlock
Grid.Row="6"
@ -89,6 +62,38 @@
HorizontalAlignment="Center"
Orientation="Horizontal">
<StackPanel Margin="10" Orientation="Vertical">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="20"
FontWeight="Bold">
<Hyperlink NavigateUri="https://github.com/Assistant/" RequestNavigate="Hyperlink_RequestNavigate">
Assistant
</Hyperlink>
</TextBlock>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16">
Creating the original
</TextBlock>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16">
Mod Assistant
</TextBlock>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16">
<Hyperlink local:HyperlinkExtensions.IsExternal="True" NavigateUri="https://ko-fi.com/N4N8JX7B">
<Run Text="{DynamicResource About:Donate}" />
</Hyperlink>
</TextBlock>
</StackPanel>
<StackPanel Margin="10" Orientation="Vertical">
<TextBlock
HorizontalAlignment="Center"
@ -251,17 +256,19 @@
Orientation="Horizontal">
<Button
x:Name="PatButton"
Width="80"
Height="30"
MinWidth="80"
Margin="0,0,5,0"
Padding="20,0,20,0"
x:FieldModifier="public"
Click="HeadpatsButton_Click"
Content="{DynamicResource About:HeadpatsButton}" />
<Button
x:Name="HugButton"
Width="80"
Height="30"
MinWidth="80"
Margin="5,0,0,0"
Padding="20,0,20,0"
x:FieldModifier="public"
Click="HugsButton_Click"
Content="{DynamicResource About:HugsButton}" />

View file

@ -4,9 +4,9 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using static ModAssistant.Http;
using static VRCMelonAssistant.Http;
namespace ModAssistant.Pages
namespace VRCMelonAssistant.Pages
{
/// <summary>
/// Interaction logic for Page1.xaml
@ -51,12 +51,32 @@ namespace ModAssistant.Pages
private async Task HeadPat()
{
PatImage.Load(await WeebCDN("pats"));
try
{
PatImage.Load(await WeebCDN("pats"));
}
catch (Exception ex)
{
Application.Current.Dispatcher.Invoke(() =>
{
Utils.ShowErrorMessageBox("Oops! Can't get headpats right now!", ex);
});
}
}
private async Task Hug()
{
HugImage.Load(await WeebCDN("hugs"));
try
{
HugImage.Load(await WeebCDN("hugs"));
}
catch (Exception ex)
{
Application.Current.Dispatcher.Invoke(() =>
{
Utils.ShowErrorMessageBox("Oops! Can't get hugs right now!", ex);
});
}
}
}
}

View file

@ -1,9 +1,9 @@
<Page
x:Class="ModAssistant.Pages.Intro"
x:Class="VRCMelonAssistant.Pages.Intro"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ModAssistant"
xmlns:local="clr-namespace:VRCMelonAssistant"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="DynamicResource Intro:Title"
d:DesignHeight="629"
@ -23,6 +23,8 @@
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
@ -59,7 +61,7 @@
Margin="15,5"
FontSize="16"
TextWrapping="Wrap">
<StaticResource ResourceKey="Intro:Terms:Term1" />
<StaticResource ResourceKey="Intro:Terms:Term0" />
</TextBlock>
<TextBlock
@ -67,7 +69,7 @@
Margin="15,5"
FontSize="16"
TextWrapping="Wrap">
<StaticResource ResourceKey="Intro:Terms:Term2" />
<StaticResource ResourceKey="Intro:Terms:Term1" />
</TextBlock>
<TextBlock
@ -75,15 +77,15 @@
Margin="15,5"
FontSize="16"
TextWrapping="Wrap">
<StaticResource ResourceKey="Intro:Terms:Term3" />
<StaticResource ResourceKey="Intro:Terms:Term2" />
</TextBlock>
<TextBlock
Grid.Row="7"
Margin="0,5"
Margin="15,5"
FontSize="16"
TextWrapping="Wrap">
<StaticResource ResourceKey="Intro:ReviewsBeatGamesFault" />
<StaticResource ResourceKey="Intro:Terms:Term3" />
</TextBlock>
<TextBlock
@ -91,7 +93,7 @@
Margin="0,5"
FontSize="16"
TextWrapping="Wrap">
<StaticResource ResourceKey="Intro:ReviewsRustySpoon" />
<StaticResource ResourceKey="Intro:ReviewsBeatGamesFault" />
</TextBlock>
<TextBlock
@ -99,20 +101,36 @@
Margin="0,5"
FontSize="16"
TextWrapping="Wrap">
<StaticResource ResourceKey="Intro:ReportIssuesNoMods" />
</TextBlock>
<TextBlock
Grid.Row="10"
Margin="0,5"
FontSize="16"
TextWrapping="Wrap">
<StaticResource ResourceKey="Intro:ReviewsRustySpoon" />
</TextBlock>
<TextBlock
Grid.Row="11"
Margin="0,5"
FontSize="16"
TextWrapping="Wrap">
<StaticResource ResourceKey="Intro:WikiGuide" />
</TextBlock>
<StackPanel
Grid.Row="10"
Grid.Row="12"
Margin="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button
Name="Agree"
Width="100"
Height="35"
Margin="0,0,10,0"
Padding="20,0,20,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="Agree_Click">
@ -124,9 +142,9 @@
</Button>
<Button
Name="Disagree"
Width="100"
Height="35"
Margin="10,0,0,0"
Padding="20,0,20,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="Disagree_Click">

View file

@ -1,10 +1,9 @@
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
namespace ModAssistant.Pages
namespace VRCMelonAssistant.Pages
{
/// <summary>
/// Interaction logic for Intro.xaml
@ -30,28 +29,19 @@ namespace ModAssistant.Pages
Properties.Settings.Default.Agreed = false;
Properties.Settings.Default.Save();
MessageBox.Show((string)FindResource("Intro:ClosingApp"));
System.Windows.Application.Current.Shutdown();
Application.Current.Shutdown();
}
private void Agree_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(MainWindow.GameVersion))
{
string line1 = (string)FindResource("Intro:VersionDownloadFailed");
string line2 = (string)FindResource("Intro:ModsTabDisabled");
MainWindow.Instance.ModsButton.IsEnabled = true;
MessageBox.Show($"{line1}.\n{line2}");
}
else
{
MainWindow.Instance.ModsButton.IsEnabled = true;
string text = (string)FindResource("Intro:ModsTabEnabled");
Utils.SendNotify(text);
MainWindow.Instance.MainText = text;
}
string text = (string) FindResource("Intro:ModsTabEnabled");
MainWindow.Instance.MainText = text;
Properties.Settings.Default.Agreed = true;
Properties.Settings.Default.Save();
MainWindow.Instance.ShowModsPage().NoAwait();
}
}
}

View file

@ -1,9 +1,9 @@
<Page
x:Class="ModAssistant.Pages.Invalid"
x:Class="VRCMelonAssistant.Pages.Invalid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ModAssistant.Pages"
xmlns:local="clr-namespace:VRCMelonAssistant.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{DynamicResource Invalid:Title}"
d:DesignHeight="629"

View file

@ -1,10 +1,9 @@
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
namespace ModAssistant.Pages
namespace VRCMelonAssistant.Pages
{
/// <summary>
/// Interaction logic for Invalid.xaml
@ -17,7 +16,7 @@ namespace ModAssistant.Pages
public Invalid()
{
InitializeComponent();
InstallDirectory = App.BeatSaberInstallDirectory;
InstallDirectory = App.VRChatInstallDirectory;
DirectoryTextBlock.Text = InstallDirectory;
}

View file

@ -1,9 +1,9 @@
<Page
x:Class="ModAssistant.Pages.Loading"
x:Class="VRCMelonAssistant.Pages.Loading"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ModAssistant.Pages"
xmlns:local="clr-namespace:VRCMelonAssistant.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Loading"
d:DesignHeight="450"
@ -43,18 +43,6 @@
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Margin="100,100,100,0"
VerticalAlignment="Center"
Source="{DynamicResource loadingInnerDrawingImage}"
Stretch="Uniform" />
<Image
Grid.Row="0"
Margin="100,100,100,0"
VerticalAlignment="Center"
Source="{DynamicResource loadingMiddleDrawingImage}"
Stretch="Uniform" />
<Image
Grid.Row="0"
Margin="100,100,100,0"

View file

@ -2,7 +2,7 @@ using System;
using System.Windows.Controls;
using System.Windows.Data;
namespace ModAssistant.Pages
namespace VRCMelonAssistant.Pages
{
/// <summary>
/// Interaction logic for Loading.xaml

View file

@ -0,0 +1,164 @@
<Page
x:Class="VRCMelonAssistant.Pages.Mods"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:VRCMelonAssistant"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="{DynamicResource Mods:Title}"
d:DesignHeight="629"
d:DesignWidth="1182"
Loaded="Page_Loaded"
mc:Ignorable="d">
<Grid>
<Grid x:Name="NoModsGrid" Visibility="Visible">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32" FontWeight="SemiBold">
<TextBlock Text="{DynamicResource Mods:NoMods}" />.
</TextBlock>
</Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock
Name="SearchText"
Grid.Row="0"
Height="20"
Padding="5,0,0,0"
Panel.ZIndex="1"
Background="{DynamicResource BottomStatusBarBackground}"
Foreground="{DynamicResource TextColor}"
Text="{DynamicResource Mods:SearchLabel}" />
<TextBox
Name="SearchBar"
Grid.Row="0"
Height="20"
Margin="0,-1,0,0"
Padding="3,1,0,0"
Panel.ZIndex="2"
Background="#00000000"
BorderThickness="0"
Foreground="{DynamicResource TextColor}"
TextChanged="SearchBar_TextChanged" />
<ListView
Name="ModsListView"
Grid.Row="1"
Grid.Column="0"
SelectionChanged="ModsListView_SelectionChanged"
SelectionMode="Single"
MouseDoubleClick="ModsListView_OnMouseDoubleClick">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Width="30">
<GridViewColumn.Header>
<Button
Name="SearchButton"
Margin="-5"
Padding="9,-1,9,0"
Background="#00000000"
BorderThickness="0"
Click="SearchButton_Click"
Content="🔍"
FontSize="11" />
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
Name="ModCheckBox"
Checked="ModCheckBox_Checked"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
IsEnabled="{Binding IsEnabled}"
Tag="{Binding ModInfo}"
Unchecked="ModCheckBox_Unchecked" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding ModName}" Header="{DynamicResource Mods:Header:Name}" />
<GridViewColumn Header="{DynamicResource Mods:Header:Author}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ModAuthor}"
MaxWidth="150" TextWrapping="NoWrap" TextTrimming="WordEllipsis"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="InstalledColumn" Header="{DynamicResource Mods:Header:Installed}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
Foreground="{Binding GetVersionColor}"
Text="{Binding InstalledVersion}"
TextDecorations="{Binding GetVersionDecoration}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding ModVersion}" Header="{DynamicResource Mods:Header:Latest}" />
<GridViewColumn x:Name="DescriptionColumn" Header="{DynamicResource Mods:Header:Description}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="{Binding PromotionMargin}" Visibility="{Binding PromotionVisibility}">
<Hyperlink NavigateUri="{Binding PromotionLink, TargetNullValue=about:blank}" RequestNavigate="Hyperlink_RequestNavigate">
<Run Text="{Binding PromotionText}" />
</Hyperlink>
</TextBlock>
<TextBlock Text="{Binding ModDescription}" MouseLeftButtonDown="ModsListView_OnMouseDoubleClick" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn
x:Name="UninstallColumn"
Width="70"
Header="{DynamicResource Mods:Header:Uninstall}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Name="Uninstall"
Click="Uninstall_Click"
Content="{DynamicResource Mods:UninstallButton}"
Foreground="Red"
IsEnabled="{Binding CanDelete}"
Tag="{Binding ModInfo}"
Visibility="{Binding CanSeeDelete}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Padding="6,0,0,0">
<Run FontSize="16" FontWeight="Bold" Text="{Binding Name.Name, Mode=OneWay}" />
<Run FontSize="12" FontStyle="Italic" Text="{Binding Name.Description, Mode=OneWay}" />
</TextBlock>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
</Grid>
</Page>

View file

@ -0,0 +1,577 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using Mono.Cecil;
using VRCMelonAssistant.Libs;
using static VRCMelonAssistant.Http;
using MessageBox = System.Windows.MessageBox;
using TextBox = System.Windows.Controls.TextBox;
namespace VRCMelonAssistant.Pages
{
/// <summary>
/// Interaction logic for Mods.xaml
/// </summary>
public sealed partial class Mods : Page
{
public static Mods Instance = new Mods();
private static readonly ModListItem.CategoryInfo BrokenCategory = new("Broken", "These mods were broken by a game update. They will be temporarily removed and restored once they are updated for the current game version");
private static readonly ModListItem.CategoryInfo RetiredCategory = new("Retired", "These mods are either no longer needed due to VRChat updates or are no longer being maintained");
private static readonly ModListItem.CategoryInfo UncategorizedCategory = new("Uncategorized", "Mods without a category assigned");
private static readonly ModListItem.CategoryInfo UnknownCategory = new("Unknown/Unverified", "Mods not coming from VRCMG. Potentially dangerous.");
public List<string> DefaultMods = new List<string>() { "UI Expansion Kit", "Finitizer", "VRCModUpdater.Loader", "VRChatUtilityKit", "Final IK Sanity", "ActionMenuApi" };
public Mod[] AllModsList;
public List<Mod> UnknownMods = new List<Mod>();
public CollectionView view;
public bool PendingChanges;
public bool HaveInstalledMods;
private readonly SemaphoreSlim _modsLoadSem = new SemaphoreSlim(1, 1);
public List<ModListItem> ModList { get; set; }
public Mods()
{
InitializeComponent();
}
private void RefreshModsList()
{
view?.Refresh();
}
public void RefreshColumns()
{
if (MainWindow.Instance.Main.Content != Instance) return;
double viewWidth = ModsListView.ActualWidth;
double totalSize = 0;
GridViewColumn description = null;
if (ModsListView.View is GridView grid)
{
foreach (var column in grid.Columns)
{
if (column.Header?.ToString() == FindResource("Mods:Header:Description").ToString())
{
description = column;
}
else
{
totalSize += column.ActualWidth;
}
if (double.IsNaN(column.Width))
{
column.Width = column.ActualWidth;
column.Width = double.NaN;
}
}
double descriptionNewWidth = viewWidth - totalSize - 35;
description.Width = descriptionNewWidth > 200 ? descriptionNewWidth : 200;
}
}
public async Task LoadMods()
{
await _modsLoadSem.WaitAsync();
try
{
MainWindow.Instance.InstallButton.IsEnabled = false;
MainWindow.Instance.InfoButton.IsEnabled = false;
AllModsList = null;
ModList = new List<ModListItem>();
UnknownMods.Clear();
HaveInstalledMods = false;
ModsListView.Visibility = Visibility.Hidden;
MainWindow.Instance.MainText = $"{FindResource("Mods:CheckingInstalledMods")}...";
await CheckInstalledMods();
InstalledColumn.Width = double.NaN;
UninstallColumn.Width = 70;
DescriptionColumn.Width = 750;
MainWindow.Instance.MainText = $"{FindResource("Mods:LoadingMods")}...";
await PopulateModsList();
ModsListView.ItemsSource = ModList;
view = (CollectionView)CollectionViewSource.GetDefaultView(ModsListView.ItemsSource);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("Category");
view.GroupDescriptions.Add(groupDescription);
this.DataContext = this;
RefreshModsList();
ModsListView.Visibility = ModList.Count == 0 ? Visibility.Hidden : Visibility.Visible;
NoModsGrid.Visibility = ModList.Count == 0 ? Visibility.Visible : Visibility.Hidden;
MainWindow.Instance.MainText = $"{FindResource("Mods:FinishedLoadingMods")}.";
MainWindow.Instance.InstallButton.IsEnabled = ModList.Count != 0;
}
finally
{
_modsLoadSem.Release();
}
}
public async Task CheckInstalledMods()
{
await GetAllMods();
await Task.Run(() =>
{
CheckInstallDir("Plugins");
CheckInstallDir("Mods");
CheckInstallDir("Plugins/Broken", isBrokenDir: true);
CheckInstallDir("Mods/Broken", isBrokenDir: true);
CheckInstallDir("Plugins/Retired", isRetiredDir: true);
CheckInstallDir("Mods/Retired", isRetiredDir: true);
});
}
public async Task GetAllMods()
{
try
{
var resp = await HttpClient.GetAsync(Utils.Constants.VRCMGModsJson);
var body = await resp.Content.ReadAsStringAsync();
AllModsList = JsonSerializer.Deserialize<Mod[]>(body);
foreach (var mod in AllModsList)
mod.category ??= HardcodedCategories.GetCategoryFor(mod) ?? "Uncategorized";
Array.Sort(AllModsList, (a, b) =>
{
var categoryCompare = String.Compare(a.category, b.category, StringComparison.Ordinal);
if (categoryCompare != 0) return categoryCompare;
return String.Compare(a.versions[0].name, b.versions[0].name, StringComparison.Ordinal);
});
}
catch (Exception e)
{
System.Windows.MessageBox.Show($"{FindResource("Mods:LoadFailed")}.\n\n" + e);
}
}
private void CheckInstallDir(string directory, bool isBrokenDir = false, bool isRetiredDir = false)
{
if (!Directory.Exists(Path.Combine(App.VRChatInstallDirectory, directory)))
{
return;
}
foreach (string file in Directory.GetFileSystemEntries(Path.Combine(App.VRChatInstallDirectory, directory), "*.dll", SearchOption.TopDirectoryOnly))
{
if (!File.Exists(file) || Path.GetExtension(file) != ".dll") continue;
var modInfo = ExtractModVersions(file);
if (modInfo.Item1 != null && modInfo.Item2 != null)
{
var haveFoundMod = false;
foreach (var mod in AllModsList)
{
if (!mod.aliases.Contains(modInfo.ModName) && mod.versions.All(it => it.name != modInfo.ModName)) continue;
HaveInstalledMods = true;
haveFoundMod = true;
mod.installedFilePath = file;
mod.installedVersion = modInfo.ModVersion;
mod.installedInBrokenDir = isBrokenDir;
mod.installedInRetiredDir = isRetiredDir;
break;
}
if (!haveFoundMod)
{
var mod = new Mod()
{
installedFilePath = file,
installedVersion = modInfo.ModVersion,
installedInBrokenDir = isBrokenDir,
installedInRetiredDir = isRetiredDir,
versions = new []
{
new Mod.ModVersion()
{
name = modInfo.ModName,
modVersion = modInfo.ModVersion,
author = modInfo.ModAuthor,
description = ""
}
}
};
UnknownMods.Add(mod);
}
}
}
}
private (string ModName, string ModVersion, string ModAuthor) ExtractModVersions(string dllPath)
{
try
{
using var asmdef = AssemblyDefinition.ReadAssembly(dllPath);
foreach (var attr in asmdef.CustomAttributes)
if (attr.AttributeType.Name == "MelonInfoAttribute" ||
attr.AttributeType.Name == "MelonModInfoAttribute")
return ((string) attr.ConstructorArguments[1].Value,
(string) attr.ConstructorArguments[2].Value, (string) attr.ConstructorArguments[3].Value);
}
catch (Exception ex)
{
var result = MessageBox.Show(
$"A mod in {Path.GetFileName(dllPath)} is invalid. Would you like to delete it to avoid this error in the future?",
"Invalid mod", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
try
{
File.Delete(dllPath);
}
catch (Exception ex2)
{
Utils.ShowErrorMessageBox($"Unable to delete file {dllPath}", ex2);
}
}
}
return (null, null, null);
}
public async Task PopulateModsList()
{
foreach (Mod mod in AllModsList.Where(x => !x.versions[0].IsBroken && !x.versions[0].IsRetired))
AddModToList(mod);
foreach (var mod in UnknownMods)
AddModToList(mod, UnknownCategory);
foreach (Mod mod in AllModsList.Where(x => x.versions[0].IsBroken))
AddModToList(mod);
foreach (Mod mod in AllModsList.Where(x => x.versions[0].IsRetired))
AddModToList(mod);
}
private void AddModToList(Mod mod, ModListItem.CategoryInfo categoryOverride = null)
{
bool preSelected = false;
var latestVersion = mod.versions[0];
if (DefaultMods.Contains(latestVersion.name) && !HaveInstalledMods || mod.installedFilePath != null)
{
preSelected = true;
}
ModListItem.CategoryInfo GetCategory(Mod mod)
{
if (mod.category == null) return UncategorizedCategory;
return new ModListItem.CategoryInfo(mod.category,
HardcodedCategories.GetCategoryDescription(mod.category));
}
ModListItem ListItem = new ModListItem()
{
IsSelected = preSelected,
IsEnabled = true,
ModName = latestVersion.name,
ModVersion = latestVersion.modVersion,
ModAuthor = HardcodedCategories.FixupAuthor(latestVersion.author),
ModDescription = latestVersion.description.Replace("\r\n", " ").Replace("\n", " "),
ModInfo = mod,
IsInstalled = mod.installedFilePath != null,
InstalledVersion = mod.installedVersion,
InstalledModInfo = mod,
Category = categoryOverride ?? (latestVersion.IsBroken ? BrokenCategory : (latestVersion.IsRetired ? RetiredCategory : GetCategory(mod)))
};
foreach (Promotion promo in Promotions.ActivePromotions)
{
if (latestVersion.name == promo.ModName)
{
ListItem.PromotionText = promo.Text;
ListItem.PromotionLink = promo.Link;
}
}
mod.ListItem = ListItem;
ModList.Add(ListItem);
}
public async void InstallMods()
{
MainWindow.Instance.InstallButton.IsEnabled = false;
if (!InstallHandlers.IsMelonLoaderInstalled())
await InstallHandlers.InstallMelonLoader();
foreach (Mod mod in AllModsList)
{
// Ignore mods that are newer than installed version or up-to-date
if (mod.ListItem.GetVersionComparison >= 0 && mod.installedInBrokenDir == mod.versions[0].IsBroken && mod.installedInRetiredDir == mod.versions[0].IsRetired) continue;
if (mod.ListItem.IsSelected)
{
MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstallingMod"), mod.versions[0].name)}...";
await InstallHandlers.InstallMod(mod);
MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstalledMod"), mod.versions[0].name)}.";
}
}
MainWindow.Instance.MainText = $"{FindResource("Mods:FinishedInstallingMods")}.";
MainWindow.Instance.InstallButton.IsEnabled = true;
RefreshModsList();
}
private void ModCheckBox_Checked(object sender, RoutedEventArgs e)
{
Mod mod = ((sender as System.Windows.Controls.CheckBox).Tag as Mod);
mod.ListItem.IsSelected = true;
}
private void ModCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
Mod mod = ((sender as System.Windows.Controls.CheckBox).Tag as Mod);
mod.ListItem.IsSelected = false;
}
public class Category
{
public string CategoryName { get; set; }
public List<ModListItem> Mods = new List<ModListItem>();
}
public class ModListItem
{
public string ModName { get; set; }
public string ModVersion { get; set; }
public string ModAuthor { get; set; }
public string ModDescription { get; set; }
public bool PreviousState { get; set; }
public bool IsEnabled { get; set; }
public bool IsSelected { get; set; }
public Mod ModInfo { get; set; }
public CategoryInfo Category { get; set; }
public Mod InstalledModInfo { get; set; }
public bool IsInstalled { get; set; }
private SemVersion _installedVersion { get; set; }
public string InstalledVersion
{
get
{
if (!IsInstalled || _installedVersion == null) return "-";
return _installedVersion.ToString();
}
set
{
if (SemVersion.TryParse(value, out SemVersion tempInstalledVersion))
{
_installedVersion = tempInstalledVersion;
}
else
{
_installedVersion = null;
}
}
}
public string GetVersionColor
{
get
{
if (!IsInstalled || _installedVersion == null) return "Black";
return _installedVersion >= ModVersion ? "Green" : "Red";
}
}
public string GetVersionDecoration
{
get
{
if (!IsInstalled || _installedVersion == null) return "None";
return _installedVersion >= ModVersion ? "None" : "Strikethrough";
}
}
public int GetVersionComparison
{
get
{
if (!IsInstalled || _installedVersion == null || _installedVersion < ModVersion) return -1;
if (_installedVersion > ModVersion) return 1;
return 0;
}
}
public bool CanDelete => IsInstalled;
public string CanSeeDelete => IsInstalled ? "Visible" : "Hidden";
public string PromotionText { get; set; }
public string PromotionLink { get; set; }
public string PromotionMargin
{
get
{
if (string.IsNullOrEmpty(PromotionText)) return "0";
return "0,0,5,0";
}
}
public Visibility PromotionVisibility => string.IsNullOrEmpty(PromotionText) ? Visibility.Collapsed : Visibility.Visible;
public record CategoryInfo(string Name, string Description)
{
public string Name { get; } = Name;
public string Description { get; } = Description;
}
}
private void ModsListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((Mods.ModListItem)Instance.ModsListView.SelectedItem == null)
{
MainWindow.Instance.InfoButton.IsEnabled = false;
}
else
{
MainWindow.Instance.InfoButton.IsEnabled = true;
}
}
private void Uninstall_Click(object sender, RoutedEventArgs e)
{
Mod mod = ((sender as System.Windows.Controls.Button).Tag as Mod);
string title = string.Format((string)FindResource("Mods:UninstallBox:Title"), mod.versions[0].name);
string body1 = string.Format((string)FindResource("Mods:UninstallBox:Body1"), mod.versions[0].name);
string body2 = string.Format((string)FindResource("Mods:UninstallBox:Body2"), mod.versions[0].name);
var result = System.Windows.Forms.MessageBox.Show($"{body1}\n{body2}", title, MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
UninstallModFromList(mod);
}
}
private void UninstallModFromList(Mod mod)
{
UninstallMod(mod.ListItem.InstalledModInfo);
mod.ListItem.IsInstalled = false;
mod.ListItem.InstalledVersion = null;
mod.ListItem.IsSelected = false;
RefreshModsList();
view.Refresh();
}
public void UninstallMod(Mod mod)
{
try
{
File.Delete(mod.installedFilePath);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"{FindResource("Mods:UninstallSingleFailed")}.\n\n" + ex);
}
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
e.Handled = true;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
RefreshColumns();
}
private void SearchButton_Click(object sender, RoutedEventArgs e)
{
if (SearchBar.Height == 0)
{
SearchBar.Focus();
Animate(SearchBar, 0, 20, new TimeSpan(0, 0, 0, 0, 300));
Animate(SearchText, 0, 20, new TimeSpan(0, 0, 0, 0, 300));
ModsListView.Items.Filter = new Predicate<object>(SearchFilter);
}
else
{
Animate(SearchBar, 20, 0, new TimeSpan(0, 0, 0, 0, 300));
Animate(SearchText, 20, 0, new TimeSpan(0, 0, 0, 0, 300));
ModsListView.Items.Filter = null;
}
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
ModsListView.Items.Filter = new Predicate<object>(SearchFilter);
if (SearchBar.Text.Length > 0)
{
SearchText.Text = null;
}
else
{
SearchText.Text = (string)FindResource("Mods:SearchLabel");
}
}
private bool SearchFilter(object mod)
{
ModListItem item = mod as ModListItem;
if (item.ModName.ToLower().Contains(SearchBar.Text.ToLower())) return true;
if (item.ModDescription.ToLower().Contains(SearchBar.Text.ToLower())) return true;
if (item.ModName.ToLower().Replace(" ", string.Empty).Contains(SearchBar.Text.ToLower().Replace(" ", string.Empty))) return true;
if (item.ModDescription.ToLower().Replace(" ", string.Empty).Contains(SearchBar.Text.ToLower().Replace(" ", string.Empty))) return true;
return false;
}
private void Animate(TextBlock target, double oldHeight, double newHeight, TimeSpan duration)
{
target.Height = oldHeight;
DoubleAnimation animation = new DoubleAnimation(newHeight, duration);
target.BeginAnimation(HeightProperty, animation);
}
private void Animate(TextBox target, double oldHeight, double newHeight, TimeSpan duration)
{
target.Height = oldHeight;
DoubleAnimation animation = new DoubleAnimation(newHeight, duration);
target.BeginAnimation(HeightProperty, animation);
}
private void ModsListView_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount != 2) return;
var selectedMod = ModsListView.SelectedItem as ModListItem;
if (selectedMod == null) return;
MainWindow.ShowModInfoWindow(selectedMod.ModInfo);
}
}
}

View file

@ -1,9 +1,9 @@
<Page
x:Class="ModAssistant.Pages.Options"
x:Class="VRCMelonAssistant.Pages.Options"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ModAssistant.Pages"
xmlns:local="clr-namespace:VRCMelonAssistant.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{DynamicResource Options:Title}"
d:DesignHeight="629"
@ -31,6 +31,7 @@
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
@ -47,6 +48,23 @@
FontWeight="Bold"
Text="{DynamicResource Options:PageTitle}" />
<TextBlock
Grid.Row="0"
Grid.Column="2"
Margin="5"
HorizontalAlignment="Right"
FontSize="22"
FontWeight="Bold"
Text="A 文" />
<ComboBox
Name="LanguageSelectComboBox"
Grid.Row="0"
Grid.Column="3"
Height="30"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center"
SelectionChanged="LanguageSelectComboBox_SelectionChanged" />
<TextBlock
Grid.Row="1"
Margin="5"
@ -88,118 +106,6 @@
Click="OpenDirButton_Click"
Content="{DynamicResource Options:OpenFolderButton}" />
<TextBlock
Grid.Row="3"
Margin="5"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="Bold"
Text="{DynamicResource Options:SaveSelectedMods}" />
<CheckBox
Name="SaveSelected"
Grid.Row="3"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Checked="SaveSelected_Checked"
IsChecked="{Binding SaveSelection, Mode=TwoWay}"
Unchecked="SaveSelected_Unchecked" />
<TextBlock
Grid.Row="4"
Margin="5"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="Bold"
Text="{DynamicResource Options:CheckInstalledMods}" />
<CheckBox
Name="CheckInstalled"
Grid.Row="4"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Checked="CheckInstalled_Checked"
IsChecked="{Binding CheckInstalledMods, Mode=TwoWay}"
Unchecked="CheckInstalled_Unchecked" />
<TextBlock
Grid.Row="5"
Margin="50,5,5,5"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="Bold"
Text="{DynamicResource Options:SelectInstalledMods}" />
<CheckBox
Name="SelectInstalled"
Grid.Row="5"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Checked="SelectInstalled_Checked"
IsChecked="{Binding SelectInstalledMods, Mode=TwoWay}"
Unchecked="SelectInstalled_Unchecked" />
<TextBlock
Grid.Row="6"
Margin="50,5,5,5"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="Bold"
Text="{DynamicResource Options:ReinstallInstalledMods}" />
<CheckBox
Name="ReinstallInstalled"
Grid.Row="6"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Checked="ReinstallInstalled_Checked"
IsChecked="{Binding ReinstallInstalledMods, Mode=TwoWay}"
Unchecked="ReinstallInstalled_Unchecked" />
<TextBlock
Grid.Row="7"
Margin="5"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="Bold">
<TextBlock Text="{DynamicResource Options:EnableOneClickInstalls}" />
: ↳
</TextBlock>
<TextBlock
Grid.Row="8"
Margin="50,5,5,5"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="Bold"
Text="{DynamicResource Options:BeatSaver}" />
<CheckBox
Name="BeatSaverProtocolHandler"
Grid.Row="8"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Checked="BeatSaverProtocolHandler_Checked"
IsChecked="{Binding BeatSaverProtocolHandlerEnabled}"
Unchecked="BeatSaverProtocolHandler_Unchecked" />
<TextBlock
Grid.Row="9"
Margin="50,5,5,5"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="Bold"
Text="{DynamicResource Options:ModelSaber}" />
<CheckBox
Name="ModelSaberProtocolHandler"
Grid.Row="9"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Checked="ModelSaberProtocolHandler_Checked"
IsChecked="{Binding ModelSaberProtocolHandlerEnabled}"
Unchecked="ModelSaberProtocolHandler_Unchecked" />
<StackPanel
Grid.Row="12"
Margin="5"
@ -261,27 +167,6 @@
Click="ApplicationThemeOpenThemesFolder_Click"
Content="{DynamicResource Options:OpenFolderButton}" />
<TextBlock
Grid.Row="14"
Margin="15,5,5,5"
HorizontalAlignment="Left"
FontSize="24"
FontWeight="Bold"
Text="{DynamicResource Options:Tools}" />
<StackPanel
Grid.Row="15"
Grid.ColumnSpan="4"
Margin="0"
HorizontalAlignment="Left"
Orientation="Horizontal">
<Button
Height="30"
Margin="5"
Padding="5"
Click="InstallPlaylistButton_Click"
Content="{DynamicResource Options:InstallPlaylist}" />
</StackPanel>
<TextBlock
Grid.Row="16"
Margin="15,5,5,5"
@ -295,12 +180,6 @@
Margin="0"
HorizontalAlignment="Left"
Orientation="Horizontal">
<Button
Height="30"
Margin="5"
Padding="5"
Click="OpenLogsDirButton_Click"
Content="{DynamicResource Options:OpenLogsButton}" />
<Button
x:Name="OpenAppData"
Height="30"
@ -308,13 +187,6 @@
Padding="5"
Click="OpenAppDataButton_Click"
Content="{DynamicResource Options:OpenAppDataButton}" />
<Button
x:Name="YeetBSIPA"
Height="30"
Margin="5"
Padding="5"
Click="YeetBSIPAButton_Click"
Content="{DynamicResource Options:UninstallBSIPAButton}" />
<Button
Height="30"
Margin="5"
@ -323,6 +195,21 @@
Click="YeetModsButton_Click">
<TextBlock Foreground="White" Text="{DynamicResource Options:RemoveAllModsButton}" />
</Button>
<Button
Height="30"
Margin="5"
Padding="5"
Background="{DynamicResource ButtonDangerBackground}"
Click="YeetMelonLoaderButton_Click">
<TextBlock Foreground="White" Text="{DynamicResource Options:RemoveMLButton}" />
</Button>
<Button
Height="30"
Margin="5"
Padding="5"
Click="InstallMelonLoaderButton_Click">
<TextBlock Foreground="White" Text="{DynamicResource Options:InstallMLButton}" />
</Button>
</StackPanel>
</Grid>

View file

@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Path = System.IO.Path;
namespace VRCMelonAssistant.Pages
{
/// <summary>
/// Interaction logic for Options.xaml
/// </summary>
public partial class Options : Page
{
public static Options Instance = new Options();
public string InstallDirectory { get; set; }
public string InstallType { get; set; }
public bool CloseWindowOnFinish { get; set; }
public string LogURL { get; private set; }
public Options()
{
InitializeComponent();
this.DataContext = this;
}
private void SelectDirButton_Click(object sender, RoutedEventArgs e)
{
Utils.GetManualDir();
DirectoryTextBlock.Text = InstallDirectory;
GameTypeTextBlock.Text = InstallType;
}
private void OpenDirButton_Click(object sender, RoutedEventArgs e)
{
Utils.OpenFolder(InstallDirectory);
}
private void Test_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Utils.GetSteamDir());
}
private void CloseWindowOnFinish_Checked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.CloseWindowOnFinish = true;
App.CloseWindowOnFinish = true;
CloseWindowOnFinish = true;
Properties.Settings.Default.Save();
}
private void CloseWindowOnFinish_Unchecked(object sender, RoutedEventArgs e)
{
Properties.Settings.Default.CloseWindowOnFinish = false;
App.CloseWindowOnFinish = false;
CloseWindowOnFinish = false;
Properties.Settings.Default.Save();
}
private void OpenAppDataButton_Click(object sender, RoutedEventArgs e)
{
string location = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"AppData", "LocalLow", "VRChat");
if (Directory.Exists(location))
{
Utils.OpenFolder(location);
}
else
{
MessageBox.Show((string)Application.Current.FindResource("Options:AppDataNotFound"));
}
}
private async void YeetModsButton_Click(object sender, RoutedEventArgs e)
{
string title = (string)Application.Current.FindResource("Options:YeetModsBox:Title");
string line1 = (string)Application.Current.FindResource("Options:YeetModsBox:RemoveAllMods");
string line2 = (string)Application.Current.FindResource("Options:YeetModsBox:CannotBeUndone");
var resp = System.Windows.Forms.MessageBox.Show($"{line1}\n{line2}", title, System.Windows.Forms.MessageBoxButtons.YesNo);
if (resp == System.Windows.Forms.DialogResult.Yes)
{
var modsDir = Path.Combine(App.VRChatInstallDirectory, "Mods");
if (Directory.Exists(modsDir))
Directory.Delete(modsDir, true);
var pluginsDir = Path.Combine(App.VRChatInstallDirectory, "Plugins");
if (Directory.Exists(pluginsDir))
Directory.Delete(pluginsDir, true);
Directory.CreateDirectory(modsDir);
Directory.CreateDirectory(pluginsDir);
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:AllModsUninstalled")}...";
}
}
private async void YeetMelonLoaderButton_Click(object sender, RoutedEventArgs e)
{
string title = (string)Application.Current.FindResource("Options:YeetMLBox:Title");
string line1 = (string)Application.Current.FindResource("Options:YeetMLBox:RemoveAllMods");
string line2 = (string)Application.Current.FindResource("Options:YeetMLBox:CannotBeUndone");
var resp = System.Windows.Forms.MessageBox.Show($"{line1}\n{line2}", title, System.Windows.Forms.MessageBoxButtons.YesNo);
if (resp == System.Windows.Forms.DialogResult.Yes)
{
InstallHandlers.RemoveMelonLoader();
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:MLUninstalled")}...";
}
}
private async void InstallMelonLoaderButton_Click(object sender, RoutedEventArgs e)
{
await InstallHandlers.InstallMelonLoader();
MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:MLInstalled")}...";
}
private void ApplicationThemeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((sender as ComboBox).SelectedItem == null)
{
Themes.ApplyWindowsTheme();
MainWindow.Instance.MainText = (string)Application.Current.FindResource("Options:CurrentThemeRemoved");
}
else
{
Themes.ApplyTheme((sender as ComboBox).SelectedItem.ToString());
}
}
public void LanguageSelectComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((sender as ComboBox).SelectedItem == null)
{
// Apply default language
Console.WriteLine("Applying default language");
Languages.LoadLanguage("en");
}
else
{
// Get the matching language from the LoadedLanguages array, then try and use it
var languageName = (sender as ComboBox).SelectedItem.ToString();
var selectedLanguage = Languages.LoadedLanguages.Find(language => language.NativeName.CompareTo(languageName) == 0);
if (Languages.LoadLanguage(selectedLanguage.Name))
{
Properties.Settings.Default.LanguageCode = selectedLanguage.Name;
Properties.Settings.Default.Save();
if (Languages.FirstRun)
{
Languages.FirstRun = false;
}
else
{
Process.Start(Utils.ExePath, App.Arguments);
Application.Current.Dispatcher.Invoke(() => { Application.Current.Shutdown(); });
}
}
}
}
private void ApplicationThemeExportTemplate_Click(object sender, RoutedEventArgs e)
{
Themes.WriteThemeToDisk("Ugly Kulu-Ya-Ku");
Themes.LoadThemes();
}
private void ApplicationThemeOpenThemesFolder_Click(object sender, RoutedEventArgs e)
{
if (Directory.Exists(Themes.ThemeDirectory))
{
Utils.OpenFolder(Themes.ThemeDirectory);
}
else
{
MessageBox.Show((string)Application.Current.FindResource("Options:ThemeFolderNotFound"));
}
}
}
}

View file

@ -7,12 +7,12 @@ using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ModAssistant")]
[assembly: AssemblyTitle("VRChat Melon Assistant")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ModAssistant")]
[assembly: AssemblyCopyright("Copyright © Assistant 2019")]
[assembly: AssemblyProduct("VRChat Melon Assistant")]
[assembly: AssemblyCopyright("Copyright © knah 2021; based on ModAssistant © Assistant 2019-2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@ -51,5 +51,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.7.0")]
[assembly: AssemblyFileVersion("1.1.7.0")]
[assembly: AssemblyVersion("1.1.26.1029")]
[assembly: AssemblyFileVersion("1.1.26.1029+vrc")]

View file

@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModAssistant.Properties
namespace VRCMelonAssistant.Properties
{
@ -44,7 +44,7 @@ namespace ModAssistant.Properties
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ModAssistant.Properties.Resources", typeof(Resources).Assembly);
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("VRCMelonAssistant.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;

View file

@ -8,11 +8,11 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModAssistant.Properties {
namespace VRCMelonAssistant.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -47,42 +47,6 @@ namespace ModAssistant.Properties {
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool SaveSelected {
get {
return ((bool)(this["SaveSelected"]));
}
set {
this["SaveSelected"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool CheckInstalled {
get {
return ((bool)(this["CheckInstalled"]));
}
set {
this["CheckInstalled"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public string SavedMods {
get {
return ((string)(this["SavedMods"]));
}
set {
this["SavedMods"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
@ -95,42 +59,6 @@ namespace ModAssistant.Properties {
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool SelectInstalled {
get {
return ((bool)(this["SelectInstalled"]));
}
set {
this["SelectInstalled"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string GameVersion {
get {
return ((string)(this["GameVersion"]));
}
set {
this["GameVersion"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string AllGameVersions {
get {
return ((string)(this["AllGameVersions"]));
}
set {
this["AllGameVersions"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
@ -169,13 +97,25 @@ namespace ModAssistant.Properties {
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool ReinstallInstalled {
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool CloseWindowOnFinish {
get {
return ((bool)(this["ReinstallInstalled"]));
return ((bool)(this["CloseWindowOnFinish"]));
}
set {
this["ReinstallInstalled"] = value;
this["CloseWindowOnFinish"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string LanguageCode {
get {
return ((string)(this["LanguageCode"]));
}
set {
this["LanguageCode"] = value;
}
}
}

View file

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="ModAssistant.Properties" GeneratedClassName="Settings">
<?xml version="1.0" encoding="UTF-8"?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="VRCMelonAssistant.Properties" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="InstallFolder" Type="System.String" Scope="User">
@ -8,27 +8,9 @@
<Setting Name="StoreType" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="SaveSelected" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="CheckInstalled" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="SavedMods" Type="System.String" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="Agreed" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="SelectInstalled" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="GameVersion" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="AllGameVersions" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="UpgradeRequired" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
@ -38,8 +20,12 @@
<Setting Name="SelectedTheme" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="ReinstallInstalled" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
<Setting Name="CloseWindowOnFinish" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="LanguageCode" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>
</SettingsFile>

View file

@ -0,0 +1,8 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VRCMelonAssistant"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:pages="clr-namespace:VRCMelonAssistant.Pages">
</ResourceDictionary>

View file

@ -26,19 +26,23 @@
</DrawingGroup>
<DrawingImage x:Key="microchipDrawingImage" Drawing="{StaticResource microchipDrawingGroup}" />
<Geometry x:Key="loadingInnerGeometry">F1 M4000,4000z M0,0z M1833.4,1475.6C1827.5,1490 1818.3,1503.3 1805.8,1514.3 1762.7,1551.9 1697.3,1547.4 1659.7,1504.3 1628.8,1468.8 1626.4,1418.3 1650.5,1380.6L1170.6,1131.3 1336.5,1597.2C1394,1576.7 1460.7,1591.9 1503.2,1640.7 1558.7,1704.4 1552.1,1801.1 1488.4,1856.6 1473.5,1869.6 1456.7,1879.1 1439.1,1885.3L1814.9,2940.7 2747.8,2941 2875.1,2016.8 1833.4,1475.6z</Geometry>
<DrawingGroup x:Key="loadingInnerDrawingGroup" ClipGeometry="M0,0 V4000 H4000 V0 H0 Z">
<GeometryDrawing Brush="{DynamicResource LoadingIconColor}" Geometry="{StaticResource loadingInnerGeometry}" />
</DrawingGroup>
<DrawingImage x:Key="loadingInnerDrawingImage" Drawing="{DynamicResource loadingInnerDrawingGroup}" />
<Geometry x:Key="loadingMiddleGeometry">F1 M4000,4000z M0,0z M3384.4,1915.4C3385.5,2231.2 3278.1,2552.4 3057.5,2818.2 2570.1,3405.4 1714.7,3499.3 1146.9,3028 579.1,2556.7 514.1,1698.6 1001.5,1111.5 1222.2,845.7 1518.2,681 1828.8,624 1491,666 1167.9,831.7 933.3,1114.2 444.1,1703.5 525.3,2577.8 1114.5,3067 1703.7,3556.2 2578.1,3475.1 3067.3,2885.8 3301.9,2603.2 3405.2,2255.1 3384.4,1915.4z</Geometry>
<DrawingGroup x:Key="loadingMiddleDrawingGroup" ClipGeometry="M0,0 V4000 H4000 V0 H0 Z">
<GeometryDrawing Brush="{DynamicResource LoadingIconColor}" Geometry="{StaticResource loadingMiddleGeometry}" />
</DrawingGroup>
<DrawingImage x:Key="loadingMiddleDrawingImage" Drawing="{DynamicResource loadingMiddleDrawingGroup}" />
<Geometry x:Key="loadingOuterGeometry">F1 M4000,4000z M0,0z M3710.9,2000L3870,1840.9 3667.5,1638.4C3501.6,870.1 2818.3,294.3 2000.3,294.3 1182.4,294.3 499.1,870 333.2,1638.2L333,1637.9 130,1840.9 289.1,2000 130,2159.1 333,2362.1 333.2,2361.8C499.2,3130 1182.5,3705.6 2000.3,3705.6 2818.3,3705.6 3501.6,3129.8 3667.5,2361.5L3870,2159 3710.9,2000z M2000.3,3489.8C1177.5,3489.8 510.5,2822.8 510.5,2000 510.5,1177.2 1177.5,510.2 2000.3,510.2 2823.1,510.2 3490.1,1177.2 3490.1,2000 3490.1,2822.8 2823.1,3489.8 2000.3,3489.8z</Geometry>
<Geometry x:Key="loadingOuterGeometry">M53.0751762,0.2930124c-0.3906021-0.3907-1.0234032-0.3907-1.4141006,0l-4.7492027,4.7500997
c-0.0007973,0.0008001-0.0019989,0.0009999-0.002697,0.0016999c0,0-0.0009995,0.0019002-0.0017014,0.0027003L0.2929758,51.6696129
c-0.3906,0.3907013-0.3906,1.0235977,0,1.414299c7.2772999,7.2787018,16.8319988,10.9170036,26.3915997,10.9161034
c9.5555973-0.0010033,19.1151981-3.6394043,26.3906002-10.9161034c7.044899-7.0452003,10.9247971-16.4199982,10.9247971-26.3955002
C63.9999733,16.7130127,60.1200752,7.3392124,53.0751762,0.2930124z M25.684576,55.2598114
c-6.6767998-0.2311974-13.2832003-2.7827988-18.5009995-7.6531982L25.684576,29.1025124V55.2598114z M27.684576,29.1020126
l18.3379002,18.3422012c0.0578003,0.0578995,0.129097,0.0895004,0.1959991,0.1301994
c-5.2234001,4.8905983-11.8432007,7.4529991-18.5338993,7.6852989V29.1020126z M47.5670738,46.2267113
c-0.040699-0.0671997-0.0724983-0.1386986-0.1306-0.1968002L29.0985756,27.6877117h26.1591988
C55.0209732,34.6395111,52.3183746,41.1540108,47.5670738,46.2267113z M29.0990753,25.6873131L47.599575,7.1839123
c4.730999,5.0665998,7.4213982,11.5671005,7.6581993,18.5034008H29.0990753z M51.6610756,51.6696129
c-6.6669998,6.6681976-15.5371017,10.339798-24.9764996,10.339798c-9.1055012,0-17.6741009-3.4241982-24.2534008-9.6498985
l3.3382998-3.3386002c5.8694005,5.5032005,13.3885002,8.2630005,20.9151001,8.262001
c7.834898-0.0009995,15.6737995-2.9849014,21.6385975-8.9508018c5.777401-5.7775002,8.9589996-13.4643974,8.9589996-21.6436996
c0-7.8449001-2.9330978-15.230299-8.2684822-20.9188004l3.3373833-3.3378999
c6.2243004,6.5804005,9.6488991,15.1505995,9.6488991,24.2566986C61.9999733,36.129612,58.3280754,45.0014114,51.6610756,51.6696129
z</Geometry>
<DrawingGroup x:Key="loadingOuterDrawingGroup" ClipGeometry="M0,0 V4000 H4000 V0 H0 Z">
<GeometryDrawing Brush="{DynamicResource LoadingIconColor}" Geometry="{StaticResource loadingOuterGeometry}" />
</DrawingGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View file

@ -1,5 +1,5 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="ModAssistantRepeatButton" TargetType="{x:Type RepeatButton}">
<Style x:Key="VRCMelonAssistantRepeatButton" TargetType="{x:Type RepeatButton}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
@ -39,7 +39,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ModAssistantSmallRepeatButton" TargetType="{x:Type RepeatButton}">
<Style x:Key="VRCMelonAssistantSmallRepeatButton" TargetType="{x:Type RepeatButton}">
<Setter Property="FrameworkElement.OverridesDefaultStyle" Value="True" />
<Setter Property="Background" Value="{DynamicResource ScrollBarBackground}" />
<Setter Property="Focusable" Value="False" />

View file

@ -25,7 +25,7 @@
Name="PART_LineUpButton"
Command="{x:Static ScrollBar.LineUpCommand}"
IsEnabled="{TemplateBinding IsMouseOver}"
Style="{DynamicResource ResourceKey=ModAssistantRepeatButton}">
Style="{DynamicResource ResourceKey=VRCMelonAssistantRepeatButton}">
<Path
Name="ArrowTop"
Margin="3,4,3,3"
@ -42,16 +42,16 @@
<RepeatButton
Background="{DynamicResource ScrollBarBackground}"
Command="{x:Static ScrollBar.PageUpCommand}"
Style="{DynamicResource ResourceKey=ModAssistantSmallRepeatButton}" />
Style="{DynamicResource ResourceKey=VRCMelonAssistantSmallRepeatButton}" />
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton
Background="{DynamicResource ScrollBarBackground}"
Command="{x:Static ScrollBar.PageDownCommand}"
Style="{DynamicResource ResourceKey=ModAssistantSmallRepeatButton}" />
Style="{DynamicResource ResourceKey=VRCMelonAssistantSmallRepeatButton}" />
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb Background="{DynamicResource ScrollBarHandle}" Style="{DynamicResource ResourceKey=ModAssistantThumb}" />
<Thumb Background="{DynamicResource ScrollBarHandle}" Style="{DynamicResource ResourceKey=VRCMelonAssistantThumb}" />
</Track.Thumb>
</Track>
<RepeatButton
@ -59,7 +59,7 @@
Grid.Row="2"
Command="{x:Static ScrollBar.LineDownCommand}"
IsEnabled="{TemplateBinding IsMouseOver}"
Style="{DynamicResource ResourceKey=ModAssistantRepeatButton}">
Style="{DynamicResource ResourceKey=VRCMelonAssistantRepeatButton}">
<Path
Name="ArrowBottom"
Margin="3,4,3,3"
@ -130,7 +130,7 @@
Name="PART_LineLeftButton"
Command="{x:Static ScrollBar.LineLeftCommand}"
IsEnabled="{TemplateBinding IsMouseOver}"
Style="{DynamicResource ResourceKey=ModAssistantRepeatButton}">
Style="{DynamicResource ResourceKey=VRCMelonAssistantRepeatButton}">
<Path
Name="ArrowLeft"
Margin="3"
@ -146,16 +146,16 @@
<RepeatButton
Background="{DynamicResource ScrollBarBackground}"
Command="{x:Static ScrollBar.PageLeftCommand}"
Style="{DynamicResource ResourceKey=ModAssistantSmallRepeatButton}" />
Style="{DynamicResource ResourceKey=VRCMelonAssistantSmallRepeatButton}" />
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton
Background="{DynamicResource ScrollBarBackground}"
Command="{x:Static ScrollBar.PageRightCommand}"
Style="{DynamicResource ResourceKey=ModAssistantSmallRepeatButton}" />
Style="{DynamicResource ResourceKey=VRCMelonAssistantSmallRepeatButton}" />
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb Background="{DynamicResource ScrollBarHandle}" Style="{DynamicResource ResourceKey=ModAssistantThumb}" />
<Thumb Background="{DynamicResource ScrollBarHandle}" Style="{DynamicResource ResourceKey=VRCMelonAssistantThumb}" />
</Track.Thumb>
</Track>
<RepeatButton
@ -163,7 +163,7 @@
Grid.Column="2"
Command="{x:Static ScrollBar.LineRightCommand}"
IsEnabled="{TemplateBinding IsMouseOver}"
Style="{DynamicResource ResourceKey=ModAssistantRepeatButton}">
Style="{DynamicResource ResourceKey=VRCMelonAssistantRepeatButton}">
<Path
Name="ArrowRight"
Margin="3"

View file

@ -1,5 +1,5 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="ModAssistantThumb" TargetType="{x:Type Thumb}">
<Style x:Key="VRCMelonAssistantThumb" TargetType="{x:Type Thumb}">
<Setter Property="FrameworkElement.OverridesDefaultStyle" Value="True" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">

View file

@ -1,6 +1,6 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- ModAssistant Dark Theme by Caeden117 and lolPants -->
<!-- VRChat Melon Assistant Dark Theme by Caeden117 and lolPants -->
<!-- Standard Styles -->
<Color x:Key="StandardContent">#E0E0E0</Color>
@ -74,7 +74,7 @@
<GridLength x:Key="ScrollBarButtonHeight">0</GridLength>
<!-- Various important elements that need to be controlled independently -->
<SolidColorBrush x:Key="ModAssistantBackground" Color="{DynamicResource ResourceKey=StandardSecondary}" />
<SolidColorBrush x:Key="VRCMelonAssistantBackground" Color="{DynamicResource ResourceKey=StandardSecondary}" />
<SolidColorBrush x:Key="FrameBackgroundColor" Color="#CC0F0F0F" />
<SolidColorBrush x:Key="BottomStatusBarBackground" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="BottomStatusBarOutline" Color="{DynamicResource ResourceKey=StandardBorder}" />

View file

@ -1,6 +1,6 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- ModAssistant Light Pink Theme by Caeden117 -->
<!-- VRChat Melon Assistant Light Pink Theme by Caeden117 -->
<!-- Default text -->
<SolidColorBrush x:Key="TextColor" Color="#101010" />
@ -64,7 +64,7 @@
<GridLength x:Key="ScrollBarButtonHeight">0</GridLength>
<!-- Various important elements that need to be controlled independently -->
<SolidColorBrush x:Key="ModAssistantBackground" Color="#FFFAE5FF" />
<SolidColorBrush x:Key="VRCMelonAssistantBackground" Color="#FFFAE5FF" />
<SolidColorBrush x:Key="FrameBackgroundColor" Color="#CCFAE5FF" />
<SolidColorBrush x:Key="BottomStatusBarBackground" Color="#FFE288D6" />
<SolidColorBrush x:Key="DirectoryBackground" Color="#FFE288D6" />

View file

@ -1,6 +1,6 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- ModAssistant Light Theme by Caeden117 and lolPants -->
<!-- VRChat Melon Assistant Light Theme by Caeden117 and lolPants -->
<!-- Standard Styles -->
<Color x:Key="StandardContent">#101010</Color>
@ -73,7 +73,7 @@
<GridLength x:Key="ScrollBarButtonHeight">0</GridLength>
<!-- Various important elements that need to be controlled independently -->
<SolidColorBrush x:Key="ModAssistantBackground" Color="{DynamicResource ResourceKey=StandardSecondary}" />
<SolidColorBrush x:Key="VRCMelonAssistantBackground" Color="{DynamicResource ResourceKey=StandardSecondary}" />
<SolidColorBrush x:Key="FrameBackgroundColor" Color="#CCF3F3F3" />
<SolidColorBrush x:Key="BottomStatusBarBackground" Color="{DynamicResource ResourceKey=StandardPrimary}" />
<SolidColorBrush x:Key="BottomStatusBarOutline" Color="{DynamicResource ResourceKey=StandardBorder}" />

View file

@ -1,16 +1,16 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--
ModAssistant Ugly Kulu-Ya-Ku Theme by Assistant
Feel free to use this as a template for designing your own themes for Mod Assistant.
VRChat Melon Assistant Ugly Kulu-Ya-Ku Theme by Assistant
Feel free to use this as a template for designing your own themes for VRChat Melon Assistant.
The Color fields take in Hexadecimal RGB (#RRGGBB) or Hexadecimal ARGB (#AARRGGBB).
They can also take in common names like "Gray", "Maroon", "Magenta", "LimeGreen", etc.
For more information, see: https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.colors
You may set color variables like below in the Standard Styles section, or simply set the colors directly.
To reload your theme, exit and re-enter the Options menu.
Inspired by https://www.nexusmods.com/monsterhunterworld/mods/2080
william requested I make this theme. One time william requested a hug from pook. pook denied said request.
I then witnessed william hounding pook, until he siezed them by breaking their foot.
@ -89,7 +89,7 @@
<GridLength x:Key="ScrollBarButtonHeight">0</GridLength>
<!-- Various important elements that need to be controlled independently -->
<SolidColorBrush x:Key="ModAssistantBackground" Color="{DynamicResource ResourceKey=StandardIcon}" />
<SolidColorBrush x:Key="VRCMelonAssistantBackground" Color="{DynamicResource ResourceKey=StandardIcon}" />
<SolidColorBrush x:Key="FrameBackgroundColor" Color="#CCFF51B6" />
<SolidColorBrush x:Key="BottomStatusBarBackground" Color="#BF489A" />
<SolidColorBrush x:Key="BottomStatusBarOutline" Color="{DynamicResource ResourceKey=StandardBorder}" />

View file

@ -1,18 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props" Condition="Exists('..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>ModAssistant</RootNamespace>
<AssemblyName>ModAssistant</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<RootNamespace>VRCMelonAssistant</RootNamespace>
<AssemblyName>VRCMelonAssistant</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -37,13 +39,50 @@
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Build.Framework, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\Microsoft.Build.Framework.15.9.20\lib\net46\Microsoft.Build.Framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Build.Utilities.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\Microsoft.Build.Utilities.Core.15.9.20\lib\net46\Microsoft.Build.Utilities.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Setup.Configuration.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\Microsoft.VisualStudio.Setup.Configuration.Interop.1.16.30\lib\net35\Microsoft.VisualStudio.Setup.Configuration.Interop.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Cecil, Version=0.11.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e">
<HintPath>..\packages\Mono.Cecil.0.11.3\lib\net40\Mono.Cecil.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Cecil.Mdb, Version=0.11.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e">
<HintPath>..\packages\Mono.Cecil.0.11.3\lib\net40\Mono.Cecil.Mdb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Cecil.Pdb, Version=0.11.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e">
<HintPath>..\packages\Mono.Cecil.0.11.3\lib\net40\Mono.Cecil.Pdb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Cecil.Rocks, Version=0.11.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e">
<HintPath>..\packages\Mono.Cecil.0.11.3\lib\net40\Mono.Cecil.Rocks.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Management" />
<Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Windows.Forms" />
@ -66,26 +105,24 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Classes\External Interfaces\BeatSaver.cs" />
<Compile Include="Classes\External Interfaces\ModelSaber.cs" />
<Compile Include="Classes\External Interfaces\Playlists.cs" />
<Compile Include="Classes\External Interfaces\Utils.cs" />
<Compile Include="Classes\HardcodedCategories.cs" />
<Compile Include="Classes\Http.cs" />
<Compile Include="Classes\HyperlinkExtensions.cs" />
<Compile Include="Classes\InstallHandlers.cs" />
<Compile Include="Classes\Languages.cs" />
<Compile Include="Classes\Promotions.cs" />
<Compile Include="Classes\Diagnostics.cs" />
<Compile Include="Classes\Themes.cs" />
<Compile Include="Classes\Updater.cs" />
<Compile Include="Libs\semver\SemVersion.cs" />
<Compile Include="Libs\semver\IntExtensions.cs" />
<Compile Include="ModInfoWindow.xaml.cs">
<DependentUpon>ModInfoWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Intro.xaml.cs">
<DependentUpon>Intro.xaml</DependentUpon>
</Compile>
<Compile Include="Classes\Mod.cs" />
<Page Include="Localisation\de.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Localisation\en-DEBUG.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -107,26 +144,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Localisation\fr.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Localisation\it.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Localisation\ko.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Localisation\nl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Localisation\zh.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="ModInfoWindow.xaml" />
<Page Include="Pages\Intro.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -139,7 +157,6 @@
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Classes\OneClickInstaller.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
@ -164,6 +181,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Resources\AppResources.xaml" />
<Page Include="Resources\Icons.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -228,10 +246,6 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Themes\BSMG.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Themes\Dark.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -274,6 +288,7 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>PublicSettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
@ -285,8 +300,20 @@
<ItemGroup>
<Resource Include="Resources\icon.ico" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Themes\BSMG\Sidebar.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props'))" />
</Target>
<Target Name="ILRepack" AfterTargets="Build" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PropertyGroup>
<WorkingDirectory>$(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)</WorkingDirectory>
</PropertyGroup>
<ILRepack OutputType="$(OutputType)" MainAssembly="$(AssemblyName).exe" OutputAssembly="$(AssemblyName).exe" InputAssemblies="$(WorkingDirectory)\Mono.Cecil.dll" WilcardInputAssemblies="true" WorkingDirectory="$(WorkingDirectory)" />
</Target>
</Project>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ILRepack.MSBuild.Task" version="2.0.13" targetFramework="net48" />
<package id="Microsoft.Build.Framework" version="15.9.20" targetFramework="net48" />
<package id="Microsoft.Build.Utilities.Core" version="15.9.20" targetFramework="net48" />
<package id="Microsoft.VisualStudio.Setup.Configuration.Interop" version="1.16.30" targetFramework="net48" developmentDependency="true" />
<package id="Mono.Cecil" version="0.11.3" targetFramework="net461" />
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net48" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net48" />
</packages>

7
tools/README.md Normal file
View file

@ -0,0 +1,7 @@
# ModAssistant Tools
> These are tools used to build / improve ModAssistant. **They are not to be distributed with the compiled binary.**
## Translation Stubs
Use `generate_translation_stubs.py` to read the English locale file and generate missing strings for all other locales. Requires Python 3.6 or above.
Simply run `python ./tools/generate_translation_stubs.py` and commit the stubs.

View file

@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
import os
import re
from shutil import copyfile
# --------------- Configuration ---------------- #
master_file = "./ModAssistant/Localisation/en.xaml"
source_dir = "./ModAssistant/Localisation"
target_dir = source_dir
comment = " <!-- NEEDS TRANSLATING -->"
# --------------- Configuration ---------------- #
def read_file(filepath):
"""
Read file content
:param filepath:
:return:
"""
with open(filepath, "r", encoding="utf-8") as file:
data = file.read()
return data
def find_files(root_directory):
"""
Find all files to be processed
:param root_directory:
:return:
"""
for root, ds, fs in os.walk(root_directory):
for fn in fs:
fullname = os.path.join(root, fn)
master_name = os.path.basename(master_file)
if master_name in fullname:
continue
yield fullname
def search(regex, m_string, group=1):
"""
Search for the first item that matches by regular expression
:param regex:
:param m_string:
:return:
"""
found = re.compile(regex, re.M).search(m_string)
result = ""
if found:
result = found.group(group)
return result
def write_file(filepath, content):
"""
Write text content to file
:param filepath:
:param content:
:return:
"""
if not os.path.exists(target_dir):
os.makedirs(target_dir, exist_ok=True)
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
def find_items(data):
keys_regex = r"<\s*([^\s>]+)\s.*Key[^>]+>"
keys_matches = re.finditer(keys_regex, data, re.M)
items = []
for n, m in enumerate(keys_matches, start=1):
if len(items):
last_dict = items[-1]
if len(last_dict):
last_dict['end'] = m.start()
items.append({"match": m[0], "name": m[1], "start": m.end()})
items[-1]['end'] = len(data)
for item in items:
item_text = data[item['start']: item['end']]
end_tag = "</%s>" % item['name']
full_tag = item['match'] + item_text[:item_text.rfind(end_tag) + len(end_tag)]
item['end_tag'] = end_tag
item['full_tag'] = full_tag
return items
def wrapper_key(match, content):
"""
Try to find the comment behind the key
:param match:
:param content:
:return:
"""
regex = "(\s*" + re.escape(match) + "(\ *<\!\-\-[^\-]+\-\->)?\s*)"
with_comment = search(r"" + str(regex), content, 1)
# print(regex)
# print(match)
# print(with_comment)
# print("-----")
if with_comment:
match = with_comment
return match
def main():
"""
Cycle processing of xaml files except master_file
:return:
"""
master_data = read_file(master_file)
print("The master file is", master_file)
items = find_items(master_data)
for f in find_files(source_dir):
content = master_data + ""
xml_data = read_file(f)
xml_dict = {}
for xml_item in find_items(xml_data):
xml_dict[xml_item['match']] = xml_item['full_tag']
for item in items:
# print(f)
# if "OneClick:Done" in item['match'] and "fr.xaml" in f:
# print("----")
# pass
if item['match'] in xml_dict.keys():
match = wrapper_key(xml_dict[item['match']], xml_data)
master_match = wrapper_key(item['full_tag'], content)
# print(master_match)
content = content.replace(master_match, match)
else:
pre_blanks = search(r"(\s*)" + re.escape(item['full_tag']), master_data)
content = re.sub(r"\s*" + re.escape(item['full_tag']), pre_blanks + item['full_tag'] + comment, content)
# Put the processed files in the "dist" directory
filepath = f.replace(source_dir, target_dir)
write_file(filepath, content)
print("Processing", f)
# Copy "master_file" to the target directory
if source_dir != target_dir:
copyfile(master_file, master_file.replace(source_dir, target_dir))
if __name__ == '__main__':
main()
print("done'd!")