├── .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 |
22 |
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
--------------------------------------------------------------------------------