├── .gitattributes
├── .gitignore
├── LICENSE.txt
├── NCMDump.sln
├── NCMDump
├── App.xaml
├── App.xaml.cs
├── AppShell.xaml
├── AppShell.xaml.cs
├── FileListPage.xaml
├── FileListPage.xaml.cs
├── GlobalVars.cs
├── MainPage.xaml
├── MainPage.xaml.cs
├── MauiProgram.cs
├── MusicDescriptor.cs
├── MusicItemList.cs
├── NCMDump.csproj
├── NeteaseCryptoMusic.cs
├── NeteaseCryptoMusicMetaData.cs
├── NeteaseMusicDataDownload.cs
├── ObservableObject.cs
├── PerformingAction.xaml
├── PerformingAction.xaml.cs
├── PermissionHelper.cs
├── Platforms
│ ├── Android
│ │ ├── AndroidManifest.xml
│ │ ├── MainActivity.cs
│ │ ├── MainApplication.cs
│ │ └── Resources
│ │ │ └── values
│ │ │ └── colors.xml
│ ├── MacCatalyst
│ │ ├── AppDelegate.cs
│ │ ├── Info.plist
│ │ └── Program.cs
│ ├── Tizen
│ │ ├── Main.cs
│ │ └── tizen-manifest.xml
│ ├── Windows
│ │ ├── App.xaml
│ │ ├── App.xaml.cs
│ │ ├── Package.appxmanifest
│ │ └── app.manifest
│ └── iOS
│ │ ├── AppDelegate.cs
│ │ ├── Info.plist
│ │ └── Program.cs
├── Properties
│ └── launchSettings.json
└── Resources
│ ├── AppIcon
│ └── launcher.svg
│ ├── Fonts
│ ├── OpenSans-Regular.ttf
│ └── OpenSans-Semibold.ttf
│ ├── Raw
│ ├── AboutAssets.txt
│ └── music.png
│ ├── Splash
│ └── splash.svg
│ └── Styles
│ ├── Colors.xaml
│ └── Styles.xaml
└── README.md
/.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
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [year] [fullname]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NCMDump.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34622.214
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NCMDump", "NCMDump\NCMDump.csproj", "{F77FEC2A-B840-4010-874B-1CA9DE0422B1}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {F77FEC2A-B840-4010-874B-1CA9DE0422B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {F77FEC2A-B840-4010-874B-1CA9DE0422B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {F77FEC2A-B840-4010-874B-1CA9DE0422B1}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
17 | {F77FEC2A-B840-4010-874B-1CA9DE0422B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
18 | {F77FEC2A-B840-4010-874B-1CA9DE0422B1}.Release|Any CPU.Build.0 = Release|Any CPU
19 | {F77FEC2A-B840-4010-874B-1CA9DE0422B1}.Release|Any CPU.Deploy.0 = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(SolutionProperties) = preSolution
22 | HideSolutionNode = FALSE
23 | EndGlobalSection
24 | GlobalSection(ExtensibilityGlobals) = postSolution
25 | SolutionGuid = {A2B80005-CC74-48BC-91AD-CDF6B66ED05C}
26 | EndGlobalSection
27 | EndGlobal
28 |
--------------------------------------------------------------------------------
/NCMDump/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/NCMDump/App.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace NCMDump
2 | {
3 | public partial class App : Application
4 | {
5 | public App()
6 | {
7 | InitializeComponent();
8 |
9 | MainPage = new AppShell();
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/NCMDump/AppShell.xaml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/NCMDump/AppShell.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace NCMDump
2 | {
3 | public partial class AppShell : Shell
4 | {
5 | public AppShell()
6 | {
7 | InitializeComponent();
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/NCMDump/FileListPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
59 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/NCMDump/FileListPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Maui.Alerts;
2 | using CommunityToolkit.Maui.Core;
3 | using CommunityToolkit.Maui.Core.Extensions;
4 | using CommunityToolkit.Maui.Storage;
5 | using System.Collections.Immutable;
6 | using System.Diagnostics;
7 |
8 | namespace NCMDump;
9 |
10 | public partial class FileListPage : ContentPage
11 | {
12 | private List Files;
13 | public MusicItemList MusicItems { get; set; } = new();
14 |
15 | private int SelectedFileCount = 0;
16 |
17 | public FileListPage(List files)
18 | {
19 | InitializeComponent();
20 | this.BindingContext = this;
21 | Files = files;
22 | }
23 |
24 | private Task UpdateFileList()
25 | {
26 | MusicItems.MusicDescriptorList.Clear();
27 |
28 | return Task.Run(() =>
29 | {
30 | GC.Collect();
31 | Files.ForEach(file =>
32 | {
33 | if (NeteaseCryptoMusic.CheckFile(file) == false)
34 | return;
35 | MusicItems.MusicDescriptorList.Add(new MusicDescriptor(file));
36 | });
37 | }
38 | );
39 | }
40 |
41 | private void ContentPage_Loaded(object sender, EventArgs e)
42 | {
43 | UpdateFileList();
44 | }
45 |
46 | private void TapGestureRecognizer_Tapped(object sender, TappedEventArgs e)
47 | {
48 | var grid = sender as Grid;
49 | var descriptor = (grid.BindingContext as MusicDescriptor);
50 | descriptor.IsItemChecked = !descriptor.IsItemChecked;
51 | }
52 |
53 | private void Button_Clicked(object sender, EventArgs e)
54 | {
55 | var newFileList = MusicItems.MusicDescriptorList.Where(x => x.IsItemChecked).Select(x => x.FileName).ToList();
56 | Navigation.PushAsync(new PerformingAction(newFileList));
57 | }
58 |
59 | private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
60 | {
61 | if (e.Value == false)
62 | {
63 | SelectedFileCount--;
64 | }
65 | else
66 | {
67 | SelectedFileCount++;
68 | }
69 | if (SelectedFileCount == 0)
70 | {
71 | NextStepButton.IsEnabled = false;
72 | }
73 | else
74 | {
75 | NextStepButton.IsEnabled = true;
76 | }
77 | }
78 |
79 | private void MusicItemListView_Scrolled(object sender, ItemsViewScrolledEventArgs e)
80 | {
81 |
82 | }
83 | }
--------------------------------------------------------------------------------
/NCMDump/GlobalVars.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 NCMDump
8 | {
9 | public class GlobalVars : ObservableObject
10 | {
11 | public static GlobalVars Configs = new();
12 |
13 | public bool DownloadCoverImage
14 | {
15 | get;
16 | set;
17 | } = false;
18 |
19 | public bool DownloadLyric
20 | {
21 | get;
22 | set;
23 | } = false;
24 |
25 | public bool OverwriteFile
26 | {
27 | get;
28 | set;
29 | } = false;
30 |
31 | public bool DeleteOldFile
32 | {
33 | get;
34 | set;
35 | } = false;
36 |
37 | private GlobalVars() { }
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/NCMDump/MainPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/NCMDump/MainPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Maui.Alerts;
2 | using CommunityToolkit.Maui.Core;
3 | using CommunityToolkit.Maui.Storage;
4 | namespace NCMDump
5 | {
6 | public partial class MainPage : ContentPage
7 | {
8 |
9 | public string PickerMode
10 | {
11 | get;
12 | set;
13 | } = "Single";
14 |
15 | public GlobalVars GlobalConfigs { get => GlobalVars.Configs; }
16 |
17 | public MainPage()
18 | {
19 | InitializeComponent();
20 | this.BindingContext = this;
21 | }
22 |
23 | private async void Button_Clicked(object sender, EventArgs e)
24 | {
25 | if (PickerMode.Equals("Single"))
26 | {
27 | var result = await FilePicker.PickAsync();
28 | if (result != null)
29 | {
30 | List files = [result.FullPath];
31 | await Navigation.PushAsync(new FileListPage(files));
32 | }
33 |
34 | }
35 | else if (PickerMode.Equals("Multiple"))
36 | {
37 | var result = await FilePicker.PickMultipleAsync();
38 | if (result != null)
39 | {
40 | List files = new List();
41 | result.ToList().ForEach(file => { files.Add(file.FullPath); });
42 | await Navigation.PushAsync(new FileListPage(files));
43 | }
44 | }
45 | else if (PickerMode.Equals("Folder"))
46 | {
47 | var result = await FolderPicker.Default.PickAsync();
48 | if (result.IsSuccessful)
49 | {
50 | List files = new();
51 | await Task.Run(() => { files = Directory.EnumerateFiles(result.Folder.Path).ToList(); });
52 | await Navigation.PushAsync(new FileListPage(files));
53 | }
54 | }
55 | }
56 |
57 | private async void ContentPage_Loaded(object sender, EventArgs e)
58 | {
59 | if(await PermissionHelper.CheckPermission() == false)
60 | {
61 | await DisplayAlert("提示", "应用没有文件权限,点击授予权限", "确定");
62 | await PermissionHelper.RequestPermission();
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/NCMDump/MauiProgram.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Maui;
2 | using Microsoft.Extensions.Logging;
3 |
4 | namespace NCMDump
5 | {
6 | public static class MauiProgram
7 | {
8 | public static MauiApp CreateMauiApp()
9 | {
10 | var builder = MauiApp.CreateBuilder();
11 | builder
12 | .UseMauiApp()
13 | .UseMauiCommunityToolkit()
14 | .ConfigureFonts(fonts =>
15 | {
16 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
17 | fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
18 | });
19 |
20 | #if DEBUG
21 | builder.Logging.AddDebug();
22 | #endif
23 |
24 | return builder.Build();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/NCMDump/MusicDescriptor.cs:
--------------------------------------------------------------------------------
1 | using NCMDump;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | public class MusicDescriptor : ObservableObject
10 | {
11 | private string musicName;
12 | private string fileInfo;
13 | private string fileName;
14 | private bool isChecked;
15 | private string status;
16 |
17 | public MusicDescriptor(string _fileName)
18 | {
19 | fileName = _fileName;
20 | var metaData = NeteaseCryptoMusic.MetaDataFromFile(_fileName);
21 | musicName = metaData.MusicName;
22 | var info = new System.IO.FileInfo(_fileName);
23 | fileInfo = metaData.Format + " | " + FileSizeToString(info.Length);
24 | isChecked = true;
25 | status = "Waiting";
26 | }
27 |
28 | public string FileName { get => fileName; }
29 |
30 | public string MusicName
31 | {
32 | get => musicName;
33 | set => Set(ref musicName, value, nameof(MusicName));
34 | }
35 |
36 | public string FileInfo
37 | {
38 | get => fileInfo;
39 | set => Set(ref fileInfo, value, nameof(FileInfo));
40 | }
41 |
42 | public bool IsItemChecked
43 | {
44 | get => isChecked;
45 | set => Set(ref isChecked, value, nameof(IsItemChecked));
46 | }
47 |
48 | public string Status
49 | {
50 | get => status;
51 | set => Set(ref status, value, nameof(Status));
52 | }
53 |
54 | public NeteaseCryptoMusic CryptoMusic
55 | {
56 | get => NeteaseCryptoMusic.FromFile(fileName);
57 | }
58 |
59 | public ImageSource CoverImage
60 | {
61 | get => NeteaseCryptoMusic.FromFile(fileName).CoverImage;
62 | }
63 |
64 | private string FileSizeToString(long len)
65 | {
66 | long fileSizeInBytes = len;
67 | double fileSizeInKB = fileSizeInBytes / 1024.0; // 字节转换为千字节(KB)
68 | double fileSizeInMB = fileSizeInKB / 1024.0; // 千字节转换为兆字节(MB)
69 | string FileSize = $"{Math.Round(fileSizeInMB, 2)} MB";
70 | return FileSize;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/NCMDump/MusicItemList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 |
9 | public class MusicItemList : ObservableObject
10 | {
11 | public ObservableCollection MusicDescriptorList { get; set; }
12 |
13 | public MusicItemList()
14 | {
15 | MusicDescriptorList = new ObservableCollection();
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/NCMDump/NCMDump.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0-ios;net8.0-maccatalyst;net8.0-android34.0
4 | $(TargetFrameworks);net8.0-windows10.0.19041.0
5 |
6 |
7 | Exe
8 | NCMDump
9 | true
10 | true
11 | enable
12 |
13 | NCMDump
14 |
15 | com.companyname.ncmdump
16 | 197a8d2b-1f7c-4f02-99fa-d2e41e047c64
17 |
18 | 1.0
19 | 1
20 | 11.0
21 | 13.1
22 | 21.0
23 | 10.0.17763.0
24 | 10.0.17763.0
25 | 6.5
26 |
27 |
28 | apk
29 | link
30 | False
31 | False
32 | False
33 | False
34 | False
35 | False
36 |
37 |
38 | link
39 | False
40 | False
41 | False
42 | False
43 | False
44 | False
45 | False
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | MSBuild:Compile
62 |
63 |
64 | MSBuild:Compile
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/NCMDump/NeteaseCryptoMusic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Security.Cryptography;
7 | using System.Text;
8 | using System.Text.Json;
9 | using System.Text.Json.Nodes;
10 | using System.Threading.Tasks;
11 | using TagLib.Ape;
12 |
13 | namespace NCMDump
14 | {
15 | public class NeteaseCryptoMusic
16 | {
17 | private static readonly byte[] core_key = { 0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57 };
18 | private static readonly byte[] meta_key = { 0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28 };
19 |
20 |
21 | public string Path { get; set; }
22 |
23 | public bool IsEnabedImages { get; set; }
24 |
25 | private NeteaseCryptoMusic(string _path, NeteaseCryptoMusicMetaData metaData, ImageSource coverimage, byte[] coverImageBitmap, bool isEnabedImages)
26 | {
27 | Path = _path;
28 | MetaData = metaData;
29 | CoverImage = coverimage;
30 | CoverImageBitmap = coverImageBitmap;
31 | IsEnabedImages = isEnabedImages;
32 | }
33 |
34 | public NeteaseCryptoMusicMetaData MetaData
35 | {
36 | get;
37 | set;
38 | }
39 |
40 | public ImageSource CoverImage
41 | {
42 | get;
43 | set;
44 | }
45 |
46 | public byte[] CoverImageBitmap
47 | {
48 | get;
49 | set;
50 | }
51 |
52 | public static bool CheckFile(string path)
53 | {
54 | try
55 | {
56 | using (var fs = System.IO.File.Open(path, FileMode.Open))
57 | {
58 | //MAGIC_HEADER
59 | if (ReadUInt32(fs) != 0x4e455443)
60 | {
61 | return false;
62 | }
63 | if (ReadUInt32(fs) != 0x4d414446)
64 | {
65 | return false;
66 | }
67 | }
68 | return true;
69 | }
70 | catch (Exception ex)
71 | {
72 | Debug.WriteLine($"Exception -> {ex.Message}");
73 | Debug.WriteLine($"StackTrace:\n {ex.StackTrace}");
74 | return false;
75 | }
76 | }
77 |
78 | public static NeteaseCryptoMusic FromFile(string path)
79 | {
80 | try
81 | {
82 | using (var fs = System.IO.File.Open(path, FileMode.Open))
83 | {
84 | //MAGIC_HEADER
85 | if (ReadUInt32(fs) != 0x4e455443)
86 | {
87 | return null;
88 | }
89 | if (ReadUInt32(fs) != 0x4d414446)
90 | {
91 | return null;
92 | }
93 |
94 | //gap1
95 | fs.Seek(2, SeekOrigin.Current);
96 |
97 | //rc4_key_enc_size
98 | var rc4_key_enc_size = ReadUInt32(fs);
99 | fs.Seek(rc4_key_enc_size, SeekOrigin.Current);
100 |
101 | //metadata_enc_size
102 | var metadata_enc_size = ReadUInt32(fs);
103 | byte[] metadata_enc = new byte[metadata_enc_size];
104 | fs.Read(metadata_enc);
105 | for (int i = 0; i < metadata_enc.Length; i++)
106 | {
107 | metadata_enc[i] ^= 0x63;
108 | }
109 |
110 | // 从 Base64 字符串进行解码
111 | var decrypt_base64 = Convert.FromBase64String(System.Text.Encoding.UTF8.GetString(GetBytesByOffset(metadata_enc, 22)));
112 | var decrypt_metadata = DecryptAex128Ecb(meta_key, decrypt_base64);
113 | var metadata = System.Text.Encoding.UTF8.GetString(decrypt_metadata).Substring(6).Trim();
114 | var metadata_object = JsonSerializer.Deserialize(metadata, NeteaseCryptoMusicMetaDataSourceGenerationContext.Default.NeteaseCryptoMusicMetaData);
115 |
116 | //skip crc32&gap2
117 | fs.Seek(9, SeekOrigin.Current);
118 |
119 | ImageSource coverimage_source;
120 |
121 | //cover_data_size
122 | var cover_data_size = ReadUInt32(fs);
123 | byte[] coverimage_bitmap;
124 | bool isEnabedImages = false;
125 | if (cover_data_size != 0)
126 | {
127 | byte[] cover_data = new byte[cover_data_size];
128 | fs.Read(cover_data);
129 | coverimage_bitmap = cover_data;
130 | coverimage_source = ImageSource.FromStream(() => { return new MemoryStream(cover_data); });
131 | isEnabedImages = true;
132 | }
133 | else
134 | {
135 | using (var stream = FileSystem.OpenAppPackageFileAsync("music.png").Result)
136 | {
137 |
138 | using (MemoryStream memoryStream = new MemoryStream())
139 | {
140 | stream.CopyTo(memoryStream);
141 | coverimage_bitmap = memoryStream.ToArray();
142 | coverimage_source = ImageSource.FromStream(() => { return new MemoryStream(coverimage_bitmap); });
143 | }
144 | }
145 | }
146 | return new NeteaseCryptoMusic(path, metadata_object, coverimage_source, coverimage_bitmap, isEnabedImages);
147 | }
148 |
149 | }
150 | catch (Exception ex)
151 | {
152 | Debug.WriteLine($"Exception -> {ex.Message}");
153 | Debug.WriteLine($"StackTrace:\n {ex.StackTrace}");
154 | return null;
155 | }
156 | }
157 |
158 | public static NeteaseCryptoMusicMetaData MetaDataFromFile(string path)
159 | {
160 | try
161 | {
162 | using (var fs = System.IO.File.Open(path, FileMode.Open))
163 | {
164 | //MAGIC_HEADER
165 | if (ReadUInt32(fs) != 0x4e455443)
166 | {
167 | return null;
168 | }
169 | if (ReadUInt32(fs) != 0x4d414446)
170 | {
171 | return null;
172 | }
173 |
174 | //gap1
175 | fs.Seek(2, SeekOrigin.Current);
176 |
177 | //rc4_key_enc_size
178 | var rc4_key_enc_size = ReadUInt32(fs);
179 | fs.Seek(rc4_key_enc_size, SeekOrigin.Current);
180 |
181 | //metadata_enc_size
182 | var metadata_enc_size = ReadUInt32(fs);
183 | byte[] metadata_enc = new byte[metadata_enc_size];
184 | fs.Read(metadata_enc);
185 | for (int i = 0; i < metadata_enc.Length; i++)
186 | {
187 | metadata_enc[i] ^= 0x63;
188 | }
189 |
190 | // 从 Base64 字符串进行解码
191 | var decrypt_base64 = Convert.FromBase64String(System.Text.Encoding.UTF8.GetString(GetBytesByOffset(metadata_enc, 22)));
192 | var decrypt_metadata = DecryptAex128Ecb(meta_key, decrypt_base64);
193 | var metadata = System.Text.Encoding.UTF8.GetString(decrypt_metadata).Substring(6).Trim();
194 | var metadata_object = JsonSerializer.Deserialize(metadata, NeteaseCryptoMusicMetaDataSourceGenerationContext.Default.NeteaseCryptoMusicMetaData);
195 | return metadata_object;
196 | }
197 |
198 | }
199 | catch (Exception ex)
200 | {
201 | Debug.WriteLine($"Exception -> {ex.Message}");
202 | Debug.WriteLine($"StackTrace:\n {ex.StackTrace}");
203 | return null;
204 | }
205 | }
206 |
207 |
208 | public bool WriteDecryptMusic(string outdir)
209 | {
210 | var NewFilePath = outdir + "/" + System.IO.Path.GetFileNameWithoutExtension(Path) + "." + MetaData.Format;
211 |
212 | if (System.IO.Path.Exists(NewFilePath) && GlobalVars.Configs.OverwriteFile == false)
213 | {
214 | Debug.WriteLine($"File Exists -> {NewFilePath}");
215 | if (GlobalVars.Configs.DeleteOldFile)
216 | {
217 | System.IO.File.Delete(Path);
218 | }
219 | return true;
220 | }
221 | try
222 | {
223 | using (var fs = System.IO.File.Open(Path, FileMode.Open))
224 | {
225 | //MAGIC_HEADER
226 | if (ReadUInt32(fs) != 0x4e455443)
227 | {
228 | return false;
229 | }
230 | if (ReadUInt32(fs) != 0x4d414446)
231 | {
232 | return false;
233 | }
234 |
235 | //gap1
236 | fs.Seek(2, SeekOrigin.Current);
237 |
238 | //rc4_key_enc_size
239 | var rc4_key_enc_size = ReadUInt32(fs);
240 | byte[] rc4_key_enc = new byte[rc4_key_enc_size];
241 | fs.Read(rc4_key_enc);
242 |
243 | for (int i = 0; i < rc4_key_enc.Length; i++)
244 | {
245 | rc4_key_enc[i] ^= 0x64;
246 | }
247 |
248 | // 此处解析出来的值应该为减去字符串 "neteasecloudmusic" 长度之后的信息
249 | var keydata = GetBytesByOffset(DecryptAex128Ecb(core_key, rc4_key_enc), 17);
250 | var box = BuildKeyBox(keydata);
251 |
252 | //metadata_enc_size
253 | var metadata_enc_size = ReadUInt32(fs);
254 | fs.Seek(metadata_enc_size, SeekOrigin.Current);
255 |
256 | //skip crc32&gap2
257 | fs.Seek(9, SeekOrigin.Current);
258 |
259 | //cover_data_size
260 | var cover_data_size = ReadUInt32(fs);
261 | fs.Seek(cover_data_size, SeekOrigin.Current);
262 |
263 | var n = 0x8000;
264 |
265 | using (var OutputFileStream = System.IO.File.Create(NewFilePath))
266 | {
267 | while (true)
268 | {
269 | var tb = new byte[n];
270 | var result = fs.Read(tb);
271 | if (result <= 0) break;
272 |
273 | for (int i = 0; i < result; i++)
274 | {
275 | var j = (byte)((i + 1) & 0xff);
276 | tb[i] ^= box[box[j] + box[(box[j] + j) & 0xff] & 0xff];
277 | }
278 |
279 | OutputFileStream.Write(tb);
280 | }
281 | OutputFileStream.Flush();
282 | }
283 |
284 | var tagfile = TagLib.File.Create(NewFilePath);
285 | if (IsEnabedImages)
286 | {
287 | var tag_pic = new TagLib.Picture(new TagLib.ByteVector(CoverImageBitmap));
288 | tagfile.Tag.Pictures = new TagLib.Picture[] { tag_pic };
289 | }
290 | else if (GlobalVars.Configs.DownloadCoverImage)
291 | {
292 | var image_bytes = NeteaseMusicDataDownload.GetCoverImage(MetaData.AlbumPic).Result;
293 | if (image_bytes != null && image_bytes.Length != 0)
294 | {
295 | var tag_pic = new TagLib.Picture(new TagLib.ByteVector(image_bytes));
296 | tagfile.Tag.Pictures = new TagLib.Picture[] { tag_pic };
297 | }
298 | }
299 | if (GlobalVars.Configs.DownloadLyric)
300 | {
301 | var music_lyric = NeteaseMusicDataDownload.GetLyric(MetaData.MusicId).Result;
302 | if (music_lyric != null)
303 | {
304 | if (music_lyric.code == 200)
305 | {
306 | tagfile.Tag.Lyrics = music_lyric.lrc.lyric;
307 | }
308 | }
309 | }
310 | tagfile.Tag.Comment = MetaData.Album;
311 | tagfile.Tag.Title = MetaData.MusicName;
312 | tagfile.Tag.AlbumArtists = [MetaData.Artist[0].ArtistName];
313 | tagfile.Tag.Performers = MetaData.Artist.Select(x => x.ArtistName).ToArray();
314 | tagfile.Tag.Album = MetaData.Album;
315 | tagfile.Save();
316 | }
317 | if (GlobalVars.Configs.DeleteOldFile)
318 | {
319 | System.IO.File.Delete(Path);
320 | }
321 | return true;
322 | }
323 | catch (Exception ex)
324 | {
325 | Debug.WriteLine($"Exception -> {ex.Message}");
326 | Debug.WriteLine($"StackTrace:\n {ex.StackTrace}");
327 | return false;
328 | }
329 | }
330 |
331 | private static byte[] DecryptAex128Ecb(byte[] keyBytes, byte[] data)
332 | {
333 | var aes = Aes.Create();
334 | if (aes != null)
335 | {
336 | aes.Padding = PaddingMode.PKCS7;
337 | aes.Mode = CipherMode.ECB;
338 | using (var decryptor = aes.CreateDecryptor(keyBytes, null))
339 | {
340 | byte[] result = decryptor.TransformFinalBlock(data, 0, data.Length);
341 | return result;
342 | }
343 | }
344 |
345 | return null;
346 | }
347 |
348 | private static byte[] BuildKeyBox(byte[] key)
349 | {
350 | byte[] box = new byte[256];
351 | for (int i = 0; i < 256; ++i)
352 | {
353 | box[i] = (byte)i;
354 | }
355 |
356 | byte keyLength = (byte)key.Length;
357 | byte c;
358 | byte lastByte = 0;
359 | byte keyOffset = 0;
360 | byte swap;
361 |
362 | for (int i = 0; i < 256; ++i)
363 | {
364 | swap = box[i];
365 | c = (byte)((swap + lastByte + key[keyOffset++]) & 0xff);
366 |
367 | if (keyOffset >= keyLength)
368 | {
369 | keyOffset = 0;
370 | }
371 |
372 | box[i] = box[c];
373 | box[c] = swap;
374 | lastByte = c;
375 | }
376 |
377 | return box;
378 | }
379 |
380 | private static byte[] GetBytesByOffset(byte[] srcBytes, int offset = 0, long length = 0)
381 | {
382 | if (length == 0)
383 | {
384 | var resultBytes = new byte[srcBytes.Length - offset];
385 | System.Array.Copy(srcBytes, offset, resultBytes, 0, srcBytes.Length - offset);
386 | return resultBytes;
387 | }
388 |
389 | var resultBytes2 = new byte[length];
390 | System.Array.Copy(srcBytes, 0, resultBytes2, 0, length);
391 | return resultBytes2;
392 | }
393 |
394 | private static UInt32 ReadUInt32(FileStream fs)
395 | {
396 | var bytes = new byte[4];
397 | fs.Read(bytes);
398 | return BitConverter.ToUInt32(bytes);
399 | }
400 | }
401 | }
402 |
--------------------------------------------------------------------------------
/NCMDump/NeteaseCryptoMusicMetaData.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Maui.Graphics.Text;
2 | using System.Collections.Generic;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using System.Xml.Linq;
6 |
7 | namespace NCMDump
8 | {
9 | public class ArtistDataConverter : JsonConverter>
10 | {
11 | public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
12 | {
13 | if (reader.TokenType != JsonTokenType.StartArray)
14 | {
15 | throw new JsonException("Expected StartArray token");
16 | }
17 |
18 | List artists = new List();
19 |
20 | while (reader.Read())
21 | {
22 | if (reader.TokenType == JsonTokenType.EndArray)
23 | {
24 | return artists;
25 | }
26 |
27 | if (reader.TokenType != JsonTokenType.StartArray)
28 | {
29 | throw new JsonException("Expected StartArray token");
30 | }
31 |
32 | string name = string.Empty;
33 | int value = 0;
34 |
35 | while (reader.Read())
36 | {
37 | if (reader.TokenType == JsonTokenType.EndArray)
38 | {
39 | break;
40 | }
41 |
42 | if (reader.TokenType != JsonTokenType.String && reader.TokenType != JsonTokenType.Number)
43 | {
44 | throw new JsonException("Expected String or Number token");
45 | }
46 |
47 | if (reader.TokenType == JsonTokenType.String)
48 | {
49 | name = reader.GetString();
50 | }
51 | else if (reader.TokenType == JsonTokenType.Number)
52 | {
53 | value = reader.GetInt32();
54 | }
55 | }
56 |
57 | artists.Add(new Artist { ArtistName = name, ArtistId = value });
58 | }
59 |
60 | throw new JsonException("Unexpected end when reading JSON.");
61 | }
62 |
63 | public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options)
64 | {
65 | writer.WriteStartArray();
66 |
67 | foreach (var artist in value)
68 | {
69 | writer.WriteStartArray();
70 | writer.WriteStringValue(artist.ArtistName);
71 | writer.WriteNumberValue(artist.ArtistId);
72 | writer.WriteEndArray();
73 | }
74 |
75 | writer.WriteEndArray();
76 | }
77 | }
78 |
79 | public class Artist
80 | {
81 | public string ArtistName { get; set; }
82 |
83 | public int ArtistId { get; set; }
84 | }
85 |
86 | public class NeteaseCryptoMusicMetaData
87 | {
88 | [JsonPropertyName("musicId")]
89 | public long MusicId { get; set; }
90 |
91 | [JsonPropertyName("musicName")]
92 | public string MusicName { get; set; }
93 |
94 | [JsonPropertyName("artist")]
95 | [JsonConverter(typeof(ArtistDataConverter))]
96 | public List Artist { get; set; }
97 |
98 | [JsonPropertyName("albumId")]
99 | public long AlbumId { get; set; }
100 |
101 | [JsonPropertyName("album")]
102 | public string Album { get; set; }
103 |
104 | [JsonPropertyName("albumPic")]
105 | public string AlbumPic { get; set; }
106 |
107 | [JsonPropertyName("bitrate")]
108 | public int Bitrate { get; set; }
109 |
110 | [JsonPropertyName("mp3DocId")]
111 | public string Mp3DocId { get; set; }
112 |
113 | [JsonPropertyName("duration")]
114 | public int Duration { get; set; }
115 |
116 | [JsonPropertyName("mvId")]
117 | public UInt64 MvId { get; set; }
118 |
119 | //[JsonPropertyName("alias")]
120 | //public List Alias { get; set; }
121 |
122 | [JsonPropertyName("transNames")]
123 | public List TransNames { get; set; }
124 |
125 | [JsonPropertyName("format")]
126 | public string Format { get; set; }
127 |
128 | [JsonPropertyName("flag")]
129 | public int Flag { get; set; }
130 | }
131 |
132 | [JsonSourceGenerationOptions(WriteIndented = true)]
133 | [JsonSerializable(typeof(NeteaseCryptoMusicMetaData))]
134 | internal partial class NeteaseCryptoMusicMetaDataSourceGenerationContext : JsonSerializerContext
135 | {
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/NCMDump/NeteaseMusicDataDownload.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Collections.Generic;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 |
6 | namespace NCMDump
7 | {
8 | public class UserData
9 | {
10 | public int id { get; set; }
11 | public int status { get; set; }
12 | public int demand { get; set; }
13 | public int userid { get; set; }
14 | public string nickname { get; set; }
15 | public long uptime { get; set; }
16 | }
17 | public class LyricData
18 | {
19 | public int version { get; set; }
20 | public string lyric { get; set; }
21 | }
22 |
23 | public class NeteaseLyric
24 | {
25 | public LyricData lrc { get; set; }
26 | public LyricData klyric { get; set; }
27 | public LyricData tlyric { get; set; }
28 | public int code { get; set; }
29 | }
30 | [JsonSourceGenerationOptions(WriteIndented = true)]
31 | [JsonSerializable(typeof(NeteaseLyric))]
32 | internal partial class NeteaseLyricSourceGenerationContext : JsonSerializerContext
33 | {
34 | }
35 | internal class NeteaseMusicDataDownload
36 | {
37 | public static async Task GetCoverImage(string url)
38 | {
39 | using (HttpClient client = new HttpClient())
40 | {
41 | try
42 | {
43 | // 发送GET请求并获取响应
44 | HttpResponseMessage response = await client.GetAsync(url);
45 | response.EnsureSuccessStatusCode();
46 |
47 | // 读取响应内容并转换为字节数组
48 | using (Stream imageStream = await response.Content.ReadAsStreamAsync())
49 | {
50 | using (MemoryStream memoryStream = new MemoryStream())
51 | {
52 | await imageStream.CopyToAsync(memoryStream);
53 | return memoryStream.ToArray();
54 | }
55 | }
56 | }
57 | catch (HttpRequestException ex)
58 | {
59 | Debug.WriteLine($"HTTP请求失败:{ex.Message}");
60 | return null;
61 | }
62 | }
63 | }
64 |
65 | public static async Task GetLyric(long music_id)
66 | {
67 | try
68 | {
69 | string LyricAPI = $"https://music.163.com/api/song/lyric?id={music_id}&lv=1&kv=1&tv=-1";
70 | using (HttpClient client = new HttpClient())
71 | {
72 | HttpResponseMessage response = await client.GetAsync(LyricAPI);
73 |
74 | if (response.IsSuccessStatusCode)
75 | {
76 | string responseBody = await response.Content.ReadAsStringAsync();
77 | return JsonSerializer.Deserialize(responseBody, NeteaseLyricSourceGenerationContext.Default.NeteaseLyric);
78 | }
79 | else
80 | {
81 | Debug.WriteLine("Failed to request the API. Status code: " + response.StatusCode);
82 | return null;
83 | }
84 | }
85 | }
86 | catch (Exception ex)
87 | {
88 | Debug.WriteLine($"Exception -> {ex.Message}");
89 | Debug.WriteLine($"StackTrace:\n {ex.StackTrace}");
90 | return null;
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/NCMDump/ObservableObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Runtime.CompilerServices;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | #nullable enable
9 |
10 | public class ObservableObject : INotifyPropertyChanged
11 | {
12 | public event PropertyChangedEventHandler? PropertyChanged;
13 |
14 | public void OnPropertyChanged(string? propertyName)
15 | {
16 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
17 | }
18 |
19 | protected bool Set(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
20 | {
21 | if (EqualityComparer.Default.Equals(field, newValue))
22 | {
23 | return false;
24 | }
25 |
26 | field = newValue;
27 | OnPropertyChanged(propertyName);
28 | return true;
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/NCMDump/PerformingAction.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
54 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/NCMDump/PerformingAction.xaml.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MapleSwan/NCMDump/ae33ed30629aedc2f7616978912158b31373ee64/NCMDump/PerformingAction.xaml.cs
--------------------------------------------------------------------------------
/NCMDump/PermissionHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | #if __ANDROID__
8 | using Android.OS;
9 | #endif
10 |
11 | namespace NCMDump
12 | {
13 | internal class PermissionHelper
14 | {
15 | public static async Task RequestPermission()
16 | {
17 | #if __ANDROID__
18 | if (Build.VERSION.SdkInt >= BuildVersionCodes.R)
19 | {
20 | if (!Android.OS.Environment.IsExternalStorageManager)
21 | {
22 | Android.Content.Intent intent = new Android.Content.Intent(Android.Provider.Settings.ActionManageAppAllFilesAccessPermission);
23 | Android.Net.Uri uri = Android.Net.Uri.FromParts("package", AppInfo.PackageName, null);
24 | intent.SetData(uri);
25 | Platform.CurrentActivity.StartActivity(intent);
26 | }
27 | return (Android.OS.Environment.IsExternalStorageManager);
28 | }
29 | else
30 | {
31 | var statusW = await Permissions.CheckStatusAsync();
32 | if (statusW != PermissionStatus.Granted)
33 | {
34 | statusW = await Permissions.RequestAsync();
35 | }
36 | if (statusW != PermissionStatus.Granted)
37 | {
38 | return false;
39 | }
40 |
41 | statusW = await Permissions.CheckStatusAsync();
42 | if (statusW != PermissionStatus.Granted)
43 | {
44 | statusW = await Permissions.RequestAsync();
45 | }
46 | if (statusW != PermissionStatus.Granted)
47 | {
48 | return false;
49 | }
50 | }
51 | return true;
52 | #else
53 | return true;
54 | #endif
55 | }
56 |
57 | public static async Task CheckPermission()
58 | {
59 | #if __ANDROID__
60 | if (Build.VERSION.SdkInt >= BuildVersionCodes.R)
61 | {
62 | return (Android.OS.Environment.IsExternalStorageManager);
63 | }
64 | else
65 | {
66 | var statusW = await Permissions.CheckStatusAsync();
67 | if (statusW != PermissionStatus.Granted)
68 | {
69 | return false;
70 | }
71 | statusW = await Permissions.CheckStatusAsync();
72 | if (statusW != PermissionStatus.Granted)
73 | {
74 | return false;
75 | }
76 | }
77 | return true;
78 | #else
79 | return true;
80 | #endif
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Android/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using Android.OS;
4 |
5 | namespace NCMDump
6 | {
7 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
8 | public class MainActivity : MauiAppCompatActivity
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Android/MainApplication.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Runtime;
3 |
4 | namespace NCMDump
5 | {
6 | [Application]
7 | public class MainApplication : MauiApplication
8 | {
9 | public MainApplication(IntPtr handle, JniHandleOwnership ownership)
10 | : base(handle, ownership)
11 | {
12 | }
13 |
14 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Android/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #512BD4
4 | #2B0B98
5 | #2B0B98
6 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/MacCatalyst/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace NCMDump
4 | {
5 | [Register("AppDelegate")]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/MacCatalyst/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIDeviceFamily
6 |
7 | 1
8 | 2
9 |
10 | UIRequiredDeviceCapabilities
11 |
12 | arm64
13 |
14 | UISupportedInterfaceOrientations
15 |
16 | UIInterfaceOrientationPortrait
17 | UIInterfaceOrientationLandscapeLeft
18 | UIInterfaceOrientationLandscapeRight
19 |
20 | UISupportedInterfaceOrientations~ipad
21 |
22 | UIInterfaceOrientationPortrait
23 | UIInterfaceOrientationPortraitUpsideDown
24 | UIInterfaceOrientationLandscapeLeft
25 | UIInterfaceOrientationLandscapeRight
26 |
27 | XSAppIconAssets
28 | Assets.xcassets/appicon.appiconset
29 |
30 |
31 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/MacCatalyst/Program.cs:
--------------------------------------------------------------------------------
1 | using ObjCRuntime;
2 | using UIKit;
3 |
4 | namespace NCMDump
5 | {
6 | public class Program
7 | {
8 | // This is the main entry point of the application.
9 | static void Main(string[] args)
10 | {
11 | // if you want to use a different Application Delegate class from "AppDelegate"
12 | // you can specify it here.
13 | UIApplication.Main(args, null, typeof(AppDelegate));
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Tizen/Main.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Maui;
2 | using Microsoft.Maui.Hosting;
3 | using System;
4 |
5 | namespace NCMDump
6 | {
7 | internal class Program : MauiApplication
8 | {
9 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
10 |
11 | static void Main(string[] args)
12 | {
13 | var app = new Program();
14 | app.Run(args);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Tizen/tizen-manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | maui-appicon-placeholder
7 |
8 |
9 |
10 |
11 | http://tizen.org/privilege/internet
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Windows/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Windows/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI.Xaml;
2 |
3 | // To learn more about WinUI, the WinUI project structure,
4 | // and more about our project templates, see: http://aka.ms/winui-project-info.
5 |
6 | namespace NCMDump.WinUI
7 | {
8 | ///
9 | /// Provides application-specific behavior to supplement the default Application class.
10 | ///
11 | public partial class App : MauiWinUIApplication
12 | {
13 | ///
14 | /// Initializes the singleton application object. This is the first line of authored code
15 | /// executed, and as such is the logical equivalent of main() or WinMain().
16 | ///
17 | public App()
18 | {
19 | this.InitializeComponent();
20 | }
21 |
22 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Windows/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | $placeholder$
15 | User Name
16 | $placeholder$.png
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/Windows/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | true/PM
12 | PerMonitorV2, PerMonitor
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/iOS/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace NCMDump
4 | {
5 | [Register("AppDelegate")]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LSRequiresIPhoneOS
6 |
7 | UIDeviceFamily
8 |
9 | 1
10 | 2
11 |
12 | UIRequiredDeviceCapabilities
13 |
14 | arm64
15 |
16 | UISupportedInterfaceOrientations
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationLandscapeLeft
20 | UIInterfaceOrientationLandscapeRight
21 |
22 | UISupportedInterfaceOrientations~ipad
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationPortraitUpsideDown
26 | UIInterfaceOrientationLandscapeLeft
27 | UIInterfaceOrientationLandscapeRight
28 |
29 | XSAppIconAssets
30 | Assets.xcassets/appicon.appiconset
31 |
32 |
33 |
--------------------------------------------------------------------------------
/NCMDump/Platforms/iOS/Program.cs:
--------------------------------------------------------------------------------
1 | using ObjCRuntime;
2 | using UIKit;
3 |
4 | namespace NCMDump
5 | {
6 | public class Program
7 | {
8 | // This is the main entry point of the application.
9 | static void Main(string[] args)
10 | {
11 | // if you want to use a different Application Delegate class from "AppDelegate"
12 | // you can specify it here.
13 | UIApplication.Main(args, null, typeof(AppDelegate));
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/NCMDump/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Windows Machine": {
4 | "commandName": "MsixPackage",
5 | "nativeDebugging": false
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/NCMDump/Resources/AppIcon/launcher.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/NCMDump/Resources/Fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MapleSwan/NCMDump/ae33ed30629aedc2f7616978912158b31373ee64/NCMDump/Resources/Fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/NCMDump/Resources/Fonts/OpenSans-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MapleSwan/NCMDump/ae33ed30629aedc2f7616978912158b31373ee64/NCMDump/Resources/Fonts/OpenSans-Semibold.ttf
--------------------------------------------------------------------------------
/NCMDump/Resources/Raw/AboutAssets.txt:
--------------------------------------------------------------------------------
1 | Any raw assets you want to be deployed with your application can be placed in
2 | this directory (and child directories). Deployment of the asset to your application
3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
4 |
5 |
6 |
7 | These files will be deployed with you package and will be accessible using Essentials:
8 |
9 | async Task LoadMauiAsset()
10 | {
11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
12 | using var reader = new StreamReader(stream);
13 |
14 | var contents = reader.ReadToEnd();
15 | }
16 |
--------------------------------------------------------------------------------
/NCMDump/Resources/Raw/music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MapleSwan/NCMDump/ae33ed30629aedc2f7616978912158b31373ee64/NCMDump/Resources/Raw/music.png
--------------------------------------------------------------------------------
/NCMDump/Resources/Splash/splash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/NCMDump/Resources/Styles/Colors.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | #512BD4
8 | #DFD8F7
9 | #2B0B98
10 | White
11 | Black
12 | #E1E1E1
13 | #C8C8C8
14 | #ACACAC
15 | #919191
16 | #6E6E6E
17 | #404040
18 | #212121
19 | #141414
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | #F7B548
35 | #FFD590
36 | #FFE5B9
37 | #28C2D1
38 | #7BDDEF
39 | #C3F2F4
40 | #3E8EED
41 | #72ACF1
42 | #A7CBF6
43 |
44 |
--------------------------------------------------------------------------------
/NCMDump/Resources/Styles/Styles.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
10 |
11 |
15 |
16 |
21 |
22 |
25 |
26 |
49 |
50 |
67 |
68 |
88 |
89 |
110 |
111 |
132 |
133 |
138 |
139 |
159 |
160 |
178 |
179 |
183 |
184 |
206 |
207 |
222 |
223 |
243 |
244 |
247 |
248 |
271 |
272 |
292 |
293 |
299 |
300 |
319 |
320 |
323 |
324 |
352 |
353 |
373 |
374 |
378 |
379 |
391 |
392 |
397 |
398 |
404 |
405 |
406 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NCMDump
2 | 尊重音乐版权,反对盗版行为,本项目仅方便已购买的音乐在其他软件或设备播放,请勿大范围传播或用于商业行为。
3 |
4 |
--------------------------------------------------------------------------------