├── .editorconfig ├── .gitattributes ├── .gitignore ├── AppSample ├── Medal_LNtVc3zsBD.png └── Medal_pWViultzXZ.png ├── AvatarLockpick.Revised ├── .gitignore ├── AvatarLockpick.Revised.csproj ├── HTML │ ├── AppStyle.css │ ├── ButtonCalls.js │ ├── index.html │ └── unlockicon.ico ├── Program.cs ├── Properties │ └── launchSettings.json ├── Utils │ ├── AppFolders.cs │ ├── AppLog.cs │ ├── AvatarUnlocker.cs │ ├── ConsoleSetup.cs │ ├── GUIcom.cs │ ├── HttpUtils.cs │ ├── MessageBoxUtils.cs │ ├── StringUtils.cs │ ├── UpdateCheck.cs │ ├── VRC.cs │ └── WindowUtils.cs ├── app.manifest └── unlockicon.ico ├── AvatarLockpick.sln ├── AvatarLockpick ├── AvatarLockpick.csproj ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── Program.cs ├── Utils │ ├── AvatarFinder.cs │ ├── Config.cs │ ├── MsgBoxUtils.cs │ └── VRCManager.cs ├── app.manifest └── unlock_icon.ico ├── HELP.md ├── README.md ├── lock_types.txt ├── unique.txt └── ver.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # IDE0320: Make anonymous function static 4 | dotnet_diagnostic.IDE0320.severity = none 5 | 6 | # CS8602: Dereference of a possibly null reference. 7 | dotnet_diagnostic.CS8602.severity = none 8 | 9 | # CS8600: Converting null literal or possible null value to non-nullable type. 10 | dotnet_diagnostic.CS8600.severity = none 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /AppSample/Medal_LNtVc3zsBD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrim-dev/AvatarLockpick/87a178d5ad3e43e87c31c8e3398256ab1fbf589c/AppSample/Medal_LNtVc3zsBD.png -------------------------------------------------------------------------------- /AppSample/Medal_pWViultzXZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrim-dev/AvatarLockpick/87a178d5ad3e43e87c31c8e3398256ab1fbf589c/AppSample/Medal_pWViultzXZ.png -------------------------------------------------------------------------------- /AvatarLockpick.Revised/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | .vscode 353 | .DS_Store -------------------------------------------------------------------------------- /AvatarLockpick.Revised/AvatarLockpick.Revised.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0-windows10.0.26100.0 6 | AnyCPU 7 | enable 8 | enable 9 | <_SuppressWinFormsTrimError>true 10 | app.manifest 11 | False 12 | ScrimDev 13 | ScrimDev 14 | Scrimmane 15 | 0.0.0.0 16 | 0.0.0.0 17 | Avatar Lockpick App 18 | unlockicon.ico 19 | False 20 | False 21 | False 22 | x64 23 | False 24 | A tool for unlocking VRChat Avatars 25 | https://github.com/scrim-dev/AvatarLockpick 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /AvatarLockpick.Revised/HTML/AppStyle.css: -------------------------------------------------------------------------------- 1 | /* --- Base & Reset --- */ 2 | * { 3 | box-sizing: border-box; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | html { 9 | /* Define theme variables on root for easier JS access */ 10 | /* Darker Dark Theme Defaults */ 11 | --bg-color: #121212; 12 | --bg-secondary-color: #181818; /* Darker secondary BG */ 13 | --text-color: #ffffff; 14 | --text-secondary-color: #b0b0b0; /* Adjusted secondary contrast slightly */ 15 | --accent-color: #1E90FF; /* Dodger Blue accent */ 16 | --accent-hover-color: #46A3FF; /* Dodger Blue hover */ 17 | --border-color: #333333; 18 | --panel-bg-color: #242424; 19 | --input-bg-color: #2d2d2d; 20 | --input-border-color: #454545; 21 | --glow-color: #ff00ff; /* Magenta glow color */ 22 | } 23 | 24 | html, body { 25 | height: 100%; 26 | overflow: hidden; 27 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 28 | font-size: 15px; /* Slightly smaller base */ 29 | line-height: 1.5; 30 | background-color: var(--bg-color); 31 | color: var(--text-color); 32 | transition: background-color 0.3s ease, color 0.3s ease; 33 | } 34 | 35 | /* Disable transitions/animations if class is present */ 36 | body.no-animations * { 37 | transition: none !important; 38 | animation: none !important; 39 | } 40 | 41 | /* --- Themes (using JS to set variables now) --- */ 42 | /* Keep classes for potential overrides if needed, but base is set on html */ 43 | body.theme-dark { 44 | /* Variables primarily set on :root / html */ 45 | } 46 | body.theme-light { 47 | /* Variables primarily set on :root / html */ 48 | } 49 | body.theme-default { 50 | /* Variables primarily set on :root / html */ 51 | } 52 | 53 | 54 | /* --- Splash Screen --- */ 55 | #splash-screen { 56 | position: fixed; 57 | top: 0; 58 | left: 0; 59 | width: 100%; 60 | height: 100%; 61 | /* Use secondary bg for splash */ 62 | background-color: var(--bg-secondary-color); 63 | display: flex; 64 | justify-content: center; 65 | align-items: center; 66 | z-index: 1000; 67 | opacity: 1; 68 | transition: opacity 0.8s ease-out; 69 | } 70 | 71 | /* Container for splash content */ 72 | .splash-content { 73 | display: flex; 74 | flex-direction: column; /* Stack title and spinner vertically */ 75 | align-items: center; /* Center items horizontally */ 76 | } 77 | 78 | #splash-screen.fade-out { 79 | opacity: 0; 80 | } 81 | 82 | #splash-screen .app-title { 83 | font-size: 3.0em; /* Slightly larger */ 84 | font-weight: 400; /* Slightly bolder */ 85 | display: flex; /* Use flex to layout spans */ 86 | justify-content: center; 87 | /* Remove previous styles */ 88 | /* background: ... */ 89 | /* background-size: ... */ 90 | /* -webkit-background-clip: ... */ 91 | /* background-clip: ... */ 92 | /* -webkit-text-fill-color: ... */ 93 | /* color: ... */ 94 | /* animation: gradient-flow ..., glow-pulse ...; */ 95 | /* text-shadow: ...; */ 96 | } 97 | 98 | /* Style for individual characters */ 99 | #splash-screen .app-title span { 100 | display: inline-block; /* Needed for transform */ 101 | color: var(--accent-color); /* Use accent color */ 102 | /* Apply wave animation */ 103 | animation: text-wave 0.8s ease-in-out infinite alternate; 104 | /* Add staggered delay using CSS variable set inline later */ 105 | animation-delay: var(--delay); 106 | } 107 | 108 | /* New wave animation */ 109 | @keyframes text-wave { 110 | from { 111 | transform: translateY(0); 112 | } 113 | to { 114 | transform: translateY(-6px); /* Adjust vertical distance */ 115 | } 116 | } 117 | 118 | /* Spinner Styles */ 119 | #splash-spinner { 120 | margin-top: 2em; /* Space between title and spinner */ 121 | width: 40px; 122 | height: 40px; 123 | border: 4px solid rgba(var(--text-color-rgb, 255, 255, 255), 0.2); /* Light border */ 124 | border-top-color: var(--accent-color); /* Use accent color for spinner top */ 125 | border-radius: 50%; 126 | animation: spinner-spin 1s linear infinite; 127 | } 128 | 129 | /* Spinner Animation */ 130 | @keyframes spinner-spin { 131 | to { 132 | transform: rotate(360deg); 133 | } 134 | } 135 | 136 | /* --- App Wrapper --- */ 137 | #app-wrapper { 138 | display: flex; 139 | height: 100vh; 140 | transition: padding-left 0.3s ease-out; /* Smooth transition when sidebar expands */ 141 | } 142 | 143 | /* --- Sidebar Toggle Button --- */ 144 | #sidebar-toggle { 145 | position: fixed; /* Keep it fixed relative to viewport */ 146 | top: 10px; 147 | left: 10px; 148 | z-index: 100; /* Above sidebar */ 149 | background: none; 150 | border: none; 151 | color: var(--text-secondary-color); 152 | font-size: 1.4em; 153 | padding: 5px; 154 | cursor: pointer; 155 | transition: color 0.2s ease, transform 0.3s ease; 156 | } 157 | 158 | #sidebar-toggle:hover { 159 | color: var(--accent-color); 160 | } 161 | 162 | /* Adjust position when sidebar is expanded */ 163 | .sidebar-expanded #sidebar-toggle { 164 | left: 190px; /* Position next to expanded sidebar */ 165 | transform: rotate(180deg); /* Optional: rotate arrow */ 166 | } 167 | 168 | 169 | /* --- Sidebar --- */ 170 | .sidebar { 171 | width: 60px; 172 | background-color: var(--bg-secondary-color); 173 | padding: 1em 0; 174 | padding-top: 50px; /* Make space for fixed toggle button */ 175 | height: 100%; 176 | position: fixed; /* Fixed position */ 177 | left: 0; 178 | top: 0; 179 | display: flex; 180 | flex-direction: column; 181 | align-items: center; 182 | border-right: 1px solid var(--border-color); 183 | box-shadow: inset -2px 0 5px rgba(0,0,0,0.1); 184 | transition: width 0.3s ease-out; /* Animate width change */ 185 | z-index: 99; 186 | overflow: hidden; /* Hide text when collapsed */ 187 | } 188 | 189 | .sidebar-expanded .sidebar { 190 | width: 180px; /* Expanded width */ 191 | } 192 | 193 | .sidebar ul { 194 | list-style: none; 195 | padding: 0; 196 | margin: 0; 197 | width: 100%; 198 | } 199 | 200 | .sidebar li { 201 | margin: 0; 202 | } 203 | 204 | .tab-button { 205 | display: flex; 206 | align-items: center; 207 | width: 100%; 208 | height: 55px; 209 | padding: 0 18px; /* Adjust padding for icon */ 210 | background: none; 211 | border: none; 212 | border-right: 4px solid transparent; 213 | color: var(--text-secondary-color); 214 | font-size: 1.4em; /* Icon size */ 215 | cursor: pointer; 216 | transition: background-color 0.25s ease, border-right-color 0.25s ease, color 0.25s ease; 217 | position: relative; 218 | justify-content: center; /* Center icon when collapsed */ 219 | } 220 | 221 | /* Expanded Tab Button Styles */ 222 | .sidebar-expanded .tab-button { 223 | justify-content: flex-start; /* Align items left */ 224 | padding: 0 20px; /* Adjust padding */ 225 | } 226 | 227 | .tab-button span.tab-text { 228 | display: none; /* Hidden by default */ 229 | margin-left: 15px; 230 | font-size: 0.7em; /* Text size relative to icon size */ 231 | white-space: nowrap; 232 | opacity: 0; 233 | transition: opacity 0.2s ease 0.1s; /* Fade in text */ 234 | } 235 | 236 | .sidebar-expanded .tab-button span.tab-text { 237 | display: inline; 238 | opacity: 1; 239 | } 240 | 241 | /* Add Tooltips (Simple Example) */ 242 | .tab-button::after { 243 | content: attr(aria-label); /* Use aria-label for tooltip text */ 244 | position: absolute; 245 | left: 110%; /* Position to the right */ 246 | top: 50%; 247 | transform: translateY(-50%); 248 | background-color: #111; 249 | color: #fff; 250 | padding: 4px 8px; 251 | border-radius: 4px; 252 | font-size: 0.75rem; 253 | white-space: nowrap; 254 | opacity: 0; 255 | visibility: hidden; 256 | transition: opacity 0.2s ease, visibility 0.2s ease; 257 | pointer-events: none; 258 | z-index: 10; 259 | } 260 | 261 | .tab-button:hover::after { 262 | opacity: 1; 263 | visibility: visible; 264 | } 265 | 266 | .tab-button i { 267 | transition: transform 0.2s ease-out; 268 | z-index: 2; 269 | position: relative; 270 | flex-shrink: 0; /* Prevent icon from shrinking */ 271 | width: 25px; /* Give icon a fixed width for alignment */ 272 | text-align: center; 273 | } 274 | 275 | .tab-button:hover { 276 | /* Use accent color with low opacity for hover background */ 277 | background-color: rgba(var(--accent-color-rgb, 222, 59, 255), 0.1); 278 | color: var(--accent-hover-color); 279 | } 280 | 281 | /* Apply tilt only if animations are enabled */ 282 | body:not(.no-animations) .tab-button:hover i { 283 | transform: rotate(25deg); 284 | } 285 | 286 | .tab-button.active { 287 | /* Use accent color with slightly higher opacity */ 288 | background-color: rgba(var(--accent-color-rgb, 222, 59, 255), 0.15); 289 | color: var(--accent-color); 290 | border-right-color: var(--accent-color); 291 | } 292 | 293 | .tab-button.active i { 294 | transform: none; /* Don't keep tilt when active */ 295 | } 296 | 297 | /* --- Content Area --- */ 298 | .content { 299 | flex-grow: 1; 300 | padding: 1.5em 2em; 301 | padding-left: 80px; /* Initial padding to account for collapsed sidebar + toggle */ 302 | overflow-y: auto; 303 | background-color: var(--bg-color); 304 | transition: padding-left 0.3s ease-out; /* Match sidebar transition */ 305 | } 306 | 307 | .sidebar-expanded .content { 308 | padding-left: 200px; /* Padding when sidebar is expanded */ 309 | } 310 | 311 | .tab-panel { 312 | display: none; 313 | animation: fadeIn 0.4s ease-out; 314 | } 315 | 316 | .tab-panel.active { 317 | display: block; 318 | } 319 | 320 | /* Panel within tabs */ 321 | .tab-content-panel { 322 | background-color: var(--panel-bg-color); 323 | padding: 1.5em; 324 | border-radius: 8px; 325 | margin-bottom: 1.5em; 326 | border: 1px solid var(--border-color); 327 | box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); 328 | } 329 | 330 | h2 { 331 | color: var(--accent-color); 332 | margin-bottom: 0.8em; 333 | font-weight: 400; 334 | font-size: 1.6em; 335 | /* Removed glow */ 336 | border-bottom: 1px solid var(--border-color); 337 | padding-bottom: 0.4em; 338 | } 339 | 340 | p { 341 | margin-bottom: 1em; 342 | color: var(--text-secondary-color); 343 | } 344 | 345 | /* --- Input Fields & Labels --- */ 346 | label { 347 | display: block; 348 | margin-bottom: 0.5em; 349 | font-weight: 500; 350 | color: var(--text-color); 351 | } 352 | 353 | input[type="text"], 354 | input[type="password"], 355 | select { 356 | width: 100%; 357 | padding: 0.8em 1em; 358 | margin-bottom: 1em; 359 | border-radius: 5px; 360 | border: 1px solid var(--input-border-color); 361 | background-color: var(--input-bg-color); 362 | color: var(--text-color); 363 | font-size: 0.95em; 364 | transition: border-color 0.2s ease, box-shadow 0.2s ease; 365 | } 366 | 367 | input[type="text"]:focus, 368 | input[type="password"]:focus, 369 | select:focus { 370 | outline: none; 371 | border-color: var(--accent-color); 372 | box-shadow: 0 0 0 3px rgba(var(--accent-color-rgb, 0, 120, 212), 0.3); /* Focus ring using accent */ 373 | } 374 | 375 | /* Convert accent hex to RGB for rgba - needs JS */ 376 | /* Example: #0078d4 -> 0, 120, 212 */ 377 | 378 | /* --- Checkboxes & Toggles --- */ 379 | .checkbox-container { 380 | display: flex; 381 | align-items: center; 382 | margin-bottom: 1em; 383 | cursor: pointer; 384 | } 385 | 386 | .checkbox-container input[type="checkbox"] { 387 | margin-right: 0.7em; 388 | height: 18px; 389 | width: 18px; 390 | accent-color: var(--accent-color); /* Style checkbox color */ 391 | } 392 | 393 | .checkbox-container label { 394 | margin-bottom: 0; /* Reset margin for inline label */ 395 | font-weight: normal; 396 | color: var(--text-secondary-color); 397 | } 398 | 399 | 400 | /* --- Settings --- */ 401 | .setting-option { 402 | margin-bottom: 1.5em; 403 | padding: 1.5em; 404 | background-color: var(--panel-bg-color); 405 | border-radius: 8px; 406 | border: 1px solid var(--border-color); 407 | } 408 | 409 | .setting-option label { 410 | color: var(--text-color); 411 | } 412 | 413 | .color-picker-container { 414 | display: flex; 415 | align-items: center; 416 | gap: 10px; /* Space between label and picker */ 417 | margin-bottom: 0.8em; 418 | } 419 | 420 | .color-picker-container label { 421 | flex-basis: 150px; /* Fixed width for labels */ 422 | flex-shrink: 0; 423 | margin-bottom: 0; 424 | } 425 | 426 | input[type="color"] { 427 | width: 40px; 428 | height: 40px; 429 | border: none; 430 | padding: 0; /* Remove default padding */ 431 | border-radius: 50%; /* Make it a circle */ 432 | cursor: pointer; 433 | background-color: transparent; 434 | box-shadow: 0 0 5px rgba(0,0,0,0.2); 435 | } 436 | /* Hide the actual color input square provided by the browser, showing only our styled circle */ 437 | input[type="color"]::-webkit-color-swatch-wrapper { 438 | padding: 0; 439 | } 440 | input[type="color"]::-webkit-color-swatch { 441 | border: none; 442 | border-radius: 50%; 443 | } 444 | input[type="color"]::-moz-color-swatch { 445 | border: none; 446 | border-radius: 50%; 447 | } 448 | 449 | 450 | /* --- Buttons (Unlock Tab, etc.) --- */ 451 | .action-button { 452 | display: inline-flex; /* Use flex for icon + text */ 453 | align-items: center; 454 | gap: 0.6em; /* Space between icon and text */ 455 | background: var(--accent-color); 456 | margin: 0.5em 0.5em 0.5em 0; /* Spacing between buttons */ 457 | padding: 0.8em 1.5em; 458 | color: #fff; 459 | text-align: center; 460 | text-decoration: none; 461 | border-radius: 5px; 462 | border: none; 463 | cursor: pointer; 464 | transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.1s ease-out; 465 | font-size: 0.95em; 466 | font-weight: 500; 467 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); 468 | } 469 | 470 | .action-button i { /* Style icon inside button */ 471 | font-size: 1.1em; 472 | line-height: 1; /* Prevent extra space */ 473 | transition: transform 0.2s ease-out; 474 | } 475 | 476 | .action-button:hover { 477 | background: var(--accent-hover-color); 478 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 479 | } 480 | 481 | /* Apply tilt to button icon on hover */ 482 | body:not(.no-animations) .action-button:hover i { 483 | transform: rotate(25deg); 484 | } 485 | 486 | /* Click animation for action buttons */ 487 | body:not(.no-animations) .action-button:active { 488 | transform: scale(0.97); 489 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); /* Slightly reduce shadow */ 490 | } 491 | 492 | /* Adjust primary button from previous example if still used */ 493 | button.primary { 494 | /* Inherit from action-button or redefine */ 495 | display: inline-flex; 496 | align-items: center; 497 | gap: 0.6em; 498 | background: var(--accent-color); 499 | margin: 1em 0; 500 | padding: 0.8em 1.5em; 501 | color: #fff; 502 | text-align: center; 503 | text-decoration: none; 504 | border-radius: 5px; 505 | border: none; 506 | cursor: pointer; 507 | transition: background-color 0.2s ease, box-shadow 0.2s ease; 508 | font-size: 0.95em; 509 | font-weight: 500; 510 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); 511 | } 512 | button.primary:hover { 513 | background: var(--accent-hover-color); 514 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 515 | } 516 | 517 | 518 | /* --- Animations --- */ 519 | @keyframes fadeIn { 520 | from { opacity: 0; transform: translateY(5px); } 521 | to { opacity: 1; transform: translateY(0); } 522 | } 523 | 524 | /* --- Simple Popup Placeholder Style --- */ 525 | /* This is very basic, implement a proper modal/popup solution later */ 526 | .popup { 527 | position: fixed; 528 | left: 50%; 529 | top: 50%; 530 | transform: translate(-50%, -50%); 531 | background-color: var(--panel-bg-color); 532 | color: var(--text-color); 533 | padding: 2em; 534 | border-radius: 8px; 535 | border: 1px solid var(--border-color); 536 | box-shadow: 0 5px 15px rgba(0,0,0,0.3); 537 | z-index: 1001; 538 | display: none; /* Hidden by default */ 539 | min-width: 300px; /* Ensure minimum width */ 540 | max-width: 450px; /* Limit maximum width */ 541 | text-align: center; 542 | } 543 | .popup.show { 544 | display: block; 545 | animation: fadeIn 0.3s ease-out; 546 | } 547 | .popup button:not(.action-button) { /* Avoid styling action buttons inside popups */ 548 | /* Style close button if needed */ 549 | margin-top: 1em; 550 | background: var(--accent-color); 551 | color: var(--text-color); 552 | border: none; 553 | padding: 0.5em 1em; 554 | border-radius: 4px; 555 | cursor: pointer; 556 | } 557 | 558 | /* Warning Panel Style */ 559 | .warning-panel { 560 | background-color: rgba(255, 165, 0, 0.1); /* Light orange background */ 561 | border-color: orange; 562 | } 563 | 564 | .warning-panel p { 565 | color: orange; /* Orange text */ 566 | font-weight: 500; 567 | } 568 | 569 | .warning-panel i { 570 | margin-right: 0.5em; 571 | } 572 | 573 | /* Styles for Confirmation Popup */ 574 | .confirmation-popup h3 { 575 | color: #DC143C; /* Crimson warning color */ 576 | margin-bottom: 0.8em; 577 | } 578 | 579 | .confirmation-popup p { 580 | color: var(--text-secondary-color); 581 | margin-bottom: 1.5em; 582 | } 583 | 584 | .popup-buttons { 585 | display: flex; 586 | justify-content: center; 587 | gap: 1em; 588 | } 589 | 590 | /* Differentiate button styles if needed */ 591 | .confirmation-popup .action-button.primary-action { 592 | /* Uses warning color for confirmation */ 593 | background-color: #DC143C; 594 | color: white; 595 | } 596 | .confirmation-popup .action-button.primary-action:hover { 597 | background-color: #E6395A; /* Lighter crimson for hover */ 598 | } 599 | 600 | .action-button.secondary-action { /* Keep this generic for cancel/other secondary */ 601 | background-color: var(--input-bg-color); 602 | color: var(--text-secondary-color); 603 | border: 1px solid var(--input-border-color); 604 | } 605 | 606 | .action-button.secondary-action:hover { 607 | background-color: var(--border-color); 608 | color: var(--text-color); 609 | } 610 | 611 | /* Style for unload button within info panel */ 612 | #loaded-avatar-info-panel .secondary-action { 613 | margin-top: 1em; /* Add some space above */ 614 | } 615 | 616 | /* Preset button styling */ 617 | .preset-buttons { 618 | display: flex; 619 | flex-wrap: wrap; /* Allow wrapping on smaller screens */ 620 | gap: 0.8em; 621 | margin-bottom: 1em; /* Space before HR */ 622 | } 623 | 624 | .preset-btn { 625 | /* Use the inline variable for background color */ 626 | background-color: var(--preset-bg); 627 | color: white; 628 | /* Make text shadow more visible on colored backgrounds */ 629 | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); 630 | } 631 | 632 | .preset-btn:hover { 633 | filter: brightness(1.15); /* Simple hover effect */ 634 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.25); 635 | } 636 | 637 | /* --- Custom Scrollbars --- */ 638 | /* Works in WebKit browsers (Chrome, Edge, Safari, Opera) */ 639 | 640 | /* Hide default scrollbar */ 641 | ::-webkit-scrollbar { 642 | width: 10px; /* Width of vertical scrollbar */ 643 | height: 10px; /* Height of horizontal scrollbar */ 644 | background-color: transparent; /* Make scrollbar track transparent */ 645 | } 646 | 647 | /* Scrollbar Handle (Thumb) */ 648 | ::-webkit-scrollbar-thumb { 649 | background-color: rgba(var(--accent-color-rgb, 30, 144, 255), 0.5); /* Use transparent accent color */ 650 | border-radius: 5px; 651 | border: 2px solid transparent; /* Add padding */ 652 | background-clip: content-box; /* Clip background to content area */ 653 | transition: background-color 0.2s ease; 654 | } 655 | 656 | /* Scrollbar Handle on hover/active */ 657 | ::-webkit-scrollbar-thumb:hover, 658 | ::-webkit-scrollbar-thumb:active { 659 | background-color: rgba(var(--accent-color-rgb, 30, 144, 255), 0.8); /* Darker/more opaque on hover */ 660 | } 661 | 662 | /* Scrollbar Track */ 663 | ::-webkit-scrollbar-track { 664 | background-color: rgba(0, 0, 0, 0.1); /* Very subtle track background */ 665 | border-radius: 5px; 666 | } 667 | 668 | /* Scrollbar Corner */ 669 | ::-webkit-scrollbar-corner { 670 | background-color: transparent; 671 | } 672 | 673 | /* Firefox specific scrollbar styling (optional, less customizable) */ 674 | /* For broader compatibility, though limited styling options */ 675 | * { 676 | scrollbar-width: thin; /* "auto" or "thin" */ 677 | scrollbar-color: var(--accent-color) rgba(0, 0, 0, 0.1); /* thumb and track color */ 678 | } 679 | 680 | /* Specific Styles for Notice Popup */ 681 | .notice-popup h3 { 682 | color: var(--accent-color); /* Use accent color for notice title */ 683 | margin-bottom: 1em; 684 | } 685 | 686 | .notice-popup p { 687 | margin-bottom: 1.5em; 688 | text-align: left; /* Align message text left */ 689 | } 690 | 691 | .popup-timer { 692 | font-size: 0.9em; 693 | color: var(--text-secondary-color); 694 | border-top: 1px solid var(--border-color); 695 | padding-top: 0.8em; 696 | margin-top: 1em; 697 | } -------------------------------------------------------------------------------- /AvatarLockpick.Revised/HTML/ButtonCalls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Button Action Handlers for AvatarLockpick GUI 3 | */ 4 | 5 | // --- Photino Communication Wrapper --- 6 | function sendMessageToDotNet(messageObject) { 7 | if (window.external && typeof window.external.sendMessage === 'function') { 8 | const messageString = JSON.stringify(messageObject); 9 | console.log("Sending to .NET:", messageString); 10 | window.external.sendMessage(messageString); 11 | } else { 12 | console.error("Photino communication (window.external.sendMessage) not available."); 13 | } 14 | } 15 | 16 | // --- Button Click Functions --- 17 | 18 | /** 19 | * Sends Avatar ID and User ID to the .NET backend. 20 | * Assumes elements with IDs 'avatar-id' and 'user-id' exist. 21 | */ 22 | function callDotNetSendIDs() { 23 | // It's better practice to get elements inside the function if they might not exist yet 24 | // or if this script loads before the main script defines the cached elements. 25 | // However, for simplicity, we'll assume they are globally available from the inline script for now. 26 | const avatarIdInput = document.getElementById('avatar-id'); 27 | const userIdInputElement = document.getElementById('user-id'); // Renamed to avoid conflict with inline script var 28 | 29 | if (!avatarIdInput || !userIdInputElement) { 30 | console.error("Required input elements not found."); 31 | return; 32 | } 33 | 34 | const avatarId = avatarIdInput.value; 35 | const userId = userIdInputElement.value; // Get current value regardless of censoring 36 | 37 | const message = { type: 'avatarInfo', avatarId, userId }; 38 | sendMessageToDotNet(message); 39 | } 40 | 41 | /** 42 | * Simulates loading avatar info based on entered IDs. 43 | * Updates the UI in the Unlock tab. 44 | */ 45 | function loadAvatarInfo() { 46 | const avatarIdInput = document.getElementById('avatar-id'); 47 | const userIdInputElement = document.getElementById('user-id'); 48 | 49 | if (!avatarIdInput || !userIdInputElement) { 50 | console.error("Required input elements not found."); 51 | // Maybe show an error popup? 52 | return; 53 | } 54 | 55 | const avatarId = avatarIdInput.value; 56 | const userId = userIdInputElement.value; 57 | 58 | if (!avatarId) { // Basic validation 59 | console.error("Avatar ID is required."); 60 | // Show error popup 61 | alert("Please enter an Avatar ID."); 62 | return; 63 | } 64 | 65 | console.log(`Loading info for Avatar: ${avatarId}, User: ${userId || 'N/A'}`); 66 | 67 | // --- Update the global state and UI (assuming vars/functions exist) --- 68 | if (typeof loadedAvatarIdSpan !== 'undefined' && typeof loadedUserIdSpan !== 'undefined') { 69 | loadedAvatarIdSpan.textContent = avatarId; 70 | loadedUserIdSpan.textContent = censorUserIdToggle.checked ? '********' : (userId || 'N/A'); 71 | } else { 72 | console.error("Loaded info spans not found."); 73 | } 74 | 75 | if (typeof isAvatarLoaded !== 'undefined' && typeof loadedAvatarId !== 'undefined' && typeof loadedUserId !== 'undefined') { 76 | isAvatarLoaded = true; // Set the global flag 77 | // Store the IDs globally (assuming loadedAvatarId and loadedUserId are declared in inline script) 78 | window.loadedAvatarId = avatarId; // Assign to window or use a dedicated object if preferred 79 | window.loadedUserId = userId; 80 | } else { 81 | console.error("Global state variables (isAvatarLoaded, loadedAvatarId, loadedUserId) not found."); 82 | } 83 | 84 | if (typeof updateUnlockUI === 'function') { 85 | updateUnlockUI(); // Call the UI update function 86 | } else { 87 | console.error("updateUnlockUI function not found."); 88 | } 89 | 90 | // Optionally, send a message to .NET to confirm loading 91 | // sendMessageToDotNet({ type: 'avatarLoaded', avatarId }); 92 | 93 | // Optionally, switch to the Unlock tab 94 | // document.querySelector('.tab-button[data-tab="unlock"]').click(); 95 | } 96 | 97 | /** 98 | * Triggers the display of the action popup. 99 | * Assumes elements with IDs 'action-popup', 'popup-title', 'popup-message' exist. 100 | * @param {string} actionType - The type of action being triggered (e.g., 'Unlock', 'Unlock All'). 101 | */ 102 | function triggerPopup(actionType) { 103 | // Check if avatar is loaded before proceeding 104 | if (!isAvatarLoaded || !window.loadedAvatarId) { 105 | console.error("No avatar loaded. Cannot perform unlock action."); 106 | alert("Please load an avatar first."); 107 | return; 108 | } 109 | 110 | console.log(`${actionType} button clicked for Avatar: ${window.loadedAvatarId}`); 111 | 112 | // Again, assuming global elements for simplicity 113 | const actionPopupElement = document.getElementById('action-popup'); 114 | const popupTitleElement = document.getElementById('popup-title'); 115 | const popupMessageElement = document.getElementById('popup-message'); 116 | 117 | if (!actionPopupElement || !popupTitleElement || !popupMessageElement) { 118 | console.error("Popup elements not found."); 119 | return; 120 | } 121 | 122 | popupTitleElement.textContent = `${actionType} Action`; 123 | popupMessageElement.textContent = `Calling .NET...`; 124 | actionPopupElement.classList.add('show'); 125 | 126 | // Example: Send message to .NET including loaded avatar details 127 | const message = { 128 | type: 'unlockAction', 129 | action: actionType, 130 | avatarId: window.loadedAvatarId, // Include loaded Avatar ID 131 | userId: window.loadedUserId || null // Include loaded User ID (or null if none) 132 | }; 133 | sendMessageToDotNet(message); 134 | } 135 | 136 | /** 137 | * Closes the action popup. 138 | * Assumes element with ID 'action-popup' exists. 139 | */ 140 | function closePopup() { 141 | // Assuming global element 142 | const actionPopupElement = document.getElementById('action-popup'); 143 | if (!actionPopupElement) { 144 | console.error("Popup element not found."); 145 | return; 146 | } 147 | actionPopupElement.classList.remove('show'); 148 | } 149 | 150 | /** 151 | * Sends a request to the .NET backend to open the help/documentation URL. 152 | */ 153 | function openHelpUrl() { 154 | console.log("Requesting help URL from .NET..."); 155 | const message = { type: 'openHelpUrl' }; 156 | sendMessageToDotNet(message); 157 | } 158 | 159 | /** 160 | * Sends a restart command to the .NET backend. 161 | * @param {string} restartType - The type of restart ('restart-novr' or 'restart-vr'). 162 | */ 163 | function triggerRestart(restartType) { 164 | // Check if avatar is loaded before proceeding (optional, depends on backend needs) 165 | if (!isAvatarLoaded || !window.loadedAvatarId) { 166 | console.error("No avatar loaded. Restart action might depend on loaded avatar."); 167 | // Optionally show a popup or just send the command anyway 168 | // alert("Please load an avatar first."); 169 | // return; 170 | } 171 | 172 | console.log(`Triggering restart: ${restartType}`); 173 | 174 | const message = { 175 | type: restartType, 176 | // Optionally include avatar/user ID if the backend needs it for context 177 | // avatarId: window.loadedAvatarId, 178 | // userId: window.loadedUserId 179 | }; 180 | sendMessageToDotNet(message); 181 | 182 | // Optionally show a simple feedback popup 183 | // For consistency, you could reuse triggerPopup or make a simpler notification 184 | const actionPopupElement = document.getElementById('action-popup'); 185 | const popupTitleElement = document.getElementById('popup-title'); 186 | const popupMessageElement = document.getElementById('popup-message'); 187 | 188 | if (actionPopupElement && popupTitleElement && popupMessageElement) { 189 | popupTitleElement.textContent = restartType === 'restart-vr' ? 'Restart (VR)' : 'Restart'; 190 | popupMessageElement.textContent = `Requesting ${popupTitleElement.textContent}...`; 191 | actionPopupElement.classList.add('show'); 192 | } else { 193 | console.warn("Popup elements not found, skipping feedback popup for restart action.") 194 | } 195 | } -------------------------------------------------------------------------------- /AvatarLockpick.Revised/HTML/unlockicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrim-dev/AvatarLockpick/87a178d5ad3e43e87c31c8e3398256ab1fbf589c/AvatarLockpick.Revised/HTML/unlockicon.ico -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Program.cs: -------------------------------------------------------------------------------- 1 | using AvatarLockpick.Revised.Utils; 2 | using Photino.NET; 3 | using System.Drawing; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace AvatarLockpick.Revised 8 | { 9 | internal class Program 10 | { 11 | public static string AppVersion = "2.1"; 12 | public static HttpUtils HttpC { get; private set; } = new(); 13 | // A unique name for the mutex to ensure only one instance runs. 14 | private const string AppMutexName = "Global\\AvatarLockpickRevised_Mutex_2A7B9C1D"; 15 | 16 | //Application Icon by: Kmg Design 17 | //GUI made with: https://github.com/tryphotino/photino.NET 18 | [STAThread] 19 | static void Main(string[] args) 20 | { 21 | Console.Title = "Loading app..."; 22 | VersionChecker.CheckForUpdates(); 23 | 24 | AppFolders.Load(); 25 | 26 | AppLog.SetupLogFile(); 27 | 28 | ConsoleSetup.Init(); 29 | Console.Title = "AvatarLockpick App"; 30 | AppLog.Warn("Startup", "Loading Application..."); 31 | 32 | ExtractResources($"{AppFolders.DataLowFolder}\\App"); 33 | 34 | // Try to grab the mutex 35 | using (Mutex mutex = new Mutex(true, AppMutexName, out bool createdNew)) 36 | { 37 | if (!createdNew) 38 | { 39 | // Another instance is already running 40 | MessageBoxUtils.ShowWarning("Another instance of AvatarLockpick is already running.", "Application Already Running"); 41 | return; // Exit the application 42 | } 43 | 44 | if (!File.Exists($"{AppFolders.DataLowFolder}\\no_startup_warn.scrim")) 45 | { 46 | MessageBoxUtils.ShowWarning("If you run into any bugs, issues or crashes contact me on discord:\nscrimmane (679060175440707605)" + 47 | "\nOr post an 'issue' on github!", "Hey!"); 48 | try { File.WriteAllText($"{AppFolders.DataLowFolder}\\no_startup_warn.scrim", "cached"); } catch { /*Nothing*/ } 49 | } 50 | 51 | HttpC.Load(); 52 | 53 | // Application logic starts here if this is the first instance 54 | string windowTitle = "AvatarLockpick"; 55 | 56 | // Creating a new PhotinoWindow instance with the fluent API 57 | var window = new PhotinoWindow() 58 | .SetTitle(windowTitle) 59 | // Resize to a percentage of the main monitor work area 60 | .SetUseOsDefaultSize(false) 61 | .SetSize(new Size(900, 600)) 62 | // Center window in the middle of the screen 63 | .Center() 64 | // Users can resize windows by default. 65 | .SetResizable(true) 66 | .RegisterCustomSchemeHandler("app", (object sender, string scheme, string url, out string contentType) => 67 | { 68 | contentType = "text/javascript"; 69 | return new MemoryStream(Encoding.UTF8.GetBytes(@"")); 70 | }) 71 | // Most event handlers can be registered after the 72 | // PhotinoWindow was instantiated by calling a registration 73 | // method like the following RegisterWebMessageReceivedHandler. 74 | // This could be added in the PhotinoWindowOptions if preferred. 75 | .RegisterWebMessageReceivedHandler((sender, message) => 76 | { 77 | var window = sender as PhotinoWindow; 78 | 79 | // The message argument is coming in from sendMessage. 80 | // "window.external.sendMessage(message: string)" 81 | string response = $"Received message: \"{message}\""; 82 | 83 | // Send a message back the to JavaScript event handler. 84 | // "window.external.receiveMessage(callback: Function)" 85 | window.SendWebMessage(response); 86 | 87 | //Send data to be processed 88 | GUIcom.Communication(message); 89 | }) 90 | .Load($"{AppFolders.DataLowFolder}\\App\\index.html"); // Can be used with relative path strings or "new URI()" instance to load a website. 91 | 92 | Thread.Sleep(1200); 93 | Console.Clear(); 94 | AppLog.Warn("Startup", "Do not close the console as it is needed. If you'd like to hide it open " + 95 | $"the hideconsole.txt file in the '{AppFolders.DataLowFolder}' folder and change it from false to true. Then close and reopen the app."); 96 | AppLog.Success("Startup", "App Loaded!"); 97 | 98 | try { AppLog.ClearLogsOnExit = bool.Parse(File.ReadAllText($"{AppFolders.DataLowFolder}\\ClearLogs.txt")); } catch { AppLog.ClearLogsOnExit = false; } 99 | 100 | ConsoleSetup.OnExit += () => 101 | { 102 | if (AppLog.ClearLogsOnExit) 103 | { 104 | if (Directory.Exists($"{AppFolders.DataLowFolder}\\Logs")) 105 | { 106 | try { Directory.Delete($"{AppFolders.DataLowFolder}\\Logs", true); } catch { } 107 | } 108 | } 109 | }; 110 | 111 | window.SetChromeless(false); 112 | window.SetDevToolsEnabled(false); 113 | window.SetIconFile($"{AppFolders.DataLowFolder}\\App\\unlockicon.ico"); 114 | window.WaitForClose(); // Starts the application event loop 115 | } 116 | } 117 | 118 | private static void ExtractResources(string outputDirectory) 119 | { 120 | var assembly = Assembly.GetExecutingAssembly(); 121 | string resourcePrefix = $"{assembly.GetName().Name}.HTML."; 122 | 123 | // Map resource names to output paths 124 | var resources = new Dictionary 125 | { 126 | { $"{resourcePrefix}index.html", Path.Combine(outputDirectory, "index.html") }, 127 | { $"{resourcePrefix}AppStyle.css", Path.Combine(outputDirectory, "AppStyle.css") }, 128 | { $"{resourcePrefix}ButtonCalls.js", Path.Combine(outputDirectory, "ButtonCalls.js") }, 129 | { $"{resourcePrefix}unlockicon.ico", Path.Combine(outputDirectory, "unlockicon.ico") } 130 | }; 131 | 132 | foreach (var resource in resources) 133 | { 134 | try 135 | { 136 | // Create directory if needed 137 | Directory.CreateDirectory(Path.GetDirectoryName(resource.Value)); 138 | 139 | using (Stream stream = assembly.GetManifestResourceStream(resource.Key)) 140 | using (FileStream fileStream = File.Create(resource.Value)) 141 | { 142 | if (stream == null) 143 | throw new FileNotFoundException($"Embedded resource not found: {resource.Key}"); 144 | 145 | stream.CopyTo(fileStream); 146 | } 147 | Console.WriteLine($"Extracted: {resource.Value}"); 148 | } 149 | catch (Exception ex) 150 | { 151 | Console.WriteLine($"Failed to extract {resource.Key}: {ex.Message}"); 152 | } 153 | } 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "HelloPhotinoApp": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/AppFolders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AvatarLockpick.Revised.Utils 8 | { 9 | internal class AppFolders 10 | { 11 | private static readonly string LocalLowAppData = Environment.GetFolderPath 12 | (Environment.SpecialFolder.LocalApplicationData).Replace("Local", "LocalLow"); 13 | public static string DataLowFolder { get; private set; } = $"{LocalLowAppData}\\alp_data"; 14 | public static void Load() 15 | { 16 | if (!Directory.Exists(DataLowFolder)) 17 | { 18 | try { Directory.Delete(DataLowFolder); } catch { } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/AppLog.cs: -------------------------------------------------------------------------------- 1 | using Pastel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace AvatarLockpick.Revised.Utils 9 | { 10 | internal class AppLog 11 | { 12 | public static bool ClearLogsOnExit = false; 13 | public static string? LogFilePath { get; private set; } 14 | public static void SetupLogFile() 15 | { 16 | var LogFile = $"Log_{DateTime.Now:d}_{DateTime.Now:HH.mm.ss}.txt".Replace("/", "_"); 17 | 18 | if (!Directory.Exists($"{AppFolders.DataLowFolder}\\Logs")) 19 | { 20 | try { Directory.CreateDirectory($"{AppFolders.DataLowFolder}\\Logs"); } 21 | catch(Exception ex) 22 | { 23 | MessageBoxUtils.ShowError(ex.Message); 24 | } 25 | 26 | try { File.WriteAllText($"{AppFolders.DataLowFolder}\\Logs\\{LogFile}", "LOGS:\n"); } catch { } 27 | LogFilePath = $"{AppFolders.DataLowFolder}\\Logs\\{LogFile}"; 28 | } 29 | else 30 | { 31 | try { File.WriteAllText($"{AppFolders.DataLowFolder}\\Logs\\{LogFile}", "LOGS:\n"); } catch { } 32 | LogFilePath = $"{AppFolders.DataLowFolder}\\Logs\\{LogFile}"; 33 | } 34 | 35 | try { File.WriteAllText($"{AppFolders.DataLowFolder}\\ClearLogs.txt", "false"); } catch { } 36 | } 37 | 38 | public static void Log(string CurrentTask, string Message) 39 | { 40 | //Time 41 | Console.Write("[".Pastel("#e8e8e8")); 42 | Console.Write(DateTime.Now.ToString("hh:mm:ss").Pastel("#00ff5e")); 43 | Console.Write("] ".Pastel("#e8e8e8")); 44 | 45 | //Message 46 | Console.Write("[".Pastel("#e8e8e8")); 47 | Console.Write(CurrentTask.Pastel("#0084ff")); 48 | Console.Write("] ".Pastel("#e8e8e8")); 49 | Console.Write($"{Message}{Environment.NewLine}".Pastel(ConsoleColor.White)); 50 | 51 | WriteToLogFile("i", Message); 52 | } 53 | 54 | public static void Success(string CurrentTask, string Message) 55 | { 56 | //Time 57 | Console.Write("[".Pastel("#e8e8e8")); 58 | Console.Write(DateTime.Now.ToString("hh:mm:ss").Pastel("#00ff5e")); 59 | Console.Write("] ".Pastel("#e8e8e8")); 60 | 61 | //Message 62 | Console.Write("[".Pastel("#e8e8e8")); 63 | Console.Write(CurrentTask.Pastel("#0084ff")); 64 | Console.Write("] ".Pastel("#e8e8e8")); 65 | Console.Write($"[SUCCESS] {Message}{Environment.NewLine}".Pastel("#00ff40")); 66 | 67 | WriteToLogFile("+", Message); 68 | } 69 | 70 | public static void Warn(string CurrentTask, string Message) 71 | { 72 | //Time 73 | Console.Write("[".Pastel("#e8e8e8")); 74 | Console.Write(DateTime.Now.ToString("hh:mm:ss").Pastel("#00ff5e")); 75 | Console.Write("] ".Pastel("#e8e8e8")); 76 | 77 | //Message 78 | Console.Write("[".Pastel("#e8e8e8")); 79 | Console.Write(CurrentTask.Pastel("#0084ff")); 80 | Console.Write("] ".Pastel("#e8e8e8")); 81 | Console.Write($"[WARN] {Message}{Environment.NewLine}".Pastel("#ffcc00")); 82 | 83 | WriteToLogFile("!", Message); 84 | } 85 | 86 | public static void Error(string CurrentTask, string Message) 87 | { 88 | //Time 89 | Console.Write("[".Pastel("#e8e8e8")); 90 | Console.Write(DateTime.Now.ToString("hh:mm:ss").Pastel("#00ff5e")); 91 | Console.Write("] ".Pastel("#e8e8e8")); 92 | 93 | //Message 94 | Console.Write("[".Pastel("#e8e8e8")); 95 | Console.Write(CurrentTask.Pastel("#0084ff")); 96 | Console.Write("] ".Pastel("#e8e8e8")); 97 | Console.Write($"[ERROR] {Message}{Environment.NewLine}".Pastel("#ff002b")); 98 | 99 | WriteToLogFile("X", Message); 100 | } 101 | 102 | private static void WriteToLogFile(string type, string message) 103 | { 104 | var logEntry = $"[{DateTime.Now}] [{type}] {message}"; 105 | if (LogFilePath != null) { File.AppendAllText(LogFilePath, logEntry + Environment.NewLine); } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/AvatarUnlocker.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.Metrics; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Text.RegularExpressions; 10 | using System.Threading.Tasks; 11 | using Windows.System; 12 | using System.Net; 13 | using static System.Net.WebRequestMethods; 14 | 15 | namespace AvatarLockpick.Revised.Utils 16 | { 17 | internal class AvatarUnlocker 18 | { 19 | //I can honestly do this better but it doesn't matter it works 20 | /// 21 | /// Starts unlock process for each type 22 | /// [1 = Unlock] 23 | /// [2 = Unlock All] 24 | /// [3 = Unlock VRCF] 25 | /// [4 = Attempt to unlock lock types from db] 26 | /// 27 | 28 | public static void Start(int Type, string UserID, string AvatarID) 29 | { 30 | Console.WriteLine("Console Loaded!"); 31 | Console.ForegroundColor = ConsoleColor.White; 32 | Console.BackgroundColor = ConsoleColor.Black; 33 | Console.Title = "AvatarLockpick Debug Console"; 34 | Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true }); 35 | try { WindowUtils.BringConsoleToFrontIfVisible(); } catch { } 36 | 37 | switch (Type) 38 | { 39 | case 1: 40 | Unlock(UserID, AvatarID); 41 | break; 42 | case 2: 43 | UnlockAll(UserID, AvatarID); 44 | break; 45 | case 3: 46 | UnlockVRCF(UserID, AvatarID); 47 | break; 48 | case 4: 49 | UnlockDB(UserID, AvatarID); 50 | break; 51 | default: 52 | Unlock(UserID, AvatarID); 53 | break; 54 | } 55 | } 56 | 57 | private static string GetVRChatAvatarPath(string userId) 58 | { 59 | // Get the path to AppData 60 | string appDataPath = Path.Combine( 61 | Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), 62 | "AppData", 63 | "LocalLow" 64 | ); 65 | 66 | string vrchatPath = Path.Combine(appDataPath, "VRChat", "VRChat", "LocalAvatarData", userId); 67 | AppLog.Log("Path", $"Checking VRChat path: {vrchatPath}"); 68 | return vrchatPath; 69 | } 70 | 71 | private static void Unlock(string UID, string AID) 72 | { 73 | AppLog.Log("Unlock", "Starting unlock process..."); 74 | 75 | try 76 | { 77 | string avatarPath = GetVRChatAvatarPath(UID); 78 | string fullAvatarPath = Path.Combine(avatarPath, AID); 79 | 80 | AppLog.Log("Path", $"Looking for avatar at path: {fullAvatarPath}"); 81 | AppLog.Log("Path", $"Directory exists: {Directory.Exists(avatarPath)}"); 82 | 83 | if (Directory.Exists(avatarPath)) 84 | { 85 | AppLog.Log("Path", "Files in directory:"); 86 | foreach (string file in Directory.GetFiles(avatarPath)) 87 | { 88 | AppLog.Log("Path", $"- {Path.GetFileName(file)}"); 89 | } 90 | } 91 | 92 | if (!System.IO.File.Exists(fullAvatarPath)) 93 | { 94 | AppLog.Error("File", $"Avatar file not found: {AID}"); 95 | } 96 | 97 | string jsonContent = System.IO.File.ReadAllText(fullAvatarPath); 98 | AppLog.Success("Path", "Successfully read file content"); 99 | AppLog.Log("JSON", $"Raw JSON content: {jsonContent}"); 100 | 101 | bool wasUnlocked = false; 102 | 103 | try 104 | { 105 | // First try to parse as a single object 106 | JObject jsonObj = JObject.Parse(jsonContent); 107 | AppLog.Success("JSON", "Successfully parsed JSON as object"); 108 | 109 | // Get the animationParameters array 110 | var animParams = jsonObj["animationParameters"] as JArray; 111 | if (animParams != null) 112 | { 113 | AppLog.Success("JSON", "Found animationParameters array"); 114 | foreach (JObject param in animParams) 115 | { 116 | var nameProperty = param["name"]?.ToString(); 117 | if (string.IsNullOrEmpty(nameProperty)) continue; 118 | 119 | // Remove ALL Unicode characters 120 | string normalizedName = Regex.Replace(nameProperty, @"[^\u0000-\u007F]+", "", RegexOptions.None); 121 | normalizedName = normalizedName.Trim(); // Also trim any remaining whitespace 122 | 123 | AppLog.Log("JSON", $"Checking parameter: {nameProperty} (normalized: {normalizedName})"); 124 | 125 | if (normalizedName.Equals("locked", StringComparison.OrdinalIgnoreCase)) 126 | { 127 | AppLog.Log("JSON", $"Found locked parameter with name: {nameProperty}"); 128 | var valueToken = param["value"]; 129 | 130 | if (valueToken != null) 131 | { 132 | AppLog.Log("JSON", $"Current value: {valueToken}"); 133 | if (valueToken.Type == JTokenType.Integer && valueToken.Value() == 1) 134 | { 135 | param["value"] = new JValue(0); 136 | wasUnlocked = true; 137 | AppLog.Success("JSON", "Changed value to 0"); 138 | MessageBoxUtils.ShowInfo("Avatar Unlocked you can now restart your game!", "Nice!"); 139 | } 140 | } 141 | } 142 | } 143 | } 144 | else 145 | { 146 | AppLog.Warn("JSON", "No animationParameters array found in JSON"); 147 | } 148 | 149 | if (wasUnlocked) 150 | { 151 | AppLog.Warn("Avatar", "Writing changes back to file..."); 152 | System.IO.File.WriteAllText(fullAvatarPath, jsonObj.ToString(Newtonsoft.Json.Formatting.None)); 153 | AppLog.Success("Avatar", "Successfully saved changes"); 154 | //return true; 155 | } 156 | } 157 | catch (JsonReaderException) 158 | { 159 | // If it's not a single object, try parsing as array 160 | try 161 | { 162 | JArray jsonArray = JArray.Parse(jsonContent); 163 | AppLog.Success("JSON", "Successfully parsed JSON as array"); 164 | 165 | foreach (JObject item in jsonArray.Children()) 166 | { 167 | var nameProperty = item["name"]?.ToString(); 168 | if (string.IsNullOrEmpty(nameProperty)) continue; 169 | 170 | string normalizedName = Regex.Replace(nameProperty, @"[^\u0000-\u007F]+", "", RegexOptions.None); 171 | 172 | if (normalizedName.Equals("locked", StringComparison.OrdinalIgnoreCase)) 173 | { 174 | AppLog.Log("JSON", $"Found locked property with name: {nameProperty}"); 175 | var valueToken = item["value"]; 176 | 177 | if (valueToken != null) 178 | { 179 | AppLog.Log("JSON", $"Current value: {valueToken}"); 180 | 181 | if (valueToken.Type == JTokenType.Integer && valueToken.Value() != 0) 182 | { 183 | item["value"] = new JValue(0); 184 | wasUnlocked = true; 185 | AppLog.Success("JSON", "Changed integer value to 0"); 186 | } 187 | else if (valueToken.Type == JTokenType.Boolean && valueToken.Value() == true) 188 | { 189 | item["value"] = new JValue(false); 190 | wasUnlocked = true; 191 | AppLog.Success("JSON", "Changed boolean value to false"); 192 | } 193 | else if (valueToken.Type == JTokenType.String) 194 | { 195 | string? strValue = valueToken.Value(); 196 | if (!string.IsNullOrEmpty(strValue) && 197 | (strValue.Equals("1") || strValue.Equals("true", StringComparison.OrdinalIgnoreCase))) 198 | { 199 | item["value"] = new JValue("0"); 200 | wasUnlocked = true; 201 | AppLog.Success("JSON", "Changed string value to '0'"); 202 | MessageBoxUtils.ShowInfo("Avatar Unlocked you can now restart your game!", "Nice!"); 203 | } 204 | } 205 | } 206 | } 207 | } 208 | 209 | if (wasUnlocked) 210 | { 211 | AppLog.Warn("JSON", "Writing changes back to file..."); 212 | System.IO.File.WriteAllText(fullAvatarPath, jsonArray.ToString(Newtonsoft.Json.Formatting.None)); 213 | AppLog.Success("JSON", "Successfully saved changes"); 214 | //return true; 215 | } 216 | } 217 | catch (Exception ex) 218 | { 219 | AppLog.Error("JSON", $"Error parsing JSON as array: {ex.Message}"); 220 | throw; 221 | } 222 | } 223 | 224 | if (wasUnlocked) 225 | { 226 | AppLog.Warn("JSON", "No locked properties found or all properties were already unlocked"); 227 | //return false; 228 | MessageBoxUtils.ShowInfo("Avatar not unlocked, no value found. Maybe try again?", "Awww!"); 229 | } 230 | else 231 | { 232 | AppLog.Warn("JSON", "No locked properties found or all properties were already unlocked"); 233 | //return false; 234 | MessageBoxUtils.ShowInfo("Avatar not unlocked, no value found. Maybe try again?", "Awww!"); 235 | } 236 | } 237 | catch (Exception ex) 238 | { 239 | AppLog.Error("ERR", $"Error: {ex.Message}"); 240 | AppLog.Error("STACK", $"Stack trace: {ex.StackTrace}"); 241 | } 242 | } 243 | 244 | private static void UnlockAll(string UID, string AID) 245 | { 246 | AppLog.Log("UnlockAllAvatars", "Starting unlock process..."); 247 | MessageBoxUtils.ShowWarning("This function isn't finished and is usually buggy.\n" + 248 | "Therefor it's disabled. I will rework it in future.\n\n(Avatars Not Unlocked)"); 249 | //To do 250 | } 251 | 252 | private static void UnlockVRCF(string UID, string AID) 253 | { 254 | AppLog.Log("UnlockVRCFuryLocks", "Starting unlock process..."); 255 | 256 | try 257 | { 258 | string avatarPath = GetVRChatAvatarPath(UID); 259 | string fullAvatarPath = Path.Combine(avatarPath, AID); 260 | 261 | AppLog.Log("Path", $"Looking for avatar at path: {fullAvatarPath}"); 262 | AppLog.Log("Path", $"Directory exists: {Directory.Exists(avatarPath)}"); 263 | 264 | if (Directory.Exists(avatarPath)) 265 | { 266 | AppLog.Log("Path", "Files in directory:"); 267 | foreach (string file in Directory.GetFiles(avatarPath)) 268 | { 269 | AppLog.Log("Path", $"- {Path.GetFileName(file)}"); 270 | } 271 | } 272 | 273 | if (!System.IO.File.Exists(fullAvatarPath)) 274 | { 275 | AppLog.Error("File", $"Avatar file not found: {AID}"); 276 | } 277 | 278 | string jsonContent = System.IO.File.ReadAllText(fullAvatarPath); 279 | AppLog.Success("Path", "Successfully read file content"); 280 | AppLog.Log("JSON", $"Raw JSON content: {jsonContent}"); 281 | 282 | bool wasUnlocked = false; 283 | 284 | try 285 | { 286 | // First try to parse as a single object 287 | JObject jsonObj = JObject.Parse(jsonContent); 288 | AppLog.Success("JSON", "Successfully parsed JSON as object"); 289 | 290 | // Get the animationParameters array 291 | var animParams = jsonObj["animationParameters"] as JArray; 292 | if (animParams != null) 293 | { 294 | AppLog.Success("JSON", "Found animationParameters array"); 295 | foreach (JObject param in animParams) 296 | { 297 | var nameProperty = param["name"]?.ToString(); 298 | if (string.IsNullOrEmpty(nameProperty)) continue; 299 | 300 | // Remove ALL Unicode characters 301 | string normalizedName = Regex.Replace(nameProperty, @"[^\u0000-\u007F]+", "", RegexOptions.None); 302 | normalizedName = normalizedName.Trim(); // Also trim any remaining whitespace 303 | 304 | AppLog.Log("JSON", $"Checking parameter: {nameProperty} (normalized: {normalizedName})"); 305 | 306 | if (normalizedName.Equals("locked", StringComparison.OrdinalIgnoreCase)) 307 | { 308 | AppLog.Log("JSON", $"Found locked parameter with name: {nameProperty}"); 309 | var valueToken = param["value"]; 310 | 311 | if (valueToken != null) 312 | { 313 | AppLog.Log("JSON", $"Current value: {valueToken}"); 314 | if (valueToken.Type == JTokenType.Integer && valueToken.Value() == 1) 315 | { 316 | param["value"] = new JValue(0); 317 | wasUnlocked = true; 318 | AppLog.Success("JSON", "Changed value to 0"); 319 | MessageBoxUtils.ShowInfo("Avatar unlocked! No more VRCFury!", "Nice!"); 320 | } 321 | } 322 | } 323 | } 324 | } 325 | else 326 | { 327 | AppLog.Warn("JSON", "No animationParameters array found in JSON"); 328 | } 329 | 330 | if (wasUnlocked) 331 | { 332 | AppLog.Warn("Avatar", "Writing changes back to file..."); 333 | System.IO.File.WriteAllText(fullAvatarPath, jsonObj.ToString(Newtonsoft.Json.Formatting.None)); 334 | AppLog.Success("Avatar", "Successfully saved changes"); 335 | //return true; 336 | } 337 | } 338 | catch (JsonReaderException) 339 | { 340 | // If it's not a single object, try parsing as array 341 | try 342 | { 343 | JArray jsonArray = JArray.Parse(jsonContent); 344 | AppLog.Success("JSON", "Successfully parsed JSON as array"); 345 | 346 | foreach (JObject item in jsonArray.Children()) 347 | { 348 | var nameProperty = item["name"]?.ToString(); 349 | if (string.IsNullOrEmpty(nameProperty)) continue; 350 | 351 | string normalizedName = Regex.Replace(nameProperty, @"[^\u0000-\u007F]+", "", RegexOptions.None); 352 | 353 | if (normalizedName.Equals("VRCF Lock/Lock", StringComparison.OrdinalIgnoreCase)) 354 | { 355 | AppLog.Log("JSON", $"Found locked property with name: {nameProperty}"); 356 | var valueToken = item["value"]; 357 | 358 | if (valueToken != null) 359 | { 360 | AppLog.Log("JSON", $"Current value: {valueToken}"); 361 | 362 | if (valueToken.Type == JTokenType.Integer && valueToken.Value() != 0) 363 | { 364 | item["value"] = new JValue(0); 365 | wasUnlocked = true; 366 | AppLog.Success("JSON", "Changed integer value to 0"); 367 | } 368 | else if (valueToken.Type == JTokenType.Boolean && valueToken.Value() == true) 369 | { 370 | item["value"] = new JValue(false); 371 | wasUnlocked = true; 372 | AppLog.Success("JSON", "Changed boolean value to false"); 373 | } 374 | else if (valueToken.Type == JTokenType.String) 375 | { 376 | string? strValue = valueToken.Value(); 377 | if (!string.IsNullOrEmpty(strValue) && 378 | (strValue.Equals("1") || strValue.Equals("true", StringComparison.OrdinalIgnoreCase))) 379 | { 380 | item["value"] = new JValue("0"); 381 | wasUnlocked = true; 382 | AppLog.Success("JSON", "Changed string value to '0'"); 383 | } 384 | } 385 | } 386 | } 387 | } 388 | 389 | if (wasUnlocked) 390 | { 391 | AppLog.Warn("JSON", "Writing changes back to file..."); 392 | System.IO.File.WriteAllText(fullAvatarPath, jsonArray.ToString(Newtonsoft.Json.Formatting.None)); 393 | AppLog.Success("JSON", "Successfully saved changes"); 394 | //return true; 395 | } 396 | } 397 | catch (Exception ex) 398 | { 399 | AppLog.Error("JSON", $"Error parsing JSON as array: {ex.Message}"); 400 | throw; 401 | } 402 | } 403 | 404 | if (wasUnlocked) 405 | { 406 | AppLog.Warn("JSON", "No locked properties found or all properties were already unlocked"); 407 | //return false; 408 | MessageBoxUtils.ShowInfo("Avatar not unlocked, no value found. Maybe try again?\n(It could also be unlocked already!)", "Awww!"); 409 | } 410 | else 411 | { 412 | AppLog.Warn("JSON", "No locked properties found or all properties were already unlocked"); 413 | //return false; 414 | MessageBoxUtils.ShowInfo("Avatar not unlocked, no value found. Maybe try again?\n(It could also be unlocked already!)", "Awww!"); 415 | } 416 | } 417 | catch (Exception ex) 418 | { 419 | AppLog.Error("ERR", $"Error: {ex.Message}"); 420 | AppLog.Error("ST", $"Stack trace: {ex.StackTrace}"); 421 | } 422 | } 423 | 424 | private static List AllLockTypes { get; } = []; 425 | private static void LoadLockTypes() 426 | { 427 | AppLog.Warn("DB", "Loading lock types from database..."); 428 | try 429 | { 430 | AllLockTypes.Clear(); 431 | AllLockTypes.AddRange(Program.HttpC.DownloadStringList( 432 | "https://raw.githubusercontent.com/scrim-dev/AvatarLockpick/refs/heads/master/lock_types.txt" 433 | )); 434 | AppLog.Success("DB", "Loaded locks"); 435 | } 436 | catch (Exception ex) 437 | { 438 | AppLog.Error("DB", $"{ex.Message}"); 439 | } 440 | } 441 | 442 | private static void UnlockDB(string UID, string AID) 443 | { 444 | AppLog.Warn("UnlockWithDatabaseScan", "Preparing..."); 445 | LoadLockTypes(); 446 | AppLog.Log("Locks", "All possible lock types:"); 447 | foreach (var lockType in AllLockTypes) 448 | { 449 | Console.WriteLine(lockType); 450 | } 451 | 452 | AppLog.Log("UnlockWithDatabaseScan", "Starting unlock process..."); 453 | 454 | try 455 | { 456 | string avatarPath = GetVRChatAvatarPath(UID); 457 | string fullAvatarPath = Path.Combine(avatarPath, AID); 458 | 459 | AppLog.Log("Path", $"Looking for avatar at path: {fullAvatarPath}"); 460 | AppLog.Log("Path", $"Directory exists: {Directory.Exists(avatarPath)}"); 461 | 462 | if (Directory.Exists(avatarPath)) 463 | { 464 | AppLog.Log("Path", "Files in directory:"); 465 | foreach (string file in Directory.GetFiles(avatarPath)) 466 | { 467 | AppLog.Log("Path", $"- {Path.GetFileName(file)}"); 468 | } 469 | } 470 | 471 | if (!System.IO.File.Exists(fullAvatarPath)) 472 | { 473 | AppLog.Error("File", $"Avatar file not found: {AID}"); 474 | } 475 | 476 | string jsonContent = System.IO.File.ReadAllText(fullAvatarPath); 477 | AppLog.Success("Path", "Successfully read file content"); 478 | AppLog.Log("JSON", $"Raw JSON content: {jsonContent}"); 479 | 480 | bool wasUnlocked = false; 481 | 482 | try 483 | { 484 | // First try to parse as a single object 485 | JObject jsonObj = JObject.Parse(jsonContent); 486 | AppLog.Success("JSON", "Successfully parsed JSON as object"); 487 | 488 | // Get the animationParameters array 489 | var animParams = jsonObj["animationParameters"] as JArray; 490 | if (animParams != null) 491 | { 492 | AppLog.Success("JSON", "Found animationParameters array"); 493 | foreach (JObject param in animParams) 494 | { 495 | var nameProperty = param["name"]?.ToString(); 496 | if (string.IsNullOrEmpty(nameProperty)) continue; 497 | 498 | // Remove ALL Unicode characters 499 | string normalizedName = Regex.Replace(nameProperty, @"[^\u0000-\u007F]+", "", RegexOptions.None); 500 | normalizedName = normalizedName.Trim(); // Also trim any remaining whitespace 501 | 502 | AppLog.Log("JSON", $"Checking parameter: {nameProperty} (normalized: {normalizedName})"); 503 | 504 | foreach (var lockType in AllLockTypes) 505 | { 506 | if (normalizedName.Equals(lockType, StringComparison.OrdinalIgnoreCase)) 507 | { 508 | AppLog.Log("JSON", $"Found locked parameter with name: {lockType}"); 509 | var valueToken = param["value"]; 510 | 511 | if (valueToken != null) 512 | { 513 | AppLog.Log("JSON", $"Current value: {valueToken}"); 514 | if (valueToken.Type == JTokenType.Integer && valueToken.Value() == 1) 515 | { 516 | param["value"] = new JValue(0); 517 | wasUnlocked = true; 518 | AppLog.Success("JSON", "Changed value to 0"); 519 | } 520 | else 521 | { 522 | param["value"] = new JValue(1); 523 | wasUnlocked = true; 524 | AppLog.Success("JSON", "Changed value to 1"); 525 | } 526 | MessageBoxUtils.ShowInfo("Avatar should be unlocked. If not try again or contact me for support.", "Nice!"); 527 | } 528 | } 529 | } 530 | } 531 | } 532 | else 533 | { 534 | AppLog.Warn("JSON", "No animationParameters array found in JSON"); 535 | } 536 | 537 | if (wasUnlocked) 538 | { 539 | AppLog.Warn("Avatar", "Writing changes back to file..."); 540 | System.IO.File.WriteAllText(fullAvatarPath, jsonObj.ToString(Newtonsoft.Json.Formatting.None)); 541 | AppLog.Success("Avatar", "Successfully saved changes"); 542 | //return true; 543 | } 544 | } 545 | catch (JsonReaderException) 546 | { 547 | // If it's not a single object, try parsing as array 548 | try 549 | { 550 | JArray jsonArray = JArray.Parse(jsonContent); 551 | AppLog.Success("JSON", "Successfully parsed JSON as array"); 552 | 553 | foreach (JObject item in jsonArray.Children()) 554 | { 555 | var nameProperty = item["name"]?.ToString(); 556 | if (string.IsNullOrEmpty(nameProperty)) continue; 557 | 558 | string normalizedName = Regex.Replace(nameProperty, @"[^\u0000-\u007F]+", "", RegexOptions.None); 559 | foreach (var lockType in AllLockTypes) 560 | { 561 | if (normalizedName.Equals(lockType, StringComparison.OrdinalIgnoreCase)) 562 | { 563 | AppLog.Log("JSON", $"Found locked property with name: {nameProperty}"); 564 | var valueToken = item["value"]; 565 | 566 | if (valueToken != null) 567 | { 568 | AppLog.Log("JSON", $"Current value: {valueToken}"); 569 | 570 | if (valueToken.Type == JTokenType.Integer && valueToken.Value() != 0) 571 | { 572 | item["value"] = new JValue(0); 573 | wasUnlocked = true; 574 | AppLog.Success("JSON", "Changed integer value to 0"); 575 | } 576 | else if (valueToken.Type == JTokenType.Boolean && valueToken.Value() == true) 577 | { 578 | item["value"] = new JValue(false); 579 | wasUnlocked = true; 580 | AppLog.Success("JSON", "Changed boolean value to false"); 581 | } 582 | else if (valueToken.Type == JTokenType.String) 583 | { 584 | string? strValue = valueToken.Value(); 585 | if (!string.IsNullOrEmpty(strValue) && 586 | (strValue.Equals("1") || strValue.Equals("true", StringComparison.OrdinalIgnoreCase))) 587 | { 588 | item["value"] = new JValue("0"); 589 | wasUnlocked = true; 590 | AppLog.Success("JSON", "Changed string value to '0'"); 591 | } 592 | } 593 | } 594 | } 595 | } 596 | } 597 | 598 | if (wasUnlocked) 599 | { 600 | AppLog.Warn("JSON", "Writing changes back to file..."); 601 | System.IO.File.WriteAllText(fullAvatarPath, jsonArray.ToString(Newtonsoft.Json.Formatting.None)); 602 | AppLog.Success("JSON", "Successfully saved changes"); 603 | //return true; 604 | } 605 | } 606 | catch (Exception ex) 607 | { 608 | AppLog.Error("JSON", $"Error parsing JSON as array: {ex.Message}"); 609 | throw; 610 | } 611 | } 612 | 613 | if (wasUnlocked) 614 | { 615 | AppLog.Warn("JSON", "No locked properties found or all properties were already unlocked"); 616 | //return false; 617 | } 618 | else 619 | { 620 | AppLog.Warn("JSON", "No locked properties found or all properties were already unlocked"); 621 | //return false; 622 | } 623 | } 624 | catch (Exception ex) 625 | { 626 | AppLog.Error("ERR", $"Error: {ex.Message}"); 627 | AppLog.Error("ST", $"Stack trace: {ex.StackTrace}"); 628 | } 629 | } 630 | } 631 | } -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/ConsoleSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection.Metadata; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace AvatarLockpick.Revised.Utils 10 | { 11 | internal class ConsoleSetup 12 | { 13 | [DllImport("user32.dll")] 14 | static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); 15 | 16 | [DllImport("user32.dll")] 17 | static extern int GetWindowLong(IntPtr hWnd, int nIndex); 18 | 19 | [DllImport("user32.dll", SetLastError = true)] 20 | static extern IntPtr FindWindow(string lpClassName, string? lpWindowName); 21 | 22 | [DllImport("user32.dll")] 23 | private static extern int DeleteMenu(IntPtr hMenu, int nPosition, int wFlags); 24 | 25 | [DllImport("user32.dll")] 26 | private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); 27 | 28 | [DllImport("kernel32.dll", ExactSpelling = true)] 29 | private static extern IntPtr GetConsoleWindow(); 30 | 31 | [DllImport("user32.dll")] 32 | static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 33 | 34 | [DllImport("user32.dll")] 35 | [return: MarshalAs(UnmanagedType.Bool)] 36 | static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); 37 | 38 | [DllImport("Kernel32")] 39 | private static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate handler, bool add); 40 | 41 | private delegate bool ConsoleEventDelegate(CtrlType sig); 42 | 43 | private enum CtrlType 44 | { 45 | CTRL_C_EVENT = 0, 46 | CTRL_BREAK_EVENT = 1, 47 | CTRL_CLOSE_EVENT = 2, 48 | CTRL_LOGOFF_EVENT = 5, 49 | CTRL_SHUTDOWN_EVENT = 6 50 | } 51 | 52 | public static event Action OnExit; 53 | 54 | const int SW_HIDE = 0; 55 | const int GWL_EXSTYLE = -20; 56 | const int WS_EX_TOOLWINDOW = 0x00000080; 57 | const uint SWP_NOMOVE = 0x0002; 58 | const uint SWP_NOSIZE = 0x0001; 59 | const uint SWP_NOZORDER = 0x0004; 60 | const uint SWP_FRAMECHANGED = 0x0020; 61 | private const int MF_BYCOMMAND = 0x00000000; 62 | private const int SC_MAXIMIZE = 0xF030; 63 | const int WS_EX_APPWINDOW = 0x00040000; 64 | const int HWND_BOTTOM = 1; 65 | 66 | public static void Init() 67 | { 68 | SetConsoleCtrlHandler(Handler, true); 69 | Console.TreatControlCAsInput = true; 70 | IntPtr handle = GetConsoleWindow(); 71 | IntPtr sysMenu = GetSystemMenu(handle, false); 72 | 73 | if (handle != IntPtr.Zero) 74 | { 75 | DeleteMenu(sysMenu, SC_MAXIMIZE, MF_BYCOMMAND); 76 | } 77 | 78 | if (!File.Exists($"{AppFolders.DataLowFolder}\\hideconsole.txt")) 79 | { 80 | try { File.WriteAllText($"{AppFolders.DataLowFolder}\\hideconsole.txt", "false"); } catch { } 81 | } 82 | 83 | try 84 | { 85 | if (File.ReadAllText($"{AppFolders.DataLowFolder}\\hideconsole.txt") == "true") 86 | { 87 | if (handle != IntPtr.Zero) 88 | { 89 | // Hide lol 90 | ShowWindow(handle, SW_HIDE); 91 | 92 | SetWindowLong(handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW); 93 | 94 | SetWindowPos(handle, IntPtr.Zero, 0, 0, 0, 0, 95 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); 96 | } 97 | 98 | //For newer windows terminal 99 | IntPtr hWnd = GetConsoleWindow(); 100 | if (hWnd != IntPtr.Zero) 101 | { 102 | ShowWindow(hWnd, SW_HIDE); 103 | 104 | int style = GetWindowLong(hWnd, GWL_EXSTYLE); 105 | SetWindowLong(hWnd, GWL_EXSTYLE, 106 | (style | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW); 107 | 108 | SetWindowPos(hWnd, (IntPtr)HWND_BOTTOM, 109 | 0, 0, 0, 0, 110 | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); 111 | 112 | IntPtr terminalHandle = FindWindow("CASCADIA_HOSTING_WINDOW_CLASS", null); 113 | if (terminalHandle != IntPtr.Zero) 114 | { 115 | int termStyle = GetWindowLong(terminalHandle, GWL_EXSTYLE); 116 | SetWindowLong(terminalHandle, GWL_EXSTYLE, 117 | (termStyle | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW); 118 | ShowWindow(terminalHandle, SW_HIDE); 119 | } 120 | } 121 | } 122 | } 123 | catch { } 124 | } 125 | 126 | private static bool Handler(CtrlType sig) 127 | { 128 | AppLog.Warn("Exit", "App closing..."); 129 | OnExit?.Invoke(); 130 | return false; 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/GUIcom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using System.Threading.Tasks; 8 | using System.Diagnostics; 9 | 10 | namespace AvatarLockpick.Revised.Utils 11 | { 12 | internal class GUIcom 13 | { 14 | public static void Communication(string s) 15 | { 16 | try 17 | { 18 | //Json Tokens 19 | JObject jsonData = JObject.Parse(s); 20 | JToken actionToken = jsonData["action"]; 21 | //JToken AviInfoActionToken = jsonData["avatarInfo"]; 22 | JToken AviIDToken = jsonData["avatarId"]; 23 | JToken UserIDToken = jsonData["userId"]; 24 | //JToken HelpUrlToken = jsonData["openHelpUrl"]; 25 | JToken TypeToken = jsonData["type"]; 26 | 27 | if ((string)jsonData["type"] == "avatarInfo") 28 | { 29 | var avatarInfo = new 30 | { 31 | AvatarId = (string)jsonData["avatarId"], 32 | UserId = (string)jsonData["userId"] 33 | }; 34 | 35 | URLStuff.OpenUrl($"https://vrchat.com/home/avatar/{avatarInfo.AvatarId}"); 36 | //URLStuff.OpenUrl($"https://vrchat.com/home/user/{avatarInfo.UserId}"); 37 | } 38 | 39 | if ((string)jsonData["type"] == "openHelpUrl") 40 | { 41 | URLStuff.OpenUrl($"https://github.com/scrim-dev/AvatarLockpick/blob/master/HELP.md"); 42 | } 43 | 44 | if ((string)jsonData["type"] == "restart-novr") 45 | { 46 | Task.Run(() => 47 | { 48 | VRC.CloseVRChat(); 49 | Task.Delay(1000); //Just in case 50 | VRC.LaunchVRChat(false); 51 | }); 52 | } 53 | 54 | if ((string)jsonData["type"] == "restart-vr") 55 | { 56 | Task.Run(() => 57 | { 58 | VRC.CloseVRChat(); 59 | Task.Delay(1000); //Just in case 60 | VRC.LaunchVRChat(true); 61 | }); 62 | } 63 | 64 | if (actionToken != null && actionToken.Type == JTokenType.String) 65 | { 66 | string action = actionToken.ToString(); 67 | switch (action) 68 | { 69 | case "Unlock": 70 | MessageBoxUtils.Show("Press OK to unlock the avatar.", "Unlock Avatar", 0x00000000U); 71 | AvatarUnlocker.Start(1, UserIDToken.ToString(), AviIDToken.ToString()); 72 | break; 73 | case "Unlock All": 74 | MessageBoxUtils.Show("Press OK to unlock all avatars", "Unlock All Avatars", 0x00000000U); 75 | AvatarUnlocker.Start(2, UserIDToken.ToString(), AviIDToken.ToString()); 76 | break; 77 | case "Unlock (VRCFury)": 78 | MessageBoxUtils.Show("Press OK to unlock VRCFury locked avatar", "Unlock VRCFury", 0x00000000U); 79 | AvatarUnlocker.Start(3, UserIDToken.ToString(), AviIDToken.ToString()); 80 | break; 81 | case "Unlock using Database": 82 | MessageBoxUtils.Show("Press OK to try unlocking the lock using the database", "Unlock via DB", 0x00000000U); 83 | AvatarUnlocker.Start(4, UserIDToken.ToString(), AviIDToken.ToString()); 84 | break; 85 | default: 86 | // Handle unknown action 87 | MessageBoxUtils.ShowInfo($"Unknown action: {action}"); 88 | break; 89 | } 90 | } 91 | } 92 | catch (JsonReaderException ex) 93 | { 94 | // Handle invalid JSON format 95 | MessageBoxUtils.ShowError($"Error parsing JSON with Newtonsoft.Json: {ex.Message}"); 96 | } 97 | catch (Exception ex) 98 | { 99 | // Handle other potential errors 100 | MessageBoxUtils.ShowError($"An unexpected error occurred: {ex.Message}"); 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/HttpUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace AvatarLockpick.Revised.Utils 9 | { 10 | internal class URLStuff 11 | { 12 | public static void OpenUrl(string url) 13 | { 14 | try 15 | { 16 | if (!url.StartsWith("http")) 17 | url = "https://" + url; 18 | 19 | Process.Start(new ProcessStartInfo 20 | { 21 | FileName = url, 22 | UseShellExecute = true 23 | }); 24 | } 25 | catch 26 | { 27 | // Fallback methods 28 | if (OperatingSystem.IsWindows()) 29 | Process.Start("explorer.exe", url); 30 | else if (OperatingSystem.IsLinux()) 31 | Process.Start("xdg-open", url); 32 | else if (OperatingSystem.IsMacOS()) 33 | Process.Start("open", url); 34 | } 35 | } 36 | } 37 | 38 | internal class HttpUtils : IDisposable 39 | { 40 | private HttpClient? _httpClient; 41 | private bool _disposed; 42 | 43 | public void Load(string? userAgent = null) 44 | { 45 | _httpClient = new HttpClient(); 46 | _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd( 47 | userAgent ?? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" 48 | ); 49 | } 50 | 51 | public async Task DownloadStringAsync(string url) 52 | { 53 | if (_httpClient == null) { return null; } 54 | 55 | return await _httpClient.GetStringAsync(url).ConfigureAwait(false); 56 | } 57 | 58 | public string? DownloadString(string url) 59 | { 60 | if (_httpClient == null) { return null; } 61 | 62 | return _httpClient.GetStringAsync(url).GetAwaiter().GetResult(); 63 | } 64 | 65 | public List DownloadStringList(string url) 66 | { 67 | string content = DownloadString(url); 68 | return [.. content.Split( 69 | ["\r\n", "\r", "\n"], 70 | StringSplitOptions.RemoveEmptyEntries 71 | )]; 72 | } 73 | 74 | public void Dispose() 75 | { 76 | if (!_disposed) 77 | { 78 | _httpClient?.Dispose(); 79 | _disposed = true; 80 | } 81 | GC.SuppressFinalize(this); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/MessageBoxUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; // Added for DllImport 2 | 3 | namespace AvatarLockpick.Revised.Utils 4 | { 5 | public static class MessageBoxUtils 6 | { 7 | // Import the user32.dll MessageBox function 8 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 9 | private static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType); 10 | 11 | // Buttons 12 | private const uint MB_OK = 0x00000000U; 13 | private const uint MB_OKCANCEL = 0x00000001U; 14 | private const uint MB_YESNOCANCEL = 0x00000003U; 15 | private const uint MB_YESNO = 0x00000004U; 16 | 17 | // Icons 18 | private const uint MB_ICONERROR = 0x00000010U; 19 | private const uint MB_ICONQUESTION = 0x00000020U; 20 | private const uint MB_ICONWARNING = 0x00000030U; 21 | private const uint MB_ICONINFORMATION = 0x00000040U; 22 | 23 | private const int IDOK = 1; 24 | private const int IDCANCEL = 2; 25 | private const int IDYES = 6; 26 | private const int IDNO = 7; 27 | 28 | /// 29 | /// Displays a native Windows message box. 30 | /// 31 | /// The text to display. 32 | /// The title bar text. 33 | /// Combined WinAPI flags for buttons and icon (e.g., MB_OK | MB_ICONINFORMATION). 34 | /// Result code from the WinAPI call (e.g., IDOK, IDCANCEL). 35 | public static int Show(string text, string caption, uint buttonsAndIcon) 36 | { 37 | // Pass IntPtr.Zero for the owner window handle (no owner) 38 | return MessageBox(IntPtr.Zero, text, caption, buttonsAndIcon); 39 | } 40 | 41 | /// 42 | /// Displays a warning message box. 43 | /// 44 | /// The warning message text. 45 | /// The title bar text. 46 | public static void ShowWarning(string text, string caption = "Warning") 47 | { 48 | Show(text, caption, MB_OK | MB_ICONWARNING); 49 | } 50 | 51 | /// 52 | /// Displays an error message box. 53 | /// 54 | /// The error message text. 55 | /// The title bar text. 56 | public static void ShowError(string text, string caption = "Error") 57 | { 58 | Show(text, caption, MB_OK | MB_ICONERROR); 59 | } 60 | 61 | /// 62 | /// Displays an information message box. 63 | /// 64 | /// The information message text. 65 | /// The title bar text. 66 | public static void ShowInfo(string text, string caption = "Information") 67 | { 68 | Show(text, caption, MB_OK | MB_ICONINFORMATION); 69 | } 70 | 71 | /// 72 | /// Displays a question message box WITH delegates. 73 | /// 74 | /// The information message text. 75 | /// The title bar text. 76 | public static void ShowQuestion(string text, string caption, 77 | Action onYes, Action ?onNo = null, 78 | bool defaultNo = false) 79 | { 80 | uint flags = MB_YESNO | MB_ICONQUESTION; 81 | 82 | if (defaultNo) 83 | { 84 | flags |= 0x00000100U; 85 | } 86 | 87 | int result = Show(text, caption, flags); 88 | 89 | if (result == IDYES) 90 | { 91 | onYes?.Invoke(); 92 | } 93 | else if (result == IDNO) 94 | { 95 | onNo?.Invoke(); 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/StringUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AvatarLockpick.Revised.Utils 8 | { 9 | internal class StringUtils 10 | { 11 | public static string Repeat(string text, int count) 12 | { 13 | return string.Concat(Enumerable.Repeat(text, count)); 14 | } 15 | 16 | public static string GenerateRandomString(int length) 17 | { 18 | const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 19 | StringBuilder result = new StringBuilder(length); 20 | Random random = new Random(); 21 | 22 | for (int i = 0; i < length; i++) 23 | { 24 | result.Append(chars[random.Next(chars.Length)]); 25 | } 26 | 27 | return result.ToString(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/UpdateCheck.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Reflection; 4 | using System.Diagnostics; 5 | using System.Diagnostics.CodeAnalysis; 6 | 7 | namespace AvatarLockpick.Revised.Utils 8 | { 9 | public static class VersionChecker 10 | { 11 | private static readonly HttpClient _httpClient = new(); 12 | 13 | // GitHub raw content URL for version file 14 | private const string VersionFileUrl = 15 | "https://raw.githubusercontent.com/scrim-dev/AvatarLockpick/refs/heads/master/ver.txt"; 16 | 17 | public static void CheckForUpdates() 18 | { 19 | try 20 | { 21 | var result = CompareVersions(); 22 | 23 | switch (result.Status) 24 | { 25 | case VersionStatus.NewVersionAvailable: 26 | AppLog.Warn("UPDCheck", $"UPDATE AVAILABLE: {result.CurrentVersion} → {result.RemoteVersion}"); 27 | MessageBoxUtils.ShowQuestion($"A new update is available!\n\nAvatarLockpick: {result.CurrentVersion} → " + 28 | $"AvatarLockpick: {result.RemoteVersion}\n\nDo you want to Update?", "Update", delegate 29 | { 30 | //All zips will be called LockpickApp.zip now 31 | URLStuff.OpenUrl($"https://github.com/scrim-dev/AvatarLockpick/releases/download/{result.RemoteVersion}/LockpickApp.zip"); 32 | }); 33 | 34 | break; 35 | 36 | case VersionStatus.UpToDate: 37 | AppLog.Log("UPDCheck", "You have the latest version"); 38 | break; 39 | 40 | case VersionStatus.LocalIsNewer: 41 | AppLog.Warn("UPDCheck", "You're running a newer version than published"); 42 | break; 43 | } 44 | } 45 | catch (Exception ex) 46 | { 47 | Console.WriteLine($"Version check failed: {ex.Message}"); 48 | } 49 | } 50 | 51 | public static VersionComparisonResult CompareVersions() 52 | { 53 | var currentVersion = GetCurrentVersion(); 54 | var remoteVersionText = FetchRemoteVersion(); 55 | 56 | if (string.IsNullOrWhiteSpace(remoteVersionText)) 57 | { 58 | return new VersionComparisonResult(VersionStatus.FailedToCheck, currentVersion); 59 | } 60 | 61 | if (!Version.TryParse(remoteVersionText.Trim(), out var remoteVersion)) 62 | { 63 | return new VersionComparisonResult( 64 | VersionStatus.InvalidRemoteVersion, 65 | currentVersion, 66 | remoteVersionStr: remoteVersionText 67 | ); 68 | } 69 | 70 | var status = remoteVersion > currentVersion ? VersionStatus.NewVersionAvailable 71 | : remoteVersion < currentVersion ? VersionStatus.LocalIsNewer 72 | : VersionStatus.UpToDate; 73 | 74 | return new VersionComparisonResult( 75 | status, 76 | currentVersion, 77 | remoteVersion 78 | ); 79 | } 80 | 81 | public static Version ?GetCurrentVersion() 82 | { 83 | // Get version from Program.AppVersion (assuming it's a string property) 84 | if (!string.IsNullOrWhiteSpace(Program.AppVersion) && 85 | Version.TryParse(Program.AppVersion, out var version)) 86 | { 87 | return version; 88 | } 89 | 90 | /*// Fallback to assembly version 91 | var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); 92 | var versionInfo = FileVersionInfo.GetVersionInfo(assembly.Location); 93 | return new Version(versionInfo.FileVersion ?? "1.0.0.0");*/ 94 | return null; 95 | } 96 | 97 | private static string FetchRemoteVersion() 98 | { 99 | try 100 | { 101 | // Add headers to prevent GitHub caching 102 | _httpClient.DefaultRequestHeaders.CacheControl = new() 103 | { 104 | NoCache = true, 105 | NoStore = true 106 | }; 107 | 108 | // Using synchronous GetString instead of async 109 | return _httpClient.GetStringAsync(VersionFileUrl).GetAwaiter().GetResult(); 110 | } 111 | catch (HttpRequestException ex) 112 | { 113 | Console.WriteLine($"Failed to fetch version: {ex.Message}"); 114 | return null; 115 | } 116 | } 117 | } 118 | 119 | public class VersionComparisonResult 120 | { 121 | public VersionStatus Status { get; } 122 | public Version CurrentVersion { get; } 123 | public Version RemoteVersion { get; } 124 | public string RemoteVersionStr { get; } 125 | 126 | public VersionComparisonResult(VersionStatus status, Version currentVersion) 127 | { 128 | Status = status; 129 | CurrentVersion = currentVersion; 130 | } 131 | 132 | public VersionComparisonResult(VersionStatus status, Version currentVersion, Version remoteVersion) 133 | : this(status, currentVersion) 134 | { 135 | RemoteVersion = remoteVersion; 136 | } 137 | 138 | public VersionComparisonResult(VersionStatus status, Version currentVersion, string remoteVersionStr) 139 | : this(status, currentVersion) 140 | { 141 | RemoteVersionStr = remoteVersionStr; 142 | } 143 | } 144 | 145 | public enum VersionStatus 146 | { 147 | UpToDate, 148 | NewVersionAvailable, 149 | LocalIsNewer, 150 | FailedToCheck, 151 | InvalidRemoteVersion 152 | } 153 | } -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/VRC.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace AvatarLockpick.Revised.Utils 9 | { 10 | internal class VRC 11 | { 12 | private const string VRCHAT_PROCESS_NAME = "VRChat"; 13 | private const string STEAM_PROTOCOL = "steam://rungameid/438100"; 14 | 15 | /// 16 | /// Gets the VRChat process if it's running, otherwise returns null 17 | /// 18 | public static Process? GetVRChatProcess() 19 | { 20 | return Process.GetProcessesByName(VRCHAT_PROCESS_NAME).FirstOrDefault(); 21 | } 22 | 23 | /// 24 | /// Checks if VRChat is currently running 25 | /// 26 | public static bool IsVRChatRunning() 27 | { 28 | return GetVRChatProcess() != null; 29 | } 30 | 31 | /// 32 | /// Checks if VRChat process is responding 33 | /// 34 | public static bool IsVRChatResponding() 35 | { 36 | var process = GetVRChatProcess(); 37 | return process?.Responding ?? false; 38 | } 39 | 40 | /// 41 | /// Launches VRChat through Steam with VR toggle 42 | /// 43 | public static void LaunchVRChat(bool vr) 44 | { 45 | if (vr) 46 | { 47 | Process.Start(new ProcessStartInfo 48 | { 49 | FileName = STEAM_PROTOCOL, 50 | UseShellExecute = true 51 | }); 52 | } 53 | else 54 | { 55 | Process.Start(new ProcessStartInfo 56 | { 57 | FileName = STEAM_PROTOCOL + " --no-vr", 58 | UseShellExecute = true 59 | }); 60 | } 61 | } 62 | 63 | /// 64 | /// Gets the current status of VRChat process 65 | /// 66 | public static string GetVRChatStatus() 67 | { 68 | var process = GetVRChatProcess(); 69 | if (process == null) 70 | return "Not Running"; 71 | 72 | return process.Responding ? "Running" : "Not Responding"; 73 | } 74 | 75 | /// 76 | /// Beams VRChat 77 | /// 78 | public static bool CloseVRChat() 79 | { 80 | var process = GetVRChatProcess(); 81 | process?.Kill(); 82 | return false; 83 | } 84 | 85 | /// 86 | /// Forcefully kills the VRChat process 87 | /// 88 | /// True if the process was killed successfully, false if it wasn't running 89 | public static bool KillVRChat() 90 | { 91 | var process = GetVRChatProcess(); 92 | if (process != null) 93 | { 94 | process.Kill(); 95 | process.WaitForExit(5000); 96 | return true; 97 | } 98 | return false; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /AvatarLockpick.Revised/Utils/WindowUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace AvatarLockpick.Revised.Utils 9 | { 10 | internal class WindowUtils 11 | { 12 | [DllImport("kernel32.dll", ExactSpelling = true)] 13 | private static extern IntPtr GetConsoleWindow(); 14 | 15 | [DllImport("user32.dll")] 16 | [return: MarshalAs(UnmanagedType.Bool)] 17 | private static extern bool IsWindowVisible(IntPtr hWnd); 18 | 19 | [DllImport("user32.dll")] 20 | [return: MarshalAs(UnmanagedType.Bool)] 21 | private static extern bool IsIconic(IntPtr hWnd); 22 | 23 | [DllImport("user32.dll")] 24 | private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 25 | 26 | [DllImport("user32.dll")] 27 | [return: MarshalAs(UnmanagedType.Bool)] 28 | private static extern bool SetForegroundWindow(IntPtr hWnd); 29 | 30 | private const int SW_RESTORE = 9; 31 | 32 | public static void BringConsoleToFrontIfVisible() 33 | { 34 | IntPtr consoleHandle = GetConsoleWindow(); 35 | if (consoleHandle != IntPtr.Zero) 36 | { 37 | if (IsWindowVisible(consoleHandle)) 38 | { 39 | if (IsIconic(consoleHandle)) 40 | { 41 | ShowWindow(consoleHandle, SW_RESTORE); 42 | } 43 | SetForegroundWindow(consoleHandle); 44 | } 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /AvatarLockpick.Revised/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 53 | 61 | 62 | 63 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /AvatarLockpick.Revised/unlockicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrim-dev/AvatarLockpick/87a178d5ad3e43e87c31c8e3398256ab1fbf589c/AvatarLockpick.Revised/unlockicon.ico -------------------------------------------------------------------------------- /AvatarLockpick.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.13.35617.110 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarLockpick", "AvatarLockpick\AvatarLockpick.csproj", "{AF545187-5E26-474C-97C1-25E95799D44C}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarLockpick.Revised", "AvatarLockpick.Revised\AvatarLockpick.Revised.csproj", "{8D8AF4BF-0400-47AC-9E7D-003DD12C6816}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {AF545187-5E26-474C-97C1-25E95799D44C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {AF545187-5E26-474C-97C1-25E95799D44C}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {AF545187-5E26-474C-97C1-25E95799D44C}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {AF545187-5E26-474C-97C1-25E95799D44C}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {8D8AF4BF-0400-47AC-9E7D-003DD12C6816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {8D8AF4BF-0400-47AC-9E7D-003DD12C6816}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {8D8AF4BF-0400-47AC-9E7D-003DD12C6816}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {8D8AF4BF-0400-47AC-9E7D-003DD12C6816}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {28B53902-4C19-4F1B-BE99-65A06481063D} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /AvatarLockpick/AvatarLockpick.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net9.0-windows10.0.26100.0 6 | enable 7 | true 8 | enable 9 | app.manifest 10 | True 11 | Scrimmane 12 | Scrimmane 13 | Scrimmane 14 | A tool for unlocking avatars 15 | 0.0.0.0 16 | 0.0.0.0 17 | unlock_icon.ico 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /AvatarLockpick/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace AvatarLockpick 2 | { 3 | partial class MainForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); 32 | AvatarMainPanel = new ReaLTaiizor.Controls.LostBorderPanel(); 33 | VERSIONTEXT = new Label(); 34 | HelpBtn = new ReaLTaiizor.Controls.LostButton(); 35 | VersionLabel = new Label(); 36 | HideWindowButton = new ReaLTaiizor.Controls.LostButton(); 37 | AppendConsoleButton = new ReaLTaiizor.Controls.LostButton(); 38 | RestartButton = new ReaLTaiizor.Controls.LostButton(); 39 | ClearSavesButton = new ReaLTaiizor.Controls.LostButton(); 40 | DeleteSavesButton = new ReaLTaiizor.Controls.LostButton(); 41 | RestartWithVRCheckBox = new ReaLTaiizor.Controls.HopeCheckBox(); 42 | HideUserIDCheckBox = new ReaLTaiizor.Controls.HopeCheckBox(); 43 | lostBorderPanel2 = new ReaLTaiizor.Controls.LostBorderPanel(); 44 | AvatarIDLabel = new Label(); 45 | OpenAvatarButton = new ReaLTaiizor.Controls.LostButton(); 46 | AvatarIDTextBox = new ReaLTaiizor.Controls.HopeTextBox(); 47 | UserIDLabel = new Label(); 48 | UserIDTextBox = new ReaLTaiizor.Controls.HopeTextBox(); 49 | UnlockButton = new ReaLTaiizor.Controls.LostButton(); 50 | UnlockALLButton = new ReaLTaiizor.Controls.LostButton(); 51 | ResetButton = new ReaLTaiizor.Controls.LostButton(); 52 | UnlockVRCFuryButton = new ReaLTaiizor.Controls.LostButton(); 53 | AutoRestartCheckBox = new ReaLTaiizor.Controls.HopeCheckBox(); 54 | lostBorderPanel1 = new ReaLTaiizor.Controls.LostBorderPanel(); 55 | DiscordBtn = new ReaLTaiizor.Controls.LostButton(); 56 | WebsiteBtn = new ReaLTaiizor.Controls.LostButton(); 57 | GithubBtn = new ReaLTaiizor.Controls.LostButton(); 58 | AvatarMainPanel.SuspendLayout(); 59 | lostBorderPanel2.SuspendLayout(); 60 | lostBorderPanel1.SuspendLayout(); 61 | SuspendLayout(); 62 | // 63 | // AvatarMainPanel 64 | // 65 | AvatarMainPanel.BackColor = Color.FromArgb(44, 44, 44); 66 | AvatarMainPanel.BorderColor = Color.FromArgb(157, 59, 255); 67 | AvatarMainPanel.Controls.Add(VERSIONTEXT); 68 | AvatarMainPanel.Controls.Add(HelpBtn); 69 | AvatarMainPanel.Controls.Add(VersionLabel); 70 | AvatarMainPanel.Controls.Add(HideWindowButton); 71 | AvatarMainPanel.Controls.Add(AppendConsoleButton); 72 | AvatarMainPanel.Controls.Add(RestartButton); 73 | AvatarMainPanel.Controls.Add(ClearSavesButton); 74 | AvatarMainPanel.Controls.Add(DeleteSavesButton); 75 | AvatarMainPanel.Controls.Add(RestartWithVRCheckBox); 76 | AvatarMainPanel.Controls.Add(HideUserIDCheckBox); 77 | AvatarMainPanel.Controls.Add(lostBorderPanel2); 78 | AvatarMainPanel.Controls.Add(AutoRestartCheckBox); 79 | AvatarMainPanel.Font = new Font("Segoe UI", 12F); 80 | AvatarMainPanel.ForeColor = Color.White; 81 | AvatarMainPanel.Location = new Point(12, 12); 82 | AvatarMainPanel.Name = "AvatarMainPanel"; 83 | AvatarMainPanel.Padding = new Padding(5); 84 | AvatarMainPanel.ShowText = false; 85 | AvatarMainPanel.Size = new Size(629, 537); 86 | AvatarMainPanel.TabIndex = 0; 87 | AvatarMainPanel.Text = "lostBorderPanel1"; 88 | // 89 | // VERSIONTEXT 90 | // 91 | VERSIONTEXT.AutoSize = true; 92 | VERSIONTEXT.ForeColor = Color.FromArgb(157, 59, 255); 93 | VERSIONTEXT.Location = new Point(25, 513); 94 | VERSIONTEXT.Name = "VERSIONTEXT"; 95 | VERSIONTEXT.Size = new Size(49, 21); 96 | VERSIONTEXT.TabIndex = 14; 97 | VERSIONTEXT.Text = "NULL"; 98 | // 99 | // HelpBtn 100 | // 101 | HelpBtn.BackColor = Color.FromArgb(30, 30, 30); 102 | HelpBtn.Font = new Font("Segoe UI", 9F); 103 | HelpBtn.ForeColor = Color.White; 104 | HelpBtn.HoverColor = Color.FromArgb(157, 59, 255); 105 | HelpBtn.Image = null; 106 | HelpBtn.Location = new Point(561, 494); 107 | HelpBtn.Name = "HelpBtn"; 108 | HelpBtn.Size = new Size(65, 40); 109 | HelpBtn.TabIndex = 24; 110 | HelpBtn.Text = "HELP!"; 111 | HelpBtn.Click += HelpBtn_Click; 112 | // 113 | // VersionLabel 114 | // 115 | VersionLabel.AutoSize = true; 116 | VersionLabel.Location = new Point(3, 513); 117 | VersionLabel.Name = "VersionLabel"; 118 | VersionLabel.Size = new Size(23, 21); 119 | VersionLabel.TabIndex = 13; 120 | VersionLabel.Text = "V:"; 121 | // 122 | // HideWindowButton 123 | // 124 | HideWindowButton.BackColor = Color.FromArgb(30, 30, 30); 125 | HideWindowButton.Font = new Font("Segoe UI", 9F); 126 | HideWindowButton.ForeColor = Color.White; 127 | HideWindowButton.HoverColor = Color.Crimson; 128 | HideWindowButton.Image = null; 129 | HideWindowButton.Location = new Point(360, 369); 130 | HideWindowButton.Name = "HideWindowButton"; 131 | HideWindowButton.Size = new Size(120, 40); 132 | HideWindowButton.TabIndex = 21; 133 | HideWindowButton.Text = "HIDE"; 134 | HideWindowButton.Click += HideWindowButton_Click; 135 | // 136 | // AppendConsoleButton 137 | // 138 | AppendConsoleButton.BackColor = Color.FromArgb(30, 30, 30); 139 | AppendConsoleButton.Font = new Font("Segoe UI", 9F); 140 | AppendConsoleButton.ForeColor = Color.White; 141 | AppendConsoleButton.HoverColor = Color.FromArgb(157, 59, 255); 142 | AppendConsoleButton.Image = null; 143 | AppendConsoleButton.Location = new Point(13, 369); 144 | AppendConsoleButton.Name = "AppendConsoleButton"; 145 | AppendConsoleButton.Size = new Size(120, 40); 146 | AppendConsoleButton.TabIndex = 20; 147 | AppendConsoleButton.Text = "Show Console"; 148 | AppendConsoleButton.Click += AppendConsoleButton_Click; 149 | // 150 | // RestartButton 151 | // 152 | RestartButton.BackColor = Color.FromArgb(30, 30, 30); 153 | RestartButton.Font = new Font("Segoe UI", 9F); 154 | RestartButton.ForeColor = Color.White; 155 | RestartButton.HoverColor = Color.FromArgb(157, 59, 255); 156 | RestartButton.Image = null; 157 | RestartButton.Location = new Point(139, 369); 158 | RestartButton.Name = "RestartButton"; 159 | RestartButton.Size = new Size(215, 40); 160 | RestartButton.TabIndex = 18; 161 | RestartButton.Text = "Restart"; 162 | RestartButton.Click += RestartButton_Click; 163 | // 164 | // ClearSavesButton 165 | // 166 | ClearSavesButton.BackColor = Color.FromArgb(30, 30, 30); 167 | ClearSavesButton.Font = new Font("Segoe UI", 9F); 168 | ClearSavesButton.ForeColor = Color.White; 169 | ClearSavesButton.HoverColor = Color.FromArgb(157, 59, 255); 170 | ClearSavesButton.Image = null; 171 | ClearSavesButton.Location = new Point(486, 58); 172 | ClearSavesButton.Name = "ClearSavesButton"; 173 | ClearSavesButton.Size = new Size(135, 40); 174 | ClearSavesButton.TabIndex = 14; 175 | ClearSavesButton.Text = "Clear Saves"; 176 | ClearSavesButton.Click += ClearSavesButton_Click; 177 | // 178 | // DeleteSavesButton 179 | // 180 | DeleteSavesButton.BackColor = Color.FromArgb(30, 30, 30); 181 | DeleteSavesButton.Font = new Font("Segoe UI", 9F); 182 | DeleteSavesButton.ForeColor = Color.White; 183 | DeleteSavesButton.HoverColor = Color.FromArgb(157, 59, 255); 184 | DeleteSavesButton.Image = null; 185 | DeleteSavesButton.Location = new Point(486, 12); 186 | DeleteSavesButton.Name = "DeleteSavesButton"; 187 | DeleteSavesButton.Size = new Size(135, 40); 188 | DeleteSavesButton.TabIndex = 13; 189 | DeleteSavesButton.Text = "Delete Saves"; 190 | DeleteSavesButton.Click += DeleteSavesButton_Click; 191 | // 192 | // RestartWithVRCheckBox 193 | // 194 | RestartWithVRCheckBox.AutoSize = true; 195 | RestartWithVRCheckBox.BackColor = Color.FromArgb(44, 44, 44); 196 | RestartWithVRCheckBox.CheckedColor = Color.FromArgb(157, 59, 255); 197 | RestartWithVRCheckBox.DisabledColor = Color.FromArgb(196, 198, 202); 198 | RestartWithVRCheckBox.DisabledStringColor = Color.FromArgb(186, 187, 189); 199 | RestartWithVRCheckBox.Enable = true; 200 | RestartWithVRCheckBox.EnabledCheckedColor = Color.FromArgb(157, 59, 255); 201 | RestartWithVRCheckBox.EnabledStringColor = Color.FromArgb(153, 153, 153); 202 | RestartWithVRCheckBox.EnabledUncheckedColor = Color.FromArgb(156, 158, 161); 203 | RestartWithVRCheckBox.Font = new Font("Segoe UI", 12F); 204 | RestartWithVRCheckBox.ForeColor = Color.White; 205 | RestartWithVRCheckBox.Location = new Point(486, 104); 206 | RestartWithVRCheckBox.Name = "RestartWithVRCheckBox"; 207 | RestartWithVRCheckBox.Size = new Size(125, 20); 208 | RestartWithVRCheckBox.TabIndex = 12; 209 | RestartWithVRCheckBox.Text = "Restart in VR"; 210 | RestartWithVRCheckBox.UseVisualStyleBackColor = false; 211 | // 212 | // HideUserIDCheckBox 213 | // 214 | HideUserIDCheckBox.AutoSize = true; 215 | HideUserIDCheckBox.BackColor = Color.FromArgb(44, 44, 44); 216 | HideUserIDCheckBox.CheckedColor = Color.FromArgb(157, 59, 255); 217 | HideUserIDCheckBox.DisabledColor = Color.FromArgb(196, 198, 202); 218 | HideUserIDCheckBox.DisabledStringColor = Color.FromArgb(186, 187, 189); 219 | HideUserIDCheckBox.Enable = true; 220 | HideUserIDCheckBox.EnabledCheckedColor = Color.FromArgb(157, 59, 255); 221 | HideUserIDCheckBox.EnabledStringColor = Color.FromArgb(153, 153, 153); 222 | HideUserIDCheckBox.EnabledUncheckedColor = Color.FromArgb(156, 158, 161); 223 | HideUserIDCheckBox.Font = new Font("Segoe UI", 12F); 224 | HideUserIDCheckBox.ForeColor = Color.White; 225 | HideUserIDCheckBox.Location = new Point(486, 156); 226 | HideUserIDCheckBox.Name = "HideUserIDCheckBox"; 227 | HideUserIDCheckBox.Size = new Size(122, 20); 228 | HideUserIDCheckBox.TabIndex = 6; 229 | HideUserIDCheckBox.Text = "Hide User ID"; 230 | HideUserIDCheckBox.UseVisualStyleBackColor = false; 231 | HideUserIDCheckBox.CheckedChanged += HideUserIDCheckBox_CheckedChanged; 232 | // 233 | // lostBorderPanel2 234 | // 235 | lostBorderPanel2.BackColor = Color.FromArgb(34, 34, 34); 236 | lostBorderPanel2.BorderColor = Color.FromArgb(157, 59, 255); 237 | lostBorderPanel2.Controls.Add(AvatarIDLabel); 238 | lostBorderPanel2.Controls.Add(OpenAvatarButton); 239 | lostBorderPanel2.Controls.Add(AvatarIDTextBox); 240 | lostBorderPanel2.Controls.Add(UserIDLabel); 241 | lostBorderPanel2.Controls.Add(UserIDTextBox); 242 | lostBorderPanel2.Controls.Add(UnlockButton); 243 | lostBorderPanel2.Controls.Add(UnlockALLButton); 244 | lostBorderPanel2.Controls.Add(ResetButton); 245 | lostBorderPanel2.Controls.Add(UnlockVRCFuryButton); 246 | lostBorderPanel2.Font = new Font("Segoe UI", 12F); 247 | lostBorderPanel2.ForeColor = Color.White; 248 | lostBorderPanel2.Location = new Point(13, 12); 249 | lostBorderPanel2.Name = "lostBorderPanel2"; 250 | lostBorderPanel2.Padding = new Padding(5); 251 | lostBorderPanel2.ShowText = false; 252 | lostBorderPanel2.Size = new Size(467, 351); 253 | lostBorderPanel2.TabIndex = 5; 254 | lostBorderPanel2.Text = "lostBorderPanel2"; 255 | // 256 | // AvatarIDLabel 257 | // 258 | AvatarIDLabel.AutoSize = true; 259 | AvatarIDLabel.Location = new Point(12, 82); 260 | AvatarIDLabel.Name = "AvatarIDLabel"; 261 | AvatarIDLabel.Size = new Size(74, 21); 262 | AvatarIDLabel.TabIndex = 7; 263 | AvatarIDLabel.Text = "Avatar ID"; 264 | // 265 | // OpenAvatarButton 266 | // 267 | OpenAvatarButton.BackColor = Color.FromArgb(20, 20, 20); 268 | OpenAvatarButton.Font = new Font("Segoe UI", 9F); 269 | OpenAvatarButton.ForeColor = Color.White; 270 | OpenAvatarButton.HoverColor = Color.FromArgb(157, 59, 255); 271 | OpenAvatarButton.Image = null; 272 | OpenAvatarButton.Location = new Point(12, 288); 273 | OpenAvatarButton.Name = "OpenAvatarButton"; 274 | OpenAvatarButton.Size = new Size(441, 40); 275 | OpenAvatarButton.TabIndex = 22; 276 | OpenAvatarButton.Text = "Open Avatar File"; 277 | OpenAvatarButton.Click += OpenAvatarButton_Click; 278 | // 279 | // AvatarIDTextBox 280 | // 281 | AvatarIDTextBox.BackColor = Color.FromArgb(40, 40, 40); 282 | AvatarIDTextBox.BaseColor = Color.FromArgb(44, 55, 66); 283 | AvatarIDTextBox.BorderColorA = Color.FromArgb(157, 59, 255); 284 | AvatarIDTextBox.BorderColorB = Color.FromArgb(157, 59, 255); 285 | AvatarIDTextBox.Font = new Font("Segoe UI", 12F); 286 | AvatarIDTextBox.ForeColor = Color.White; 287 | AvatarIDTextBox.Hint = ""; 288 | AvatarIDTextBox.Location = new Point(12, 106); 289 | AvatarIDTextBox.MaxLength = 32767; 290 | AvatarIDTextBox.Multiline = false; 291 | AvatarIDTextBox.Name = "AvatarIDTextBox"; 292 | AvatarIDTextBox.PasswordChar = '\0'; 293 | AvatarIDTextBox.ScrollBars = ScrollBars.None; 294 | AvatarIDTextBox.SelectedText = ""; 295 | AvatarIDTextBox.SelectionLength = 0; 296 | AvatarIDTextBox.SelectionStart = 0; 297 | AvatarIDTextBox.Size = new Size(441, 38); 298 | AvatarIDTextBox.TabIndex = 6; 299 | AvatarIDTextBox.TabStop = false; 300 | AvatarIDTextBox.Text = "avtr_XXXXXXXXXXXXXXXXXXXX"; 301 | AvatarIDTextBox.UseSystemPasswordChar = false; 302 | AvatarIDTextBox.TextChanged += AvatarIDTextBox_TextChanged; 303 | // 304 | // UserIDLabel 305 | // 306 | UserIDLabel.AutoSize = true; 307 | UserIDLabel.Location = new Point(12, 9); 308 | UserIDLabel.Name = "UserIDLabel"; 309 | UserIDLabel.Size = new Size(61, 21); 310 | UserIDLabel.TabIndex = 5; 311 | UserIDLabel.Text = "User ID"; 312 | // 313 | // UserIDTextBox 314 | // 315 | UserIDTextBox.BackColor = Color.FromArgb(40, 40, 40); 316 | UserIDTextBox.BaseColor = Color.FromArgb(44, 55, 66); 317 | UserIDTextBox.BorderColorA = Color.FromArgb(157, 59, 255); 318 | UserIDTextBox.BorderColorB = Color.FromArgb(157, 59, 255); 319 | UserIDTextBox.Font = new Font("Segoe UI", 12F); 320 | UserIDTextBox.ForeColor = Color.White; 321 | UserIDTextBox.Hint = ""; 322 | UserIDTextBox.Location = new Point(12, 32); 323 | UserIDTextBox.MaxLength = 32767; 324 | UserIDTextBox.Multiline = false; 325 | UserIDTextBox.Name = "UserIDTextBox"; 326 | UserIDTextBox.PasswordChar = '\0'; 327 | UserIDTextBox.ScrollBars = ScrollBars.None; 328 | UserIDTextBox.SelectedText = ""; 329 | UserIDTextBox.SelectionLength = 0; 330 | UserIDTextBox.SelectionStart = 0; 331 | UserIDTextBox.Size = new Size(441, 38); 332 | UserIDTextBox.TabIndex = 5; 333 | UserIDTextBox.TabStop = false; 334 | UserIDTextBox.Text = "usr_XXXXXXXXXXXXXXXXXXXX"; 335 | UserIDTextBox.UseSystemPasswordChar = false; 336 | UserIDTextBox.TextChanged += UserIDTextBox_TextChanged; 337 | // 338 | // UnlockButton 339 | // 340 | UnlockButton.BackColor = Color.FromArgb(20, 20, 20); 341 | UnlockButton.Font = new Font("Segoe UI", 9F); 342 | UnlockButton.ForeColor = Color.White; 343 | UnlockButton.HoverColor = Color.LimeGreen; 344 | UnlockButton.Image = null; 345 | UnlockButton.Location = new Point(12, 150); 346 | UnlockButton.Name = "UnlockButton"; 347 | UnlockButton.Size = new Size(224, 40); 348 | UnlockButton.TabIndex = 15; 349 | UnlockButton.Text = "Unlock Avatar"; 350 | UnlockButton.Click += UnlockButton_Click; 351 | // 352 | // UnlockALLButton 353 | // 354 | UnlockALLButton.BackColor = Color.FromArgb(20, 20, 20); 355 | UnlockALLButton.Font = new Font("Segoe UI", 9F); 356 | UnlockALLButton.ForeColor = Color.White; 357 | UnlockALLButton.HoverColor = Color.Gold; 358 | UnlockALLButton.Image = null; 359 | UnlockALLButton.Location = new Point(12, 196); 360 | UnlockALLButton.Name = "UnlockALLButton"; 361 | UnlockALLButton.Size = new Size(441, 40); 362 | UnlockALLButton.TabIndex = 19; 363 | UnlockALLButton.Text = "Unlock ALL"; 364 | UnlockALLButton.Click += UnlockALLButton_Click; 365 | // 366 | // ResetButton 367 | // 368 | ResetButton.BackColor = Color.FromArgb(20, 20, 20); 369 | ResetButton.Font = new Font("Segoe UI", 9F); 370 | ResetButton.ForeColor = Color.White; 371 | ResetButton.HoverColor = Color.Crimson; 372 | ResetButton.Image = null; 373 | ResetButton.Location = new Point(12, 242); 374 | ResetButton.Name = "ResetButton"; 375 | ResetButton.Size = new Size(441, 40); 376 | ResetButton.TabIndex = 17; 377 | ResetButton.Text = "Reset Avatar"; 378 | ResetButton.Click += ResetButton_Click; 379 | // 380 | // UnlockVRCFuryButton 381 | // 382 | UnlockVRCFuryButton.BackColor = Color.FromArgb(20, 20, 20); 383 | UnlockVRCFuryButton.Font = new Font("Segoe UI", 9F); 384 | UnlockVRCFuryButton.ForeColor = Color.White; 385 | UnlockVRCFuryButton.HoverColor = Color.LimeGreen; 386 | UnlockVRCFuryButton.Image = null; 387 | UnlockVRCFuryButton.Location = new Point(242, 150); 388 | UnlockVRCFuryButton.Name = "UnlockVRCFuryButton"; 389 | UnlockVRCFuryButton.Size = new Size(211, 40); 390 | UnlockVRCFuryButton.TabIndex = 16; 391 | UnlockVRCFuryButton.Text = "Unlock [VRCFURY]"; 392 | UnlockVRCFuryButton.Click += UnlockVRCFuryButton_Click; 393 | // 394 | // AutoRestartCheckBox 395 | // 396 | AutoRestartCheckBox.AutoSize = true; 397 | AutoRestartCheckBox.BackColor = Color.FromArgb(44, 44, 44); 398 | AutoRestartCheckBox.CheckedColor = Color.FromArgb(157, 59, 255); 399 | AutoRestartCheckBox.DisabledColor = Color.FromArgb(196, 198, 202); 400 | AutoRestartCheckBox.DisabledStringColor = Color.FromArgb(186, 187, 189); 401 | AutoRestartCheckBox.Enable = true; 402 | AutoRestartCheckBox.EnabledCheckedColor = Color.FromArgb(157, 59, 255); 403 | AutoRestartCheckBox.EnabledStringColor = Color.FromArgb(153, 153, 153); 404 | AutoRestartCheckBox.EnabledUncheckedColor = Color.FromArgb(156, 158, 161); 405 | AutoRestartCheckBox.Font = new Font("Segoe UI", 12F); 406 | AutoRestartCheckBox.ForeColor = Color.White; 407 | AutoRestartCheckBox.Location = new Point(486, 130); 408 | AutoRestartCheckBox.Name = "AutoRestartCheckBox"; 409 | AutoRestartCheckBox.Size = new Size(121, 20); 410 | AutoRestartCheckBox.TabIndex = 3; 411 | AutoRestartCheckBox.Text = "Auto Restart"; 412 | AutoRestartCheckBox.UseVisualStyleBackColor = false; 413 | AutoRestartCheckBox.CheckedChanged += AutoRestartCheckBox_CheckedChanged; 414 | // 415 | // lostBorderPanel1 416 | // 417 | lostBorderPanel1.BackColor = Color.FromArgb(44, 44, 44); 418 | lostBorderPanel1.BorderColor = Color.FromArgb(157, 59, 255); 419 | lostBorderPanel1.Controls.Add(DiscordBtn); 420 | lostBorderPanel1.Controls.Add(WebsiteBtn); 421 | lostBorderPanel1.Controls.Add(GithubBtn); 422 | lostBorderPanel1.Font = new Font("Segoe UI", 12F); 423 | lostBorderPanel1.ForeColor = Color.White; 424 | lostBorderPanel1.Location = new Point(647, 12); 425 | lostBorderPanel1.Name = "lostBorderPanel1"; 426 | lostBorderPanel1.Padding = new Padding(5); 427 | lostBorderPanel1.ShowText = false; 428 | lostBorderPanel1.Size = new Size(225, 537); 429 | lostBorderPanel1.TabIndex = 22; 430 | lostBorderPanel1.Text = "lostBorderPanel1"; 431 | // 432 | // DiscordBtn 433 | // 434 | DiscordBtn.BackColor = Color.FromArgb(30, 30, 30); 435 | DiscordBtn.Font = new Font("Segoe UI", 9F); 436 | DiscordBtn.ForeColor = Color.White; 437 | DiscordBtn.HoverColor = Color.FromArgb(157, 59, 255); 438 | DiscordBtn.Image = null; 439 | DiscordBtn.Location = new Point(3, 104); 440 | DiscordBtn.Name = "DiscordBtn"; 441 | DiscordBtn.Size = new Size(219, 40); 442 | DiscordBtn.TabIndex = 25; 443 | DiscordBtn.Text = "Discord"; 444 | DiscordBtn.Click += DiscordBtn_Click; 445 | // 446 | // WebsiteBtn 447 | // 448 | WebsiteBtn.BackColor = Color.FromArgb(30, 30, 30); 449 | WebsiteBtn.Font = new Font("Segoe UI", 9F); 450 | WebsiteBtn.ForeColor = Color.White; 451 | WebsiteBtn.HoverColor = Color.FromArgb(157, 59, 255); 452 | WebsiteBtn.Image = null; 453 | WebsiteBtn.Location = new Point(3, 58); 454 | WebsiteBtn.Name = "WebsiteBtn"; 455 | WebsiteBtn.Size = new Size(219, 40); 456 | WebsiteBtn.TabIndex = 23; 457 | WebsiteBtn.Text = "Website"; 458 | WebsiteBtn.Click += WebsiteBtn_Click; 459 | // 460 | // GithubBtn 461 | // 462 | GithubBtn.BackColor = Color.FromArgb(30, 30, 30); 463 | GithubBtn.Font = new Font("Segoe UI", 9F); 464 | GithubBtn.ForeColor = Color.White; 465 | GithubBtn.HoverColor = Color.FromArgb(157, 59, 255); 466 | GithubBtn.Image = null; 467 | GithubBtn.Location = new Point(3, 12); 468 | GithubBtn.Name = "GithubBtn"; 469 | GithubBtn.Size = new Size(219, 40); 470 | GithubBtn.TabIndex = 22; 471 | GithubBtn.Text = "Github"; 472 | GithubBtn.Click += GithubBtn_Click; 473 | // 474 | // MainForm 475 | // 476 | AutoScaleDimensions = new SizeF(7F, 15F); 477 | AutoScaleMode = AutoScaleMode.Font; 478 | BackColor = Color.FromArgb(35, 35, 35); 479 | ClientSize = new Size(884, 561); 480 | Controls.Add(lostBorderPanel1); 481 | Controls.Add(AvatarMainPanel); 482 | ForeColor = Color.White; 483 | FormBorderStyle = FormBorderStyle.FixedSingle; 484 | Icon = (Icon)resources.GetObject("$this.Icon"); 485 | MaximizeBox = false; 486 | MaximumSize = new Size(900, 600); 487 | MinimumSize = new Size(900, 600); 488 | Name = "MainForm"; 489 | SizeGripStyle = SizeGripStyle.Hide; 490 | StartPosition = FormStartPosition.CenterScreen; 491 | Text = "Avatar Lockpick"; 492 | Load += MainForm_Load; 493 | AvatarMainPanel.ResumeLayout(false); 494 | AvatarMainPanel.PerformLayout(); 495 | lostBorderPanel2.ResumeLayout(false); 496 | lostBorderPanel2.PerformLayout(); 497 | lostBorderPanel1.ResumeLayout(false); 498 | ResumeLayout(false); 499 | } 500 | 501 | #endregion 502 | 503 | private ReaLTaiizor.Controls.LostBorderPanel AvatarMainPanel; 504 | private ReaLTaiizor.Controls.HopeCheckBox AutoRestartCheckBox; 505 | private ReaLTaiizor.Controls.LostBorderPanel lostBorderPanel2; 506 | private ReaLTaiizor.Controls.HopeTextBox UserIDTextBox; 507 | private Label AvatarIDLabel; 508 | private ReaLTaiizor.Controls.HopeTextBox AvatarIDTextBox; 509 | private Label UserIDLabel; 510 | private ReaLTaiizor.Controls.HopeCheckBox HideUserIDCheckBox; 511 | private Label VersionLabel; 512 | private Label VERSIONTEXT; 513 | private ReaLTaiizor.Controls.HopeCheckBox RestartWithVRCheckBox; 514 | private ReaLTaiizor.Controls.LostButton DeleteSavesButton; 515 | private ReaLTaiizor.Controls.LostButton OpenAvatarButton; 516 | private ReaLTaiizor.Controls.LostButton HideWindowButton; 517 | private ReaLTaiizor.Controls.LostButton AppendConsoleButton; 518 | private ReaLTaiizor.Controls.LostButton UnlockALLButton; 519 | private ReaLTaiizor.Controls.LostButton RestartButton; 520 | private ReaLTaiizor.Controls.LostButton ResetButton; 521 | private ReaLTaiizor.Controls.LostButton UnlockVRCFuryButton; 522 | private ReaLTaiizor.Controls.LostButton UnlockButton; 523 | private ReaLTaiizor.Controls.LostButton ClearSavesButton; 524 | private ReaLTaiizor.Controls.LostBorderPanel lostBorderPanel1; 525 | private ReaLTaiizor.Controls.LostButton HelpBtn; 526 | private ReaLTaiizor.Controls.LostButton WebsiteBtn; 527 | private ReaLTaiizor.Controls.LostButton GithubBtn; 528 | private ReaLTaiizor.Controls.LostButton DiscordBtn; 529 | } 530 | } 531 | -------------------------------------------------------------------------------- /AvatarLockpick/MainForm.cs: -------------------------------------------------------------------------------- 1 | using AvatarLockpick.Utils; 2 | using Microsoft.VisualBasic; 3 | using System; 4 | using System.ComponentModel; 5 | using System.Diagnostics; 6 | using System.Runtime.InteropServices; 7 | using System.Windows.Forms; 8 | 9 | namespace AvatarLockpick 10 | { 11 | public partial class MainForm : Form 12 | { 13 | [DllImport("dwmapi.dll")] 14 | private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize); 15 | 16 | private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19; 17 | private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20; 18 | 19 | private static bool UseImmersiveDarkMode(IntPtr handle, bool enabled) 20 | { 21 | if (IsWindows10OrGreater(17763)) 22 | { 23 | var attribute = DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1; 24 | if (IsWindows10OrGreater(18985)) 25 | { 26 | attribute = DWMWA_USE_IMMERSIVE_DARK_MODE; 27 | } 28 | 29 | int useImmersiveDarkMode = enabled ? 1 : 0; 30 | return DwmSetWindowAttribute(handle, (int)attribute, ref useImmersiveDarkMode, sizeof(int)) == 0; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | private static bool IsWindows10OrGreater(int build = -1) 37 | { 38 | return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= build; 39 | } 40 | 41 | [DllImport("kernel32.dll")] 42 | private static extern IntPtr GetConsoleWindow(); 43 | 44 | [DllImport("user32.dll")] 45 | private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 46 | 47 | private const int SW_HIDE = 0; 48 | private const int SW_SHOW = 5; 49 | 50 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 51 | public static bool AutoRestartTog { get; set; } = false; 52 | 53 | [DllImport("kernel32.dll", SetLastError = true)] 54 | static extern bool AllocConsole(); 55 | 56 | [DllImport("kernel32.dll", SetLastError = true)] 57 | static extern bool FreeConsole(); 58 | 59 | private bool hasConsole = false; 60 | 61 | private Config _config; 62 | private bool _isLoading = false; 63 | 64 | public MainForm() 65 | { 66 | InitializeComponent(); 67 | UseImmersiveDarkMode(Handle, true); 68 | 69 | // First load config 70 | _isLoading = true; 71 | _config = Config.Load(); 72 | 73 | // Then apply loaded config 74 | UserIDTextBox.Text = _config.UserId ?? "usr_XXXXXXXXXXXXXXXXXXXX"; 75 | AvatarIDTextBox.Text = _config.AvatarId ?? "avtr_XXXXXXXXXXXXXXXXXXXX"; 76 | HideUserIDCheckBox.Checked = _config.HideUserId; 77 | AutoRestartCheckBox.Checked = _config.AutoRestart; 78 | AutoRestartTog = _config.AutoRestart; 79 | 80 | // Finally wire up event handlers 81 | UserIDTextBox.TextChanged += UserIDTextBox_TextChanged; 82 | AvatarIDTextBox.TextChanged += AvatarIDTextBox_TextChanged; 83 | _isLoading = false; 84 | } 85 | 86 | private void SaveConfig() 87 | { 88 | if (_isLoading) return; // Don't save while loading initial values 89 | 90 | Console.WriteLine($"Saving config - UserID: {UserIDTextBox.Text}, AvatarID: {AvatarIDTextBox.Text}"); 91 | _config.UserId = UserIDTextBox.Text; 92 | _config.AvatarId = AvatarIDTextBox.Text; 93 | _config.HideUserId = HideUserIDCheckBox.Checked; 94 | _config.AutoRestart = AutoRestartCheckBox.Checked; 95 | _config.Save(); 96 | } 97 | 98 | private void UserIDTextBox_TextChanged(object? sender, EventArgs e) 99 | { 100 | SaveConfig(); 101 | } 102 | 103 | private void AvatarIDTextBox_TextChanged(object? sender, EventArgs e) 104 | { 105 | SaveConfig(); 106 | } 107 | 108 | private void MainForm_Load(object sender, EventArgs e) 109 | { 110 | MsgBoxUtils.ShowInfo("Welcome to AvatarLockpick! This tool is designed to help you unlock avatars in VRChat. " + 111 | "Please make sure you have changed into the avatar you want to unlock before using this!", "Welcome"); 112 | VERSIONTEXT.Text = Program.AppVersion; 113 | } 114 | 115 | private void AutoRestartCheckBox_CheckedChanged(object sender, EventArgs e) 116 | { 117 | AutoRestartTog = AutoRestartCheckBox.Checked; 118 | SaveConfig(); 119 | } 120 | 121 | private void HideUserIDCheckBox_CheckedChanged(object sender, EventArgs e) 122 | { 123 | UserIDTextBox.UseSystemPasswordChar = HideUserIDCheckBox.Checked; 124 | SaveConfig(); 125 | } 126 | 127 | private void UnlockButton_Click(object sender, EventArgs e) 128 | { 129 | if (string.IsNullOrEmpty(UserIDTextBox.Text) || string.IsNullOrEmpty(AvatarIDTextBox.Text)) 130 | { 131 | MsgBoxUtils.ShowError("Please enter a User ID and Avatar ID!", "Error"); 132 | return; 133 | } 134 | 135 | bool success = AvatarFinder.UnlockSpecificAvatar(UserIDTextBox.Text, AvatarIDTextBox.Text); 136 | if (AutoRestartTog) 137 | { 138 | VRCManager.CloseVRChat(); 139 | VRCManager.LaunchVRChat(RestartWithVRCheckBox.Checked); 140 | } 141 | 142 | if (success) 143 | { 144 | MsgBoxUtils.ShowInfo("Avatar unlocked successfully!", "Success"); 145 | } 146 | else 147 | { 148 | MsgBoxUtils.ShowError("Failed to unlock avatar!", "Error"); 149 | } 150 | } 151 | 152 | private void UnlockVRCFuryButton_Click(object sender, EventArgs e) 153 | { 154 | if (string.IsNullOrEmpty(UserIDTextBox.Text) || string.IsNullOrEmpty(AvatarIDTextBox.Text)) 155 | { 156 | MsgBoxUtils.ShowError("Please enter a User ID and Avatar ID!", "Error"); 157 | return; 158 | } 159 | 160 | bool success = AvatarFinder.UnlockVRCFAvatar(UserIDTextBox.Text, AvatarIDTextBox.Text); 161 | if (AutoRestartTog) 162 | { 163 | VRCManager.CloseVRChat(); 164 | VRCManager.LaunchVRChat(RestartWithVRCheckBox.Checked); 165 | } 166 | 167 | if (success) 168 | { 169 | MsgBoxUtils.ShowInfo("Avatar unlocked successfully!", "Success"); 170 | } 171 | else 172 | { 173 | MsgBoxUtils.ShowError("Failed to unlock avatar!", "Error"); 174 | } 175 | } 176 | 177 | private void UnlockALLButton_Click(object sender, EventArgs e) 178 | { 179 | if (string.IsNullOrEmpty(UserIDTextBox.Text)) 180 | { 181 | MsgBoxUtils.ShowError("Please enter a User ID", "Error"); 182 | return; 183 | } 184 | 185 | bool success = AvatarFinder.UnlockAvatars(UserIDTextBox.Text); 186 | if (success) 187 | { 188 | MsgBoxUtils.ShowInfo("All avatars unlocked successfully!", "Success"); 189 | } 190 | else 191 | { 192 | MsgBoxUtils.ShowError("Failed to unlock all avatars!", "Error"); 193 | } 194 | } 195 | 196 | private void ResetButton_Click(object sender, EventArgs e) 197 | { 198 | if (string.IsNullOrEmpty(UserIDTextBox.Text) || string.IsNullOrEmpty(AvatarIDTextBox.Text)) 199 | { 200 | MsgBoxUtils.ShowError("Please enter a User ID and Avatar ID!", "Error"); 201 | return; 202 | } 203 | 204 | bool success = AvatarFinder.DeleteSpecificAvatar(UserIDTextBox.Text, AvatarIDTextBox.Text); 205 | if (success) 206 | { 207 | MsgBoxUtils.ShowInfo("Avatar reset successfully!", "Success"); 208 | } 209 | else 210 | { 211 | MsgBoxUtils.ShowError("Failed to reset avatar!", "Error"); 212 | } 213 | } 214 | 215 | private void OpenAvatarButton_Click(object sender, EventArgs e) 216 | { 217 | AvatarFinder.OpenAvatarInNotepad(UserIDTextBox.Text, AvatarIDTextBox.Text); 218 | } 219 | 220 | private void AppendConsoleButton_Click(object sender, EventArgs e) 221 | { 222 | if (!hasConsole) 223 | { 224 | AllocConsole(); 225 | Console.Title = "AvatarLockpick Debug Console"; 226 | Console.WriteLine("Console initialized. Debug output will appear here."); 227 | hasConsole = true; 228 | } 229 | else 230 | { 231 | FreeConsole(); 232 | hasConsole = false; 233 | } 234 | } 235 | 236 | private void RestartButton_Click(object sender, EventArgs e) 237 | { 238 | VRCManager.CloseVRChat(); 239 | VRCManager.LaunchVRChat(RestartWithVRCheckBox.Checked); 240 | } 241 | 242 | private void HideWindowButton_Click(object sender, EventArgs e) 243 | { 244 | WindowState = FormWindowState.Minimized; 245 | } 246 | 247 | private void DeleteSavesButton_Click(object sender, EventArgs e) 248 | { 249 | Config.ClearConfig(); 250 | } 251 | 252 | private void ClearSavesButton_Click(object sender, EventArgs e) 253 | { 254 | _config.Clear(); 255 | } 256 | 257 | private void HelpBtn_Click(object sender, EventArgs e) 258 | { 259 | OpenUrlInBrowser("https://github.com/scrim-dev/AvatarLockpick/blob/master/HELP.md"); 260 | } 261 | 262 | private void GithubBtn_Click(object sender, EventArgs e) 263 | { 264 | OpenUrlInBrowser("https://github.com/scrim-dev"); 265 | } 266 | 267 | private void WebsiteBtn_Click(object sender, EventArgs e) 268 | { 269 | OpenUrlInBrowser("https://github.com/scrim-dev/AvatarLockpick"); 270 | } 271 | 272 | private void DiscordBtn_Click(object sender, EventArgs e) 273 | { 274 | OpenUrlInBrowser("https://discord.com/users/679060175440707605"); 275 | } 276 | 277 | private static void OpenUrlInBrowser(string url) 278 | { 279 | try 280 | { 281 | Process.Start(new ProcessStartInfo 282 | { 283 | FileName = url, 284 | UseShellExecute = true 285 | }); 286 | } 287 | catch { Console.WriteLine("Failed to open url."); } 288 | } 289 | } 290 | } -------------------------------------------------------------------------------- /AvatarLockpick/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AvatarLockpick 5 | { 6 | internal static class Program 7 | { 8 | [DllImport("user32.dll")] 9 | private static extern bool SetForegroundWindow(IntPtr hWnd); 10 | 11 | public const string mutexName = "AvatarLockpickMutex"; 12 | 13 | public static string AppVersion { get; set; } = "1.1"; 14 | 15 | [STAThread] 16 | static void Main() 17 | { 18 | using var mutex = new Mutex(true, mutexName, out bool createdNew); 19 | 20 | if (!createdNew) 21 | { 22 | MessageBox.Show("AvatarLockpick is already running!", "AvatarLockpick", MessageBoxButtons.OK, MessageBoxIcon.Warning); 23 | 24 | var existing = Process.GetCurrentProcess(); 25 | foreach (var process in Process.GetProcessesByName(existing.ProcessName)) 26 | { 27 | if (process.Id != existing.Id) 28 | { 29 | SetForegroundWindow(process.MainWindowHandle); 30 | break; 31 | } 32 | } 33 | 34 | return; 35 | } 36 | 37 | ApplicationConfiguration.Initialize(); 38 | Application.Run(new MainForm()); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /AvatarLockpick/Utils/AvatarFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Text.RegularExpressions; 8 | using System.Diagnostics; 9 | using System.Runtime.InteropServices; 10 | using Newtonsoft.Json.Linq; 11 | using Newtonsoft.Json; 12 | 13 | namespace AvatarLockpick.Utils 14 | { 15 | internal class AvatarFinder 16 | { 17 | [DllImport("kernel32.dll", SetLastError = true)] 18 | static extern IntPtr GetStdHandle(int nStdHandle); 19 | 20 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 21 | static extern bool WriteConsoleW( 22 | IntPtr hConsoleOutput, 23 | string lpBuffer, 24 | uint nNumberOfCharsToWrite, 25 | out uint lpNumberOfCharsWritten, 26 | IntPtr lpReserved); 27 | 28 | private const int STD_OUTPUT_HANDLE = -11; 29 | private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); 30 | 31 | private static void AppendToConsole(string message) 32 | { 33 | var handle = GetStdHandle(STD_OUTPUT_HANDLE); 34 | if (handle != INVALID_HANDLE_VALUE) 35 | { 36 | WriteConsoleW(handle, $"[{DateTime.Now:HH:mm:ss}] {message}\n", (uint)message.Length + 14, out _, IntPtr.Zero); 37 | } 38 | else 39 | { 40 | Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {message}"); 41 | } 42 | } 43 | 44 | //Unlocks all avatars for a user 45 | public static string GetVRChatAvatarPath(string userId) 46 | { 47 | // Get the path to AppData 48 | string appDataPath = Path.Combine( 49 | Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), 50 | "AppData", 51 | "LocalLow" 52 | ); 53 | 54 | string vrchatPath = Path.Combine(appDataPath, "VRChat", "VRChat", "LocalAvatarData", userId); 55 | AppendToConsole($"Checking VRChat path: {vrchatPath}"); 56 | return vrchatPath; 57 | } 58 | 59 | //Unlocks a specific avatar for a user 60 | public static bool UnlockSpecificAvatar(string userId, string avatarFileName) 61 | { 62 | try 63 | { 64 | string avatarPath = GetVRChatAvatarPath(userId); 65 | string fullAvatarPath = Path.Combine(avatarPath, avatarFileName); 66 | 67 | AppendToConsole($"Looking for avatar at path: {fullAvatarPath}"); 68 | AppendToConsole($"Directory exists: {Directory.Exists(avatarPath)}"); 69 | 70 | if (Directory.Exists(avatarPath)) 71 | { 72 | AppendToConsole("Files in directory:"); 73 | foreach (string file in Directory.GetFiles(avatarPath)) 74 | { 75 | AppendToConsole($"- {Path.GetFileName(file)}"); 76 | } 77 | } 78 | 79 | if (!File.Exists(fullAvatarPath)) 80 | { 81 | throw new FileNotFoundException($"Avatar file not found: {avatarFileName}"); 82 | } 83 | 84 | string jsonContent = File.ReadAllText(fullAvatarPath); 85 | AppendToConsole("Successfully read file content"); 86 | AppendToConsole($"Raw JSON content: {jsonContent}"); 87 | 88 | bool wasUnlocked = false; 89 | 90 | try 91 | { 92 | // First try to parse as a single object 93 | JObject jsonObj = JObject.Parse(jsonContent); 94 | AppendToConsole("Successfully parsed JSON as object"); 95 | 96 | // Get the animationParameters array 97 | var animParams = jsonObj["animationParameters"] as JArray; 98 | if (animParams != null) 99 | { 100 | AppendToConsole("Found animationParameters array"); 101 | foreach (JObject param in animParams) 102 | { 103 | var nameProperty = param["name"]?.ToString(); 104 | if (string.IsNullOrEmpty(nameProperty)) continue; 105 | 106 | // Remove ALL Unicode characters 107 | string normalizedName = Regex.Replace(nameProperty, @"[^\u0000-\u007F]+", "", RegexOptions.None); 108 | normalizedName = normalizedName.Trim(); // Also trim any remaining whitespace 109 | 110 | AppendToConsole($"Checking parameter: {nameProperty} (normalized: {normalizedName})"); 111 | 112 | if (normalizedName.Equals("locked", StringComparison.OrdinalIgnoreCase)) 113 | { 114 | AppendToConsole($"Found locked parameter with name: {nameProperty}"); 115 | var valueToken = param["value"]; 116 | 117 | if (valueToken != null) 118 | { 119 | AppendToConsole($"Current value: {valueToken}"); 120 | if (valueToken.Type == JTokenType.Integer && valueToken.Value() == 1) 121 | { 122 | param["value"] = new JValue(0); 123 | wasUnlocked = true; 124 | AppendToConsole("Changed value to 0"); 125 | } 126 | } 127 | } 128 | } 129 | } 130 | else 131 | { 132 | AppendToConsole("No animationParameters array found in JSON"); 133 | } 134 | 135 | if (wasUnlocked) 136 | { 137 | AppendToConsole("Writing changes back to file..."); 138 | File.WriteAllText(fullAvatarPath, jsonObj.ToString(Newtonsoft.Json.Formatting.None)); 139 | AppendToConsole("Successfully saved changes"); 140 | return true; 141 | } 142 | } 143 | catch (JsonReaderException) 144 | { 145 | // If it's not a single object, try parsing as array 146 | try 147 | { 148 | JArray jsonArray = JArray.Parse(jsonContent); 149 | AppendToConsole("Successfully parsed JSON as array"); 150 | 151 | foreach (JObject item in jsonArray.Children()) 152 | { 153 | var nameProperty = item["name"]?.ToString(); 154 | if (string.IsNullOrEmpty(nameProperty)) continue; 155 | 156 | string normalizedName = Regex.Replace(nameProperty, @"[^\u0000-\u007F]+", "", RegexOptions.None); 157 | 158 | if (normalizedName.Equals("locked", StringComparison.OrdinalIgnoreCase)) 159 | { 160 | AppendToConsole($"Found locked property with name: {nameProperty}"); 161 | var valueToken = item["value"]; 162 | 163 | if (valueToken != null) 164 | { 165 | AppendToConsole($"Current value: {valueToken}"); 166 | 167 | if (valueToken.Type == JTokenType.Integer && valueToken.Value() != 0) 168 | { 169 | item["value"] = new JValue(0); 170 | wasUnlocked = true; 171 | AppendToConsole("Changed integer value to 0"); 172 | } 173 | else if (valueToken.Type == JTokenType.Boolean && valueToken.Value() == true) 174 | { 175 | item["value"] = new JValue(false); 176 | wasUnlocked = true; 177 | AppendToConsole("Changed boolean value to false"); 178 | } 179 | else if (valueToken.Type == JTokenType.String) 180 | { 181 | string? strValue = valueToken.Value(); 182 | if (!string.IsNullOrEmpty(strValue) && 183 | (strValue.Equals("1") || strValue.Equals("true", StringComparison.OrdinalIgnoreCase))) 184 | { 185 | item["value"] = new JValue("0"); 186 | wasUnlocked = true; 187 | AppendToConsole("Changed string value to '0'"); 188 | } 189 | } 190 | } 191 | } 192 | } 193 | 194 | if (wasUnlocked) 195 | { 196 | AppendToConsole("Writing changes back to file..."); 197 | File.WriteAllText(fullAvatarPath, jsonArray.ToString(Newtonsoft.Json.Formatting.None)); 198 | AppendToConsole("Successfully saved changes"); 199 | return true; 200 | } 201 | } 202 | catch (Exception ex) 203 | { 204 | AppendToConsole($"Error parsing JSON as array: {ex.Message}"); 205 | throw; 206 | } 207 | } 208 | 209 | if (wasUnlocked) 210 | { 211 | AppendToConsole("No locked properties found or all properties were already unlocked"); 212 | return false; 213 | } 214 | else 215 | { 216 | AppendToConsole("No locked properties found or all properties were already unlocked"); 217 | return false; 218 | } 219 | } 220 | catch (Exception ex) 221 | { 222 | AppendToConsole($"Error: {ex.Message}"); 223 | AppendToConsole($"Stack trace: {ex.StackTrace}"); 224 | throw new Exception($"Error unlocking avatar {avatarFileName}: {ex.Message}", ex); 225 | } 226 | } 227 | 228 | public static bool DeleteSpecificAvatar(string userId, string avatarFileName) 229 | { 230 | try 231 | { 232 | string avatarPath = GetVRChatAvatarPath(userId); 233 | string fullAvatarPath = Path.Combine(avatarPath, avatarFileName); 234 | 235 | if (!File.Exists(fullAvatarPath)) 236 | { 237 | throw new FileNotFoundException($"Avatar file not found: {avatarFileName}"); 238 | } 239 | 240 | File.Delete(fullAvatarPath); 241 | return true; 242 | } 243 | catch (Exception ex) 244 | { 245 | throw new Exception($"Error deleting avatar {avatarFileName}: {ex.Message}", ex); 246 | } 247 | } 248 | 249 | // Original method kept for backwards compatibility 250 | public static bool UnlockAvatars(string userId) 251 | { 252 | try 253 | { 254 | string avatarPath = GetVRChatAvatarPath(userId); 255 | 256 | if (!Directory.Exists(avatarPath)) 257 | { 258 | throw new DirectoryNotFoundException($"Avatar directory not found for user ID: {userId}"); 259 | } 260 | 261 | string[] avatarFiles = Directory.GetFiles(avatarPath); 262 | bool anyAvatarUnlocked = false; 263 | 264 | foreach (string avatarFile in avatarFiles) 265 | { 266 | try 267 | { 268 | anyAvatarUnlocked |= UnlockSpecificAvatar(userId, Path.GetFileName(avatarFile)); 269 | } 270 | catch 271 | { 272 | // Skip files that can't be processed 273 | continue; 274 | } 275 | } 276 | 277 | return anyAvatarUnlocked; 278 | } 279 | catch (Exception ex) 280 | { 281 | throw new Exception($"Error unlocking avatars: {ex.Message}", ex); 282 | } 283 | } 284 | 285 | public static bool OpenAvatarInNotepad(string userId, string avatarFileName) 286 | { 287 | try 288 | { 289 | string avatarPath = GetVRChatAvatarPath(userId); 290 | string fullAvatarPath = Path.Combine(avatarPath, avatarFileName); 291 | 292 | if (!File.Exists(fullAvatarPath)) 293 | { 294 | throw new FileNotFoundException($"Avatar file not found: {avatarFileName}"); 295 | } 296 | 297 | Process.Start("notepad.exe", fullAvatarPath); 298 | return true; 299 | } 300 | catch (Exception ex) 301 | { 302 | throw new Exception($"Error opening avatar in Notepad {avatarFileName}: {ex.Message}", ex); 303 | } 304 | } 305 | 306 | public static bool UnlockVRCFAvatar(string userId, string avatarFileName) 307 | { 308 | try 309 | { 310 | string avatarPath = GetVRChatAvatarPath(userId); 311 | string fullAvatarPath = Path.Combine(avatarPath, avatarFileName); 312 | 313 | if (!File.Exists(fullAvatarPath)) 314 | { 315 | throw new FileNotFoundException($"Avatar file not found: {avatarFileName}"); 316 | } 317 | 318 | string jsonContent = File.ReadAllText(fullAvatarPath); 319 | JObject jsonObj = JObject.Parse(jsonContent); 320 | bool modified = false; 321 | 322 | // Target the specific parameters directly 323 | var animationParameters = jsonObj["animationParameters"] as JArray; 324 | if (animationParameters != null) 325 | { 326 | foreach (JObject param in animationParameters.Children()) 327 | { 328 | string paramName = param["name"]?.Value() ?? string.Empty; 329 | 330 | if (paramName.Equals("VRCF Lock/Password Version", StringComparison.OrdinalIgnoreCase)) 331 | { 332 | param["value"] = 1; 333 | modified = true; 334 | AppendToConsole("Updated Password Version to 1"); 335 | } 336 | else if (paramName.Equals("VRCF Lock/Lock", StringComparison.OrdinalIgnoreCase)) 337 | { 338 | param["value"] = 0; 339 | modified = true; 340 | AppendToConsole("Updated Lock to 0"); 341 | } 342 | } 343 | } 344 | 345 | if (modified) 346 | { 347 | File.WriteAllText(fullAvatarPath, jsonObj.ToString(Formatting.None)); 348 | AppendToConsole("Successfully saved changes to avatar file"); 349 | return true; 350 | } 351 | 352 | AppendToConsole("No required lock parameters found in avatar file"); 353 | return false; 354 | } 355 | catch (Exception ex) 356 | { 357 | AppendToConsole($"Error processing avatar: {ex.Message}"); 358 | throw new Exception($"Failed to unlock avatar {avatarFileName}: {ex.Message}", ex); 359 | } 360 | } 361 | } 362 | } -------------------------------------------------------------------------------- /AvatarLockpick/Utils/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Newtonsoft.Json; 4 | 5 | namespace AvatarLockpick.Utils 6 | { 7 | public class Config 8 | { 9 | public string? UserId { get; set; } 10 | public string? AvatarId { get; set; } 11 | public bool HideUserId { get; set; } 12 | public bool AutoRestart { get; set; } 13 | 14 | private static readonly string ConfigPath = Path.Combine( 15 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 16 | "AvatarLockpick", 17 | "config.json" 18 | ); 19 | 20 | public static Config Load() 21 | { 22 | try 23 | { 24 | if (File.Exists(ConfigPath)) 25 | { 26 | Console.WriteLine($"Loading config from: {ConfigPath}"); 27 | string json = File.ReadAllText(ConfigPath); 28 | Console.WriteLine($"Loaded config content: {json}"); 29 | var config = JsonConvert.DeserializeObject(json) ?? new Config(); 30 | Console.WriteLine($"Deserialized config - UserId: {config.UserId}, AvatarId: {config.AvatarId}"); 31 | return config; 32 | } 33 | } 34 | catch (Exception ex) 35 | { 36 | Console.WriteLine($"Error loading config: {ex.Message}"); 37 | } 38 | Console.WriteLine("Creating new config"); 39 | return new Config(); 40 | } 41 | 42 | public void Save() 43 | { 44 | try 45 | { 46 | string dirPath = Path.GetDirectoryName(ConfigPath)!; 47 | if (!Directory.Exists(dirPath)) 48 | { 49 | Console.WriteLine($"Creating config directory: {dirPath}"); 50 | Directory.CreateDirectory(dirPath); 51 | } 52 | 53 | string json = JsonConvert.SerializeObject(this, Formatting.Indented); 54 | Console.WriteLine($"Saving config to {ConfigPath}"); 55 | Console.WriteLine($"Config content: {json}"); 56 | File.WriteAllText(ConfigPath, json); 57 | Console.WriteLine("Config saved successfully"); 58 | } 59 | catch (Exception ex) 60 | { 61 | Console.WriteLine($"Error saving config: {ex.Message}"); 62 | } 63 | } 64 | 65 | public static void ClearConfig() 66 | { 67 | try 68 | { 69 | if (File.Exists(ConfigPath)) 70 | { 71 | Console.WriteLine($"Deleting config file: {ConfigPath}"); 72 | File.Delete(ConfigPath); 73 | Console.WriteLine("Config file deleted successfully"); 74 | } 75 | } 76 | catch (Exception ex) 77 | { 78 | Console.WriteLine($"Error deleting config: {ex.Message}"); 79 | } 80 | } 81 | 82 | public void Clear() 83 | { 84 | UserId = null; 85 | AvatarId = null; 86 | HideUserId = false; 87 | AutoRestart = false; 88 | Save(); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /AvatarLockpick/Utils/MsgBoxUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | 8 | namespace AvatarLockpick.Utils 9 | { 10 | internal static class MsgBoxUtils 11 | { 12 | private const string APP_NAME = "AvatarLockpick"; 13 | 14 | public static DialogResult ShowError(string message, string title = "Error") 15 | { 16 | return MessageBox.Show(message, $"{APP_NAME} - {title}", MessageBoxButtons.OK, MessageBoxIcon.Error); 17 | } 18 | 19 | public static DialogResult ShowWarning(string message, string title = "Warning") 20 | { 21 | return MessageBox.Show(message, $"{APP_NAME} - {title}", MessageBoxButtons.OK, MessageBoxIcon.Warning); 22 | } 23 | 24 | public static DialogResult ShowInfo(string message, string title = "Information") 25 | { 26 | return MessageBox.Show(message, $"{APP_NAME} - {title}", MessageBoxButtons.OK, MessageBoxIcon.Information); 27 | } 28 | 29 | /*public static DialogResult ShowQuestion(string message, string title = "Question") 30 | { 31 | return MessageBox.Show(message, $"{APP_NAME} - {title}", MessageBoxButtons.YesNo, MessageBoxIcon.Question); 32 | }*/ 33 | 34 | public static DialogResult ShowCustom(string message, string title, MessageBoxButtons buttons, MessageBoxIcon icon) 35 | { 36 | return MessageBox.Show(message, $"{APP_NAME} - {title}", buttons, icon); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AvatarLockpick/Utils/VRCManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Diagnostics; 7 | 8 | namespace AvatarLockpick.Utils 9 | { 10 | internal class VRCManager 11 | { 12 | private const string VRCHAT_PROCESS_NAME = "VRChat"; 13 | private const string STEAM_PROTOCOL = "steam://rungameid/438100"; 14 | 15 | /// 16 | /// Gets the VRChat process if it's running, otherwise returns null 17 | /// 18 | public static Process? GetVRChatProcess() 19 | { 20 | return Process.GetProcessesByName(VRCHAT_PROCESS_NAME).FirstOrDefault(); 21 | } 22 | 23 | /// 24 | /// Checks if VRChat is currently running 25 | /// 26 | public static bool IsVRChatRunning() 27 | { 28 | return GetVRChatProcess() != null; 29 | } 30 | 31 | /// 32 | /// Checks if VRChat process is responding 33 | /// 34 | public static bool IsVRChatResponding() 35 | { 36 | var process = GetVRChatProcess(); 37 | return process?.Responding ?? false; 38 | } 39 | 40 | /// 41 | /// Launches VRChat through Steam with VR toggle 42 | /// 43 | public static void LaunchVRChat(bool vr) 44 | { 45 | if (vr) 46 | { 47 | Process.Start(new ProcessStartInfo 48 | { 49 | FileName = STEAM_PROTOCOL, 50 | UseShellExecute = true 51 | }); 52 | } 53 | else 54 | { 55 | Process.Start(new ProcessStartInfo 56 | { 57 | FileName = STEAM_PROTOCOL + " --no-vr", 58 | UseShellExecute = true 59 | }); 60 | } 61 | } 62 | 63 | /// 64 | /// Gets the current status of VRChat process 65 | /// 66 | public static string GetVRChatStatus() 67 | { 68 | var process = GetVRChatProcess(); 69 | if (process == null) 70 | return "Not Running"; 71 | 72 | return process.Responding ? "Running" : "Not Responding"; 73 | } 74 | 75 | /// 76 | /// Beams VRChat 77 | /// 78 | public static bool CloseVRChat() 79 | { 80 | var process = GetVRChatProcess(); 81 | process?.Kill(); 82 | return false; 83 | } 84 | 85 | /// 86 | /// Forcefully kills the VRChat process 87 | /// 88 | /// True if the process was killed successfully, false if it wasn't running 89 | public static bool KillVRChat() 90 | { 91 | var process = GetVRChatProcess(); 92 | if (process != null) 93 | { 94 | process.Kill(); 95 | process.WaitForExit(5000); 96 | return true; 97 | } 98 | return false; 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /AvatarLockpick/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 53 | 61 | 62 | 63 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /AvatarLockpick/unlock_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrim-dev/AvatarLockpick/87a178d5ad3e43e87c31c8e3398256ab1fbf589c/AvatarLockpick/unlock_icon.ico -------------------------------------------------------------------------------- /HELP.md: -------------------------------------------------------------------------------- 1 | # How to use 2 | > ## STEP 1 3 | + Start VRChat 4 | + Switch into any avatar of your choosing 5 | + Wait for the avatar to load **FULLY** (then wait a bit just in case it is not cached) 6 | + Open the AvatarLockpick application 7 | + Input **YOUR** VRChat User ID, then input the **AVATAR's** ID 8 | > ## STEP 2 9 | + Unlock the avatar via the normal unlock method **OR** if it has custom locks or VRCF locks use the "Unlock (VRCFURY)" or "Unlock using Database" 10 | + After that restart your game and enjoy the unlocked avatar 11 | 12 | > ### **IMPORTANT** 13 | You **NEED** to restart the game as VRChat loads the avatar's save data / config data at game launch 14 | 15 | > ### Notes 16 | + Yes you can use this to unlock your private avatars if you have forgotten the password 17 | + Should work no matter if it's private or public 18 | + Share with your friends they'll probably love it 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AvatarLockpick 2 | Ever wanted to use a VRChat avatar but it was locked? Well now you can unlock it using AvatarLockpick! 3 | 4 | ## What's this all about? 5 | AvatarLockpick is user friendly tool for unlocking VRChat avatars. It works by scanning through your VRChat cache and helps you access avatars that would otherwise be locked away. Pretty neat, right? 6 | 7 | ## ⚠️ Important Note 8 | > **This tool is NOT associated with VRChat Inc. in any way.** 9 | > 10 | > AvatarLockpick is completely safe to use as it: 11 | > - Only scans your local VRChat cache files 12 | > - Never modifies the game or its processes 13 | > - Runs entirely as an external application 14 | > - Does not interact with VRChat's runtime 15 | > 16 | > Using this tool will NOT result in a ban as it operates within VRChat's Terms of Service by only reading locally cached files. 17 | > 18 | > Also beware that some avatars have locks that are scrambled or hidden so some avatars may not unlock fully. (I will try my best to figure out ways to still unlock these avatars) 19 | 20 | ## Preview 21 | Alt text 22 | Alt text 23 | 24 | ## Getting Started 25 | Read this: ℹ️[Help Me](https://github.com/scrim-dev/AvatarLockpick/blob/master/HELP.md) 26 | -------------------------------------------------------------------------------- /lock_types.txt: -------------------------------------------------------------------------------- 1 | R18/unlock 2 | R18/lock 3 | unlock 4 | lock 5 | locked 6 | VRCF Lock/Lock 7 | Unlocked NSFW 8 | Lock NSFW 9 | NSFW 10 | locker 11 | unlocker 12 | VRCF Lock/Password Version 13 | Password 14 | Password Lock 15 | VF123_SecurityLockMenu 16 | VF123_SecurityLockSync 17 | Lock Sync 18 | Avatar Lock 19 | VF138_SecurityLockSync 20 | VF138_SecurityLockMenu 21 | VF131_SecurityLockSync 22 | VF131_SecurityLockMenu 23 | SecurityLockSync 24 | SecurityLockMenu 25 | unlock_avatar 26 | unlock avatar 27 | PasswordInput 28 | Password_Input 29 | Ad 30 | 18plus 31 | 18+ 32 | Unlock NSFW 33 | MenuLock 34 | LockInput -------------------------------------------------------------------------------- /unique.txt: -------------------------------------------------------------------------------- 1 | avtr_a1586adf-3846-4d78-a302-46da50bc7e44 2 | avtr_472e4eec-cc02-4c1f-824a-aa78c387d82a -------------------------------------------------------------------------------- /ver.txt: -------------------------------------------------------------------------------- 1 | 2.1 --------------------------------------------------------------------------------