├── Langs
├── Langs.ja.Designer.cs
├── Langs.en-US.Designer.cs
├── Langs.zh-CN1.Designer.cs
├── Langs.Designer.cs
├── Langs.resx
└── Langs.zh-CN.resx
├── bass.dll
├── salt.pdn
├── bass_fx.dll
├── favicon.ico
├── Image
├── bg_dummy.jpg
├── outline_search_white_24dp.png
└── outline_find_replace_white_24dp.png
├── .vscode
└── settings.json
├── README.md
├── Properties
├── Settings.settings
├── AssemblyInfo.cs
├── Settings.Designer.cs
├── Resources.Designer.cs
└── Resources.resx
├── AutoSaveModule
├── GlobalAutoSaveContext.cs
├── IAutoSave.cs
├── IAutoSaveContext.cs
├── LocalAutoSaveContext.cs
├── AutoSaveIndex.cs
├── GlobalAutoSave.cs
├── LocalAutoSave.cs
├── IAutoSaveRecoverer.cs
├── AutoSaveExceptions.cs
├── IAutoSaveIndexManager.cs
├── SafeTerminationDetector.cs
├── AutoSaveManager.cs
├── FumenInfos.cs
├── AutoSaveRecoverer.cs
└── AutoSaveIndexManager.cs
├── .github
└── workflows
│ └── main.yml
├── SubWindow
├── MuriCheckResult.xaml.cs
├── BPMtap.xaml.cs
├── BPMtap.xaml
├── MuriCheckResult.xaml
├── MuriCheck.xaml
├── Infomation.xaml
├── Infomation.xaml.cs
├── AutoSaveRecover.xaml
├── EditorSettingPanel.xaml.cs
├── SoundSetting.xaml.cs
├── AutoSaveRecover.xaml.cs
├── EditorSettingPanel.xaml
├── SoundSetting.xaml
└── MuriCheck.xaml.cs
├── MajdataEdit.sln
├── App.xaml.cs
├── WebControl.cs
├── .gitattributes
├── MajdataEdit.csproj
├── Majson.cs
├── .gitignore
├── Mirror.cs
└── slide_time.json
/Langs/Langs.ja.Designer.cs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Langs/Langs.en-US.Designer.cs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Langs/Langs.zh-CN1.Designer.cs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bass.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LingFeng-bbben/MajdataEdit/HEAD/bass.dll
--------------------------------------------------------------------------------
/salt.pdn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LingFeng-bbben/MajdataEdit/HEAD/salt.pdn
--------------------------------------------------------------------------------
/bass_fx.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LingFeng-bbben/MajdataEdit/HEAD/bass_fx.dll
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LingFeng-bbben/MajdataEdit/HEAD/favicon.ico
--------------------------------------------------------------------------------
/Image/bg_dummy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LingFeng-bbben/MajdataEdit/HEAD/Image/bg_dummy.jpg
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet.preferCSharpExtension": true,
3 | "cmake.configureOnOpen": true
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ***
2 | 请参阅[MajdataView](https://github.com/LingFeng-bbben/MajdataView)
3 | ***
4 | 今后的更新和说明也将在那个页面发布。请留意。
5 |
--------------------------------------------------------------------------------
/Image/outline_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LingFeng-bbben/MajdataEdit/HEAD/Image/outline_search_white_24dp.png
--------------------------------------------------------------------------------
/Image/outline_find_replace_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LingFeng-bbben/MajdataEdit/HEAD/Image/outline_find_replace_white_24dp.png
--------------------------------------------------------------------------------
/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/AutoSaveModule/GlobalAutoSaveContext.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | namespace MajdataEdit.AutoSaveModule;
7 |
8 | ///
9 | /// 全局自动保存上下文
10 | ///
11 | public class GlobalAutoSaveContext : IAutoSaveContext
12 | {
13 | public string GetSavePath()
14 | {
15 | return Environment.CurrentDirectory + "/.autosave";
16 | }
17 | }
--------------------------------------------------------------------------------
/AutoSaveModule/IAutoSave.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | namespace MajdataEdit.AutoSaveModule;
7 |
8 | ///
9 | /// 自动保存行为接口
10 | /// 职责仅为进行自动保存行为
11 | ///
12 | internal interface IAutoSave
13 | {
14 | ///
15 | /// 执行自动保存行为
16 | ///
17 | /// 是否成功保存
18 | bool DoAutoSave();
19 | }
--------------------------------------------------------------------------------
/AutoSaveModule/IAutoSaveContext.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | namespace MajdataEdit.AutoSaveModule;
7 |
8 | ///
9 | /// 自动保存上下文接口
10 | /// 接口可以获取自动保存需要的上下文内容,如路径
11 | ///
12 | internal interface IAutoSaveContext
13 | {
14 | ///
15 | /// 获取保存路径,不包含文件名,结尾没有斜杠
16 | ///
17 | ///
18 | string GetSavePath();
19 | }
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | run-name: Build editor exe
3 | on: [push, pull_request]
4 | jobs:
5 | build:
6 | runs-on: windows-latest
7 | steps:
8 | - uses: actions/checkout@v3
9 | - uses: actions/setup-dotnet@v3
10 | with:
11 | dotnet-version: 6.0.x
12 | - run: dotnet restore
13 | - run: dotnet publish -c Release -r win-x64 --no-self-contained
14 | - run: ls -R
15 | - uses: actions/upload-artifact@v3
16 | with:
17 | name: Build
18 | path: bin\Release\net6.0-windows\win-x64\publish
19 |
--------------------------------------------------------------------------------
/AutoSaveModule/LocalAutoSaveContext.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | namespace MajdataEdit.AutoSaveModule;
7 |
8 | ///
9 | /// 本地自动保存上下文
10 | ///
11 | public class LocalAutoSaveContext : IAutoSaveContext
12 | {
13 | public string GetSavePath()
14 | {
15 | var maidataDir = MainWindow.maidataDir;
16 | if (maidataDir.Length == 0) throw new LocalDirNotOpenYetException();
17 |
18 | return MainWindow.maidataDir + "/.autosave";
19 | }
20 | }
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Windows;
3 |
4 | // 将 ComVisible 设置为 false 会使此程序集中的类型
5 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
6 | //请将此类型的 ComVisible 特性设置为 true。
7 | [assembly: ComVisible(false)]
8 |
9 | //若要开始生成可本地化的应用程序,请设置
10 | //.csproj 文件中的 CultureYouAreCodingWith
11 | //例如,如果您在源文件中使用的是美国英语,
12 | //使用的是美国英语,请将 设置为 en-US。 然后取消
13 | //对以下 NeutralResourceLanguage 特性的注释。 更新
14 | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
15 |
16 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
17 |
18 |
19 | [assembly: ThemeInfo(
20 | ResourceDictionaryLocation.None, //主题特定资源词典所处位置
21 | //(未在页面中找到资源时使用,
22 | //或应用程序资源字典中找到时使用)
23 | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
24 | //(未在页面中找到资源时使用,
25 | //、应用程序或任何主题专用资源字典中找到时使用)
26 | )]
--------------------------------------------------------------------------------
/AutoSaveModule/AutoSaveIndex.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | namespace MajdataEdit.AutoSaveModule;
7 |
8 | ///
9 | /// 自动保存索引 用于索引当前环境中自动保存的文件
10 | ///
11 | public class AutoSaveIndex
12 | {
13 | ///
14 | /// 已存在的自动保存文件数量
15 | ///
16 | public int Count = 0;
17 |
18 | ///
19 | /// 自动保存文件列表
20 | ///
21 | public List FilesInfo = new();
22 |
23 | public class FileInfo
24 | {
25 | ///
26 | /// 自动保存文件名
27 | ///
28 | public string? FileName;
29 |
30 | ///
31 | /// 原先的文件路径
32 | ///
33 | public string? RawPath;
34 |
35 | ///
36 | /// 自动保存时间
37 | ///
38 | public long SavedTime;
39 | }
40 | }
--------------------------------------------------------------------------------
/AutoSaveModule/GlobalAutoSave.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | namespace MajdataEdit.AutoSaveModule;
7 |
8 | ///
9 | /// 全局自动保存
10 | /// 它将自动保存的文件存储在majdata的根目录中
11 | ///
12 | public class GlobalAutoSave : IAutoSave
13 | {
14 | private readonly IAutoSaveIndexManager indexManager = new AutoSaveIndexManager();
15 | private readonly IAutoSaveContext saveContext = new GlobalAutoSaveContext();
16 |
17 | public GlobalAutoSave()
18 | {
19 | indexManager.ChangePath(saveContext.GetSavePath());
20 | indexManager.SetMaxAutoSaveCount(AutoSaveManager.GLOBAL_AUTOSAVE_MAX_COUNT);
21 | }
22 |
23 |
24 | public bool DoAutoSave()
25 | {
26 | var newSaveFilePath = indexManager.GetNewAutoSaveFileName();
27 |
28 | SimaiProcess.SaveData(newSaveFilePath);
29 |
30 | indexManager.RefreshIndex();
31 |
32 | return true;
33 | }
34 | }
--------------------------------------------------------------------------------
/SubWindow/MuriCheckResult.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 | using System.Windows.Input;
4 |
5 | namespace MajdataEdit;
6 |
7 | ///
8 | /// Window1.xaml 的交互逻辑
9 | ///
10 | public class ErrorInfo
11 | {
12 | public int positionX;
13 | public int positionY;
14 |
15 | public ErrorInfo(int _posX, int _posY)
16 | {
17 | positionX = _posX;
18 | positionY = _posY;
19 | }
20 | }
21 |
22 | public partial class MuriCheckResult : Window
23 | {
24 | public List errorPosition = new();
25 |
26 | public MuriCheckResult()
27 | {
28 | InitializeComponent();
29 | }
30 |
31 | public void ListBoxItem_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
32 | {
33 | var item = (ListBoxItem)sender;
34 | var index = int.Parse(item.Name[2..]);
35 | var errorInfo = errorPosition[index];
36 |
37 | ((MainWindow)Owner).ScrollToFumenContentSelection(errorInfo.positionX, errorInfo.positionY);
38 | }
39 | }
--------------------------------------------------------------------------------
/AutoSaveModule/LocalAutoSave.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | namespace MajdataEdit.AutoSaveModule;
7 |
8 | ///
9 | /// 本地自动保存
10 | /// 它将自动保存的文件存储在当前谱面的目录中
11 | ///
12 | public class LocalAutoSave : IAutoSave
13 | {
14 | private readonly IAutoSaveIndexManager indexManager = new AutoSaveIndexManager();
15 | private readonly IAutoSaveContext saveContext = new LocalAutoSaveContext();
16 |
17 | public LocalAutoSave()
18 | {
19 | indexManager.SetMaxAutoSaveCount(AutoSaveManager.LOCAL_AUTOSAVE_MAX_COUNT);
20 | }
21 |
22 |
23 | public bool DoAutoSave()
24 | {
25 | // 本地自动保存前 总是尝试将当前目录更新到目前打开的文件夹上
26 | indexManager.ChangePath(saveContext.GetSavePath());
27 |
28 | var newSaveFilePath = indexManager.GetNewAutoSaveFileName();
29 |
30 | SimaiProcess.SaveData(newSaveFilePath);
31 |
32 | indexManager.RefreshIndex();
33 |
34 | return true;
35 | }
36 | }
--------------------------------------------------------------------------------
/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | //
5 | // Changes to this file may cause incorrect behavior and will be lost if
6 | // the code is regenerated.
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 | namespace MajdataEdit.Properties {
11 |
12 |
13 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
14 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
15 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
16 |
17 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
18 |
19 | public static Settings Default {
20 | get {
21 | return defaultInstance;
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/MajdataEdit.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28307.1525
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MajdataEdit", "MajdataEdit.csproj", "{83EE1671-5045-4416-9D8B-054F389A511F}"
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 | {83EE1671-5045-4416-9D8B-054F389A511F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {83EE1671-5045-4416-9D8B-054F389A511F}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {83EE1671-5045-4416-9D8B-054F389A511F}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {83EE1671-5045-4416-9D8B-054F389A511F}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {8DFDC425-4B58-40AD-B262-BD69B3321253}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Windows;
3 | using System.Windows.Threading;
4 | using WPFLocalizeExtension.Engine;
5 |
6 | namespace MajdataEdit;
7 |
8 | ///
9 | /// App.xaml 的交互逻辑
10 | ///
11 | public partial class App : Application
12 | {
13 | public App()
14 | {
15 | LocalizeDictionary.Instance.SetCurrentThreadCulture = true;
16 | }
17 |
18 | private void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
19 | {
20 | if (e.Exception.GetType() == typeof(COMException) &&
21 | e.Exception.Message.IndexOf("UCEERR_RENDERTHREADFAILURE") != -1)
22 | {
23 | // 需要开启软件渲染
24 | MessageBox.Show(MajdataEdit.MainWindow.GetLocalizedString("SoftRenderError"),
25 | MajdataEdit.MainWindow.GetLocalizedString("Error"));
26 | Shutdown(114);
27 | return;
28 | }
29 |
30 | MessageBox.Show(e.Exception.Source + " At:\n" + e.Exception.Message + "\n" + e.Exception.StackTrace, "发生错误",
31 | MessageBoxButton.OK, MessageBoxImage.Error);
32 | e.Handled = true;
33 | }
34 | }
--------------------------------------------------------------------------------
/SubWindow/BPMtap.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace MajdataEdit;
4 |
5 | ///
6 | /// BPMtap.xaml 的交互逻辑
7 | ///
8 | public partial class BPMtap : Window
9 | {
10 | private List bpms = new();
11 | private DateTime lastTime = DateTime.MinValue;
12 |
13 | public BPMtap()
14 | {
15 | InitializeComponent();
16 | }
17 |
18 | private void Tap_Button_Click(object sender, RoutedEventArgs e)
19 | {
20 | if (lastTime != DateTime.MinValue)
21 | {
22 | var delta = (DateTime.Now - lastTime).TotalSeconds;
23 | bpms.Add(60d / delta);
24 | }
25 |
26 | lastTime = DateTime.Now;
27 | double sum = 0;
28 | if (bpms.Count <= 0) return;
29 | if (bpms.Count > 20) bpms.RemoveAt(0);
30 | foreach (var bpm in bpms) sum += bpm;
31 | var avg = sum / bpms.Count;
32 |
33 | Tap_Button.Content = string.Format("{0:N1}", avg);
34 | }
35 |
36 | private void Reset_Button_Click(object sender, RoutedEventArgs e)
37 | {
38 | bpms = new List();
39 | lastTime = DateTime.MinValue;
40 | Tap_Button.Content = "Tap";
41 | }
42 | }
--------------------------------------------------------------------------------
/AutoSaveModule/IAutoSaveRecoverer.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | namespace MajdataEdit.AutoSaveModule;
7 |
8 | internal interface IAutoSaveRecoverer
9 | {
10 | ///
11 | /// 获取本地的自动保存文件列表
12 | ///
13 | ///
14 | List GetLocalAutoSaves();
15 |
16 | ///
17 | /// 获取全局的自动保存文件列表
18 | ///
19 | ///
20 | List GetGlobalAutoSaves();
21 |
22 | ///
23 | /// 获取所有的自动保存文件列表 包括本地的和全局的
24 | ///
25 | ///
26 | List GetAllAutoSaves();
27 |
28 | ///
29 | /// 获取指定路径的谱面的信息
30 | ///
31 | ///
32 | ///
33 | FumenInfos GetFumenInfos(string path);
34 |
35 | ///
36 | /// 根据recoveredFileInfo恢复文件
37 | ///
38 | ///
39 | ///
40 | bool RecoverFile(AutoSaveIndex.FileInfo recoveredFileInfo);
41 | }
--------------------------------------------------------------------------------
/WebControl.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Net.Http;
3 | using System.Reflection;
4 | using System.Text;
5 |
6 | namespace MajdataEdit;
7 |
8 | internal static class WebControl
9 | {
10 | public static string RequestPOST(string url, string data = "")
11 | {
12 | try
13 | {
14 | using var client = new HttpClient();
15 |
16 | var webRequest = new HttpRequestMessage(HttpMethod.Post, url)
17 | {
18 | Content = new StringContent(data, Encoding.UTF8)
19 | };
20 |
21 | var response = client.Send(webRequest);
22 | using var reader = new StreamReader(response.Content.ReadAsStream());
23 |
24 | return reader.ReadToEnd();
25 | }
26 | catch
27 | {
28 | return "ERROR";
29 | }
30 | }
31 |
32 | public static string RequestGETAsync(string url)
33 | {
34 | var executingAssembly = Assembly.GetExecutingAssembly();
35 |
36 | using var httpClient = new HttpClient();
37 | var request = new HttpRequestMessage(HttpMethod.Get, url);
38 | request.Headers.Add("User-Agent", $"{executingAssembly.GetName().Name!} / {executingAssembly.GetName().Version!.ToString(3)}");
39 | var response = httpClient.Send(request);
40 | using var reader = new StreamReader(response.Content.ReadAsStream());
41 |
42 | return reader.ReadToEnd();
43 | }
44 | }
--------------------------------------------------------------------------------
/SubWindow/BPMtap.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/AutoSaveModule/AutoSaveExceptions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | using System.Runtime.Serialization;
7 |
8 | namespace MajdataEdit.AutoSaveModule;
9 |
10 | internal class AutoSaveIndexNotReadyException : Exception
11 | {
12 | public AutoSaveIndexNotReadyException()
13 | {
14 | }
15 |
16 | public AutoSaveIndexNotReadyException(string message) : base(message)
17 | {
18 | }
19 |
20 | public AutoSaveIndexNotReadyException(string message, Exception innerException) : base(message, innerException)
21 | {
22 | }
23 |
24 | protected AutoSaveIndexNotReadyException(SerializationInfo info, StreamingContext context) : base(info, context)
25 | {
26 | }
27 |
28 | public override string Message => base.Message;
29 | }
30 |
31 | internal class LocalDirNotOpenYetException : Exception
32 | {
33 | public LocalDirNotOpenYetException()
34 | {
35 | }
36 |
37 | public LocalDirNotOpenYetException(string message) : base(message)
38 | {
39 | }
40 |
41 | public LocalDirNotOpenYetException(string message, Exception innerException) : base(message, innerException)
42 | {
43 | }
44 |
45 | protected LocalDirNotOpenYetException(SerializationInfo info, StreamingContext context) : base(info, context)
46 | {
47 | }
48 |
49 | public override string Message => base.Message;
50 | }
--------------------------------------------------------------------------------
/AutoSaveModule/IAutoSaveIndexManager.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | namespace MajdataEdit.AutoSaveModule;
7 |
8 | ///
9 | /// 自动保存索引文件的管理接口
10 | ///
11 | internal interface IAutoSaveIndexManager
12 | {
13 | ///
14 | /// 修改当前工作路径
15 | ///
16 | ///
17 | void ChangePath(string path);
18 |
19 | ///
20 | /// 获取最多可以存储多少个自动保存文件
21 | ///
22 | ///
23 | int GetMaxAutoSaveCount();
24 |
25 | ///
26 | /// 设置最多可以存储多少个自动保存文件
27 | ///
28 | ///
29 | void SetMaxAutoSaveCount(int maxAutoSaveCount);
30 |
31 | ///
32 | /// 获取索引文件管理器是否已经就绪
33 | ///
34 | /// 若已经就绪则返回true
35 | bool IsReady();
36 |
37 | ///
38 | /// 获取一个新的自动保存文件名
39 | ///
40 | ///
41 | string GetNewAutoSaveFileName();
42 |
43 | ///
44 | /// 刷新并维护索引。如果当前已经存储的文件数量超出了最大限制,则删除过时的自动保存文件
45 | ///
46 | void RefreshIndex();
47 |
48 | ///
49 | /// 获取当前存在多少个自动保存文件
50 | ///
51 | ///
52 | int GetFileCount();
53 |
54 | ///
55 | /// 获取当前的自动保存文件信息列表
56 | ///
57 | ///
58 | List GetFileInfos();
59 | }
--------------------------------------------------------------------------------
/AutoSaveModule/SafeTerminationDetector.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | using System.IO;
7 |
8 | namespace MajdataEdit.AutoSaveModule;
9 |
10 | ///
11 | /// 异常退出检测器
12 | /// 单例运行,生命周期等同于Edit
13 | ///
14 | public sealed class SafeTerminationDetector
15 | {
16 | public readonly string RecordPath = Environment.CurrentDirectory + "/PROGRAM_RUNNING";
17 |
18 | private SafeTerminationDetector()
19 | {
20 | }
21 |
22 | ///
23 | /// 检查上一次退出是否为正常退出
24 | ///
25 | /// 如果上次为正常退出则返回true,否则返回false
26 | public bool IsLastTerminationSafe()
27 | {
28 | if (File.Exists(RecordPath)) return false;
29 |
30 | return true;
31 | }
32 |
33 | ///
34 | /// 启动程序时,调用此函数
35 | /// **注意!需在IsLastTerminationSafe之前调用!**
36 | ///
37 | public void RecordProgramStart()
38 | {
39 | File.WriteAllText(RecordPath, "");
40 | }
41 |
42 | public void ChangePath(string path)
43 | {
44 | File.WriteAllText(RecordPath, path);
45 | }
46 |
47 | ///
48 | /// 退出程序时,调用此函数
49 | ///
50 | public void RecordProgramClose()
51 | {
52 | File.Delete(RecordPath);
53 | }
54 |
55 | #region Singleton
56 |
57 | private static readonly SafeTerminationDetector _instance = new();
58 |
59 | public static SafeTerminationDetector Of()
60 | {
61 | return _instance;
62 | }
63 |
64 | #endregion
65 | }
--------------------------------------------------------------------------------
/SubWindow/MuriCheckResult.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 |
31 |
32 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/Langs/Langs.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace MajdataEdit.Langs {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | public class Langs {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Langs() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | public static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MajdataEdit.Langs.Langs", typeof(Langs).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 重写当前线程的 CurrentUICulture 属性,对
51 | /// 使用此强类型资源类的所有资源查找执行重写。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | public static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// 查找类似 sdadad 的本地化字符串。
65 | ///
66 | public static string FIles {
67 | get {
68 | return ResourceManager.GetString("FIles", resourceCulture);
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/MajdataEdit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net6.0-windows
5 | enable
6 | enable
7 | true
8 |
9 | 4.4.0
10 |
11 | bbben
12 | https://github.com/LingFeng-bbben/MajdataEdit
13 | Copyright © 2021
14 | favicon.ico
15 | false
16 | true
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | PreserveNewest
35 | bass.dll
36 |
37 |
38 | PreserveNewest
39 | bass_fx.dll
40 |
41 |
42 | PreserveNewest
43 | slide_time.json
44 |
45 |
46 | PreserveNewest
47 | LICENSE_MajdataEdit
48 |
49 |
50 |
51 |
52 | SettingsSingleFileGenerator
53 | Settings.Designer.cs
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/AutoSaveModule/AutoSaveManager.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | using System.Timers;
7 | using Timer = System.Timers.Timer;
8 |
9 | namespace MajdataEdit.AutoSaveModule;
10 |
11 | ///
12 | /// 自动保存管理类
13 | /// **单例运行**
14 | /// 其提供自动保存行为的计时能力,同时管理IAutoSave实现类的对象
15 | ///
16 | public sealed class AutoSaveManager
17 | {
18 | public static readonly int LOCAL_AUTOSAVE_MAX_COUNT = 5;
19 | public static readonly int GLOBAL_AUTOSAVE_MAX_COUNT = 30;
20 |
21 | private readonly List autoSavers = new();
22 |
23 | ///
24 | /// 自动保存计时Timer 默认每60秒检查一次
25 | ///
26 | private readonly Timer autoSaveTimer = new(1000 * 60);
27 |
28 | ///
29 | /// 自上次保存后,是否产生了修改
30 | ///
31 | private bool isFileChanged;
32 |
33 |
34 | ///
35 | /// 构造函数
36 | ///
37 | private AutoSaveManager()
38 | {
39 | // 本地存储者和全局存储者
40 | autoSavers.Add(new LocalAutoSave());
41 | autoSavers.Add(new GlobalAutoSave());
42 |
43 | // 存储事件
44 | autoSaveTimer.AutoReset = true;
45 | autoSaveTimer.Elapsed += autoSaveTimer_Elapsed;
46 | }
47 |
48 | ///
49 | /// 获取自动保存Timer间隔
50 | ///
51 | ///
52 | public double GetAutoSaveTimerInterval()
53 | {
54 | return autoSaveTimer.Interval;
55 | }
56 |
57 | ///
58 | /// 设置自动保存Timer间隔
59 | ///
60 | ///
61 | public void SetAutoSaveTimerInterval(double interval)
62 | {
63 | autoSaveTimer.Interval = interval;
64 | }
65 |
66 | ///
67 | /// 文件发生了改动
68 | ///
69 | public void SetFileChanged()
70 | {
71 | isFileChanged = true;
72 | }
73 |
74 | ///
75 | /// Timer触发事件
76 | ///
77 | ///
78 | ///
79 | private void autoSaveTimer_Elapsed(object? sender, ElapsedEventArgs e)
80 | {
81 | // 若文件未改动,则跳过此次自动保存
82 | if (!isFileChanged) return;
83 |
84 | // 执行保存行为
85 | foreach (var saver in autoSavers) saver.DoAutoSave();
86 |
87 | // 标记变更已被保存
88 | isFileChanged = false;
89 | }
90 |
91 | public void SetAutoSaveEnable(bool enabled)
92 | {
93 | if (enabled)
94 | autoSaveTimer.Start();
95 | else
96 | autoSaveTimer.Stop();
97 | }
98 |
99 | #region Singleton
100 |
101 | private static volatile AutoSaveManager? _instance;
102 | private static readonly object syncLock = new();
103 |
104 | public static AutoSaveManager Of()
105 | {
106 | if (_instance == null)
107 | lock (syncLock)
108 | {
109 | if (_instance == null) _instance = new AutoSaveManager();
110 | }
111 |
112 | return _instance;
113 | }
114 |
115 | #endregion
116 | }
--------------------------------------------------------------------------------
/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace MajdataEdit.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MajdataEdit.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 重写当前线程的 CurrentUICulture 属性,对
51 | /// 使用此强类型资源类的所有资源查找执行重写。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// 查找 System.Drawing.Bitmap 类型的本地化资源。
65 | ///
66 | internal static System.Drawing.Bitmap bg_dummy {
67 | get {
68 | object obj = ResourceManager.GetObject("bg_dummy", resourceCulture);
69 | return ((System.Drawing.Bitmap)(obj));
70 | }
71 | }
72 |
73 | ///
74 | /// 查找类似 shabi 的本地化字符串。
75 | ///
76 | internal static string File {
77 | get {
78 | return ResourceManager.GetString("File", resourceCulture);
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/AutoSaveModule/FumenInfos.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | using System.IO;
7 | using System.Text;
8 |
9 | namespace MajdataEdit.AutoSaveModule;
10 |
11 | ///
12 | /// 谱面信息
13 | ///
14 | public class FumenInfos
15 | {
16 | public string Artist = "";
17 | public string Designer = "";
18 | public float First;
19 | public string[] Fumens = new string[7];
20 | public string[] Levels = new string[7];
21 | public string OtherCommands = "";
22 | public string Title = "";
23 |
24 | public FumenInfos()
25 | {
26 | }
27 |
28 | public FumenInfos(string title, string artist, string designer, string otherCommands, float first, string[] levels,
29 | string[] fumens)
30 | {
31 | Title = title;
32 | Artist = artist;
33 | Designer = designer;
34 | OtherCommands = otherCommands;
35 | First = first;
36 | Levels = levels;
37 | Fumens = fumens;
38 | }
39 |
40 | ///
41 | /// 从path中读取谱面信息
42 | ///
43 | ///
44 | ///
45 | public static FumenInfos FromFile(string path)
46 | {
47 | var title = "";
48 | var artist = "";
49 | var designer = "";
50 | var other_commands = "";
51 | var first = 0f;
52 | var levels = new string[7];
53 | var fumens = new string[7];
54 |
55 | var maidataTxt = File.ReadAllLines(path, Encoding.UTF8);
56 |
57 | for (var i = 0; i < maidataTxt.Length; i++)
58 | if (maidataTxt[i].StartsWith("&title="))
59 | title = GetValue(maidataTxt[i]);
60 | else if (maidataTxt[i].StartsWith("&artist="))
61 | artist = GetValue(maidataTxt[i]);
62 | else if (maidataTxt[i].StartsWith("&des="))
63 | designer = GetValue(maidataTxt[i]);
64 | else if (maidataTxt[i].StartsWith("&first="))
65 | first = float.Parse(GetValue(maidataTxt[i]));
66 | else if (maidataTxt[i].StartsWith("&lv_") || maidataTxt[i].StartsWith("&inote_"))
67 | for (var j = 1; j < 8 && i < maidataTxt.Length; j++)
68 | {
69 | if (maidataTxt[i].StartsWith("&lv_" + j + "="))
70 | levels[j - 1] = GetValue(maidataTxt[i]);
71 | if (maidataTxt[i].StartsWith("&inote_" + j + "="))
72 | {
73 | var TheNote = "";
74 | TheNote += GetValue(maidataTxt[i]) + "\n";
75 | i++;
76 | for (; i < maidataTxt.Length; i++)
77 | {
78 | if (i < maidataTxt.Length)
79 | if (maidataTxt[i].StartsWith("&"))
80 | break;
81 | TheNote += maidataTxt[i] + "\n";
82 | }
83 |
84 | fumens[j - 1] = TheNote;
85 | }
86 | }
87 | else
88 | other_commands += maidataTxt[i].Trim() + "\n";
89 |
90 | other_commands = other_commands.Trim();
91 |
92 | return new FumenInfos(title, artist, designer, other_commands, first, levels, fumens);
93 | }
94 |
95 | private static string GetValue(string varline)
96 | {
97 | return varline.Split('=')[1];
98 | }
99 | }
--------------------------------------------------------------------------------
/AutoSaveModule/AutoSaveRecoverer.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | using System.IO;
7 |
8 | namespace MajdataEdit.AutoSaveModule;
9 |
10 | public class AutoSaveRecoverer : IAutoSaveRecoverer
11 | {
12 | private readonly IAutoSaveContext globalContext = new GlobalAutoSaveContext();
13 | private readonly IAutoSaveIndexManager globalIndex;
14 | private readonly IAutoSaveContext localContext = new LocalAutoSaveContext();
15 | private readonly IAutoSaveIndexManager localIndex;
16 |
17 | public AutoSaveRecoverer()
18 | {
19 | localIndex = new AutoSaveIndexManager(AutoSaveManager.LOCAL_AUTOSAVE_MAX_COUNT);
20 | try
21 | {
22 | localIndex.ChangePath(localContext.GetSavePath());
23 | }
24 | catch (LocalDirNotOpenYetException)
25 | {
26 | }
27 |
28 | globalIndex = new AutoSaveIndexManager(AutoSaveManager.GLOBAL_AUTOSAVE_MAX_COUNT);
29 | globalIndex.ChangePath(globalContext.GetSavePath());
30 | }
31 |
32 | public List GetLocalAutoSaves()
33 | {
34 | var result = new List();
35 |
36 | try
37 | {
38 | localIndex.ChangePath(localContext.GetSavePath());
39 | }
40 | catch (LocalDirNotOpenYetException)
41 | {
42 | return result;
43 | }
44 |
45 | result.AddRange(localIndex.GetFileInfos());
46 | result.Sort(delegate(AutoSaveIndex.FileInfo f1, AutoSaveIndex.FileInfo f2)
47 | {
48 | return f2.SavedTime.CompareTo(f1.SavedTime);
49 | });
50 |
51 | return result;
52 | }
53 |
54 | public List GetGlobalAutoSaves()
55 | {
56 | var result = new List();
57 | result.AddRange(globalIndex.GetFileInfos());
58 | result.Sort(delegate(AutoSaveIndex.FileInfo f1, AutoSaveIndex.FileInfo f2)
59 | {
60 | return f2.SavedTime.CompareTo(f1.SavedTime);
61 | });
62 |
63 | return result;
64 | }
65 |
66 | public List GetAllAutoSaves()
67 | {
68 | var result = new List();
69 |
70 | result.AddRange(GetLocalAutoSaves());
71 | result.AddRange(GetGlobalAutoSaves());
72 |
73 | return result;
74 | }
75 |
76 | public FumenInfos GetFumenInfos(string path)
77 | {
78 | return FumenInfos.FromFile(path);
79 | }
80 |
81 | public bool RecoverFile(AutoSaveIndex.FileInfo recoveredFileInfo)
82 | {
83 | // 原始的maidata路径
84 | var rawMaidataPath = recoveredFileInfo.RawPath + "/maidata.txt";
85 | // 原始maidata恢复前备份路径
86 | var backupMaidataPath = recoveredFileInfo.RawPath + "/maidata.before_recovery.txt";
87 | // 自动保存maidata路径
88 | var autosaveMaidataPath = recoveredFileInfo.FileName;
89 |
90 | try
91 | {
92 | // 删除之前的备份(若有)
93 | if (File.Exists(backupMaidataPath)) File.Delete(backupMaidataPath);
94 | // 备份恢复前的maidata
95 | File.Move(rawMaidataPath, backupMaidataPath);
96 | // 将自动保存maidata恢复到原目录
97 | File.Copy(autosaveMaidataPath!, rawMaidataPath);
98 | }
99 | catch
100 | {
101 | return false;
102 | }
103 |
104 | return true;
105 | }
106 | }
--------------------------------------------------------------------------------
/Majson.cs:
--------------------------------------------------------------------------------
1 | namespace MajdataEdit;
2 |
3 | internal class Majson
4 | {
5 | public string artist = "default";
6 | public string designer = "default";
7 | public string difficulty = "EZ";
8 | public int diffNum = 0;
9 | public string level = "1";
10 | public List timingList = new();
11 | public string title = "default";
12 | }
13 |
14 | internal class EditRequestjson
15 | {
16 | public float audioSpeed;
17 | public float backgroundCover;
18 | public EditorComboIndicator comboStatusType;
19 | public EditorPlayMethod editorPlayMethod;
20 | public EditorControlMethod control;
21 | public string? jsonPath;
22 | public float noteSpeed;
23 | public long startAt;
24 | public float startTime;
25 | public float touchSpeed;
26 | public bool smoothSlideAnime;
27 | }
28 |
29 | public enum EditorPlayMethod
30 | {
31 | Classic,DJAuto,Random,Disabled
32 | }
33 |
34 | public enum EditorComboIndicator
35 | {
36 | None,
37 |
38 | // List of viable indicators that won't be a static content.
39 | // ScoreBorder, AchievementMaxDown, ScoreDownDeluxe are static.
40 | Combo,
41 | ScoreClassic,
42 | AchievementClassic,
43 | AchievementDownClassic,
44 | AchievementDeluxe = 11,
45 | AchievementDownDeluxe,
46 | ScoreDeluxe,
47 |
48 | // Please prefix custom indicator with C
49 | CScoreDedeluxe = 101,
50 | CScoreDownDedeluxe,
51 | MAX
52 | }
53 |
54 | internal enum EditorControlMethod
55 | {
56 | Start,
57 | Stop,
58 | OpStart,
59 | Pause,
60 | Continue,
61 | Record
62 | }
63 |
64 | //this setting is per maidata
65 | internal class MajSetting
66 | {
67 | public float Answer_Level = 0.7f;
68 |
69 | public float BGM_Level = 0.7f;
70 | public float Break_Level = 0.7f;
71 | public float Break_Slide_Level = 0.7f;
72 | public float Ex_Level = 0.7f;
73 | public float Hanabi_Level = 0.7f;
74 | public float Judge_Level = 0.7f;
75 | public int lastEditDiff;
76 | public double lastEditTime;
77 | public float Slide_Level = 0.7f;
78 | public float Touch_Level = 0.7f;
79 | }
80 |
81 | //this setting is global
82 | public class EditorSetting
83 | {
84 | public bool AutoCheckUpdate = true;
85 | public float backgroundCover = 0.6f;
86 | public int ChartRefreshDelay = 1000;
87 | public EditorComboIndicator comboStatusType = 0;
88 | public EditorPlayMethod editorPlayMethod;
89 | public string DecreasePlaybackSpeedKey = "Ctrl+o";
90 | public float Default_Answer_Level = 0.7f;
91 | public float Default_BGM_Level = 0.7f;
92 | public float Default_Break_Level = 0.7f;
93 | public float Default_Break_Slide_Level = 0.7f;
94 | public float Default_Ex_Level = 0.7f;
95 | public float Default_Hanabi_Level = 0.7f;
96 | public float Default_Judge_Level = 0.7f;
97 | public float Default_Slide_Level = 0.7f;
98 | public float Default_Touch_Level = 0.7f;
99 | public float DefaultSlideAccuracy = 0.2f;
100 | public float FontSize = 12;
101 | public string IncreasePlaybackSpeedKey = "Ctrl+p";
102 | public string Language = "en-US";
103 | public string Mirror180Key = "Ctrl+l";
104 | public string Mirror45Key = "Ctrl+OemSemicolon";
105 | public string MirrorCcw45Key = "Ctrl+OemQuotes";
106 | public string MirrorLeftRightKey = "Ctrl+j";
107 | public string MirrorUpDownKey = "Ctrl+k";
108 | public string PlayPauseKey = "Ctrl+Shift+c";
109 | public float playSpeed = 7.5f;
110 | public string PlayStopKey = "Ctrl+Shift+x";
111 | public int RenderMode = 0; //0=硬件渲染(默认),1=软件渲染
112 | public int SyntaxCheckLevel = 1; //0=禁用,1=警告(默认),2=启用
113 | public string SaveKey = "Ctrl+s";
114 | public string SendViewerKey = "Ctrl+Shift+z";
115 | public float touchSpeed = 7.5f;
116 | public bool SmoothSlideAnime = false;
117 | }
--------------------------------------------------------------------------------
/SubWindow/MuriCheck.xaml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
38 |
48 |
49 |
50 |
53 |
54 |
57 |
67 |
68 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/SubWindow/Infomation.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
17 |
19 |
21 |
23 |
25 |
27 |
29 |
31 |
36 |
41 |
43 |
45 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/SubWindow/Infomation.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Windows;
3 | using System.Windows.Input;
4 | using System.Windows.Media;
5 | using System.Windows.Media.Imaging;
6 | using Microsoft.Win32;
7 |
8 | namespace MajdataEdit;
9 |
10 | ///
11 | /// Infomation.xaml 的交互逻辑
12 | ///
13 | public partial class Infomation : Window
14 | {
15 | public Infomation()
16 | {
17 | InitializeComponent();
18 | }
19 |
20 | private void OKButton_Click(object sender, RoutedEventArgs e)
21 | {
22 | SimaiProcess.title = TitleTextbox.Text;
23 | SimaiProcess.artist = ArtistTextbox.Text;
24 | SimaiProcess.designer = DesignTextbox.Text;
25 | SimaiProcess.other_commands = OtherTextbox.Text;
26 | Close();
27 | }
28 |
29 | private void InfomationWindow_Loaded(object sender, RoutedEventArgs e)
30 | {
31 | TitleTextbox.Text = SimaiProcess.title;
32 | ArtistTextbox.Text = SimaiProcess.artist;
33 | DesignTextbox.Text = SimaiProcess.designer;
34 | OtherTextbox.Text = SimaiProcess.other_commands;
35 | LoadImageFromDefault();
36 | RenderOptions.SetBitmapScalingMode(SaltImage, BitmapScalingMode.HighQuality);
37 | }
38 |
39 | private void LoadImageFromDefault()
40 | {
41 | if (File.Exists(MainWindow.maidataDir + "/bg.png"))
42 | LoadImageFromByte(File.ReadAllBytes(MainWindow.maidataDir + "/bg.png"));
43 | else if (File.Exists(MainWindow.maidataDir + "/bg.jpg"))
44 | LoadImageFromByte(File.ReadAllBytes(MainWindow.maidataDir + "/bg.jpg"));
45 | else
46 | SaltImage.Source =
47 | new BitmapImage(new Uri("pack://application:,,,/MajdataEdit;component/Image/bg_dummy.jpg"));
48 | }
49 |
50 | private void LoadImageFromByte(byte[] data)
51 | {
52 | var image = new BitmapImage();
53 | image.BeginInit();
54 | image.StreamSource = new MemoryStream(data);
55 | image.EndInit();
56 | SaltImage.Source = image;
57 | }
58 |
59 | private void ReadMetadata(string path)
60 | {
61 | var formattedPath = path;
62 |
63 | if (!path.EndsWith(".ogg") && !path.EndsWith(".mp3"))
64 | {
65 | var ext = ".mp3";
66 |
67 | if (File.Exists(path + ".ogg")) ext = ".ogg";
68 |
69 | formattedPath += ext;
70 | }
71 |
72 | var file = TagLib.File.Create(formattedPath);
73 | TitleTextbox.Text = file.Tag.Title;
74 | ArtistTextbox.Text = file.Tag.JoinedPerformers;
75 | if (file.Tag.Pictures.Length > 0)
76 | {
77 | var pic = file.Tag.Pictures[0];
78 | LoadImageFromByte(pic.Data.Data);
79 | var result = MessageBox.Show(MainWindow.GetLocalizedString("IsOverridePicture"),
80 | MainWindow.GetLocalizedString("Info"), MessageBoxButton.YesNo);
81 | if (result != MessageBoxResult.Yes)
82 | {
83 | LoadImageFromDefault();
84 | return;
85 | }
86 |
87 | if (pic.MimeType.Contains("jpeg"))
88 | {
89 | DelBGs();
90 | File.WriteAllBytes(MainWindow.maidataDir + "/bg.jpg", pic.Data.Data);
91 | }
92 |
93 | if (pic.MimeType.Contains("png"))
94 | {
95 | DelBGs();
96 | File.WriteAllBytes(MainWindow.maidataDir + "/bg.png", pic.Data.Data);
97 | }
98 |
99 | Console.WriteLine(pic.MimeType);
100 | }
101 | }
102 |
103 | private void ReadTrackButton_Click(object sender, RoutedEventArgs e)
104 | {
105 | ReadMetadata(MainWindow.maidataDir + "/track");
106 | }
107 |
108 | private void ReadFileButton_Click(object sender, RoutedEventArgs e)
109 | {
110 | var openFileDialog = new OpenFileDialog
111 | {
112 | Filter = "*.mp3, *.ogg|*.mp3;*.ogg"
113 | };
114 | if ((bool)openFileDialog.ShowDialog()!) ReadMetadata(openFileDialog.FileName);
115 | }
116 |
117 | private void SaltImage_MouseDown(object sender, MouseButtonEventArgs e)
118 | {
119 | var openFileDialog = new OpenFileDialog
120 | {
121 | InitialDirectory = "",
122 | Filter = "图片|*.png;*.jpg"
123 | };
124 | if ((bool)openFileDialog.ShowDialog()!)
125 | {
126 | var data = File.ReadAllBytes(openFileDialog.FileName);
127 | var info = new FileInfo(openFileDialog.FileName);
128 | LoadImageFromByte(data);
129 | DelBGs();
130 | File.Copy(openFileDialog.FileName, MainWindow.maidataDir + "/bg" + info.Extension);
131 | }
132 | }
133 |
134 | private void DelBGs()
135 | {
136 | if (File.Exists(MainWindow.maidataDir + "/bg.png")) File.Delete(MainWindow.maidataDir + "/bg.png");
137 | if (File.Exists(MainWindow.maidataDir + "/bg.jpg")) File.Delete(MainWindow.maidataDir + "/bg.jpg");
138 | }
139 |
140 | private void CancelButton_Click(object sender, RoutedEventArgs e)
141 | {
142 | Close();
143 | }
144 | }
--------------------------------------------------------------------------------
/AutoSaveModule/AutoSaveIndexManager.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Moying-moe All rights reserved. Licensed under the MIT license.
3 | See LICENSE in the project root for license information.
4 | */
5 |
6 | using System.IO;
7 | using Newtonsoft.Json;
8 |
9 | namespace MajdataEdit.AutoSaveModule;
10 |
11 | internal class AutoSaveIndexManager : IAutoSaveIndexManager
12 | {
13 | private string? curPath;
14 | private AutoSaveIndex? index;
15 | private bool isReady;
16 | private int maxAutoSaveCount;
17 |
18 | public AutoSaveIndexManager()
19 | {
20 | maxAutoSaveCount = 5;
21 | }
22 |
23 | public AutoSaveIndexManager(int maxAutoSaveCount)
24 | {
25 | this.maxAutoSaveCount = maxAutoSaveCount;
26 | }
27 |
28 | public void ChangePath(string path)
29 | {
30 | if (path != curPath)
31 | {
32 | // 只有当新目录和之前设置的目录不同时,才会触发index文件读写
33 | curPath = path;
34 | LoadOrCreateIndexFile();
35 | }
36 |
37 | isReady = true;
38 | }
39 |
40 | public int GetFileCount()
41 | {
42 | if (!IsReady()) throw new AutoSaveIndexNotReadyException("AutoSaveIndexManager is not ready yet.");
43 |
44 | return index!.Count;
45 | }
46 |
47 | public List GetFileInfos()
48 | {
49 | if (!IsReady()) throw new AutoSaveIndexNotReadyException("AutoSaveIndexManager is not ready yet.");
50 |
51 | return index!.FilesInfo;
52 | }
53 |
54 | public int GetMaxAutoSaveCount()
55 | {
56 | return maxAutoSaveCount;
57 | }
58 |
59 | public string GetNewAutoSaveFileName()
60 | {
61 | var path = curPath + "/autosave." + GetCurrentTimeString() + ".txt";
62 |
63 | var fileInfo = new AutoSaveIndex.FileInfo
64 | {
65 | FileName = path,
66 | SavedTime = DateTimeOffset.Now.AddHours(8).ToUnixTimeSeconds(),
67 | RawPath = MainWindow.maidataDir
68 | };
69 | index!.FilesInfo.Add(fileInfo);
70 |
71 | index.Count++;
72 |
73 | // 将变更存储到index文件中
74 | UpdateIndexFile();
75 |
76 | return path;
77 | }
78 |
79 | public bool IsReady()
80 | {
81 | return isReady;
82 | }
83 |
84 | public void RefreshIndex()
85 | {
86 | // 先扫描一遍,如果有文件已经被删了就先移除掉
87 | for (var i = index!.Count - 1; i >= 0; i--)
88 | {
89 | var fileInfo = index.FilesInfo[i];
90 | if (!File.Exists(fileInfo.FileName))
91 | {
92 | index.FilesInfo.RemoveAt(i);
93 | index.Count--;
94 | }
95 | }
96 |
97 | // 然后从this.index.FileInfo的表头开始删除 直到保证自动保存文件的数量符合maxAutoSaveCount的要求
98 | while (index.Count > maxAutoSaveCount)
99 | {
100 | var fileInfo = index.FilesInfo[0];
101 | File.Delete(fileInfo.FileName!);
102 | index.FilesInfo.RemoveAt(0);
103 | index.Count--;
104 | }
105 |
106 | // 将变更存储到index文件中
107 | UpdateIndexFile();
108 | }
109 |
110 | public void SetMaxAutoSaveCount(int maxAutoSaveCount)
111 | {
112 | this.maxAutoSaveCount = maxAutoSaveCount;
113 | Console.WriteLine("maxAutoSaveCount:" + maxAutoSaveCount);
114 | }
115 |
116 |
117 | private void LoadOrCreateIndexFile()
118 | {
119 | CreateDirectoryIfNotExists(curPath!);
120 | KeepDirectoryHidden(curPath!);
121 |
122 | var indexFilePath = curPath + "/.index.json";
123 | if (!File.Exists(indexFilePath))
124 | {
125 | index = new AutoSaveIndex();
126 | UpdateIndexFile();
127 | }
128 | else
129 | {
130 | LoadIndexFromFile();
131 | }
132 | }
133 |
134 |
135 | ///
136 | /// 若文件夹不存在则创建
137 | ///
138 | ///
139 | private void CreateDirectoryIfNotExists(string dirPath)
140 | {
141 | if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
142 | }
143 |
144 | ///
145 | /// 保证文件夹处于隐藏状态
146 | ///
147 | ///
148 | private void KeepDirectoryHidden(string dirPath)
149 | {
150 | var dirInfo = new DirectoryInfo(dirPath);
151 |
152 | if ((dirInfo.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden)
153 | dirInfo.Attributes = FileAttributes.Hidden;
154 | }
155 |
156 | ///
157 | /// 将saveIndex存储到index文件中
158 | ///
159 | private void UpdateIndexFile()
160 | {
161 | var indexPath = curPath + "/.index.json";
162 |
163 | var jsonText = JsonConvert.SerializeObject(index);
164 | File.WriteAllText(indexPath, jsonText);
165 | }
166 |
167 | ///
168 | /// 从index文件读取saveIndex
169 | ///
170 | private void LoadIndexFromFile()
171 | {
172 | var indexPath = curPath + "/.index.json";
173 |
174 | var jsonText = File.ReadAllText(indexPath);
175 | index = JsonConvert.DeserializeObject(jsonText);
176 | }
177 |
178 | ///
179 | /// 获取当前时间字符串
180 | ///
181 | ///
182 | private string GetCurrentTimeString()
183 | {
184 | var now = DateTime.Now;
185 |
186 | return now.Year + "-" +
187 | now.Month + "-" +
188 | now.Day + "_" +
189 | now.Hour + "-" +
190 | now.Minute + "-" +
191 | now.Second;
192 | }
193 | }
--------------------------------------------------------------------------------
/SubWindow/AutoSaveRecover.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
48 |
50 |
52 |
54 |
55 |
57 |
59 |
61 |
63 |
65 |
67 |
69 |
71 |
73 |
75 |
77 |
79 |
80 |
81 |
85 |
86 |
88 |
90 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/SubWindow/EditorSettingPanel.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using System.Windows.Input;
6 | using System.Windows.Interop;
7 | using System.Windows.Media;
8 | using WPFLocalizeExtension.Engine;
9 |
10 | namespace MajdataEdit;
11 |
12 | ///
13 | /// BPMtap.xaml 的交互逻辑
14 | ///
15 | public partial class EditorSettingPanel : Window
16 | {
17 | private readonly bool dialogMode;
18 | private readonly string[] langList = new string[3] { "zh-CN", "en-US", "ja" }; // 语言列表
19 | private bool saveFlag;
20 |
21 | public EditorSettingPanel(bool _dialogMode = false)
22 | {
23 | dialogMode = _dialogMode;
24 | InitializeComponent();
25 |
26 | if (dialogMode) Cancel_Button.IsEnabled = false;
27 | }
28 |
29 | private void Window_Loaded(object sender, RoutedEventArgs e)
30 | {
31 | var window = (MainWindow)Owner;
32 |
33 | var curLang = window.editorSetting!.Language;
34 | var boxIndex = -1;
35 | for (var i = 0; i < langList.Length; i++)
36 | if (curLang == langList[i])
37 | {
38 | boxIndex = i;
39 | break;
40 | }
41 |
42 | if (boxIndex == -1)
43 | // 如果没有语言设置 或者语言未知 就自动切换到English
44 | boxIndex = 1;
45 |
46 | LanguageComboBox.SelectedIndex = boxIndex;
47 |
48 | RenderModeComboBox.SelectedIndex = window.editorSetting.RenderMode;
49 |
50 | ViewerCover.Text = window.editorSetting.backgroundCover.ToString();
51 | ViewerSpeed.Text = window.editorSetting.playSpeed.ToString("F1"); // 转化为形如"7.0", "9.5"这样的速度
52 | ViewerTouchSpeed.Text = window.editorSetting.touchSpeed.ToString("F1");
53 | ComboDisplay.SelectedIndex = Array.IndexOf(
54 | Enum.GetValues(window.editorSetting.comboStatusType.GetType()),
55 | window.editorSetting.comboStatusType
56 | );
57 | if (ComboDisplay.SelectedIndex < 0)
58 | ComboDisplay.SelectedIndex = 0;
59 |
60 | PlayMethod.SelectedIndex = Array.IndexOf(
61 | Enum.GetValues(window.editorSetting.editorPlayMethod.GetType()),
62 | window.editorSetting.editorPlayMethod
63 | );
64 | if(PlayMethod.SelectedIndex < 0)
65 | PlayMethod.SelectedIndex = 0;
66 |
67 | ChartRefreshDelay.Text = window.editorSetting.ChartRefreshDelay.ToString();
68 | AutoUpdate.IsChecked = window.editorSetting.AutoCheckUpdate;
69 | SmoothSlideAnime.IsChecked = window.editorSetting.SmoothSlideAnime;
70 | SyntaxCheckLevel.SelectedIndex = window.editorSetting.SyntaxCheckLevel;
71 | }
72 |
73 | private void LanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
74 | {
75 | //LanguageComboBox.SelectedIndex
76 | LocalizeDictionary.Instance.Culture = new CultureInfo(langList[LanguageComboBox.SelectedIndex]);
77 | }
78 |
79 | private void RenderModeComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
80 | {
81 | RenderOptions.ProcessRenderMode =
82 | RenderModeComboBox.SelectedIndex == 0 ? RenderMode.Default : RenderMode.SoftwareOnly;
83 | }
84 |
85 | private void ViewerCover_MouseWheel(object sender, MouseWheelEventArgs e)
86 | {
87 | var offset = float.Parse(ViewerCover.Text);
88 | offset += e.Delta > 0 ? 0.1f : -0.1f;
89 | ViewerCover.Text = offset.ToString();
90 | }
91 |
92 | private void ViewerSpeed_MouseWheel(object sender, MouseWheelEventArgs e)
93 | {
94 | var offset = float.Parse(ViewerSpeed.Text);
95 | offset += e.Delta > 0 ? 0.5f : -0.5f;
96 | ViewerSpeed.Text = offset.ToString();
97 | }
98 |
99 | private void ViewerTouchSpeed_MouseWheel(object sender, MouseWheelEventArgs e)
100 | {
101 | var offset = float.Parse(ViewerTouchSpeed.Text);
102 | offset += e.Delta > 0 ? 0.5f : -0.5f;
103 | ViewerTouchSpeed.Text = offset.ToString();
104 | }
105 |
106 | private void Save_Button_Click(object sender, RoutedEventArgs e)
107 | {
108 | var window = (MainWindow)Owner;
109 | window.editorSetting!.Language = langList[LanguageComboBox.SelectedIndex];
110 | window.editorSetting!.RenderMode = RenderModeComboBox.SelectedIndex;
111 | window.editorSetting!.backgroundCover = float.Parse(ViewerCover.Text);
112 | window.editorSetting!.playSpeed = float.Parse(ViewerSpeed.Text);
113 | window.editorSetting!.touchSpeed = float.Parse(ViewerTouchSpeed.Text);
114 | window.editorSetting!.ChartRefreshDelay = int.Parse(ChartRefreshDelay.Text);
115 | window.editorSetting!.AutoCheckUpdate = (bool) AutoUpdate.IsChecked!;
116 | window.editorSetting!.SmoothSlideAnime = (bool) SmoothSlideAnime.IsChecked!;
117 | window.editorSetting!.editorPlayMethod = (EditorPlayMethod)PlayMethod.SelectedIndex;
118 | window.editorSetting!.SyntaxCheckLevel = SyntaxCheckLevel.SelectedIndex;
119 | // window.editorSetting.isComboEnabled = (bool) ComboDisplay.IsChecked!;
120 | window.editorSetting!.comboStatusType = (EditorComboIndicator)Enum.GetValues(
121 | window.editorSetting!.comboStatusType.GetType()
122 | ).GetValue(ComboDisplay.SelectedIndex)!;
123 | window.SaveEditorSetting();
124 |
125 | window.ViewerCover.Content = window.editorSetting.backgroundCover.ToString();
126 | window.ViewerSpeed.Content = window.editorSetting.playSpeed.ToString("F1"); // 转化为形如"7.0", "9.5"这样的速度
127 | window.ViewerTouchSpeed.Content = window.editorSetting.touchSpeed.ToString("F1");
128 | window.chartChangeTimer.Interval = window.editorSetting.ChartRefreshDelay;
129 |
130 |
131 | saveFlag = true;
132 | window.SyntaxCheck();
133 | Close();
134 | }
135 |
136 | private void Cancel_Button_Click(object sender, RoutedEventArgs e)
137 | {
138 | Close();
139 | }
140 |
141 | private void Window_Closing(object sender, CancelEventArgs e)
142 | {
143 | if (!saveFlag)
144 | {
145 | // 取消或直接关闭窗口
146 | if (dialogMode)
147 | {
148 | // 模态窗口状态下 则阻止关闭
149 | e.Cancel = true;
150 | MessageBox.Show(MainWindow.GetLocalizedString("NoEditorSetting"),
151 | MainWindow.GetLocalizedString("Error"));
152 | }
153 | else
154 | {
155 | LocalizeDictionary.Instance.Culture = new CultureInfo(((MainWindow)Owner).editorSetting!.Language);
156 | }
157 | }
158 | else
159 | {
160 | if (dialogMode) DialogResult = true;
161 | }
162 | }
163 | }
--------------------------------------------------------------------------------
/Langs/Langs.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | text/microsoft-resx
112 |
113 |
114 | 2.0
115 |
116 |
117 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
118 | PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
123 | PublicKeyToken=b77a5c561934e089
124 |
125 |
126 |
127 | sdadad
128 |
129 |
--------------------------------------------------------------------------------
/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | text/microsoft-resx
112 |
113 |
114 | 2.0
115 |
116 |
117 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
118 | PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
123 | PublicKeyToken=b77a5c561934e089
124 |
125 |
126 |
128 |
129 | ..\image\bg_dummy.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral,
130 | PublicKeyToken=b03f5f7f11d50a3a
131 |
132 |
133 |
134 | shabi
135 |
136 |
--------------------------------------------------------------------------------
/.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 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | [Aa][Rr][Mm]/
24 | [Aa][Rr][Mm]64/
25 | bld/
26 | [Bb]in/
27 | [Oo]bj/
28 | [Ll]og/
29 |
30 | # Visual Studio 2015/2017 cache/options directory
31 | .vs/
32 | # Uncomment if you have tasks that create the project's static files in wwwroot
33 | #wwwroot/
34 |
35 | # Visual Studio 2017 auto generated files
36 | Generated\ Files/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # Benchmark Results
52 | BenchmarkDotNet.Artifacts/
53 |
54 | # .NET Core
55 | project.lock.json
56 | project.fragment.lock.json
57 | artifacts/
58 |
59 | # StyleCop
60 | StyleCopReport.xml
61 |
62 | # Files built by Visual Studio
63 | *_i.c
64 | *_p.c
65 | *_h.h
66 | *.ilk
67 | *.meta
68 | *.obj
69 | *.iobj
70 | *.pch
71 | *.pdb
72 | *.ipdb
73 | *.pgc
74 | *.pgd
75 | *.rsp
76 | *.sbr
77 | *.tlb
78 | *.tli
79 | *.tlh
80 | *.tmp
81 | *.tmp_proj
82 | *_wpftmp.csproj
83 | *.log
84 | *.vspscc
85 | *.vssscc
86 | .builds
87 | *.pidb
88 | *.svclog
89 | *.scc
90 |
91 | # Chutzpah Test files
92 | _Chutzpah*
93 |
94 | # Visual C++ cache files
95 | ipch/
96 | *.aps
97 | *.ncb
98 | *.opendb
99 | *.opensdf
100 | *.sdf
101 | *.cachefile
102 | *.VC.db
103 | *.VC.VC.opendb
104 |
105 | # Visual Studio profiler
106 | *.psess
107 | *.vsp
108 | *.vspx
109 | *.sap
110 |
111 | # Visual Studio Trace Files
112 | *.e2e
113 |
114 | # TFS 2012 Local Workspace
115 | $tf/
116 |
117 | # Guidance Automation Toolkit
118 | *.gpState
119 |
120 | # ReSharper is a .NET coding add-in
121 | _ReSharper*/
122 | *.[Rr]e[Ss]harper
123 | *.DotSettings.user
124 |
125 | # JustCode is a .NET coding add-in
126 | .JustCode
127 |
128 | # TeamCity is a build add-in
129 | _TeamCity*
130 |
131 | # DotCover is a Code Coverage Tool
132 | *.dotCover
133 |
134 | # AxoCover is a Code Coverage Tool
135 | .axoCover/*
136 | !.axoCover/settings.json
137 |
138 | # Visual Studio code coverage results
139 | *.coverage
140 | *.coveragexml
141 |
142 | # NCrunch
143 | _NCrunch_*
144 | .*crunch*.local.xml
145 | nCrunchTemp_*
146 |
147 | # MightyMoose
148 | *.mm.*
149 | AutoTest.Net/
150 |
151 | # Web workbench (sass)
152 | .sass-cache/
153 |
154 | # Installshield output folder
155 | [Ee]xpress/
156 |
157 | # DocProject is a documentation generator add-in
158 | DocProject/buildhelp/
159 | DocProject/Help/*.HxT
160 | DocProject/Help/*.HxC
161 | DocProject/Help/*.hhc
162 | DocProject/Help/*.hhk
163 | DocProject/Help/*.hhp
164 | DocProject/Help/Html2
165 | DocProject/Help/html
166 |
167 | # Click-Once directory
168 | publish/
169 |
170 | # Publish Web Output
171 | *.[Pp]ublish.xml
172 | *.azurePubxml
173 | # Note: Comment the next line if you want to checkin your web deploy settings,
174 | # but database connection strings (with potential passwords) will be unencrypted
175 | *.pubxml
176 | *.publishproj
177 |
178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
179 | # checkin your Azure Web App publish settings, but sensitive information contained
180 | # in these scripts will be unencrypted
181 | PublishScripts/
182 |
183 | # NuGet Packages
184 | *.nupkg
185 | # The packages folder can be ignored because of Package Restore
186 | **/[Pp]ackages/*
187 | # except build/, which is used as an MSBuild target.
188 | !**/[Pp]ackages/build/
189 | # Uncomment if necessary however generally it will be regenerated when needed
190 | #!**/[Pp]ackages/repositories.config
191 | # NuGet v3's project.json files produces more ignorable files
192 | *.nuget.props
193 | *.nuget.targets
194 |
195 | # Microsoft Azure Build Output
196 | csx/
197 | *.build.csdef
198 |
199 | # Microsoft Azure Emulator
200 | ecf/
201 | rcf/
202 |
203 | # Windows Store app package directories and files
204 | AppPackages/
205 | BundleArtifacts/
206 | Package.StoreAssociation.xml
207 | _pkginfo.txt
208 | *.appx
209 |
210 | # Visual Studio cache files
211 | # files ending in .cache can be ignored
212 | *.[Cc]ache
213 | # but keep track of directories ending in .cache
214 | !?*.[Cc]ache/
215 |
216 | # Others
217 | ClientBin/
218 | ~$*
219 | *~
220 | *.dbmdl
221 | *.dbproj.schemaview
222 | *.jfm
223 | *.pfx
224 | *.publishsettings
225 | orleans.codegen.cs
226 |
227 | # Including strong name files can present a security risk
228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
229 | #*.snk
230 |
231 | # Since there are multiple workflows, uncomment next line to ignore bower_components
232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
233 | #bower_components/
234 |
235 | # RIA/Silverlight projects
236 | Generated_Code/
237 |
238 | # Backup & report files from converting an old project file
239 | # to a newer Visual Studio version. Backup files are not needed,
240 | # because we have git ;-)
241 | _UpgradeReport_Files/
242 | Backup*/
243 | UpgradeLog*.XML
244 | UpgradeLog*.htm
245 | ServiceFabricBackup/
246 | *.rptproj.bak
247 |
248 | # SQL Server files
249 | *.mdf
250 | *.ldf
251 | *.ndf
252 |
253 | # Business Intelligence projects
254 | *.rdl.data
255 | *.bim.layout
256 | *.bim_*.settings
257 | *.rptproj.rsuser
258 | *- Backup*.rdl
259 |
260 | # Microsoft Fakes
261 | FakesAssemblies/
262 |
263 | # GhostDoc plugin setting file
264 | *.GhostDoc.xml
265 |
266 | # Node.js Tools for Visual Studio
267 | .ntvs_analysis.dat
268 | node_modules/
269 |
270 | # Visual Studio 6 build log
271 | *.plg
272 |
273 | # Visual Studio 6 workspace options file
274 | *.opt
275 |
276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
277 | *.vbw
278 |
279 | # Visual Studio LightSwitch build output
280 | **/*.HTMLClient/GeneratedArtifacts
281 | **/*.DesktopClient/GeneratedArtifacts
282 | **/*.DesktopClient/ModelManifest.xml
283 | **/*.Server/GeneratedArtifacts
284 | **/*.Server/ModelManifest.xml
285 | _Pvt_Extensions
286 |
287 | # Paket dependency manager
288 | .paket/paket.exe
289 | paket-files/
290 |
291 | # FAKE - F# Make
292 | .fake/
293 |
294 | # JetBrains Rider
295 | .idea/
296 | *.sln.iml
297 |
298 | # CodeRush personal settings
299 | .cr/personal
300 |
301 | # Python Tools for Visual Studio (PTVS)
302 | __pycache__/
303 | *.pyc
304 |
305 | # Cake - Uncomment if you are using it
306 | # tools/**
307 | # !tools/packages.config
308 |
309 | # Tabs Studio
310 | *.tss
311 |
312 | # Telerik's JustMock configuration file
313 | *.jmconfig
314 |
315 | # BizTalk build output
316 | *.btp.cs
317 | *.btm.cs
318 | *.odx.cs
319 | *.xsd.cs
320 |
321 | # OpenCover UI analysis results
322 | OpenCover/
323 |
324 | # Azure Stream Analytics local run output
325 | ASALocalRun/
326 |
327 | # MSBuild Binary and Structured Log
328 | *.binlog
329 |
330 | # NVidia Nsight GPU debugger configuration file
331 | *.nvuser
332 |
333 | # MFractors (Xamarin productivity tool) working folder
334 | .mfractor/
335 |
336 | # Local History for Visual Studio
337 | .localhistory/
338 |
339 | # BeatPulse healthcheck temp database
340 | healthchecksdb
--------------------------------------------------------------------------------
/SubWindow/SoundSetting.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Timers;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using Un4seen.Bass;
6 | using Timer = System.Timers.Timer;
7 |
8 | namespace MajdataEdit;
9 |
10 | ///
11 | /// SoundSetting.xaml 的交互逻辑
12 | ///
13 | public partial class SoundSetting : Window
14 | {
15 | private readonly MainWindow MainWindow;
16 | private readonly Dictionary SliderValueBindingMap = new(); // Slider和ValueLabel的绑定关系
17 |
18 | private readonly Timer UpdateLevelTimer = new(1);
19 |
20 | public SoundSetting()
21 | {
22 | MainWindow = (Application.Current.Windows
23 | .Cast()
24 | .FirstOrDefault(window => window is MainWindow) as MainWindow)!;
25 |
26 | InitializeComponent();
27 | }
28 |
29 | private void SoundSettingWindow_Loaded(object sender, RoutedEventArgs e)
30 | {
31 | SliderValueBindingMap.Add(BGM_Slider, BGM_Value);
32 | SliderValueBindingMap.Add(Answer_Slider, Answer_Value);
33 | SliderValueBindingMap.Add(Judge_Slider, Judge_Value);
34 | SliderValueBindingMap.Add(Break_Slider, Break_Value);
35 | SliderValueBindingMap.Add(BreakSlide_Slider, BreakSlide_Value);
36 | SliderValueBindingMap.Add(Slide_Slider, Slide_Value);
37 | SliderValueBindingMap.Add(EX_Slider, EX_Value);
38 | SliderValueBindingMap.Add(Touch_Slider, Touch_Value);
39 | SliderValueBindingMap.Add(Hanabi_Slider, Hanabi_Value);
40 |
41 | SetSlider(BGM_Slider, MainWindow.bgmStream, MainWindow.trackStartStream, MainWindow.allperfectStream,
42 | MainWindow.clockStream);
43 | SetSlider(Answer_Slider, MainWindow.answerStream);
44 | SetSlider(Judge_Slider, MainWindow.judgeStream);
45 | SetSlider(Break_Slider, MainWindow.breakStream, MainWindow.judgeBreakStream);
46 | SetSlider(BreakSlide_Slider, MainWindow.breakSlideStream, MainWindow.judgeBreakSlideStream);
47 | SetSlider(Slide_Slider, MainWindow.slideStream, MainWindow.breakSlideStartStream);
48 | SetSlider(EX_Slider, MainWindow.judgeExStream);
49 | SetSlider(Touch_Slider, MainWindow.touchStream);
50 | SetSlider(Hanabi_Slider, MainWindow.hanabiStream, MainWindow.holdRiserStream);
51 |
52 | UpdateLevelTimer.AutoReset = true;
53 | UpdateLevelTimer.Elapsed += UpdateLevelTimer_Elapsed;
54 | UpdateLevelTimer.Start();
55 | }
56 |
57 | private void UpdateLevelTimer_Elapsed(object? sender, ElapsedEventArgs e)
58 | {
59 | Dispatcher.Invoke(() =>
60 | {
61 | UpdateProgressBar(BGM_Level, MainWindow.bgmStream, MainWindow.trackStartStream, MainWindow.allperfectStream,
62 | MainWindow.clockStream);
63 | UpdateProgressBar(Answer_Level, MainWindow.answerStream);
64 | UpdateProgressBar(Judge_Level, MainWindow.judgeStream);
65 | UpdateProgressBar(Break_Level, MainWindow.breakStream, MainWindow.judgeBreakStream);
66 | UpdateProgressBar(BreakSlide_Level, MainWindow.breakSlideStream, MainWindow.judgeBreakSlideStream);
67 | UpdateProgressBar(Slide_Level, MainWindow.slideStream, MainWindow.breakSlideStartStream);
68 | UpdateProgressBar(EX_Level, MainWindow.judgeExStream);
69 | UpdateProgressBar(Touch_Level, MainWindow.touchStream);
70 | UpdateProgressBar(Hanabi_Level, MainWindow.hanabiStream, MainWindow.holdRiserStream);
71 | });
72 | }
73 |
74 | private void UpdateProgressBar(ProgressBar bar, params int[] channels)
75 | {
76 | var values = new double[channels.Length];
77 | var ampLevel = 0f;
78 | for (var i = 0; i < channels.Length; i++)
79 | {
80 | Bass.BASS_ChannelGetAttribute(channels[i], BASSAttribute.BASS_ATTRIB_VOL, ref ampLevel);
81 | values[i] = Utils.LevelToDB(Utils.LowWord(Bass.BASS_ChannelGetLevel(channels[i])) * ampLevel, 32768) + 40;
82 | }
83 |
84 | var value = values.Max();
85 | if (!double.IsNaN(value) && !double.IsInfinity(value)) bar.Value = value * ampLevel;
86 | if (double.IsNegativeInfinity(value)) bar.Value = bar.Minimum;
87 | if (double.IsPositiveInfinity(value)) bar.Value = bar.Maximum;
88 | if (double.IsNaN(value)) bar.Value -= 1;
89 | }
90 |
91 | private void SetSlider(Slider slider, params int[] channels)
92 | {
93 | var ampLevel = 0f;
94 | foreach (var channel in channels)
95 | Bass.BASS_ChannelGetAttribute(channel, BASSAttribute.BASS_ATTRIB_VOL, ref ampLevel);
96 | slider.Value = ampLevel;
97 | SliderValueBindingMap[slider].Content = slider.Value.ToString("P0");
98 |
99 | void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
100 | {
101 | var sld = (Slider)sender;
102 | foreach (var channel in channels)
103 | Bass.BASS_ChannelSetAttribute(channel, BASSAttribute.BASS_ATTRIB_VOL, (float)sld.Value);
104 |
105 | SliderValueBindingMap[sld].Content = sld.Value.ToString("P0");
106 | }
107 |
108 | slider.ValueChanged += Slider_ValueChanged;
109 | }
110 |
111 | private void SoundSettingWindow_Closing(object sender, CancelEventArgs e)
112 | {
113 | UpdateLevelTimer.Stop();
114 | UpdateLevelTimer.Dispose();
115 | }
116 |
117 | private void BtnSetDefault_Click(object sender, RoutedEventArgs e)
118 | {
119 | Bass.BASS_ChannelGetAttribute(MainWindow.bgmStream, BASSAttribute.BASS_ATTRIB_VOL,
120 | ref MainWindow.editorSetting!.Default_BGM_Level);
121 | Bass.BASS_ChannelGetAttribute(MainWindow.answerStream, BASSAttribute.BASS_ATTRIB_VOL,
122 | ref MainWindow.editorSetting!.Default_Answer_Level);
123 | Bass.BASS_ChannelGetAttribute(MainWindow.judgeStream, BASSAttribute.BASS_ATTRIB_VOL,
124 | ref MainWindow.editorSetting!.Default_Judge_Level);
125 | Bass.BASS_ChannelGetAttribute(MainWindow.breakStream, BASSAttribute.BASS_ATTRIB_VOL,
126 | ref MainWindow.editorSetting!.Default_Break_Level);
127 | Bass.BASS_ChannelGetAttribute(MainWindow.breakSlideStream, BASSAttribute.BASS_ATTRIB_VOL,
128 | ref MainWindow.editorSetting!.Default_Break_Slide_Level);
129 | Bass.BASS_ChannelGetAttribute(MainWindow.slideStream, BASSAttribute.BASS_ATTRIB_VOL,
130 | ref MainWindow.editorSetting!.Default_Slide_Level);
131 | Bass.BASS_ChannelGetAttribute(MainWindow.judgeExStream, BASSAttribute.BASS_ATTRIB_VOL,
132 | ref MainWindow.editorSetting!.Default_Ex_Level);
133 | Bass.BASS_ChannelGetAttribute(MainWindow.touchStream, BASSAttribute.BASS_ATTRIB_VOL,
134 | ref MainWindow.editorSetting!.Default_Touch_Level);
135 | Bass.BASS_ChannelGetAttribute(MainWindow.hanabiStream, BASSAttribute.BASS_ATTRIB_VOL,
136 | ref MainWindow.editorSetting!.Default_Hanabi_Level);
137 | MainWindow.SaveEditorSetting();
138 | MessageBox.Show(MainWindow.GetLocalizedString("SetVolumeDefaultSuccess"));
139 | }
140 |
141 | private void BtnSetToDefault_Click(object sender, RoutedEventArgs e)
142 | {
143 | BGM_Slider.Value = MainWindow.editorSetting!.Default_BGM_Level;
144 | Answer_Slider.Value = MainWindow.editorSetting!.Default_Answer_Level;
145 | Judge_Slider.Value = MainWindow.editorSetting!.Default_Judge_Level;
146 | Break_Slider.Value = MainWindow.editorSetting!.Default_Break_Level;
147 | BreakSlide_Slider.Value = MainWindow.editorSetting!.Default_Break_Slide_Level;
148 | Slide_Slider.Value = MainWindow.editorSetting!.Default_Slide_Level;
149 | EX_Slider.Value = MainWindow.editorSetting!.Default_Ex_Level;
150 | Touch_Slider.Value = MainWindow.editorSetting!.Default_Touch_Level;
151 | Hanabi_Slider.Value = MainWindow.editorSetting!.Default_Hanabi_Level;
152 | }
153 | }
--------------------------------------------------------------------------------
/SubWindow/AutoSaveRecover.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Documents;
5 | using MajdataEdit.AutoSaveModule;
6 |
7 | namespace MajdataEdit;
8 |
9 | ///
10 | /// AutoSaveRecover.xaml 的交互逻辑
11 | ///
12 | public partial class AutoSaveRecover : Window
13 | {
14 | private readonly IAutoSaveRecoverer Recoverer = new AutoSaveRecoverer();
15 | private readonly List> RecoverList = new();
16 | private int currentSelectedIndex = -1;
17 | private int currentSelectedLevel = -1;
18 |
19 | public AutoSaveRecover()
20 | {
21 | InitializeComponent();
22 | InitializeAutosaveList();
23 | }
24 |
25 | private void InitializeAutosaveList()
26 | {
27 | var fileInfos = Recoverer.GetLocalAutoSaves();
28 |
29 | var i = 0;
30 |
31 | foreach (var fileInfo in fileInfos)
32 | {
33 | AddNewItem(fileInfo, i, false);
34 | i++;
35 | }
36 |
37 | fileInfos = Recoverer.GetGlobalAutoSaves();
38 |
39 | foreach (var fileInfo in fileInfos)
40 | {
41 | AddNewItem(fileInfo, i, true);
42 | i++;
43 | }
44 | }
45 |
46 | ///
47 | /// 添加一个条目
48 | ///
49 | ///
50 | ///
51 | ///
52 | private void AddNewItem(AutoSaveIndex.FileInfo fileInfo, int i, bool isGlobal)
53 | {
54 | if (!File.Exists(fileInfo.FileName))
55 | {
56 | Console.WriteLine(fileInfo.FileName + " is not exists! skip");
57 | return;
58 | }
59 |
60 | var fumenInfo = Recoverer.GetFumenInfos(fileInfo.FileName);
61 | RecoverList.Add(new Tuple(fileInfo, fumenInfo));
62 |
63 |
64 | var item = new ListBoxItem
65 | {
66 | Name = "item" + i,
67 | Content = GetItemDisplayText(fileInfo, fumenInfo, isGlobal),
68 | ToolTip = GetItemDisplayText(fileInfo, fumenInfo, isGlobal)
69 | };
70 | item.AddHandler(ListBoxItem.SelectedEvent, new RoutedEventHandler(ListBoxItem_Selected));
71 | Autosave_Listbox.Items.Add(item);
72 | }
73 |
74 | public void ListBoxItem_Selected(object sender, RoutedEventArgs e)
75 | {
76 | var item = (ListBoxItem)sender;
77 | currentSelectedIndex = int.Parse(item.Name.Substring(4));
78 |
79 | var currentRecoverItem = RecoverList[currentSelectedIndex];
80 |
81 | Lb_Path.Content = currentRecoverItem.Item1.RawPath!.Replace('/', '\\');
82 | Lb_Title.Content = currentRecoverItem.Item2.Title;
83 | Lb_Artist.Content = currentRecoverItem.Item2.Artist;
84 | Lb_Designer.Content = currentRecoverItem.Item2.Designer;
85 |
86 | Button[] fumenButton = { Btn_Easy, Btn_Basic, Btn_Advance, Btn_Expert, Btn_Master, Btn_ReMaster, Btn_Original };
87 |
88 |
89 | for (var i = 0; i < 7; i++)
90 | {
91 | var fumenText = currentRecoverItem.Item2.Fumens[i];
92 | if (fumenText == null)
93 | {
94 | fumenButton[i].IsEnabled = false;
95 | }
96 | else
97 | {
98 | fumenText = fumenText.Trim();
99 |
100 | if (fumenText.Length == 0)
101 | fumenButton[i].IsEnabled = false;
102 | else
103 | fumenButton[i].IsEnabled = true;
104 | }
105 | }
106 |
107 | // 先检查之前选中的难度有没有谱面
108 | if (currentSelectedLevel != -1 && fumenButton[currentSelectedLevel].IsEnabled)
109 | {
110 | var content = RecoverList[currentSelectedIndex].Item2.Fumens[currentSelectedLevel];
111 | SetRtbFumenContent(content);
112 | }
113 | else
114 | {
115 | // 否则 按照Mas, ReMas, Exp, Adv, Bas, Eas, Ori的顺序尝试选中一个谱面
116 | int[] authSeq = { 4, 5, 3, 2, 1, 0, 6 };
117 | foreach (var i in authSeq)
118 | if (fumenButton[i].IsEnabled)
119 | {
120 | currentSelectedLevel = i;
121 | var content = RecoverList[currentSelectedIndex].Item2.Fumens[currentSelectedLevel];
122 | SetRtbFumenContent(content);
123 | break;
124 | }
125 | }
126 | }
127 |
128 | private string GetItemDisplayText(AutoSaveIndex.FileInfo fileInfo, FumenInfos fumenInfo, bool isGlobal)
129 | {
130 | var result = fumenInfo.Title + " ";
131 |
132 | if (isGlobal)
133 | result += "(global)";
134 | else
135 | result += "(local)";
136 |
137 | result += " - ";
138 |
139 | result += FormatUnixTime(fileInfo.SavedTime);
140 |
141 | return result;
142 | }
143 |
144 | private string FormatUnixTime(long unixTime)
145 | {
146 | var dateTime = DateTimeOffset.FromUnixTimeSeconds(unixTime);
147 |
148 | return dateTime.Hour + ":" +
149 | dateTime.Minute + ":" +
150 | dateTime.Second + " " +
151 | dateTime.Year + "/" +
152 | dateTime.Month + "/" +
153 | dateTime.Day;
154 | }
155 |
156 | private void SetRtbFumenContent(string content)
157 | {
158 | Rtb_Fumen.Document.Blocks.Clear();
159 |
160 | if (content == null) return;
161 |
162 | var lines = content.Split('\n');
163 | foreach (var line in lines)
164 | {
165 | var paragraph = new Paragraph();
166 | paragraph.Inlines.Add(line);
167 | Rtb_Fumen.Document.Blocks.Add(paragraph);
168 | }
169 | }
170 |
171 | private void Btn_Easy_Click(object sender, RoutedEventArgs e)
172 | {
173 | currentSelectedLevel = 0;
174 |
175 | var content = RecoverList[currentSelectedIndex].Item2.Fumens[currentSelectedLevel];
176 | SetRtbFumenContent(content);
177 | }
178 |
179 | private void Btn_Basic_Click(object sender, RoutedEventArgs e)
180 | {
181 | currentSelectedLevel = 1;
182 |
183 | var content = RecoverList[currentSelectedIndex].Item2.Fumens[currentSelectedLevel];
184 | SetRtbFumenContent(content);
185 | }
186 |
187 | private void Btn_Advance_Click(object sender, RoutedEventArgs e)
188 | {
189 | currentSelectedLevel = 2;
190 |
191 | var content = RecoverList[currentSelectedIndex].Item2.Fumens[currentSelectedLevel];
192 | SetRtbFumenContent(content);
193 | }
194 |
195 | private void Btn_Expert_Click(object sender, RoutedEventArgs e)
196 | {
197 | currentSelectedLevel = 3;
198 |
199 | var content = RecoverList[currentSelectedIndex].Item2.Fumens[currentSelectedLevel];
200 | SetRtbFumenContent(content);
201 | }
202 |
203 | private void Btn_Master_Click(object sender, RoutedEventArgs e)
204 | {
205 | currentSelectedLevel = 4;
206 |
207 | var content = RecoverList[currentSelectedIndex].Item2.Fumens[currentSelectedLevel];
208 | SetRtbFumenContent(content);
209 | }
210 |
211 | private void Btn_ReMaster_Click(object sender, RoutedEventArgs e)
212 | {
213 | currentSelectedLevel = 5;
214 |
215 | var content = RecoverList[currentSelectedIndex].Item2.Fumens[currentSelectedLevel];
216 | SetRtbFumenContent(content);
217 | }
218 |
219 | private void Btn_Original_Click(object sender, RoutedEventArgs e)
220 | {
221 | currentSelectedLevel = 6;
222 |
223 | var content = RecoverList[currentSelectedIndex].Item2.Fumens[currentSelectedLevel];
224 | SetRtbFumenContent(content);
225 | }
226 |
227 | private void Btn_Recover_Click(object sender, RoutedEventArgs e)
228 | {
229 | var currentItem = RecoverList[currentSelectedIndex];
230 |
231 | var result = MessageBox.Show(
232 | string.Format(MainWindow.GetLocalizedString("RecoveryDoubleCheck"),
233 | FormatUnixTime(currentItem.Item1.SavedTime), currentItem.Item1.RawPath),
234 | MainWindow.GetLocalizedString("Attention"),
235 | MessageBoxButton.YesNo
236 | );
237 |
238 | if (result == MessageBoxResult.No) return;
239 |
240 | Recoverer.RecoverFile(currentItem.Item1);
241 | ((MainWindow)Owner).OpenFile(currentItem.Item1.RawPath!);
242 | Close();
243 | }
244 |
245 | private void Btn_Cancel_Click(object sender, RoutedEventArgs e)
246 | {
247 | Close();
248 | }
249 | }
--------------------------------------------------------------------------------
/SubWindow/EditorSettingPanel.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
32 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
67 |
70 | 中文
71 | English
72 | 日本語
73 |
74 |
75 |
77 |
80 |
81 |
82 |
83 |
84 |
86 |
89 |
90 |
92 |
97 |
98 |
100 |
104 |
105 |
107 |
111 |
112 |
114 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
130 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
140 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
152 |
162 |
163 |
164 |
165 |
166 |
168 |
169 |
170 |
172 |
177 |
182 |
183 |
184 |
185 |
186 |
--------------------------------------------------------------------------------
/Mirror.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Text.RegularExpressions;
3 |
4 | namespace MajdataEdit;
5 |
6 | //小额负责的部分哟
7 | internal static class Mirror
8 | {
9 | public enum HandleType
10 | {
11 | LRMirror,
12 | UDMirror,
13 | HalfRotation,
14 | Rotation45,
15 | CcwRotation45
16 | }
17 |
18 | private static readonly Dictionary MIRROR_LEFT_RIGHT_MAP = new()
19 | {
20 | { '1', '8' },
21 | { '2', '7' },
22 | { '3', '6' },
23 | { '4', '5' },
24 | { '5', '4' },
25 | { '6', '3' },
26 | { '7', '2' },
27 | { '8', '1' },
28 | { 'q', 'p' },
29 | { 'p', 'q' },
30 | { '<', '>' },
31 | { '>', '<' },
32 | { 'z', 's' },
33 | { 's', 'z' }
34 | };
35 |
36 | // 当遇到这些字符时 使用特殊的映射表
37 | private static readonly HashSet MIRROR_SPECIAL_PREFIX = new() { 'D', 'E' };
38 |
39 | private static readonly Dictionary MIRROR_LEFT_RIGHT_SPECIAL_MAP = new()
40 | {
41 | { '8', '2' },
42 | { '2', '8' },
43 | { '3', '7' },
44 | { '7', '3' },
45 | { '4', '6' },
46 | { '6', '4' },
47 | { '1', '1' },
48 | { '5', '5' }
49 | };
50 |
51 | private static readonly Dictionary MIRROR_UPSIDE_DOWN_MAP = new()
52 | {
53 | { '4', '1' },
54 | { '5', '8' },
55 | { '6', '7' },
56 | { '3', '2' },
57 | { '7', '6' },
58 | { '2', '3' },
59 | { '8', '5' },
60 | { '1', '4' },
61 | { 'q', 'p' },
62 | { 'p', 'q' },
63 | { 'z', 's' },
64 | { 's', 'z' }
65 | };
66 |
67 | private static readonly Dictionary MIRROR_UPSIDE_DOWN_SPECIAL_MAP = new()
68 | {
69 | { '4', '2' },
70 | { '2', '4' },
71 | { '1', '5' },
72 | { '5', '1' },
73 | { '8', '6' },
74 | { '6', '8' },
75 | { '3', '3' },
76 | { '7', '7' }
77 | };
78 |
79 | private static readonly Dictionary ROTATE_CW_45_MAP = new()
80 | {
81 | { '8', '1' },
82 | { '7', '8' },
83 | { '6', '7' },
84 | { '5', '6' },
85 | { '4', '5' },
86 | { '3', '4' },
87 | { '2', '3' },
88 | { '1', '2' }
89 | };
90 |
91 | private static readonly Dictionary ROTATE_CCW_45_MAP = new()
92 | {
93 | { '1', '8' },
94 | { '2', '1' },
95 | { '3', '2' },
96 | { '4', '3' },
97 | { '5', '4' },
98 | { '6', '5' },
99 | { '7', '6' },
100 | { '8', '7' }
101 | };
102 |
103 | private static readonly HashSet ROTATE_CW_45_SPECIAL_PREFIX = new() { '2', '6' };
104 |
105 | private static readonly HashSet ROTATE_CCW_45_SPECIAL_PREFIX = new() { '3', '7' };
106 |
107 | private static readonly Dictionary ROTATE_45_SPECIAL_MAP = new()
108 | {
109 | { '<', '>' },
110 | { '>', '<' }
111 | };
112 |
113 | private static readonly string HS_SEQUENCE = "'))
171 | {
172 | if (isPartIgnored)
173 | {
174 | // 需要忽略的段落 直接原样加进去就行了
175 | resultString.Append(curPart.ToString());
176 | }
177 | else
178 | {
179 | // 需要镜像计算的段落 调用子方法进行转换
180 | resultString.Append(NoteMirrorPart(curPart.ToString(), type));
181 | }
182 |
183 | isPartIgnored = false;
184 | hsStatus = 0;
185 | curPart.Clear();
186 | }
187 | }
188 |
189 | // 处理完以后可能还会有剩余的没做转换(可能结尾的逗号没选进去) 也要处理一下
190 | if (curPart.Length > 0)
191 | {
192 | if (isPartIgnored)
193 | {
194 | // 需要忽略的段落 直接原样加进去就行了
195 | resultString.Append(curPart.ToString());
196 | }
197 | else
198 | {
199 | // 需要镜像计算的段落 调用子方法进行转换
200 | resultString.Append(NoteMirrorPart(curPart.ToString(), type));
201 | }
202 | }
203 |
204 | return resultString.ToString();
205 | }
206 |
207 | private static string NoteMirrorPart(string str, HandleType type)
208 | {
209 | switch (type)
210 | {
211 | case HandleType.LRMirror:
212 | str = NormalMirrorPart(str, MIRROR_LEFT_RIGHT_MAP, MIRROR_LEFT_RIGHT_SPECIAL_MAP, MIRROR_SPECIAL_PREFIX);
213 | break;
214 | case HandleType.UDMirror:
215 | str = NormalMirrorPart(str, MIRROR_UPSIDE_DOWN_MAP, MIRROR_UPSIDE_DOWN_SPECIAL_MAP, MIRROR_SPECIAL_PREFIX);
216 | break;
217 | case HandleType.HalfRotation:
218 | // 180 = 左右+上下
219 | str = NormalMirrorPart(str, MIRROR_LEFT_RIGHT_MAP, MIRROR_LEFT_RIGHT_SPECIAL_MAP, MIRROR_SPECIAL_PREFIX);
220 | str = NormalMirrorPart(str, MIRROR_UPSIDE_DOWN_MAP, MIRROR_UPSIDE_DOWN_SPECIAL_MAP, MIRROR_SPECIAL_PREFIX);
221 | break;
222 | case HandleType.Rotation45:
223 | str = NormalMirrorPart(str, ROTATE_CW_45_MAP, ROTATE_45_SPECIAL_MAP, ROTATE_CW_45_SPECIAL_PREFIX);
224 | break;
225 | case HandleType.CcwRotation45:
226 | str = NormalMirrorPart(str, ROTATE_CCW_45_MAP, ROTATE_45_SPECIAL_MAP, ROTATE_CCW_45_SPECIAL_PREFIX);
227 | break;
228 | }
229 |
230 | return str;
231 | }
232 |
233 | private static string NormalMirrorPart(string str, Dictionary normalMap, Dictionary specialMap, HashSet specialPrefix)
234 | {
235 | StringBuilder result = new StringBuilder();
236 | bool isSpecialPrefix = false;
237 | bool isInBracket = false;
238 |
239 | foreach (char c in str)
240 | {
241 | // 空白字符忽略
242 | if (Char.IsWhiteSpace(c))
243 | {
244 | result.Append(c);
245 | continue;
246 | }
247 |
248 | // 以下是处理字符的部分
249 | if (isInBracket || c == '[')
250 | {
251 | // 进入方括号时忽略 因为这里是时长之类的设置
252 | // 注意当前字符为'['的时候也进入此分支 因为此时isInBrancket还是false 需要等到下面那个if块才会变
253 | result.Append(c);
254 | }
255 | else if (isSpecialPrefix)
256 | {
257 | // 如果前面读到一个D或者E 则使用特殊的映射
258 | isSpecialPrefix = false;
259 | if (specialMap.ContainsKey(c))
260 | {
261 | result.Append(specialMap[c]);
262 | }
263 | else if (int.TryParse(c.ToString(), out int i) && normalMap.ContainsKey(c))
264 | {
265 | result.Append(normalMap[c]);
266 | }
267 | else
268 | {
269 | result.Append(c);
270 | }
271 | }
272 | else
273 | {
274 | // 其他情况 则使用默认映射
275 | if (normalMap.ContainsKey(c))
276 | {
277 | result.Append(normalMap[c]);
278 | }
279 | else
280 | {
281 | result.Append(c);
282 | }
283 | }
284 |
285 | // 以下是记录状态的部分
286 | // 记录是否进入了方括号内(即时间)
287 | if (c == '[')
288 | {
289 | isInBracket = true;
290 | }
291 | else if (c == ']')
292 | {
293 | isInBracket = false;
294 | }
295 | // 记录是否是一个特殊前缀 若是 则下面的字符需要使用特别的映射
296 | // 在左右或上下镜像中,用于D、E区Touch的特殊处理
297 | // 在45旋转中,本意是对Simai的弱智">","<"进行特殊处理,但没有判断本Note是否为Tap
298 | if (specialPrefix.Contains(c))
299 | {
300 | isSpecialPrefix = true;
301 | }
302 | }
303 |
304 | return result.ToString();
305 | }
306 | }
--------------------------------------------------------------------------------
/SubWindow/SoundSetting.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
16 |
19 |
22 |
25 |
27 |
29 |
30 |
31 |
34 |
37 |
40 |
42 |
43 |
45 |
48 |
51 |
54 |
56 |
57 |
59 |
62 |
65 |
68 |
70 |
71 |
73 |
76 |
79 |
82 |
84 |
85 |
87 |
90 |
93 |
96 |
98 |
99 |
101 |
104 |
107 |
110 |
113 |
114 |
116 |
119 |
122 |
125 |
128 |
129 |
131 |
134 |
137 |
140 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
151 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/slide_time.json:
--------------------------------------------------------------------------------
1 | {
2 | "-": {
3 | "2": [
4 | {
5 | "area": 1,
6 | "time": 0.30836
7 | },
8 | {
9 | "area": 2,
10 | "time": 0.77108
11 | }
12 | ],
13 | "3": [
14 | {
15 | "area": 3,
16 | "time": 0.83415
17 | }
18 | ],
19 | "4": [
20 | {
21 | "area": 4,
22 | "time": 0.84112
23 | }
24 | ],
25 | "5": [
26 | {
27 | "area": 5,
28 | "time": 0.83415
29 | }
30 | ],
31 | "6": [
32 | {
33 | "area": 7,
34 | "time": 0.30836
35 | },
36 | {
37 | "area": 6,
38 | "time": 0.77108
39 | }
40 | ]
41 | },
42 | "v": {
43 | "1": [
44 | {
45 | "area": 1,
46 | "time": 0.84244
47 | }
48 | ],
49 | "2": [
50 | {
51 | "area": 2,
52 | "time": 0.84244
53 | }
54 | ],
55 | "3": [
56 | {
57 | "area": 3,
58 | "time": 0.84244
59 | }
60 | ],
61 | "5": [
62 | {
63 | "area": 5,
64 | "time": 0.84244
65 | }
66 | ],
67 | "6": [
68 | {
69 | "area": 6,
70 | "time": 0.84244
71 | }
72 | ],
73 | "7": [
74 | {
75 | "area": 7,
76 | "time": 0.84244
77 | }
78 | ]
79 | },
80 | "p": {
81 | "0": [
82 | {
83 | "area": 0,
84 | "time": 0.90878
85 | }
86 | ],
87 | "1": [
88 | {
89 | "area": 1,
90 | "time": 0.90049
91 | }
92 | ],
93 | "2": [
94 | {
95 | "area": 2,
96 | "time": 0.8839
97 | }
98 | ],
99 | "3": [
100 | {
101 | "area": 3,
102 | "time": 0.87561
103 | }
104 | ],
105 | "4": [
106 | {
107 | "area": 4,
108 | "time": 0.85902
109 | }
110 | ],
111 | "5": [
112 | {
113 | "area": 5,
114 | "time": 0.92537
115 | }
116 | ],
117 | "6": [
118 | {
119 | "area": 6,
120 | "time": 0.92537
121 | }
122 | ],
123 | "7": [
124 | {
125 | "area": 7,
126 | "time": 0.91707
127 | }
128 | ]
129 | },
130 | "q": {
131 | "0": [
132 | {
133 | "area": 0,
134 | "time": 0.90878
135 | }
136 | ],
137 | "7": [
138 | {
139 | "area": 7,
140 | "time": 0.90049
141 | }
142 | ],
143 | "6": [
144 | {
145 | "area": 6,
146 | "time": 0.8839
147 | }
148 | ],
149 | "5": [
150 | {
151 | "area": 5,
152 | "time": 0.87561
153 | }
154 | ],
155 | "4": [
156 | {
157 | "area": 4,
158 | "time": 0.85902
159 | }
160 | ],
161 | "3": [
162 | {
163 | "area": 3,
164 | "time": 0.92537
165 | }
166 | ],
167 | "2": [
168 | {
169 | "area": 2,
170 | "time": 0.92537
171 | }
172 | ],
173 | "1": [
174 | {
175 | "area": 1,
176 | "time": 0.91707
177 | }
178 | ]
179 | },
180 | "s": {
181 | "4": [
182 | {
183 | "area": 4,
184 | "time": 0.86732
185 | }
186 | ]
187 | },
188 | "z": {
189 | "4": [
190 | {
191 | "area": 4,
192 | "time": 0.86732
193 | }
194 | ]
195 | },
196 | "w": {
197 | "4": [
198 | {
199 | "area": 3,
200 | "time": 0.85073
201 | },
202 | {
203 | "area": 4,
204 | "time": 0.85073
205 | },
206 | {
207 | "area": 5,
208 | "time": 0.85073
209 | }
210 | ]
211 | },
212 | "pp": {
213 | "0": [
214 | {
215 | "area": 2,
216 | "time": 0.54836
217 | },
218 | {
219 | "area": 1,
220 | "time": 0.75745
221 | },
222 | {
223 | "area": 0,
224 | "time": 0.94982
225 | }
226 | ],
227 | "1": [
228 | {
229 | "area": 2,
230 | "time": 0.68925
231 | },
232 | {
233 | "area": 1,
234 | "time": 0.93281
235 | }
236 | ],
237 | "2": [
238 | {
239 | "area": 2,
240 | "time": 0.86562
241 | }
242 | ],
243 | "3": [
244 | {
245 | "area": 2,
246 | "time": 0.38689
247 | },
248 | {
249 | "area": 1,
250 | "time": 0.53807
251 | },
252 | {
253 | "area": 3,
254 | "time": 0.94121
255 | }
256 | ],
257 | "4": [
258 | {
259 | "area": 2,
260 | "time": 0.38689
261 | },
262 | {
263 | "area": 1,
264 | "time": 0.53807
265 | },
266 | {
267 | "area": 4,
268 | "time": 0.94121
269 | }
270 | ],
271 | "5": [
272 | {
273 | "area": 2,
274 | "time": 0.40369
275 | },
276 | {
277 | "area": 1,
278 | "time": 0.53807
279 | },
280 | {
281 | "area": 5,
282 | "time": 0.94121
283 | }
284 | ],
285 | "6": [
286 | {
287 | "area": 2,
288 | "time": 0.42048
289 | },
290 | {
291 | "area": 1,
292 | "time": 0.56326
293 | },
294 | {
295 | "area": 6,
296 | "time": 0.93281
297 | }
298 | ],
299 | "7": [
300 | {
301 | "area": 2,
302 | "time": 0.45408
303 | },
304 | {
305 | "area": 1,
306 | "time": 0.63045
307 | },
308 | {
309 | "area": 7,
310 | "time": 0.92441
311 | }
312 | ]
313 | },
314 | "qq": {
315 | "0": [
316 | {
317 | "area": 6,
318 | "time": 0.54836
319 | },
320 | {
321 | "area": 7,
322 | "time": 0.75745
323 | },
324 | {
325 | "area": 0,
326 | "time": 0.94982
327 | }
328 | ],
329 | "7": [
330 | {
331 | "area": 6,
332 | "time": 0.68925
333 | },
334 | {
335 | "area": 7,
336 | "time": 0.93281
337 | }
338 | ],
339 | "6": [
340 | {
341 | "area": 6,
342 | "time": 0.86562
343 | }
344 | ],
345 | "5": [
346 | {
347 | "area": 6,
348 | "time": 0.38689
349 | },
350 | {
351 | "area": 7,
352 | "time": 0.53807
353 | },
354 | {
355 | "area": 5,
356 | "time": 0.94121
357 | }
358 | ],
359 | "4": [
360 | {
361 | "area": 6,
362 | "time": 0.38689
363 | },
364 | {
365 | "area": 7,
366 | "time": 0.53807
367 | },
368 | {
369 | "area": 4,
370 | "time": 0.94121
371 | }
372 | ],
373 | "3": [
374 | {
375 | "area": 6,
376 | "time": 0.40369
377 | },
378 | {
379 | "area": 7,
380 | "time": 0.53807
381 | },
382 | {
383 | "area": 3,
384 | "time": 0.94121
385 | }
386 | ],
387 | "2": [
388 | {
389 | "area": 6,
390 | "time": 0.42048
391 | },
392 | {
393 | "area": 7,
394 | "time": 0.56326
395 | },
396 | {
397 | "area": 2,
398 | "time": 0.93281
399 | }
400 | ],
401 | "1": [
402 | {
403 | "area": 6,
404 | "time": 0.45408
405 | },
406 | {
407 | "area": 7,
408 | "time": 0.63045
409 | },
410 | {
411 | "area": 1,
412 | "time": 0.92441
413 | }
414 | ]
415 | },
416 | "V": {
417 | "2,4": [
418 | {
419 | "area": 1,
420 | "time": 0.15437
421 | },
422 | {
423 | "area": 2,
424 | "time": 0.37439
425 | },
426 | {
427 | "area": 3,
428 | "time": 0.64375
429 | },
430 | {
431 | "area": 4,
432 | "time": 0.87835
433 | }
434 | ],
435 | "2,5": [
436 | {
437 | "area": 1,
438 | "time": 0.12852
439 | },
440 | {
441 | "area": 2,
442 | "time": 0.32225
443 | },
444 | {
445 | "area": 5,
446 | "time": 0.90442
447 | }
448 | ],
449 | "2,6": [
450 | {
451 | "area": 1,
452 | "time": 0.11594
453 | },
454 | {
455 | "area": 2,
456 | "time": 0.30488
457 | },
458 | {
459 | "area": 6,
460 | "time": 0.89573
461 | }
462 | ],
463 | "2,7": [
464 | {
465 | "area": 1,
466 | "time": 0.12837
467 | },
468 | {
469 | "area": 2,
470 | "time": 0.31948
471 | },
472 | {
473 | "area": 7,
474 | "time": 0.89663
475 | }
476 | ],
477 | "6,4": [
478 | {
479 | "area": 7,
480 | "time": 0.15437
481 | },
482 | {
483 | "area": 6,
484 | "time": 0.37439
485 | },
486 | {
487 | "area": 5,
488 | "time": 0.64375
489 | },
490 | {
491 | "area": 4,
492 | "time": 0.87835
493 | }
494 | ],
495 | "6,3": [
496 | {
497 | "area": 7,
498 | "time": 0.12852
499 | },
500 | {
501 | "area": 6,
502 | "time": 0.32225
503 | },
504 | {
505 | "area": 3,
506 | "time": 0.90442
507 | }
508 | ],
509 | "6,2": [
510 | {
511 | "area": 7,
512 | "time": 0.11594
513 | },
514 | {
515 | "area": 6,
516 | "time": 0.30488
517 | },
518 | {
519 | "area": 2,
520 | "time": 0.89573
521 | }
522 | ],
523 | "6,1": [
524 | {
525 | "area": 7,
526 | "time": 0.12837
527 | },
528 | {
529 | "area": 6,
530 | "time": 0.31948
531 | },
532 | {
533 | "area": 1,
534 | "time": 0.89663
535 | }
536 | ]
537 | },
538 | ">": {
539 | "0": [
540 | {
541 | "area": 1,
542 | "time": 0.08142
543 | },
544 | {
545 | "area": 2,
546 | "time": 0.21027
547 | },
548 | {
549 | "area": 3,
550 | "time": 0.33496
551 | },
552 | {
553 | "area": 4,
554 | "time": 0.45966
555 | },
556 | {
557 | "area": 5,
558 | "time": 0.58851
559 | },
560 | {
561 | "area": 6,
562 | "time": 0.71736
563 | },
564 | {
565 | "area": 7,
566 | "time": 0.84205
567 | },
568 | {
569 | "area": 0,
570 | "time": 0.96675
571 | }
572 | ],
573 | "1": [
574 | {
575 | "area": 1,
576 | "time": 0.7132
577 | }
578 | ],
579 | "2": [
580 | {
581 | "area": 1,
582 | "time": 0.33496
583 | },
584 | {
585 | "area": 2,
586 | "time": 0.86699
587 | }
588 | ],
589 | "3": [
590 | {
591 | "area": 1,
592 | "time": 0.21858
593 | },
594 | {
595 | "area": 2,
596 | "time": 0.56772
597 | },
598 | {
599 | "area": 3,
600 | "time": 0.91271
601 | }
602 | ],
603 | "4": [
604 | {
605 | "area": 1,
606 | "time": 0.16523
607 | },
608 | {
609 | "area": 2,
610 | "time": 0.42401
611 | },
612 | {
613 | "area": 3,
614 | "time": 0.67444
615 | },
616 | {
617 | "area": 4,
618 | "time": 0.93322
619 | }
620 | ],
621 | "5": [
622 | {
623 | "area": 1,
624 | "time": 0.13129
625 | },
626 | {
627 | "area": 2,
628 | "time": 0.33496
629 | },
630 | {
631 | "area": 3,
632 | "time": 0.53863
633 | },
634 | {
635 | "area": 4,
636 | "time": 0.74645
637 | },
638 | {
639 | "area": 5,
640 | "time": 0.95012
641 | }
642 | ],
643 | "6": [
644 | {
645 | "area": 1,
646 | "time": 0.11051
647 | },
648 | {
649 | "area": 2,
650 | "time": 0.27677
651 | },
652 | {
653 | "area": 3,
654 | "time": 0.44719
655 | },
656 | {
657 | "area": 4,
658 | "time": 0.6176
659 | },
660 | {
661 | "area": 5,
662 | "time": 0.78802
663 | },
664 | {
665 | "area": 6,
666 | "time": 0.95844
667 | }
668 | ],
669 | "7": [
670 | {
671 | "area": 1,
672 | "time": 0.09388
673 | },
674 | {
675 | "area": 2,
676 | "time": 0.23936
677 | },
678 | {
679 | "area": 3,
680 | "time": 0.38484
681 | },
682 | {
683 | "area": 4,
684 | "time": 0.53032
685 | },
686 | {
687 | "area": 5,
688 | "time": 0.67164
689 | },
690 | {
691 | "area": 6,
692 | "time": 0.81711
693 | },
694 | {
695 | "area": 7,
696 | "time": 0.96259
697 | }
698 | ]
699 | },
700 | "<": {
701 | "0": [
702 | {
703 | "area": 7,
704 | "time": 0.08142
705 | },
706 | {
707 | "area": 6,
708 | "time": 0.21027
709 | },
710 | {
711 | "area": 5,
712 | "time": 0.33496
713 | },
714 | {
715 | "area": 4,
716 | "time": 0.45966
717 | },
718 | {
719 | "area": 3,
720 | "time": 0.58851
721 | },
722 | {
723 | "area": 2,
724 | "time": 0.71736
725 | },
726 | {
727 | "area": 1,
728 | "time": 0.84205
729 | },
730 | {
731 | "area": 0,
732 | "time": 0.96675
733 | }
734 | ],
735 | "7": [
736 | {
737 | "area": 7,
738 | "time": 0.7132
739 | }
740 | ],
741 | "6": [
742 | {
743 | "area": 7,
744 | "time": 0.33496
745 | },
746 | {
747 | "area": 6,
748 | "time": 0.86699
749 | }
750 | ],
751 | "5": [
752 | {
753 | "area": 7,
754 | "time": 0.21858
755 | },
756 | {
757 | "area": 6,
758 | "time": 0.56772
759 | },
760 | {
761 | "area": 5,
762 | "time": 0.91271
763 | }
764 | ],
765 | "4": [
766 | {
767 | "area": 7,
768 | "time": 0.16523
769 | },
770 | {
771 | "area": 6,
772 | "time": 0.42401
773 | },
774 | {
775 | "area": 5,
776 | "time": 0.67444
777 | },
778 | {
779 | "area": 4,
780 | "time": 0.93322
781 | }
782 | ],
783 | "3": [
784 | {
785 | "area": 7,
786 | "time": 0.13129
787 | },
788 | {
789 | "area": 6,
790 | "time": 0.33496
791 | },
792 | {
793 | "area": 5,
794 | "time": 0.53863
795 | },
796 | {
797 | "area": 4,
798 | "time": 0.74645
799 | },
800 | {
801 | "area": 3,
802 | "time": 0.95012
803 | }
804 | ],
805 | "2": [
806 | {
807 | "area": 7,
808 | "time": 0.11051
809 | },
810 | {
811 | "area": 6,
812 | "time": 0.27677
813 | },
814 | {
815 | "area": 5,
816 | "time": 0.44719
817 | },
818 | {
819 | "area": 4,
820 | "time": 0.6176
821 | },
822 | {
823 | "area": 3,
824 | "time": 0.78802
825 | },
826 | {
827 | "area": 2,
828 | "time": 0.95844
829 | }
830 | ],
831 | "1": [
832 | {
833 | "area": 7,
834 | "time": 0.09388
835 | },
836 | {
837 | "area": 6,
838 | "time": 0.23936
839 | },
840 | {
841 | "area": 5,
842 | "time": 0.38484
843 | },
844 | {
845 | "area": 4,
846 | "time": 0.53032
847 | },
848 | {
849 | "area": 3,
850 | "time": 0.67164
851 | },
852 | {
853 | "area": 2,
854 | "time": 0.81711
855 | },
856 | {
857 | "area": 1,
858 | "time": 0.96259
859 | }
860 | ]
861 | },
862 | "^": {
863 | "1": [
864 | {
865 | "area": 1,
866 | "time": 0.7132
867 | }
868 | ],
869 | "2": [
870 | {
871 | "area": 1,
872 | "time": 0.33496
873 | },
874 | {
875 | "area": 2,
876 | "time": 0.86699
877 | }
878 | ],
879 | "3": [
880 | {
881 | "area": 1,
882 | "time": 0.21858
883 | },
884 | {
885 | "area": 2,
886 | "time": 0.56772
887 | },
888 | {
889 | "area": 3,
890 | "time": 0.91271
891 | }
892 | ],
893 | "7": [
894 | {
895 | "area": 7,
896 | "time": 0.7132
897 | }
898 | ],
899 | "6": [
900 | {
901 | "area": 7,
902 | "time": 0.33496
903 | },
904 | {
905 | "area": 6,
906 | "time": 0.86699
907 | }
908 | ],
909 | "5": [
910 | {
911 | "area": 7,
912 | "time": 0.21858
913 | },
914 | {
915 | "area": 6,
916 | "time": 0.56772
917 | },
918 | {
919 | "area": 5,
920 | "time": 0.91271
921 | }
922 | ]
923 | }
924 | }
925 |
--------------------------------------------------------------------------------
/Langs/Langs.zh-CN.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 要关闭View吗?
122 |
123 |
124 | 未保存,要保存吗?
125 |
126 |
127 | 注意
128 |
129 |
130 | BPM Tap
131 |
132 |
133 | 背景亮度
134 |
135 |
136 | 难度
137 |
138 |
139 | 编辑
140 |
141 |
142 | 错误
143 |
144 |
145 | 文件
146 |
147 |
148 | GitHub
149 |
150 |
151 | maidata.txt已存在
152 |
153 |
154 | 谱面信息
155 |
156 |
157 | 谱面无理检测
158 |
159 |
160 | 180°翻转选择
161 |
162 |
163 | 45°顺时针旋转选择
164 |
165 |
166 | 左右翻转选择
167 |
168 |
169 | 上下翻转选择
170 |
171 |
172 | 新建
173 |
174 |
175 | 未找到maidata.txt
176 |
177 |
178 | 请存入track.mp3 / track.ogg
179 |
180 |
181 | 偏移
182 |
183 |
184 | 打开
185 |
186 |
187 | 播放速度
188 |
189 |
190 | 请确保你打开了MajdataView且端口(8013)畅通
191 |
192 |
193 | 录制模式
194 |
195 |
196 | 保存
197 |
198 |
199 | 另存为
200 |
201 |
202 | 请设置艺术家
203 |
204 |
205 | 请设置做谱人
206 |
207 |
208 | 设置
209 |
210 |
211 | 请设置标题
212 |
213 |
214 | 正在以软件渲染模式运行
215 |
216 |
217 | 速度
218 |
219 |
220 | 文本跟随
221 |
222 |
223 | 工具
224 |
225 |
226 | (未保存)
227 |
228 |
229 | 音量
230 |
231 |
232 | 可能不支持非44100Hz的文件
233 |
234 |
235 | 警告
236 |
237 |
238 | 取消
239 |
240 |
241 | 语言
242 |
243 |
244 | 编辑器设置
245 |
246 |
247 | 请完成编辑器初始设置
248 |
249 |
250 | 检测完毕, 共发现{0}个多押无理,{1}个撞尾无理。您可在 检测结果 窗口中查看
251 | 请注意:谱面无理检测提供的意见并不一定准确,结果仅供参考。
252 |
253 |
254 | 检测完毕, 共发现{0}个撞尾无理。您可在 检测结果 窗口中查看
255 | 请注意:谱面无理检测提供的意见并不一定准确,结果仅供参考。
256 |
257 |
258 | 检测结果
259 |
260 |
261 | 双击可转到对应错误行
262 |
263 |
264 | 提示
265 |
266 |
267 | 开启多押检测
268 |
269 |
270 | [多押无理]
271 |
272 |
273 | 可能形成了{0}押
274 |
275 |
276 | 检测三押或三押以上的多押无理
277 | 如果您的谱面允许多押存在 如协力宴谱 您可以禁用这个选项
278 |
279 |
280 | Slide撞尾检测精度必须是个数字! 请检查您输入是否有误, 如误输入了空格.
281 |
282 |
283 | Slide撞尾检测精度:
284 |
285 |
286 | [撞尾无理] "{0}"({1}L,{2}C)可能会撞上"{3}"({4}L,{5}C) 二者间隔{6}ms
287 |
288 |
289 | 撞尾无理:当slide经过tap(A区)时,这个tap可能会被误触发
290 | 检查撞尾无理的精度,单位是秒。
291 | 建议您不要设置低于0.15的值,因为0.15秒是GOOD判定的区间,
292 | 如果小于这个值,必定无理的撞尾就无法被检测出来。
293 | 您可以设置略高于0.15的值,如0.2,此时容易蹭尾的配置也会被
294 | 检测出来
295 |
296 |
297 | 开始检测
298 |
299 |
300 | [语法错误] "{0}"({1}L,{2}C)解析失败,可能存在语法错误
301 |
302 |
303 | 这是什么
304 |
305 |
306 | 渲染进程错误!
307 | 请尝试通过强制软件渲染使用本软件
308 | (通过双击文件夹中的 "强制软件渲染.bat"启动本程序)
309 |
310 |
311 | 从头开始播放,并带有动画
312 |
313 |
314 | 查找与替换
315 |
316 |
317 | 查找下一个
318 |
319 |
320 | 替换下一个
321 |
322 |
323 | mp3中有封面。要一并载入(并替换当前)封面吗?
324 |
325 |
326 | 从其他mp3中读取
327 |
328 |
329 | 从track.mp3 / track.ogg 中读取
330 |
331 |
332 | 重新对准编辑器窗口
333 |
334 |
335 | 覆盖模式下,输入的文字将替换光标后的内容。
336 | 按Insert键回到标准模式。
337 |
338 |
339 | 覆盖模式
340 |
341 |
342 | Touch速度
343 |
344 |
345 | 请在 设置->编辑器设置 中调整
346 |
347 |
348 | 渲染模式
349 |
350 |
351 | 硬件渲染
352 |
353 |
354 | 软件渲染
355 |
356 |
357 | 将当前音量设为默认
358 |
359 |
360 | 设置成功,之后新建的新谱面将会沿用目前的音量设置
361 |
362 |
363 | 检查更新
364 |
365 |
366 | (你可以在编辑器设置中禁用自动检查更新功能)
367 |
368 |
369 | 检查到新版本 {0}
370 | (当前版本为 {1})
371 |
372 | 是否前往下载?
373 |
374 |
375 | 您已经是最新版本了,无需更新
376 |
377 |
378 | 网络请求失败,请检查网络后重试
379 |
380 |
381 | 谱面刷新延迟(毫秒)
382 |
383 |
384 | 自动检查更新(启动时)
385 |
386 |
387 | 重置音量到默认值
388 |
389 |
390 | 45°逆时针旋转选择
391 |
392 |
393 | 即将开始渲染,将导出视频到谱面目录。
394 | 请注意在渲染时不要改变Viewer窗口大小。
395 | 现在按Viewer右上角< >可以设置渲染分辨率。
396 | 请保证分辨率宽度为偶数。
397 | 按确定开始渲染。
398 |
399 |
400 | 屏幕中心显示
401 |
402 |
403 | 导出视频
404 |
405 |
406 | Achievement (+) (旧框)
407 |
408 |
409 | Achievement (+) (DX)
410 |
411 |
412 | Achievement (-) (旧框)
413 |
414 |
415 | Achievement (-) 1 (DX)
416 |
417 |
418 | Combo
419 |
420 |
421 | Normalized Score (+)
422 |
423 |
424 | Normalized Score (-)
425 |
426 |
427 | 无
428 |
429 |
430 | Score (Classic)
431 |
432 |
433 | DX Score
434 |
435 |
436 | 自动保存恢复
437 |
438 |
439 | 恢复
440 |
441 |
442 | 检测到上次Majdata未正常关闭,是否要打开自动保存恢复窗口?
443 |
444 |
445 | 确认要将此数据(自动保存于{0})恢复至{1}吗?
446 | (恢复之前的maidata.txt会被更名为maidata.before_recovery.txt)
447 | {0}=自动保存时间,{1}=原maidata路径
448 |
449 |
450 | 平滑Slide动画
451 |
452 |
453 | 启用后,Slide轨迹会随着星星消失,而不是以判定区为单位消失
454 |
455 |
456 | 禁用
457 |
458 |
459 | 警告
460 |
461 |
462 | 开启
463 |
464 |
465 | 语法检查
466 |
467 |
468 | 内部错误
469 |
470 |
471 | [警告] 谱面应当以"E"结尾:"{0}"({1}L,{2}C)
472 |
473 |
474 | 模拟方式
475 |
476 |
477 | 默认
478 |
479 |
480 | 关闭
481 |
482 |
483 | 模拟游玩 (DJAuto)
484 |
485 |
486 | 模拟游玩 (随机)
487 |
488 |
489 | 禁用
490 |
491 |
492 | 警告
493 |
494 |
495 | 开启
496 |
497 |
498 | 语法检查
499 |
500 |
501 | 内部错误
502 |
503 |
504 | [警告] 谱面应当以"E"结尾:"{0}"({1}L,{2}C)
505 |
506 |
--------------------------------------------------------------------------------
/SubWindow/MuriCheck.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text.RegularExpressions;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using System.Windows.Input;
6 | using Newtonsoft.Json;
7 | using Newtonsoft.Json.Linq;
8 |
9 | namespace MajdataEdit;
10 |
11 | ///
12 | /// BPMtap.xaml 的交互逻辑
13 | ///
14 | /*
15 | * 请原谅我把检测逻辑和界面逻辑写到一个文件里去了
16 | * 因为我懒
17 | * 如果有空我会把他分出去的
18 | * 感谢!
19 | */
20 | internal class MaimaiOperationMultNote
21 | {
22 | public int endArea;
23 | public double endTime;
24 | public string noteContent;
25 | public int ntype;
26 | public int positionX;
27 | public int positionY;
28 |
29 | public int startArea;
30 |
31 | // 用于多押检测的类 表示一次操作
32 | public double startTime;
33 |
34 | public MaimaiOperationMultNote(double _startTime, double _endTime, int _startArea,
35 | int _endArea, int _ntype, string _noteContent, int _positionX,
36 | int _positionY)
37 | {
38 | startTime = _startTime;
39 | endTime = _endTime;
40 | startArea = _startArea;
41 | endArea = _endArea;
42 | ntype = _ntype;
43 | noteContent = _noteContent;
44 | positionX = _positionX;
45 | positionY = _positionY;
46 | }
47 | }
48 |
49 | internal class MaimaiOperationSlide
50 | {
51 | public int area;
52 | public string noteContent;
53 | public int ntype;
54 | public int positionX;
55 |
56 | public int positionY;
57 |
58 | // 用于撞尾检测的类 表示一次操作
59 | public double time;
60 |
61 | public MaimaiOperationSlide(double _time, int _area, int _ntype,
62 | string _noteContent, int _positionX, int _positionY)
63 | {
64 | time = _time;
65 | area = _area;
66 | ntype = _ntype;
67 | noteContent = _noteContent;
68 | positionX = _positionX;
69 | positionY = _positionY;
70 | }
71 | }
72 |
73 | public partial class MuriCheck : Window
74 | {
75 | private MuriCheckResult? mcr;
76 | public JObject? SLIDE_TIME; // 无理检测用的SLIDE_TIME数据
77 |
78 | public MuriCheck()
79 | {
80 | InitializeComponent();
81 | }
82 |
83 | private void ReadMuriCheckSlideTime()
84 | {
85 | using var r = new StreamReader("./slide_time.json");
86 | var json = r.ReadToEnd();
87 | SLIDE_TIME = JsonConvert.DeserializeObject(json)!;
88 | }
89 |
90 | private int notePos(int pos, bool relative)
91 | {
92 | if (pos <= 0) pos += 8;
93 |
94 | if (relative)
95 | pos %= 8;
96 | else
97 | pos = (pos - 1) % 8 + 1;
98 | return pos;
99 | }
100 |
101 | private void StartCheck_Button_Click(object sender, RoutedEventArgs e)
102 | {
103 | double slideAccuracy;
104 | try
105 | {
106 | slideAccuracy = double.Parse(SlideAccuracy_TextBox.Text);
107 | }
108 | catch (FormatException)
109 | {
110 | MessageBox.Show(MainWindow.GetLocalizedString("SlideAccInputError"),
111 | MainWindow.GetLocalizedString("Error"));
112 | SlideAccuracy_TextBox.Text = "";
113 | return;
114 | }
115 |
116 | BeatmapMuriCheck(MultNote_Checkbox.IsChecked == true, slideAccuracy);
117 | }
118 |
119 | private void addWarning(string content, int posX, int posY)
120 | {
121 | if (mcr != null)
122 | {
123 | mcr.errorPosition.Add(new ErrorInfo(posX, posY));
124 | var resultRow = new ListBoxItem
125 | {
126 | Content = content,
127 | Name = "rr" + mcr.CheckResult_Listbox.Items.Count
128 | };
129 | resultRow.AddHandler(PreviewMouseDoubleClickEvent,
130 | new MouseButtonEventHandler(mcr.ListBoxItem_PreviewMouseDoubleClick));
131 | mcr.CheckResult_Listbox.Items.Add(resultRow);
132 | }
133 | }
134 |
135 | private int multNoteDetect()
136 | {
137 | var TIME_EPS = 5;
138 |
139 | // 注释可见 https://github.com/Moying-moe/maimaiMuriDetector MaiMuriDetector.multNoteDetect(self, eps=5)
140 |
141 | var prog = @"(\d)(.+?)(\d{1,2})\[.+?\]";
142 | var errorCnt = 0;
143 |
144 | var opSequence = new List();
145 |
146 | foreach (var noteGroup in SimaiProcess.notelist)
147 | {
148 | var baseTime = noteGroup.time;
149 | var positionX = noteGroup.rawTextPositionX;
150 | var positionY = noteGroup.rawTextPositionY;
151 |
152 | foreach (var note in noteGroup.getNotes())
153 | if (note.noteType == SimaiNoteType.Tap)
154 | {
155 | opSequence.Add(new MaimaiOperationMultNote(
156 | Math.Round(baseTime, TIME_EPS),
157 | Math.Round(baseTime, TIME_EPS),
158 | note.startPosition,
159 | note.startPosition,
160 | 0,
161 | note.noteContent!,
162 | positionX,
163 | positionY
164 | ));
165 | }
166 | else if (note.noteType == SimaiNoteType.Slide)
167 | {
168 | opSequence.Add(new MaimaiOperationMultNote(
169 | Math.Round(baseTime, TIME_EPS),
170 | Math.Round(baseTime, TIME_EPS),
171 | note.startPosition,
172 | note.startPosition,
173 | 1,
174 | note.noteContent!,
175 | positionX,
176 | positionY
177 | ));
178 | int endPosition;
179 | try
180 | {
181 | var temp = Regex.Match(note.noteContent!, prog);
182 | endPosition = int.Parse(
183 | temp.Groups[3].Value.Substring(temp.Groups[3].Value.Length - 1, 1)
184 | );
185 | }
186 | catch
187 | {
188 | addWarning(string.Format(
189 | MainWindow.GetLocalizedString("SyntaxError"),
190 | note.noteContent,
191 | positionY + 1,
192 | positionX + 1
193 | ), positionX, positionY);
194 | continue;
195 | }
196 |
197 | opSequence.Add(new MaimaiOperationMultNote(
198 | Math.Round(note.slideStartTime, TIME_EPS),
199 | Math.Round(note.slideStartTime + note.slideTime, TIME_EPS),
200 | note.startPosition,
201 | endPosition,
202 | 3,
203 | note.noteContent!,
204 | positionX,
205 | positionY
206 | ));
207 | }
208 | else if (note.noteType == SimaiNoteType.Hold)
209 | {
210 | opSequence.Add(new MaimaiOperationMultNote(
211 | Math.Round(baseTime, TIME_EPS),
212 | Math.Round(baseTime + note.holdTime, TIME_EPS),
213 | note.startPosition,
214 | note.startPosition,
215 | 2,
216 | note.noteContent!,
217 | positionX,
218 | positionY
219 | ));
220 | }
221 | else
222 | {
223 | // TODO: dx谱面兼容
224 | MessageBox.Show("无理检测暂时不支持dx谱面! / dx map not support now", "警告");
225 | return -1;
226 | }
227 | }
228 |
229 | opSequence.Sort(delegate(MaimaiOperationMultNote x, MaimaiOperationMultNote y)
230 | {
231 | if (x.startTime == y.startTime)
232 | {
233 | if (x.ntype == y.ntype)
234 | return 0;
235 | return x.ntype < y.ntype ? -1 : 1;
236 | }
237 |
238 | return x.startTime < y.startTime ? -1 : 1;
239 | });
240 |
241 | var inHandling = new List();
242 |
243 | foreach (var op in opSequence)
244 | {
245 | for (var i = inHandling.Count - 1; i >= 0; i--)
246 | if (inHandling[i].endTime < op.startTime)
247 | inHandling.RemoveAt(i);
248 |
249 | if (op.ntype == 3)
250 | {
251 | for (var i = inHandling.Count - 1; i >= 0; i--)
252 | if (inHandling[i].endTime == op.startTime &&
253 | inHandling[i].endArea == op.startArea)
254 | inHandling.RemoveAt(i);
255 | }
256 | else if (op.ntype == 1)
257 | {
258 | for (var i = inHandling.Count - 1; i >= 0; i--)
259 | if (inHandling[i].ntype == 1 &&
260 | inHandling[i].startTime == op.startTime &&
261 | inHandling[i].startArea == op.startArea)
262 | inHandling.RemoveAt(i);
263 | }
264 |
265 | inHandling.Add(op);
266 |
267 | if (inHandling.Count > 2)
268 | {
269 | var warningText = MainWindow.GetLocalizedString("MultNoteError1");
270 | foreach (var e in inHandling)
271 | {
272 | if (e.ntype == 1) warningText += "*";
273 | warningText += string.Format(
274 | "\"{0}\"({1}L,{2}C) ",
275 | e.noteContent, e.positionY + 1, e.positionX + 1
276 | );
277 | }
278 |
279 | warningText += string.Format(MainWindow.GetLocalizedString("MultNoteError2"), inHandling.Count);
280 | addWarning(warningText, inHandling[0].positionX, inHandling[0].positionY);
281 | errorCnt++;
282 | }
283 | }
284 |
285 | return errorCnt;
286 | }
287 |
288 | private int slideDetect(double judgementLength)
289 | {
290 | // 注释可见 https://github.com/Moying-moe/maimaiMuriDetector MaiMuriDetector.slideDetect(self, judgementLength = 0.15)
291 |
292 | var prog = @"(\d)(.+?)(\d{1,2})\[.+?\]";
293 |
294 | var opSequence = new List();
295 |
296 | foreach (var noteGroup in SimaiProcess.notelist)
297 | {
298 | var baseTime = noteGroup.time;
299 | var positionX = noteGroup.rawTextPositionX;
300 | var positionY = noteGroup.rawTextPositionY;
301 |
302 | foreach (var note in noteGroup.getNotes())
303 | if (note.noteType == SimaiNoteType.Tap ||
304 | note.noteType == SimaiNoteType.Hold)
305 | {
306 | opSequence.Add(new MaimaiOperationSlide(
307 | baseTime,
308 | note.startPosition,
309 | 0,
310 | note.noteContent!,
311 | positionX,
312 | positionY
313 | ));
314 | }
315 | else if (note.noteType == SimaiNoteType.Slide)
316 | {
317 | // 星星头加入队列
318 | opSequence.Add(new MaimaiOperationSlide(
319 | baseTime,
320 | note.startPosition,
321 | 0,
322 | note.noteContent!,
323 | positionX,
324 | positionY
325 | ));
326 | string sStart;
327 | string sType;
328 | string sEnd;
329 |
330 | try
331 | {
332 | var temp = Regex.Match(note.noteContent!, prog);
333 | sStart = temp.Groups[1].Value;
334 | sType = temp.Groups[2].Value;
335 | sEnd = temp.Groups[3].Value;
336 | }
337 | catch
338 | {
339 | addWarning(string.Format(
340 | MainWindow.GetLocalizedString("SyntaxError"),
341 | note.noteContent,
342 | positionY + 1,
343 | positionX + 1
344 | ), positionX, positionY);
345 | continue;
346 | }
347 |
348 | if (sType == "V")
349 | {
350 | // 转折型
351 | var sEnd0 = notePos(
352 | int.Parse(sEnd.Substring(0, 1)) - int.Parse(sStart),
353 | true
354 | );
355 | var sEnd1 = notePos(
356 | int.Parse(sEnd.Substring(1, 1)) - int.Parse(sStart),
357 | true
358 | );
359 | sEnd = sEnd0 + "," + sEnd1;
360 | }
361 | else
362 | {
363 | sEnd = notePos(int.Parse(sEnd) - int.Parse(sStart), true).ToString();
364 | }
365 |
366 | if (sType == ">" &&
367 | int.Parse(sStart) >= 3 && int.Parse(sStart) <= 6)
368 | /*
369 | * WARNING:
370 | * 这其实是一个测定数据时的遗留问题
371 | * 在测定数据的时候,对于每一种slide,都以1开头来测定,并存储相对的位置
372 | * 在实际判定的时候,会根据实际的起点和相对位置计算绝对位置,也就是说,是在测定数据的基础上进行了旋转
373 | * 但是>和<型的slide,其方向会受到起点位置的影响
374 | * 以>为例,当起点是7812时,是顺时针,起点是3456时,则为逆时针
375 | * 但是在测定时,因为起点总是1,所以>总是顺时针的,<总是逆时针的
376 | * --- 换言之,在SLIDE_TIME里,>不表示向右开始回旋的slide,而表示“总是顺时针的回旋slide” ---
377 | * 所以此处选择对>和时 和测定方向相反
382 | sType = "<";
383 | else if (sType == "<" &&
384 | int.Parse(sStart) >= 3 && int.Parse(sStart) <= 6)
385 | sType = ">";
386 |
387 | JToken sTimeInfo;
388 | try
389 | {
390 | sTimeInfo = SLIDE_TIME![sType]![sEnd]!;
391 | foreach (var each in sTimeInfo!)
392 | {
393 | var timeRatio = each["time"]!.ToObject();
394 | var passArea = each["area"]!.ToObject();
395 | opSequence.Add(new MaimaiOperationSlide(
396 | timeRatio * note.slideTime + note.slideStartTime,
397 | notePos(passArea + int.Parse(sStart), false),
398 | 1,
399 | note.noteContent!,
400 | positionX,
401 | positionY
402 | ));
403 | }
404 | }
405 | catch
406 | {
407 | addWarning(string.Format(
408 | MainWindow.GetLocalizedString("SyntaxError"),
409 | note.noteContent,
410 | positionY + 1,
411 | positionX + 1
412 | ), positionX, positionY);
413 | }
414 | }
415 | else
416 | {
417 | // TODO: dx谱面兼容
418 | MessageBox.Show("无理检测暂时不支持dx谱面! / dx map not support now", "警告");
419 | return -1;
420 | }
421 | }
422 |
423 | opSequence.Sort(delegate(MaimaiOperationSlide x, MaimaiOperationSlide y)
424 | {
425 | if (x.time == y.time)
426 | {
427 | if (x.ntype == y.ntype)
428 | return 0;
429 | return x.ntype > y.ntype ? -1 : 1;
430 | }
431 |
432 | return x.time < y.time ? -1 : 1;
433 | });
434 | var errorCnt = 0;
435 |
436 | var inJudgement = new List();
437 |
438 | foreach (var op in opSequence)
439 | {
440 | var curTime = op.time;
441 |
442 | for (var i = inJudgement.Count - 1; i >= 0; i--)
443 | if (inJudgement[i].time + judgementLength < curTime)
444 | inJudgement.RemoveAt(i);
445 |
446 | if (op.ntype == 1)
447 | inJudgement.Add(op);
448 | else if (op.ntype == 0)
449 | foreach (var e in inJudgement)
450 | if (e.area == op.area &&
451 | op.time - judgementLength < e.time &&
452 | e.time < op.time)
453 | {
454 | addWarning(string.Format(
455 | MainWindow.GetLocalizedString("SlideError"),
456 | e.noteContent, e.positionY + 1, e.positionX + 1,
457 | op.noteContent, op.positionY + 1, op.positionX + 1,
458 | Math.Floor((op.time - e.time) * 1000)
459 | ), e.positionX, e.positionY);
460 | errorCnt++;
461 | }
462 | }
463 |
464 | return errorCnt;
465 | }
466 |
467 | private void BeatmapMuriCheck(bool multNoteEnable, double slideCheckAccuracy)
468 | {
469 | if (mcr != null) mcr.Close();
470 | mcr = new MuriCheckResult();
471 | mcr.Owner = Owner;
472 | mcr.CheckResult_Listbox.Items.Clear();
473 |
474 | int multNoteError;
475 | if (multNoteEnable)
476 | {
477 | multNoteError = multNoteDetect();
478 | if (multNoteError == -1)
479 | // 不支持dx谱面 退出
480 | return;
481 | }
482 | else
483 | {
484 | multNoteError = -114514; // This is a MAGIC NUMBER, Do not touch ;)
485 | // This is really a MAGIC NUMBER, Do not touch Xp
486 | }
487 |
488 | var slideError = slideDetect(slideCheckAccuracy);
489 | if (slideError == -1)
490 | // 不支持dx谱面 退出
491 | return;
492 | mcr.Show();
493 | if (multNoteEnable)
494 | MessageBox.Show(
495 | string.Format(MainWindow.GetLocalizedString("CheckDone1"),
496 | multNoteError, slideError),
497 | MainWindow.GetLocalizedString("Info")
498 | );
499 | else
500 | MessageBox.Show(
501 | string.Format(MainWindow.GetLocalizedString("CheckDone2"),
502 | slideError),
503 | MainWindow.GetLocalizedString("Info")
504 | );
505 | }
506 |
507 | private void Window_Loaded(object sender, RoutedEventArgs e)
508 | {
509 | SlideAccuracy_TextBox.Text = ((MainWindow)Owner).editorSetting!.DefaultSlideAccuracy.ToString();
510 | }
511 |
512 | private void Window_Initialized(object sender, EventArgs e)
513 | {
514 | ReadMuriCheckSlideTime();
515 | }
516 | }
--------------------------------------------------------------------------------