1
0
mirror of https://github.com/SirusDoma/VoxCharger.git synced 2024-11-23 22:51:01 +01:00

Initial commit

This commit is contained in:
SirusDoma 2020-04-19 03:24:48 +07:00
commit 1e9878672e
83 changed files with 17085 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

247
.gitignore vendored Normal file
View File

@ -0,0 +1,247 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
.idea/
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
[Xx]64/
[Xx]86/
bld/
[Bb]in/
[Oo]bj/
[Tt]mp/
[Tt]emp/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Un-comment the next line if you do not want to checkin
# your web deploy settings because they may include unencrypted
# passwords
#*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# LightSwitch generated files
GeneratedArtifacts/
ModelManifest.xml
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/

BIN
2dxbuild.exe Normal file

Binary file not shown.

BIN
2dxwavconvert.exe Normal file

Binary file not shown.

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 CXO2
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

22
Program.cs Normal file
View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace VoxCharger
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("VoxCharger")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("VoxCharger")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b049dc1d-7404-4437-ae8c-1ff6a0a321dc")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

93
Properties/Resources.Designer.cs generated Normal file
View File

@ -0,0 +1,93 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace VoxCharger.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("VoxCharger.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap jk_dummy {
get {
object obj = ResourceManager.GetObject("jk_dummy", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap jk_dummy_b {
get {
object obj = ResourceManager.GetObject("jk_dummy_b", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap jk_dummy_s {
get {
object obj = ResourceManager.GetObject("jk_dummy_s", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

130
Properties/Resources.resx Normal file
View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="jk_dummy" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\jk_dummy.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="jk_dummy_b" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\jk_dummy_b.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="jk_dummy_s" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\jk_dummy_s.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

30
Properties/Settings.Designer.cs generated Normal file
View File

@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace VoxCharger.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

64
README.md Normal file
View File

@ -0,0 +1,64 @@
# VoxCharger #
- **Author**: CXO2
- **Email**: com@cxo2.me
- **Version**: 0.9.7b
Recharge your KFC Chicken sauce.
Written under C# language, this program allow you to manage music asset files of your KFC installation.
Additionally, it include built-in converter to import ksh into vox that can be consumed by KFC.
If you're not familiar with these file formats, then this sauce is not for you.
## Prerequisite ##
### .NET Framework 4.7
This program require .NET Framework 4.7 in order to run properly.
### Latest datecodes or newer only
Latest omnimix structures is slightly changed compared to previous datecodes, it won't load anything that doesn't match with `2020011500` structure.
### Backup
Backup your data before using this program, it able to modify and delete your music assets.
Keep in mind that the program won't allow you to make any changes against original mix.
### No IFS Support
Use [IFS LayeredFS](https://github.com/mon/ifs_layeredfs) in your KFC installation. This program will not pack your assets into IFS, nor attempt to process existing ones.
When mix with ifs files is selected, the program won't be able load music assets properly.
If you need to pack your music assets into ifs, use another tool that process ifs file (for an instance: [mon ifs tools](https://github.com/mon/ifstools)).
### 2DX Tools
Make sure that mon `2dxwavconvert.exe` and `2dxbuild.exe` are placed in the same directory along with the program.
Can't find it? you can grab the tools [here](https://github.com/mon/2dxTools/releases).
## Remarks ##
### Mix Lock
Original mix is locked to prevent you (yes, you) to break your KFC installation.
Again, use [IFS LayeredFS](https://github.com/mon/ifs_layeredfs). If you haven't heard this then you're totally missing out!
### Conversion Output
Vox have some sense in it's file format than ksh file, as the result, not all attributes can be mapped precisely and potentially lead into bug in the output file.
Remember, stupid input get stupid output. But if you believe it's a bug, feel free to open issue or PR.
### Music Preview
Imported music preview from normal audio files may broken or not trimmed properly in the output file. Preview offset in ksh is also ignored, you need dedicated preview file with proper fade in and out. Keep in mind that `2dxbuld.exe` will not do this for you. Until program supports built in encoder, you have to provide 2dx preview manually for proper preview file.
If you can't live without it, consider making PR to this feature.
### Music DB
Some attributes are kept hidden from editor, For existing songs, some of their attributes might untouched throughout save iteration, but the rest of attributes might be replaced with dummy / stub data.
If this really concern you, make sure to backup your `music_db.xml` / `music_db.merged.xml`.
### Asset File Modification
Replacing asset files such as vox, 2dx and graphic files are happen immediately after changes are confirmed. In other hand, metadata need to be saved manually by `File -> Save` or `CTRL+S`.
Note that you can postpone asset modification until you save metadata by disabling autosave in `Edit -> Autosave Assets`
# License #
This is an open-sourced application licensed under the [MIT License](http://github.com/SirusDoma/VoxCharger/blob/master/LICENSE)

BIN
Resources/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

BIN
Resources/jk_dummy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
Resources/jk_dummy_b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

BIN
Resources/jk_dummy_s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

62
Sources/2dx/DxTool.cs Normal file
View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
namespace VoxCharger
{
public static class DxTool
{
private const string ConverterFileName = "2dxwavconvert.exe";
private const string BuilderFileName = "2dxbuild.exe";
public static string ConvertToWave(string inputFileName, bool preview = false)
{
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDir);
string output = Path.Combine(tempDir, $"{Directory.GetFiles(tempDir).Length}.wav");
Execute(
ConverterFileName,
$"{inputFileName} {output}" + (preview ? " preview" : string.Empty)
);
return tempDir;
}
public static void Build(string inputDir, string outputFileName)
{
Execute(
BuilderFileName,
outputFileName,
inputDir
);
}
private static void Execute(string fileName, string args, string workingDir = null)
{
if (!File.Exists(fileName))
throw new FileNotFoundException($"{fileName} not found", fileName);
workingDir = workingDir ?? Environment.CurrentDirectory;
var info = new ProcessStartInfo()
{
FileName = fileName,
Arguments = args,
WorkingDirectory = workingDir,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true
};
using (var process = Process.Start(info))
{
process.WaitForExit();
if (process.ExitCode != 0)
throw new ApplicationException($"{fileName} execution failed:\n{process.StandardOutput.ReadToEnd()}");
}
}
}
}

325
Sources/AssetManager.cs Normal file
View File

@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Diagnostics;
using System.Text;
namespace VoxCharger
{
public static class AssetManager
{
#region --- Properties ---
private static List<string> MixList = new List<string>();
public static string MixName { get; private set; }
public static string GamePath { get; private set; }
public static string MixPath { get; set; }
public static MusicDb Headers { get; private set; } = new MusicDb();
public static string MdbFilename { get; private set; }
#endregion
#region --- Mix Management ---
public static void Initialize(string gamePath)
{
// Validate the existence of main music db
string dbFilename = Path.Combine(gamePath, @"data\others\music_db.xml");
if (!File.Exists(dbFilename))
throw new FormatException("Invalid Game Directory");
// Validate whether cache exists, perform full load when cache is not available
string cacheFilename = Path.Combine(gamePath, @"data_mods\_cache\others\music_db.xml");
// Load original headers data
Headers = new MusicDb();
Headers.Load(File.Exists(cacheFilename) ? cacheFilename : dbFilename);
// Look for other mixes
MixList.Clear();
string modsPath = Path.Combine(gamePath, @"data_mods\");
foreach (var modDir in Directory.GetDirectories(modsPath))
{
// Get directory name
string modName = new DirectoryInfo(modDir).Name;
if (modName == "_cache")
continue;
// Validate whether the mod is a mix mod
dbFilename = Path.Combine(modDir, @"others\music_db.merged.xml");
if (!File.Exists(dbFilename))
continue;
// Only supports latest KFC datecode with music folder
if (!Directory.Exists(Path.Combine(modDir, @"music\")))
continue;
// Confirmed mod path, append into music db, ignore cache to avoid uncached mix being excluded
Headers.Load(dbFilename, true);
MixList.Add(modName);
}
GamePath = gamePath;
}
public static void CreateMix(string mixName)
{
string mixPath = Path.Combine(GamePath, @"data_mods\", mixName);
if (Directory.Exists(mixPath))
throw new IOException("Mix directory is already exists");
// Create necessary directory
Directory.CreateDirectory(Path.Combine(mixPath, @"graphics\s_jacket00_ifs\"));
Directory.CreateDirectory(Path.Combine(mixPath, @"music\"));
Directory.CreateDirectory(Path.Combine(mixPath, @"others\"));
// Create empty db
MdbFilename = Path.Combine(mixPath, @"others\music_db.merged.xml");
File.WriteAllText(MdbFilename, "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?><mdb></mdb>");
Headers.Clear();
MixName = mixName;
MixPath = mixPath;
}
public static void LoadMix(string mixName)
{
string dataPath = string.IsNullOrEmpty(mixName) ? @"data\" : @"data_mods\";
string mixPath = Path.Combine(GamePath, dataPath, mixName);
if (!Directory.Exists(mixPath))
throw new DirectoryNotFoundException("Mix directory missing");
// No way it happen since combo box is dropdownlist, but well.. :v
if (!string.IsNullOrEmpty(mixName) && !MixList.Contains(mixName))
MixList.Add(mixName);
// Locate the music db, if unavailable, create it
MdbFilename = Path.Combine(mixPath, @"others\", string.IsNullOrEmpty(mixName) ? "music_db.xml" : "music_db.merged.xml");
Headers.Load(MdbFilename);
MixName = mixName;
MixPath = mixPath;
}
public static string[] GetMixes()
{
return MixList.ToArray();
}
#endregion
#region --- Asset Management ---
public static void Import2DX(string source, VoxHeader header, bool preview = false)
{
if (!File.Exists(source))
throw new FileNotFoundException($"{source} not found", source);
Import2DX(source, Get2DXPath(header, preview), preview);
}
public static void Import2DX(string source, VoxHeader header, Difficulty difficulty, bool preview = false)
{
if (!File.Exists(source))
throw new FileNotFoundException($"{source} not found", source);
Import2DX(source, Get2DXPath(header, difficulty, preview), preview);
}
private static void Import2DX(string source, string output, bool preview = false)
{
if (!source.EndsWith(".2dx") && !source.EndsWith(".s3v"))
{
string tmp = DxTool.ConvertToWave(source, preview);
DxTool.Build(tmp, output);
Directory.Delete(tmp, true);
}
else
{
File.Copy(source, output);
}
}
public static void ImportJacket(VoxHeader header, Image image)
{
ImportJacket(header, Difficulty.Novice, image);
}
public static void ImportJacket(VoxHeader header, Difficulty diff, Image image)
{
// Texture Small
Image texSmall = new Bitmap(image, new Size(130, 130));
texSmall.Save($"{GetThumbnailJacketPath(header, diff)}.png", ImageFormat.Png);
texSmall.Dispose();
texSmall = new Bitmap(image, new Size(108, 108));
texSmall.Save($"{GetJacketPath(header, diff)}_s.png", ImageFormat.Png);
texSmall.Dispose();
// Texture Big
Image texBig = new Bitmap(image, new Size(676, 676));
texBig.Save($"{GetJacketPath(header, diff)}_b.png", ImageFormat.Png);
texBig.Dispose();
// Texture Normal
Image texNormal = new Bitmap(image, new Size(300, 300));
texNormal.Save($"{GetJacketPath(header, diff)}.png", ImageFormat.Png);
texNormal.Dispose();
}
public static void ImportVox(VoxHeader header)
{
Directory.CreateDirectory(GetMusicPath(header));
foreach (var level in header.Levels.Values)
{
if (level.Chart == null)
continue;
var path = GetVoxPath(header, level.Difficulty);
level.Chart.Serialize(path);
}
}
public static void ImportVox(VoxHeader header, Difficulty diff, VoxChart chart)
{
Directory.CreateDirectory(GetMusicPath(header));
chart.Serialize(GetVoxPath(header, diff));
}
public static void ImportVox(VoxHeader header, Difficulty diff, string content)
{
File.WriteAllText(GetVoxPath(header, diff), content);
}
public static void DeleteAssets(VoxHeader header)
{
string musicPath = GetMusicPath(header);
if (Directory.Exists(musicPath))
Directory.Delete(musicPath, true);
string jacketPath = Path.Combine(
MixPath,
$"graphics\\s_jacket00_ifs\\"
);
string pattern = $"jk_{header.ID:D4}*";
foreach (string jacket in Directory.GetFiles(jacketPath, pattern))
{
if (File.Exists(jacket))
File.Delete(jacket);
}
}
#endregion
#region --- Asset Identifier ---
public static string GetDifficultyCodes(VoxHeader header, Difficulty difficulty)
{
switch (difficulty)
{
case Difficulty.Novice: return "1n";
case Difficulty.Advanced: return "2a";
case Difficulty.Exhaust: return "3e";
default:
if (header.InfVersion == InfiniteVersion.MXM)
return "5m";
return "4i";
}
}
public static string GetMusicPath(VoxHeader header)
{
return Path.Combine(
MixPath,
$"music\\{header.CodeName}\\"
);
}
public static string GetVoxPath(VoxHeader header, Difficulty difficulty)
{
return Path.Combine(
GetMusicPath(header),
$"{header.CodeName}_{GetDifficultyCodes(header, difficulty)}.vox"
);
}
public static string Get2DXPath(VoxHeader header, bool preview = false)
{
string ext = preview ? "_pre.2dx" : ".2dx";
return Path.Combine(
GetMusicPath(header),
$"{header.CodeName}{ext}"
);
}
public static string Get2DXPath(VoxHeader header, Difficulty difficulty, bool preview = false)
{
string ext = preview ? "_pre.2dx" : ".2dx";
return Path.Combine(
GetMusicPath(header),
$"{header.CodeName}_{GetDifficultyCodes(header, difficulty)}{ext}"
);
}
public static string GetJacketPath(VoxHeader header, Difficulty difficulty)
{
int index = (int)difficulty;
if (difficulty == Difficulty.Infinite && header.InfVersion == InfiniteVersion.MXM)
index = 5;
return Path.Combine(GetMusicPath(header), $"jk_{header.ID:D4}_{index}");
}
public static string GetDefaultJacketPath(VoxHeader header)
{
return GetJacketPath(header, Difficulty.Novice);
}
public static string GetThumbnailJacketPath(VoxHeader header, Difficulty difficulty)
{
int index = (int)difficulty;
if (difficulty == Difficulty.Infinite && header.InfVersion == InfiniteVersion.MXM)
index = 5;
string thumbnailDir = "s_jacket00_ifs";
string graphicsDir = Path.Combine(MixPath, @"graphics\");
if (Directory.Exists(graphicsDir))
{
foreach (string dir in Directory.GetDirectories(graphicsDir))
{
string name = new DirectoryInfo(dir).Name;
if (name.StartsWith("s_jacket") && name.EndsWith("_ifs"))
{
thumbnailDir = name;
break;
}
}
}
return Path.Combine(
graphicsDir,
$"{thumbnailDir}\\",
$"jk_{header.ID:D4}_{index}_t"
);
}
public static string GetDefaultThumbnailJacketPath(VoxHeader header)
{
return GetThumbnailJacketPath(header, Difficulty.Novice);
}
#endregion
#region --- Utilities ---
#endregion
}
}

View File

@ -0,0 +1,80 @@
using System;
using System.Linq;
namespace VoxCharger
{
public partial class Effect
{
public class BitCrusher : Effect
{
public float Mix { get; set; }
public int Reduction { get; set; }
public BitCrusher(float mix, int reduction)
: base(FxType.BitCrusher)
{
Mix = mix;
Reduction = reduction;
}
private BitCrusher()
: base(FxType.None)
{
}
public static new BitCrusher FromVox(string data)
{
var bitCrusher = new BitCrusher();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.BitCrusher)
return bitCrusher;
if (prop.Length != 3)
return bitCrusher;
try
{
bitCrusher.Type = type;
bitCrusher.Mix = float.Parse(prop[1]);
bitCrusher.Reduction = int.Parse(prop[2]);
}
catch (Exception)
{
bitCrusher.Type = FxType.None;
}
return bitCrusher;
}
public static new BitCrusher FromKsh(KshDefinition definition)
{
var bitCrusher = new BitCrusher();
try
{
definition.GetValue("mix", out float mix);
definition.GetValue("reduction", out int samples);
bitCrusher.Mix = mix;
bitCrusher.Reduction = samples;
bitCrusher.Type = FxType.BitCrusher;
}
catch (Exception)
{
bitCrusher.Type = FxType.None;
}
return bitCrusher;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type},\t{Mix:0.00},\t{Reduction}";
}
}
}
}

105
Sources/Effects/Effect.cs Normal file
View File

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
namespace VoxCharger
{
public partial class Effect
{
public static readonly Effect Empty = new Effect();
public static readonly Effect Default = new Retrigger(24, 95f, 1f, 1f, 0.85f, 0.16f, true);
public FxType Type { get; private set; }
public int Id { get; set; } = 1;
public Effect()
: this(FxType.None)
{
}
public Effect(FxType type)
{
Type = type;
}
public override string ToString()
{
return "0,\t0,\t0,\t0,\t0,\t0,\t0";
}
public static Effect FromVox(string data)
{
if (string.IsNullOrEmpty(data))
return null;
var prop = data.Trim().Split(',');
if (!Enum.TryParse(prop[0], out FxType type))
return null;
switch(type)
{
case FxType.Retrigger:
case FxType.RetriggerEx: return Retrigger.FromVox(data);
case FxType.Gate: return Gate.FromVox(data);
case FxType.Phaser: return Phaser.FromVox(data);
case FxType.TapeStopEx:
case FxType.TapeStop: return TapeStop.FromVox(data);
case FxType.SideChain: return SideChain.FromVox(data);
case FxType.Wobble: return Wobble.FromVox(data);
case FxType.BitCrusher: return BitCrusher.FromVox(data);
case FxType.PitchShift: return PitchShift.FromVox(data);
case FxType.LowPass: return LowPass.FromVox(data);
case FxType.Flanger: return Flanger.FromVox(data);
default: return new Effect();
}
}
public static Effect FromKsh(string data)
{
if (string.IsNullOrEmpty(data))
return null;
var prop = data.Trim().Split(',');
if (!Enum.TryParse(prop[0].Replace("Echo", "Retrigger"), out FxType type))
return null;
switch(type)
{
case FxType.Retrigger:
case FxType.RetriggerEx: return Retrigger.FromKsh(data);
case FxType.Gate: return Gate.FromKsh(data);
case FxType.Phaser: return Phaser.FromKsh(data);
case FxType.TapeStopEx:
case FxType.TapeStop: return TapeStop.FromKsh(data);
case FxType.SideChain: return SideChain.FromKsh(data);
case FxType.Wobble: return Wobble.FromKsh(data);
case FxType.PitchShift: return PitchShift.FromKsh(data);
case FxType.Flanger: return Flanger.FromKsh(data);
default: return Default;
}
}
public static Effect FromKsh(KshDefinition definition)
{
if (!definition.GetString("type", out string type))
return new Effect();
switch(type)
{
case "Echo":
case "Retrigger": return Retrigger.FromKsh(definition);
case "Gate": return Gate.FromKsh(definition);
case "Phaser": return Phaser.FromKsh(definition);
case "TapeStop": return TapeStop.FromKsh(definition);
case "SideChain": return SideChain.FromKsh(definition);
case "LowPass":
case "Wobble": return Wobble.FromKsh(definition);
case "PitchShift":
case "BitCrusher": return BitCrusher.FromKsh(definition);
case "Flanger": return Flanger.FromKsh(definition);
default: return new Effect();
}
}
}
}

102
Sources/Effects/Flanger.cs Normal file
View File

@ -0,0 +1,102 @@
using System;
using System.Linq;
namespace VoxCharger
{
public partial class Effect
{
public class Flanger : Effect
{
public float Mix { get; set; }
public float Samples { get; set; }
public float Depth { get; set; }
public float Period { get; set; }
public Flanger(float mix, float samples, float depth, float period)
: base(FxType.Flanger)
{
Mix = mix;
Samples = samples;
Depth = depth;
Period = period;
}
public Flanger()
: base(FxType.None)
{
}
public static new Flanger FromVox(string data)
{
var highPass = new Flanger();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.Flanger)
return highPass;
if (prop.Length != 5)
return highPass;
try
{
highPass.Type = type;
highPass.Mix = float.Parse(prop[1]);
highPass.Samples = float.Parse(prop[2]);
highPass.Depth = float.Parse(prop[3]);
highPass.Period = float.Parse(prop[4]);
}
catch (Exception)
{
highPass.Type = FxType.None;
}
return highPass;
}
public static new Phaser FromKsh(string data)
{
var prop = data.Trim().Split(';').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.Flanger)
return null;
return new Phaser(80.00f, 2.00f, 0.50f, 90, 2.00f);
}
public static new Flanger FromKsh(KshDefinition definition)
{
var flanger = new Flanger();
try
{
definition.GetValue("mix", out float mix);
definition.GetValue("depth", out int depth);
definition.GetValue("period", out float period);
flanger.Mix = mix;
flanger.Samples = int.TryParse(definition.Value, out int samples) ? samples * 10 : 0;
flanger.Depth = depth;
flanger.Period = period;
flanger.Type = FxType.Flanger;
}
catch (Exception)
{
flanger.Type = FxType.None;
}
return flanger;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type}," +
$"\t{Mix:0.00}," +
$"\t{Samples:0.00}," +
$"\t{Depth:0.00}," +
$"\t{Period:0.00}";
}
}
}
}

21
Sources/Effects/FxType.cs Normal file
View File

@ -0,0 +1,21 @@
using System;
namespace VoxCharger
{
public enum FxType
{
None = 0,
Retrigger = 1,
Gate = 2,
Phaser = 3,
TapeStop = 4,
SideChain = 5,
Wobble = 6,
BitCrusher = 7,
RetriggerEx = 8,
PitchShift = 9,
TapeStopEx = 10,
LowPass = 11,
Flanger = 12,
}
}

104
Sources/Effects/Gate.cs Normal file
View File

@ -0,0 +1,104 @@
using System;
using System.Linq;
namespace VoxCharger
{
public partial class Effect
{
public class Gate : Effect
{
public float Mix { get; set; }
public int Length { get; set; }
public float Rate { get; set; }
public Gate(float mix, int length, float rate)
: base(FxType.Gate)
{
Mix = mix;
Length = length;
Rate = rate;
}
private Gate()
: base(FxType.None)
{
}
public static new Gate FromVox(string data)
{
var gate = new Gate();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.Gate)
return gate;
if (prop.Length != 4)
return gate;
try
{
gate.Type = type;
gate.Mix = float.Parse(prop[1]);
gate.Length = int.Parse(prop[2]);
gate.Rate = float.Parse(prop[3]);
}
catch (Exception)
{
gate.Type = FxType.None;
}
return gate;
}
public static new Gate FromKsh(string data)
{
var gate = new Gate();
var prop = data.Trim().Split(';').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.Gate)
return gate;
float waveLength = 8f;
if (prop.Length > 1)
waveLength = float.TryParse(prop[1], out waveLength) ? waveLength : 8f;
gate.Type = type;
gate.Mix = 100.00f;
gate.Length = (int)(waveLength / 2);
gate.Rate = 1.00f;
return gate;
}
public static new Gate FromKsh(KshDefinition definition)
{
var gate = new Gate();
try
{
if (!definition.GetValue("mix", out float mix) || !definition.GetValue("waveLength", out int waveLength))
return gate;
gate.Mix = mix;
gate.Length = waveLength / 2;
gate.Rate = 1.00f;
gate.Type = FxType.Gate;
}
catch (Exception)
{
gate.Type = FxType.None;
}
return gate;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type},\t{Mix:0.00},\t{Length},\t{Rate:0.00}";
}
}
}
}

View File

@ -0,0 +1,84 @@
using System;
using System.Linq;
namespace VoxCharger
{
public partial class Effect
{
public class LowPass : Effect
{
public float Mix { get; set; }
public float Frequency { get; set; }
public LowPass(float mix, float frequency)
: base(FxType.LowPass)
{
Mix = mix;
Frequency = frequency;
}
private LowPass()
: base(FxType.None)
{
}
public static new LowPass FromVox(string data)
{
var lowPass = new LowPass();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.LowPass)
return lowPass;
if (prop.Length != 5)
return lowPass;
try
{
lowPass.Type = type;
lowPass.Mix = float.Parse(prop[1]);
lowPass.Frequency = float.Parse(prop[3]);
}
catch (Exception)
{
lowPass.Type = FxType.None;
}
return lowPass;
}
public static new LowPass FromKsh(KshDefinition definition)
{
var lowPass = new LowPass();
try
{
definition.GetValue("loFreq", out int loFreq);
definition.GetValue("hiFreq", out int hiFreq);
lowPass.Mix = 100f;
lowPass.Frequency = loFreq != 0f ? loFreq : hiFreq;
lowPass.Type = FxType.LowPass;
}
catch (Exception)
{
lowPass.Type = FxType.None;
}
return lowPass;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type}," +
$"\t{Mix:0.00}," +
$"\t0," + // Unknown param
$"\t{Frequency:0.00}," +
$"\t0"; // Unknown too
}
}
}
}

111
Sources/Effects/Phaser.cs Normal file
View File

@ -0,0 +1,111 @@
using System;
using System.Linq;
namespace VoxCharger
{
public partial class Effect
{
public class Phaser : Effect
{
public float Mix { get; set; }
public float Period { get; set; }
public float Feedback { get; set; }
public int StereoWidth { get; set; }
public float HiCutGain { get; set; }
public Phaser(float mix, float period, float feedback, int stereoWidth, float hiCutGain)
: base(FxType.Phaser)
{
Mix = mix;
Period = period;
Feedback = feedback;
StereoWidth = stereoWidth;
HiCutGain = hiCutGain;
}
private Phaser()
: base(FxType.None)
{
}
public static new Phaser FromVox(string data)
{
var flanger = new Phaser();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.Phaser)
return flanger;
if (prop.Length != 6)
return flanger;
try
{
flanger.Type = type;
flanger.Mix = float.Parse(prop[1]);
flanger.Period = int.Parse(prop[2]);
flanger.Feedback = float.Parse(prop[3]);
flanger.StereoWidth = int.Parse(prop[4]);
flanger.HiCutGain = float.Parse(prop[5]);
}
catch (Exception)
{
flanger.Type = FxType.None;
}
return flanger;
}
public static new Wobble FromKsh(string data)
{
var prop = data.Trim().Split(';').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.Phaser)
return null;
var wobble = new Wobble(100.00f, 1500.00f, 20000.00f, 0.50f, 1.41f);
wobble.Flag = 1;
return wobble;
}
public static new Phaser FromKsh(KshDefinition definition)
{
var phaser = new Phaser();
try
{
definition.GetValue("mix", out float mix);
definition.GetValue("period", out float period);
definition.GetValue("feedback", out float feedback);
definition.GetValue("stereoWidth", out int stereoWidth);
definition.GetValue("hiCutGain", out float hiCutGain);
phaser.Mix = mix;
phaser.Period = period;
phaser.Feedback = feedback;
phaser.StereoWidth = stereoWidth;
phaser.HiCutGain = hiCutGain;
phaser.Type = FxType.Phaser;
}
catch (Exception)
{
phaser.Type = FxType.None;
}
return phaser;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type}," +
$"\t{Mix:0.00}," +
$"\t{Period:0.00}," +
$"\t{Feedback:0.00}," +
$"\t{StereoWidth}," +
$"\t{HiCutGain:0.00}";
}
}
}
}

View File

@ -0,0 +1,98 @@
using System;
using System.Linq;
namespace VoxCharger
{
public partial class Effect
{
public class PitchShift : Effect
{
public float Mix { get; set; }
public float Reduction { get; set; }
public PitchShift(float mix, float reduction)
: base(FxType.PitchShift)
{
Mix = mix;
Reduction = reduction;
}
private PitchShift()
: base(FxType.None)
{
}
public static new PitchShift FromVox(string data)
{
var pitchShift = new PitchShift();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.PitchShift)
return pitchShift;
if (prop.Length != 3)
return pitchShift;
try
{
pitchShift.Type = type;
pitchShift.Mix = float.Parse(prop[1]);
pitchShift.Reduction = float.Parse(prop[2]);
}
catch (Exception)
{
pitchShift.Type = FxType.None;
}
return pitchShift;
}
public static new PitchShift FromKsh(string data)
{
var pitchShift = new PitchShift();
var prop = data.Trim().Split(';').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.PitchShift)
return pitchShift;
float pitch = 12f;
if (prop.Length > 1)
pitch = float.TryParse(prop[1], out pitch) ? pitch : 12f;
pitchShift.Type = type;
pitchShift.Mix = 100.00f;
pitchShift.Reduction = pitch;
return pitchShift;
}
public static new PitchShift FromKsh(KshDefinition definition)
{
var pitchShift = new PitchShift();
try
{
definition.GetValue("mix", out float mix);
definition.GetValue("reduction", out float samples);
pitchShift.Mix = mix;
pitchShift.Reduction = samples;
pitchShift.Type = FxType.PitchShift;
}
catch (Exception)
{
pitchShift.Type = FxType.None;
}
return pitchShift;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type},\t{Mix:0.00},\t{Reduction:0.00}";
}
}
}
}

View File

@ -0,0 +1,138 @@
using System;
using System.Linq;
using System.Diagnostics;
using System.Collections.Generic;
namespace VoxCharger
{
public partial class Effect
{
public class Retrigger : Effect
{
public int WaveLength { get; set; }
public float Mix { get; set; }
public float UpdatePeriod { get; set; }
public float Feedback { get; set; }
public float Rate { get; set; }
public float Tick { get; set; }
public bool Updatable { get; set; }
public Retrigger(int waveLength, float mix, float updatePeriod, float feedback, float rate, float tick, bool updatable = false)
: base(updatable ? FxType.RetriggerEx : FxType.Retrigger)
{
WaveLength = waveLength;
Mix = mix;
UpdatePeriod = updatePeriod;
Feedback = feedback;
Rate = rate;
Tick = tick;
Updatable = updatable;
}
private Retrigger()
: base(FxType.None)
{
}
public static new Retrigger FromVox(string data)
{
var retrigger = new Retrigger();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || (type != FxType.Retrigger && type != FxType.RetriggerEx))
return retrigger;
if (prop.Length < 7)
return retrigger;
try
{
retrigger.WaveLength = int.Parse(prop[1]);
retrigger.Mix = float.Parse(prop[2]);
retrigger.UpdatePeriod = float.Parse(prop[3]);
retrigger.Feedback = float.Parse(prop[4]);
retrigger.Rate = float.Parse(prop[5]);
retrigger.Tick = float.Parse(prop[6]);
retrigger.Updatable = type == FxType.RetriggerEx;
retrigger.Type = type;
}
catch (Exception)
{
retrigger.Type = FxType.None;
}
return retrigger;
}
public static new Retrigger FromKsh(string data)
{
var retrigger = new Retrigger();
var prop = data.Trim().Split(';').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0].Replace("Echo", "Retrigger"), out FxType type) || (type != FxType.Retrigger && type != FxType.RetriggerEx))
return retrigger;
float waveLength = 8f;
if (prop.Length > 1)
waveLength = float.TryParse(prop[1], out waveLength) ? waveLength : 8f;
float defaultFeedback = prop[0] == "Echo" ? 0.6f : 1.0f;
float feedback = defaultFeedback;
if (prop.Length > 2)
feedback = float.TryParse(prop[2], out feedback) ? feedback : defaultFeedback;
retrigger.WaveLength = (int)(waveLength / 2);
retrigger.Mix = 100.0f;
retrigger.UpdatePeriod = 2.00f;
retrigger.Feedback = 1.00f;
retrigger.Rate = 0.70f;
retrigger.Tick = 0.15f;
retrigger.Updatable = true;
retrigger.Type = FxType.RetriggerEx;
return retrigger;
}
public static new Retrigger FromKsh(KshDefinition definition)
{
var retrigger = new Retrigger();
try
{
if (!definition.GetValue("mix", out float mix) || !definition.GetValue("updatePeriod", out float updatePeriod))
return retrigger;
retrigger.WaveLength = definition.GetValue("waveLength", out int waveLength) ? waveLength : 0;
retrigger.Mix = mix;
retrigger.Feedback = definition.GetValue("feedbackLevel", out float feedback) ? feedback : 0f;
retrigger.Rate = definition.GetValue("rate", out float rate) ? rate : 0f;
retrigger.UpdatePeriod = updatePeriod * 4f;
retrigger.Tick = updatePeriod < 1.0f ? 1.0f - updatePeriod : 0f;
retrigger.Updatable = definition.GetString("updateTrigger", out string _);
retrigger.Type = retrigger.Updatable ? FxType.RetriggerEx : FxType.Retrigger;
}
catch (Exception)
{
retrigger.Type = FxType.None;
}
return retrigger;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type}," +
$"\t{WaveLength}," +
$"\t{Mix:0.00}," +
$"\t{UpdatePeriod:0.00}," +
$"\t{Feedback:0.00}," +
$"\t{Rate:0.00}," +
$"\t{Tick:0.00}" +
(Updatable ? ",\t0.00" : string.Empty);
}
}
}
}

View File

@ -0,0 +1,117 @@
using System;
using System.Linq;
namespace VoxCharger
{
public partial class Effect
{
public class SideChain : Effect
{
public float Mix { get; set; }
public float Period { get; set; }
public int Hold { get; set; }
public int Attack { get; set; }
public int Release { get; set; }
public SideChain(float mix, float period, int hold, int attack, int release)
: base(FxType.SideChain)
{
Mix = mix;
Period = period;
Hold = hold;
Attack = attack;
Release = release;
}
public SideChain()
: base(FxType.None)
{
}
public static new SideChain FromVox(string data)
{
var sideChain = new SideChain();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.SideChain)
return sideChain;
if (prop.Length != 6)
return sideChain;
try
{
sideChain.Type = type;
sideChain.Mix = float.Parse(prop[1]);
sideChain.Period = int.Parse(prop[2]);
sideChain.Hold = int.Parse(prop[3]);
sideChain.Attack = int.Parse(prop[4]);
sideChain.Release = int.Parse(prop[5]);
}
catch (Exception)
{
sideChain.Type = FxType.None;
}
return sideChain;
}
public static new SideChain FromKsh(string data)
{
var sideChain = new SideChain();
var prop = data.Trim().Split(';').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.SideChain)
return sideChain;
sideChain.Type = type;
sideChain.Mix = 90.00f;
sideChain.Period = 1.00f;
sideChain.Hold = 45;
sideChain.Attack = 50;
sideChain.Release = 60;
return sideChain;
}
public static new SideChain FromKsh(KshDefinition definition)
{
var sideChain = new SideChain();
try
{
definition.GetValue("mix", out float mix);
definition.GetValue("period", out float period);
definition.GetValue("holdTime", out int hold);
definition.GetValue("attackTime", out int attack);
definition.GetValue("releaseTime", out int release);
sideChain.Mix = mix;
sideChain.Period = period / 2f;
sideChain.Hold = hold;
sideChain.Attack = attack;
sideChain.Release = release;
sideChain.Type = FxType.SideChain;
}
catch (Exception)
{
sideChain.Type = FxType.None;
}
return sideChain;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type}," +
$"\t{Mix:0.00}," +
$"\t{Period:0.00}," +
$"\t{Hold}," +
$"\t{Attack}," +
$"\t{Release}";
}
}
}
}

110
Sources/Effects/TapeStop.cs Normal file
View File

@ -0,0 +1,110 @@
using System;
using System.Linq;
namespace VoxCharger
{
public partial class Effect
{
public class TapeStop : Effect
{
public float Mix { get; set; }
public float Speed { get; set; }
public float Rate { get; set; }
public TapeStop(float mix, float speed, float rate)
: base(FxType.TapeStop)
{
Mix = mix;
Speed = speed;
Rate = rate;
}
private TapeStop()
: base(FxType.None)
{
}
public static new TapeStop FromVox(string data)
{
var tapeStop = new TapeStop();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || (type != FxType.TapeStop && type != FxType.TapeStopEx))
return tapeStop;
if (prop.Length < 4)
return tapeStop;
try
{
tapeStop.Type = type;
tapeStop.Mix = float.Parse(prop[1]);
tapeStop.Speed = float.Parse(prop[2]);
tapeStop.Rate = float.Parse(prop[3]);
}
catch (Exception)
{
tapeStop.Type = FxType.None;
}
return tapeStop;
}
public static new TapeStop FromKsh(string data)
{
var tapeStop = new TapeStop();
var prop = data.Trim().Split(';').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || (type != FxType.TapeStop && type != FxType.TapeStopEx))
return tapeStop;
if (prop.Length < 4)
return tapeStop;
float rate = 50f;
if (prop.Length > 1)
rate = float.TryParse(prop[1], out rate) ? rate : 50f;
rate = -(rate - 20 - 40) / 40f;
if (rate < 0)
rate = 0.1f;
tapeStop.Type = type;
tapeStop.Mix = 100.00f;
tapeStop.Speed = 8.00f;
tapeStop.Rate = rate;
return tapeStop;
}
public static new TapeStop FromKsh(KshDefinition definition)
{
var tapeStop = new TapeStop();
try
{
if (!definition.GetValue("mix", out float mix) || !definition.GetValue("speed", out int speed))
return tapeStop;
tapeStop.Mix = mix;
tapeStop.Speed = speed / 10f;
tapeStop.Rate = (tapeStop.Speed / 2f) / 10f;
tapeStop.Type = FxType.TapeStop;
}
catch (Exception)
{
tapeStop.Type = FxType.None;
}
return tapeStop;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type},\t{Mix:0.00},\t{Speed:0.00},\t{Rate:0.00}";
}
}
}
}

119
Sources/Effects/Wobble.cs Normal file
View File

@ -0,0 +1,119 @@
using System;
using System.Linq;
namespace VoxCharger
{
public partial class Effect
{
public class Wobble : Effect
{
public float Mix { get; set; }
public float LowFrequency { get; set; }
public float HiFrequency { get; set; }
public float WaveLength { get; set; }
public float Resonance { get; set; }
public int Flag { get; set; } = 1;
public Wobble(float mix, float lowFrequency, float hiFrequency, float waveLength, float resonance)
: base(FxType.Wobble)
{
Mix = mix;
LowFrequency = lowFrequency;
HiFrequency = hiFrequency;
WaveLength = waveLength;
Resonance = resonance;
}
private Wobble()
: base(FxType.None)
{
}
public static new Wobble FromVox(string data)
{
var wobble = new Wobble();
var prop = data.Trim().Split(',').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.Wobble)
return wobble;
if (prop.Length != 8)
return wobble;
try
{
wobble.Type = type;
wobble.Mix = float.Parse(prop[3]);
wobble.LowFrequency = float.Parse(prop[4]);
wobble.HiFrequency = float.Parse(prop[5]);
wobble.WaveLength = float.Parse(prop[6]);
wobble.Resonance = float.Parse(prop[7]);
}
catch (Exception)
{
wobble.Type = FxType.None;
}
return wobble;
}
public static new Wobble FromKsh(string data)
{
var wobble = new Wobble();
var prop = data.Trim().Split(';').Select(p => p.Trim()).ToArray();
if (!Enum.TryParse(prop[0], out FxType type) || type != FxType.Wobble)
return wobble;
wobble.Type = type;
wobble.Mix = 100.00f;
wobble.LowFrequency = 500.00f;
wobble.HiFrequency = 20000.00f;
wobble.WaveLength = 4.00f;
wobble.Resonance = 1.41f;
return wobble;
}
public static new Wobble FromKsh(KshDefinition definition)
{
var wobble = new Wobble();
try
{
definition.GetValue("mix", out float mix);
definition.GetValue("loFreq", out float lowFreq);
definition.GetValue("hiFreq", out float highFreq);
definition.GetValue("waveLength", out float waveLength);
definition.GetValue("resonance", out float resonance);
wobble.Flag = 3;
wobble.Mix = mix;
wobble.LowFrequency = lowFreq;
wobble.HiFrequency = highFreq;
wobble.WaveLength = waveLength * 4;
wobble.Resonance = resonance;
wobble.Type = FxType.Wobble;
}
catch (Exception)
{
wobble.Type = FxType.None;
}
return wobble;
}
public override string ToString()
{
if (Type == FxType.None)
return base.ToString();
return $"{(int)Type}," +
$"\t,0,\t{Flag}," + // Unknown params
$"\t{Mix:0.00}," +
$"\t{LowFrequency:0.00}," +
$"\t{HiFrequency:0.00}," +
$"\t{WaveLength:0.00}," +
$"\t{Resonance:0.00}";
}
}
}
}

25
Sources/Events/BPM.cs Normal file
View File

@ -0,0 +1,25 @@
using System;
namespace VoxCharger
{
public abstract partial class Event
{
public class BPM : Event
{
public float Value { get; set; }
public bool IsStop { get; set; }
public BPM(Time time, float value)
: base(time)
{
Value = value;
}
public override string ToString()
{
return $"{base.ToString()}\t{Value:0.00}\t{4}" + (IsStop ? "-" : string.Empty);
}
}
}
}

64
Sources/Events/Button.cs Normal file
View File

@ -0,0 +1,64 @@
using System;
namespace VoxCharger
{
public abstract partial class Event
{
public enum ButtonTrack
{
A = 3,
B = 4,
C = 5,
D = 6,
FxL = 2,
FxR = 7
}
public enum ChipFx
{
None = 0,
Fx2 = 2,
Clap = 4,
ClapImpact = 3,
ClapPunchy = 5,
Snare = 6,
SnareLow = 8,
Fx7 = 7,
Fx9 = 9,
Fx10 = 10,
Fx11 = 11,
Fx12 = 12,
Fx13 = 13,
Fx14 = 14
}
public class Button : Event
{
public ButtonTrack Track { get; set; }
public int HoldLength { get; set; }
public Effect HoldFx { get; set; }
public ChipFx HitFx { get; set; }
public bool IsFx => Track == ButtonTrack.FxL || Track == ButtonTrack.FxR;
public Button(Time time, ButtonTrack track, int holdLength, Effect holdFx = null, ChipFx chipFx = ChipFx.None)
: base (time)
{
Track = track;
HoldLength = holdLength;
HoldFx = holdFx;
HitFx = chipFx;
}
public override string ToString()
{
int fx = 0;
if (HoldFx != null)
fx = HoldFx.Id;
else
fx = (int)HitFx;
return $"{base.ToString()}\t{HoldLength}\t{fx}";
}
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VoxCharger
{
public abstract partial class Camera : Event
{
public WorkType Work { get; private set; } = WorkType.None;
public int Duration { get; set; }
public float Start { get; set; }
public float End { get; set; }
public Camera(Time time, WorkType work)
: base(time)
{
Work = work;
}
public static Camera Create(WorkType work, Time time, int duration, float start, float end)
{
switch (work)
{
case WorkType.Rotation: return new Rotation(time, duration, start, end);
case WorkType.Radian: return new Radian(time, duration, start, end);
case WorkType.Tilt: return new Tilt(time, duration, start, end);
case WorkType.LaneClear: return new LaneClear(time, duration, start, end);
}
return null;
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VoxCharger
{
public abstract partial class Camera
{
public class LaneClear : Camera
{
public LaneClear(Time time, int duration, float start, float end)
: base(time, WorkType.LaneClear)
{
Duration = duration;
Start = start;
End = end;
}
public override string ToString()
{
return $"{base.ToString()}" +
$"\tLaneY" +
$"\t2" +
$"\t{Duration:0.00}" +
$"\t{Start:0.00}" +
$"\t{End:0.00}" +
$"\t0.00" +
$"\t0.00";
}
}
}
}

View File

@ -0,0 +1,31 @@
using System;
namespace VoxCharger
{
public abstract partial class Camera
{
public class Radian : Camera
{
public Radian(Time time, int duration, float start, float end)
: base(time, WorkType.Radian)
{
Duration = duration;
Start = start;
End = end;
}
public override string ToString()
{
return $"{base.ToString()}" +
$"\tCAM_Radi" +
$"\t2" +
$"\t{Duration}" +
$"\t{Start:0.00}" +
$"\t{End:0.00}" +
$"\t0.00" +
$"\t0.00";
}
}
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace VoxCharger
{
public abstract partial class Camera
{
public class Rotation : Camera
{
public Rotation(Time time, int duration, float start, float end)
: base(time, WorkType.Rotation)
{
Duration = duration;
Start = start;
End = end;
}
public override string ToString()
{
return $"{base.ToString()}" +
$"\tCAM_RotX" +
$"\t2" +
$"\t{Duration}" +
$"\t{Start:0.00}" +
$"\t{End:0.00}" +
$"\t0.00" +
$"\t0.00";
}
}
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace VoxCharger
{
public abstract partial class Camera
{
public class Tilt : Camera
{
public Tilt(Time time, int duration, float start, float end)
: base(time, WorkType.Rotation)
{
Duration = duration;
Start = start;
End = end;
}
public override string ToString()
{
return $"{base.ToString()}" +
$"\tTilt" +
$"\t2" +
$"\t{Duration}" +
$"\t{Start:0.00}" +
$"\t{End:0.00}" +
$"\t0.00" + // Can be 0.00, 2.00 or 3.00
$"\t0.00";
}
}
}
}

View File

@ -0,0 +1,19 @@
using System;
namespace VoxCharger
{
public abstract partial class Camera
{
public enum WorkType
{
None,
Rotation,
Radian,
Realize,
AirLeftScaleX,
AirLeftScaleY,
Tilt,
LaneClear
}
}
}

21
Sources/Events/Event.cs Normal file
View File

@ -0,0 +1,21 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace VoxCharger
{
public abstract partial class Event
{
public Time Time { get; set; }
protected Event(Time time)
{
Time = new Time(time.Measure, time.Beat, time.Offset);
}
public override string ToString()
{
return Time.ToString();
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace VoxCharger
{
public class EventCollection : ICollection<Event>
{
private List<Event> Events = new List<Event>();
public int Count => Events.Count;
public bool IsReadOnly => false;
public EventCollection()
{
}
public Event[] this[int measure]
{
get => Events.FindAll((ev) => ev.Time.Measure == measure).ToArray();
}
public Event[] this[Time time]
{
get => Events.FindAll((ev) => ev.Time == time).ToArray();
set => Events.AddRange(value.Where(ev => ev != null).Select(ev => { ev.Time = time; return ev; }));
}
public Event.TimeSignature GetTimeSignature(int measure)
{
return GetTimeSignature(new Time(measure, 1, 0));
}
public Event.TimeSignature GetTimeSignature(Time time)
{
var timeSig = Events.LastOrDefault(ev =>
ev is Event.TimeSignature && (ev.Time == time || ev.Time.Measure < time.Measure)
) as Event.TimeSignature;
return timeSig != null ? timeSig : new Event.TimeSignature(time, 4, 4);
}
public Event.BPM GetBPM(int measure)
{
return GetBPM(new Time(measure, 1, 0));
}
public Event.BPM GetBPM(Time time)
{
return Events.LastOrDefault(ev =>
ev is Event.BPM && (ev.Time == time || ev.Time.Measure < time.Measure)
) as Event.BPM;
}
public void Add(Event ev)
{
if (ev != null)
Events.Add(ev);
}
public void Add(params Event[] ev)
{
Events.AddRange(new List<Event>(ev).FindAll(e => e != null));
}
public bool Remove(Event ev)
{
return Events.Remove(ev);
}
public bool Contains(Event ev)
{
return Events.Contains(ev);
}
public void CopyTo(Event[] events, int index)
{
Events.CopyTo(events, index);
}
public void Clear()
{
Events.Clear();
}
public IEnumerator<Event> GetEnumerator()
{
return Events.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Events.GetEnumerator();
}
}
}

92
Sources/Events/Laser.cs Normal file
View File

@ -0,0 +1,92 @@
using System;
namespace VoxCharger
{
public abstract partial class Event
{
public enum LaserTrack
{
Left = 1,
Right = 8
}
public enum LaserFlag
{
Tick = 0,
Start = 1,
End = 2
}
public enum SlamImpact
{
None = 0,
Measure = 1,
HalfMeasure = 2,
ThreeBeat = 3,
TripleMeasure = 4,
Swing = 5
}
public enum LaserFilter
{
Peak = 0,
LowPass = 1,
HighPass = 3,
BitCrusher = 5
}
public enum SlamDirection
{
Left,
Right
}
public class Laser : Event
{
public LaserTrack Track { get; set; }
public int Offset { get; set; }
public LaserFlag Flag { get; set; }
public SlamImpact Impact { get; set; }
public int Range { get; set; }
public LaserFilter Filter { get; set; }
public bool Slam { get; set; }
public Laser(Time time, LaserTrack track, int offset, LaserFlag flag, SlamImpact impact)
: base (time)
{
Track = track;
Offset = offset;
Flag = flag;
Impact = impact;
}
public Laser(Time time, LaserTrack track, int offset, LaserFlag flag, SlamImpact impact, LaserFilter filter, int range)
: this(time, track, offset, flag, impact)
{
Filter = filter;
Range = range;
}
public override string ToString()
{
return $"{base.ToString()}\t{Offset}\t{(int)Flag}\t{(int)Impact}\t{(int)Filter}\t{Range}\t0";
}
}
// TODO: Pairing like this tend to break stuffs, need more tests, or don't use it at all
public class Slam : Event
{
public Laser Start { get; set; }
public Laser End { get; set; }
public LaserTrack Track => Start.Track;
public SlamDirection Direction => Start.Offset > End.Offset ? SlamDirection.Left : SlamDirection.Right;
public Slam(Time time, Laser start, Laser end)
: base (time)
{
Start = start;
End = end;
}
}
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace VoxCharger
{
public abstract partial class Event
{
public class TimeSignature : Event
{
public int Beat { get; set; }
public int Note { get; set; }
public TimeSignature(Time time, int beat, int note)
: base(time)
{
Beat = beat;
Note = note;
}
public static implicit operator (int beat, int note)(TimeSignature signature)
{
return (signature.Beat, signature.Note);
}
public override string ToString()
{
return $"{base.ToString()}\t{Beat}\t{Note}";
}
}
}
}

23
Sources/Events/Stop.cs Normal file
View File

@ -0,0 +1,23 @@
using System;
namespace VoxCharger
{
public abstract partial class Event
{
public class Stop : Event
{
public int Duration { get; set; }
public Stop(Time time)
: base(time)
{
}
public Stop(Time time, int duration)
: base(time)
{
Duration = duration;
}
}
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace VoxCharger
{
public abstract partial class Event
{
public enum TiltType
{
Normal = 0,
Large = 1,
Incremental = 2
}
public class TiltMode : Event
{
public TiltType Mode { get; set; }
public TiltMode(Time time, TiltType mode)
: base(time)
{
Mode = mode;
}
public override string ToString()
{
return $"{base.ToString()}\t{(int)Mode}";
}
}
}
}

165
Sources/Events/Time.cs Normal file
View File

@ -0,0 +1,165 @@
using System;
namespace VoxCharger
{
public class Time
{
public int Measure { get; set; }
public int Beat { get; set; }
public int Offset { get; set; }
public Time()
: this(1, 1, 0)
{
}
public Time(int measure, int beat, int cell)
{
Measure = measure;
Beat = beat;
Offset = cell;
}
public static Time FromString(string input)
{
try
{
var data = input.Split(',');
if (data.Length != 3)
return null;
return new Time()
{
Measure = int.Parse(data[0]),
Beat = int.Parse(data[1]),
Offset = int.Parse(data[2])
};
}
catch (Exception)
{
return null;
}
}
public static Time FromOffset(float absOffset, (int beat, int note) signature)
{
float remaining = (absOffset % 192f) % (192f / signature.beat);
float divisor = (192f / signature.beat);
int measure = (int)(absOffset / 192f);
int beat = (int)((absOffset % 192f) / divisor) + 1;
int offset = (int)Math.Round((remaining / (divisor / signature.note)) * ((192f / 4f) / 4f));
// Since the value rounded up, there's chance this happen (precision rounding problem)
beat += offset / 48;
measure += beat > signature.beat ? 1 : 0;
beat = beat > signature.beat ? signature.beat - (beat - 1) : beat;
offset %= 48;
return new Time(measure, beat, offset);
}
public int GetAbsoluteOffset((int beat, int note) signature)
{
float offset = Measure * 192f;
offset += ((Beat - 1) * (192f / signature.beat));
offset += ((Offset / (192f / 4f)) * (192f / signature.beat));
return (int)Math.Round(offset);
}
public int Difference(Time time, (int beat, int note) signature)
{
// Only effective when both using same time signature
float tick = ((192f / 4f) * (4f / signature.note));
return (int)((Measure - time.Measure) * (tick * signature.beat) +
(Beat - time.Beat) * tick +
(Offset - time.Offset));
}
public Time Add(int position, (int beat, int note) signature)
{
int measure = Measure + (int)(position / 192f);
int beat = Beat + (int)((position % 192f) / (192f / signature.beat));
float absolute = (position % 192f) % (192f / signature.beat);
int offset = Offset + (int)Math.Round((absolute / ((192f / signature.beat) / signature.note)) * ((192f / 4f) / 4f));
beat += offset / 48;
offset %= 48;
measure += beat / signature.beat;
beat %= signature.beat;
if (beat == 0)
{
measure -= 1;
beat = signature.beat;
}
return new Time(measure, beat, offset);
}
public Time Add(Time time, (int beat, int note) signature)
{
int measure = Measure + time.Measure;
int beat = Beat + time.Beat;
int offset = Offset + time.Offset;
beat += offset / 48;
offset %= 48;
measure += beat / signature.beat;
beat %= signature.beat;
if (beat == 0)
{
measure -= 1;
beat = signature.beat;
}
return new Time(measure, beat, offset);
}
public static bool operator ==(Time a, Time b)
{
if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
return ReferenceEquals(a, b);
return a.Measure == b.Measure && a.Beat == b.Beat && a.Offset == b.Offset;
}
public static bool operator !=(Time a, Time b)
{
if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
return ReferenceEquals(a, b);
return a.Measure != b.Measure || a.Beat != b.Beat || a.Offset != b.Offset;
}
public static Time operator +(Time time, int position)
{
return time.Add(position, (4, 4));
}
public static Time operator +(Time a, Time b)
{
return a.Add(b, (4, 4));
}
public override bool Equals(object obj)
{
return obj is Time time && time == this;
}
public override int GetHashCode()
{
return Measure + Beat + Offset;
}
public override string ToString()
{
return $"{Measure:D3},{Beat:D2},{Offset:D2}";
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Windows.Forms;
namespace VoxCharger
{
public partial class AboutForm : Form
{
public AboutForm()
{
InitializeComponent();
}
private void OnEmailLinkLabelLinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
System.Diagnostics.Process.Start("mailto://o2jam@cxo2.me");
}
private void OnCloseButtonClick(object sender, EventArgs e)
{
Close();
}
}
}

152
Sources/Forms/AboutForm.designer.cs generated Normal file
View File

@ -0,0 +1,152 @@
namespace VoxCharger
{
partial class AboutForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AboutForm));
this.ProfilePictureBox = new System.Windows.Forms.PictureBox();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.EmailLinkLabel = new System.Windows.Forms.LinkLabel();
this.label4 = new System.Windows.Forms.Label();
this.CloseButton = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.ProfilePictureBox)).BeginInit();
this.SuspendLayout();
//
// ProfilePictureBox
//
this.ProfilePictureBox.Image = ((System.Drawing.Image)(resources.GetObject("ProfilePictureBox.Image")));
this.ProfilePictureBox.Location = new System.Drawing.Point(21, 21);
this.ProfilePictureBox.Name = "ProfilePictureBox";
this.ProfilePictureBox.Size = new System.Drawing.Size(80, 80);
this.ProfilePictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
this.ProfilePictureBox.TabIndex = 0;
this.ProfilePictureBox.TabStop = false;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label1.Location = new System.Drawing.Point(107, 21);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(64, 13);
this.label1.TabIndex = 1;
this.label1.Text = "VoxCharger";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label2.Location = new System.Drawing.Point(107, 36);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(77, 13);
this.label2.TabIndex = 2;
this.label2.Text = "Version 0.9.7b";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label3.Location = new System.Drawing.Point(107, 51);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(131, 13);
this.label3.TabIndex = 3;
this.label3.Text = "Copyright © 2020 - CXO2";
//
// EmailLinkLabel
//
this.EmailLinkLabel.AutoSize = true;
this.EmailLinkLabel.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.EmailLinkLabel.Location = new System.Drawing.Point(107, 67);
this.EmailLinkLabel.Name = "EmailLinkLabel";
this.EmailLinkLabel.Size = new System.Drawing.Size(87, 13);
this.EmailLinkLabel.TabIndex = 4;
this.EmailLinkLabel.TabStop = true;
this.EmailLinkLabel.Text = "o2jam@cxo2.me";
this.EmailLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnEmailLinkLabelLinkClicked);
//
// label4
//
this.label4.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.label4.AutoSize = true;
this.label4.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label4.Location = new System.Drawing.Point(21, 118);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(250, 26);
this.label4.TabIndex = 5;
this.label4.Text = "This freeware program.\r\nUse at your own risk with no warranty of any kind.\r\n";
//
// CloseButton
//
this.CloseButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.CloseButton.Location = new System.Drawing.Point(21, 151);
this.CloseButton.Name = "CloseButton";
this.CloseButton.Size = new System.Drawing.Size(292, 26);
this.CloseButton.TabIndex = 6;
this.CloseButton.Text = "OK";
this.CloseButton.UseVisualStyleBackColor = true;
this.CloseButton.Click += new System.EventHandler(this.OnCloseButtonClick);
//
// AboutForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(334, 189);
this.Controls.Add(this.CloseButton);
this.Controls.Add(this.label4);
this.Controls.Add(this.EmailLinkLabel);
this.Controls.Add(this.label3);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.ProfilePictureBox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "AboutForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "About VoxCharger";
((System.ComponentModel.ISupportInitialize)(this.ProfilePictureBox)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.PictureBox ProfilePictureBox;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.LinkLabel EmailLinkLabel;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Button CloseButton;
}
}

3180
Sources/Forms/AboutForm.resx Normal file

File diff suppressed because it is too large Load Diff

596
Sources/Forms/ConverterForm.Designer.cs generated Normal file
View File

@ -0,0 +1,596 @@
namespace VoxCharger
{
partial class ConverterForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.LevelGroupBox = new System.Windows.Forms.GroupBox();
this.InfEditButton = new System.Windows.Forms.Button();
this.ExhEditButton = new System.Windows.Forms.Button();
this.AdvEditButton = new System.Windows.Forms.Button();
this.NovEditButton = new System.Windows.Forms.Button();
this.JacketInfPictureBox = new System.Windows.Forms.PictureBox();
this.JacketExhPictureBox = new System.Windows.Forms.PictureBox();
this.JacketAdvPictureBox = new System.Windows.Forms.PictureBox();
this.JacketNovPictureBox = new System.Windows.Forms.PictureBox();
this.PathTextBox = new System.Windows.Forms.TextBox();
this.OptionsGroupBox = new System.Windows.Forms.GroupBox();
this.SlamImpactCheckBox = new System.Windows.Forms.CheckBox();
this.VersionDropDown = new System.Windows.Forms.ComboBox();
this.InfVerDropDown = new System.Windows.Forms.ComboBox();
this.BackgroundDropDown = new System.Windows.Forms.ComboBox();
this.BackgroundLabel = new System.Windows.Forms.Label();
this.RealignOffsetCheckBox = new System.Windows.Forms.CheckBox();
this.AsciiAutoCheckBox = new System.Windows.Forms.CheckBox();
this.InfVerLabel = new System.Windows.Forms.Label();
this.CameraCheckBox = new System.Windows.Forms.CheckBox();
this.TrackButtonCheckBox = new System.Windows.Forms.CheckBox();
this.TrackLaserCheckBox = new System.Windows.Forms.CheckBox();
this.TrackLabel = new System.Windows.Forms.Label();
this.EffectsLabel = new System.Windows.Forms.Label();
this.MeasureLabel = new System.Windows.Forms.Label();
this.AsciiTextBox = new System.Windows.Forms.TextBox();
this.LongFxCheckBox = new System.Windows.Forms.CheckBox();
this.ChipFxCheckBox = new System.Windows.Forms.CheckBox();
this.MusicCodeLabel = new System.Windows.Forms.Label();
this.CancelConvertButton = new System.Windows.Forms.Button();
this.ProcessConvertButton = new System.Windows.Forms.Button();
this.TargetLabel = new System.Windows.Forms.Label();
this.LevelGroupBox.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.JacketInfPictureBox)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.JacketExhPictureBox)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.JacketAdvPictureBox)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.JacketNovPictureBox)).BeginInit();
this.OptionsGroupBox.SuspendLayout();
this.SuspendLayout();
//
// LevelGroupBox
//
this.LevelGroupBox.Controls.Add(this.InfEditButton);
this.LevelGroupBox.Controls.Add(this.ExhEditButton);
this.LevelGroupBox.Controls.Add(this.AdvEditButton);
this.LevelGroupBox.Controls.Add(this.NovEditButton);
this.LevelGroupBox.Controls.Add(this.JacketInfPictureBox);
this.LevelGroupBox.Controls.Add(this.JacketExhPictureBox);
this.LevelGroupBox.Controls.Add(this.JacketAdvPictureBox);
this.LevelGroupBox.Controls.Add(this.JacketNovPictureBox);
this.LevelGroupBox.Location = new System.Drawing.Point(12, 44);
this.LevelGroupBox.Name = "LevelGroupBox";
this.LevelGroupBox.Size = new System.Drawing.Size(447, 165);
this.LevelGroupBox.TabIndex = 15;
this.LevelGroupBox.TabStop = false;
this.LevelGroupBox.Text = "Levels";
//
// InfEditButton
//
this.InfEditButton.Location = new System.Drawing.Point(331, 133);
this.InfEditButton.Name = "InfEditButton";
this.InfEditButton.Size = new System.Drawing.Size(108, 26);
this.InfEditButton.TabIndex = 18;
this.InfEditButton.Tag = "4";
this.InfEditButton.Text = "--";
this.InfEditButton.UseVisualStyleBackColor = true;
this.InfEditButton.Click += new System.EventHandler(this.OnLevelEditButtonClick);
//
// ExhEditButton
//
this.ExhEditButton.Location = new System.Drawing.Point(223, 133);
this.ExhEditButton.Name = "ExhEditButton";
this.ExhEditButton.Size = new System.Drawing.Size(108, 26);
this.ExhEditButton.TabIndex = 17;
this.ExhEditButton.Tag = "3";
this.ExhEditButton.Text = "EXH";
this.ExhEditButton.UseVisualStyleBackColor = true;
this.ExhEditButton.Click += new System.EventHandler(this.OnLevelEditButtonClick);
//
// AdvEditButton
//
this.AdvEditButton.Location = new System.Drawing.Point(115, 133);
this.AdvEditButton.Name = "AdvEditButton";
this.AdvEditButton.Size = new System.Drawing.Size(108, 26);
this.AdvEditButton.TabIndex = 16;
this.AdvEditButton.Tag = "2";
this.AdvEditButton.Text = "ADV";
this.AdvEditButton.UseVisualStyleBackColor = true;
this.AdvEditButton.Click += new System.EventHandler(this.OnLevelEditButtonClick);
//
// NovEditButton
//
this.NovEditButton.Location = new System.Drawing.Point(7, 133);
this.NovEditButton.Name = "NovEditButton";
this.NovEditButton.Size = new System.Drawing.Size(108, 26);
this.NovEditButton.TabIndex = 15;
this.NovEditButton.Tag = "1";
this.NovEditButton.Text = "NOV";
this.NovEditButton.UseVisualStyleBackColor = true;
this.NovEditButton.Click += new System.EventHandler(this.OnLevelEditButtonClick);
//
// JacketInfPictureBox
//
this.JacketInfPictureBox.Image = global::VoxCharger.Properties.Resources.jk_dummy_s;
this.JacketInfPictureBox.Location = new System.Drawing.Point(331, 19);
this.JacketInfPictureBox.Name = "JacketInfPictureBox";
this.JacketInfPictureBox.Size = new System.Drawing.Size(108, 108);
this.JacketInfPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.JacketInfPictureBox.TabIndex = 6;
this.JacketInfPictureBox.TabStop = false;
this.JacketInfPictureBox.Tag = "4";
//
// JacketExhPictureBox
//
this.JacketExhPictureBox.Image = global::VoxCharger.Properties.Resources.jk_dummy_s;
this.JacketExhPictureBox.Location = new System.Drawing.Point(223, 19);
this.JacketExhPictureBox.Name = "JacketExhPictureBox";
this.JacketExhPictureBox.Size = new System.Drawing.Size(108, 108);
this.JacketExhPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.JacketExhPictureBox.TabIndex = 29;
this.JacketExhPictureBox.TabStop = false;
this.JacketExhPictureBox.Tag = "3";
//
// JacketAdvPictureBox
//
this.JacketAdvPictureBox.Image = global::VoxCharger.Properties.Resources.jk_dummy_s;
this.JacketAdvPictureBox.Location = new System.Drawing.Point(115, 19);
this.JacketAdvPictureBox.Name = "JacketAdvPictureBox";
this.JacketAdvPictureBox.Size = new System.Drawing.Size(108, 108);
this.JacketAdvPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.JacketAdvPictureBox.TabIndex = 1;
this.JacketAdvPictureBox.TabStop = false;
this.JacketAdvPictureBox.Tag = "2";
//
// JacketNovPictureBox
//
this.JacketNovPictureBox.Image = global::VoxCharger.Properties.Resources.jk_dummy_s;
this.JacketNovPictureBox.Location = new System.Drawing.Point(7, 19);
this.JacketNovPictureBox.Name = "JacketNovPictureBox";
this.JacketNovPictureBox.Size = new System.Drawing.Size(108, 108);
this.JacketNovPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.JacketNovPictureBox.TabIndex = 0;
this.JacketNovPictureBox.TabStop = false;
this.JacketNovPictureBox.Tag = "1";
//
// PathTextBox
//
this.PathTextBox.Location = new System.Drawing.Point(50, 17);
this.PathTextBox.Name = "PathTextBox";
this.PathTextBox.ReadOnly = true;
this.PathTextBox.Size = new System.Drawing.Size(409, 20);
this.PathTextBox.TabIndex = 16;
//
// OptionsGroupBox
//
this.OptionsGroupBox.Controls.Add(this.SlamImpactCheckBox);
this.OptionsGroupBox.Controls.Add(this.VersionDropDown);
this.OptionsGroupBox.Controls.Add(this.InfVerDropDown);
this.OptionsGroupBox.Controls.Add(this.BackgroundDropDown);
this.OptionsGroupBox.Controls.Add(this.BackgroundLabel);
this.OptionsGroupBox.Controls.Add(this.RealignOffsetCheckBox);
this.OptionsGroupBox.Controls.Add(this.AsciiAutoCheckBox);
this.OptionsGroupBox.Controls.Add(this.InfVerLabel);
this.OptionsGroupBox.Controls.Add(this.CameraCheckBox);
this.OptionsGroupBox.Controls.Add(this.TrackButtonCheckBox);
this.OptionsGroupBox.Controls.Add(this.TrackLaserCheckBox);
this.OptionsGroupBox.Controls.Add(this.TrackLabel);
this.OptionsGroupBox.Controls.Add(this.EffectsLabel);
this.OptionsGroupBox.Controls.Add(this.MeasureLabel);
this.OptionsGroupBox.Controls.Add(this.AsciiTextBox);
this.OptionsGroupBox.Controls.Add(this.LongFxCheckBox);
this.OptionsGroupBox.Controls.Add(this.ChipFxCheckBox);
this.OptionsGroupBox.Controls.Add(this.MusicCodeLabel);
this.OptionsGroupBox.Location = new System.Drawing.Point(12, 215);
this.OptionsGroupBox.Name = "OptionsGroupBox";
this.OptionsGroupBox.Size = new System.Drawing.Size(447, 170);
this.OptionsGroupBox.TabIndex = 1;
this.OptionsGroupBox.TabStop = false;
this.OptionsGroupBox.Text = "Options";
//
// SlamImpactCheckBox
//
this.SlamImpactCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.SlamImpactCheckBox.AutoSize = true;
this.SlamImpactCheckBox.Checked = true;
this.SlamImpactCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
this.SlamImpactCheckBox.Location = new System.Drawing.Point(293, 114);
this.SlamImpactCheckBox.Name = "SlamImpactCheckBox";
this.SlamImpactCheckBox.Size = new System.Drawing.Size(84, 17);
this.SlamImpactCheckBox.TabIndex = 20;
this.SlamImpactCheckBox.Text = "Slam Impact";
this.SlamImpactCheckBox.UseVisualStyleBackColor = true;
//
// VersionDropDown
//
this.VersionDropDown.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.VersionDropDown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.VersionDropDown.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.VersionDropDown.FormattingEnabled = true;
this.VersionDropDown.Items.AddRange(new object[] {
"Sound Voltex: Booth",
"Sound Voltex II: Infinite Infection",
"Sound Voltex III: Gravity Wars",
"Sound Voltex IV: Heavenly Haven",
"Sound Voltex V: VividWave"});
this.VersionDropDown.Location = new System.Drawing.Point(90, 42);
this.VersionDropDown.Name = "VersionDropDown";
this.VersionDropDown.Size = new System.Drawing.Size(247, 21);
this.VersionDropDown.TabIndex = 19;
//
// InfVerDropDown
//
this.InfVerDropDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.InfVerDropDown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.InfVerDropDown.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.InfVerDropDown.FormattingEnabled = true;
this.InfVerDropDown.Items.AddRange(new object[] {
"MXM",
"INF",
"GRV",
"HVN",
"VVD"});
this.InfVerDropDown.Location = new System.Drawing.Point(343, 42);
this.InfVerDropDown.Name = "InfVerDropDown";
this.InfVerDropDown.Size = new System.Drawing.Size(96, 21);
this.InfVerDropDown.TabIndex = 18;
this.InfVerDropDown.SelectedIndexChanged += new System.EventHandler(this.OnInfVerDropDownSelectedIndexChanged);
//
// BackgroundDropDown
//
this.BackgroundDropDown.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.BackgroundDropDown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.BackgroundDropDown.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.BackgroundDropDown.FormattingEnabled = true;
this.BackgroundDropDown.Items.AddRange(new object[] {
"00",
"01",
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"09",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"18",
"19",
"27",
"29",
"30",
"31",
"34",
"36",
"38",
"39",
"40",
"41",
"42",
"43",
"44",
"45",
"46",
"47",
"48",
"49",
"50",
"51",
"53",
"54",
"57",
"58",
"59",
"60",
"61",
"63",
"65",
"66",
"67",
"68",
"69",
"70",
"71",
"72",
"73",
"74",
"75",
"76",
"77",
"78",
"79",
"80",
"81"});
this.BackgroundDropDown.Location = new System.Drawing.Point(90, 65);
this.BackgroundDropDown.Name = "BackgroundDropDown";
this.BackgroundDropDown.Size = new System.Drawing.Size(349, 21);
this.BackgroundDropDown.TabIndex = 17;
this.BackgroundDropDown.SelectedIndexChanged += new System.EventHandler(this.OnBackgroundDropDownSelectedIndexChanged);
//
// BackgroundLabel
//
this.BackgroundLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.BackgroundLabel.Location = new System.Drawing.Point(10, 65);
this.BackgroundLabel.Name = "BackgroundLabel";
this.BackgroundLabel.Size = new System.Drawing.Size(74, 20);
this.BackgroundLabel.TabIndex = 16;
this.BackgroundLabel.Text = "Background";
this.BackgroundLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// RealignOffsetCheckBox
//
this.RealignOffsetCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.RealignOffsetCheckBox.AutoSize = true;
this.RealignOffsetCheckBox.Location = new System.Drawing.Point(90, 91);
this.RealignOffsetCheckBox.Name = "RealignOffsetCheckBox";
this.RealignOffsetCheckBox.Size = new System.Drawing.Size(141, 17);
this.RealignOffsetCheckBox.TabIndex = 15;
this.RealignOffsetCheckBox.Text = "Adapt Start Music Offset";
this.RealignOffsetCheckBox.UseVisualStyleBackColor = true;
//
// AsciiAutoCheckBox
//
this.AsciiAutoCheckBox.AutoSize = true;
this.AsciiAutoCheckBox.Checked = true;
this.AsciiAutoCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
this.AsciiAutoCheckBox.Location = new System.Drawing.Point(391, 23);
this.AsciiAutoCheckBox.Name = "AsciiAutoCheckBox";
this.AsciiAutoCheckBox.Size = new System.Drawing.Size(48, 17);
this.AsciiAutoCheckBox.TabIndex = 14;
this.AsciiAutoCheckBox.Text = "Auto";
this.AsciiAutoCheckBox.UseVisualStyleBackColor = true;
this.AsciiAutoCheckBox.CheckedChanged += new System.EventHandler(this.OnAsciiAutoCheckBoxCheckedChanged);
//
// InfVerLabel
//
this.InfVerLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.InfVerLabel.Location = new System.Drawing.Point(10, 42);
this.InfVerLabel.Name = "InfVerLabel";
this.InfVerLabel.Size = new System.Drawing.Size(74, 20);
this.InfVerLabel.TabIndex = 12;
this.InfVerLabel.Text = "Version";
this.InfVerLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// CameraCheckBox
//
this.CameraCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.CameraCheckBox.AutoSize = true;
this.CameraCheckBox.Checked = true;
this.CameraCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
this.CameraCheckBox.Location = new System.Drawing.Point(225, 114);
this.CameraCheckBox.Name = "CameraCheckBox";
this.CameraCheckBox.Size = new System.Drawing.Size(62, 17);
this.CameraCheckBox.TabIndex = 5;
this.CameraCheckBox.Text = "Camera";
this.CameraCheckBox.UseVisualStyleBackColor = true;
//
// TrackButtonCheckBox
//
this.TrackButtonCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.TrackButtonCheckBox.AutoSize = true;
this.TrackButtonCheckBox.Checked = true;
this.TrackButtonCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
this.TrackButtonCheckBox.Location = new System.Drawing.Point(157, 137);
this.TrackButtonCheckBox.Name = "TrackButtonCheckBox";
this.TrackButtonCheckBox.Size = new System.Drawing.Size(62, 17);
this.TrackButtonCheckBox.TabIndex = 7;
this.TrackButtonCheckBox.Text = "Buttons";
this.TrackButtonCheckBox.UseVisualStyleBackColor = true;
//
// TrackLaserCheckBox
//
this.TrackLaserCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.TrackLaserCheckBox.AutoSize = true;
this.TrackLaserCheckBox.Checked = true;
this.TrackLaserCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
this.TrackLaserCheckBox.Location = new System.Drawing.Point(90, 137);
this.TrackLaserCheckBox.Name = "TrackLaserCheckBox";
this.TrackLaserCheckBox.Size = new System.Drawing.Size(57, 17);
this.TrackLaserCheckBox.TabIndex = 6;
this.TrackLaserCheckBox.Text = "Lasers";
this.TrackLaserCheckBox.UseVisualStyleBackColor = true;
//
// TrackLabel
//
this.TrackLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.TrackLabel.Location = new System.Drawing.Point(10, 134);
this.TrackLabel.Name = "TrackLabel";
this.TrackLabel.Size = new System.Drawing.Size(74, 20);
this.TrackLabel.TabIndex = 11;
this.TrackLabel.Text = "Tracks";
this.TrackLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// EffectsLabel
//
this.EffectsLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.EffectsLabel.Location = new System.Drawing.Point(10, 111);
this.EffectsLabel.Name = "EffectsLabel";
this.EffectsLabel.Size = new System.Drawing.Size(74, 20);
this.EffectsLabel.TabIndex = 10;
this.EffectsLabel.Text = "Effects";
this.EffectsLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// MeasureLabel
//
this.MeasureLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.MeasureLabel.Location = new System.Drawing.Point(10, 88);
this.MeasureLabel.Name = "MeasureLabel";
this.MeasureLabel.Size = new System.Drawing.Size(74, 20);
this.MeasureLabel.TabIndex = 6;
this.MeasureLabel.Text = "Offset";
this.MeasureLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// AsciiTextBox
//
this.AsciiTextBox.Location = new System.Drawing.Point(90, 20);
this.AsciiTextBox.Name = "AsciiTextBox";
this.AsciiTextBox.ReadOnly = true;
this.AsciiTextBox.Size = new System.Drawing.Size(295, 20);
this.AsciiTextBox.TabIndex = 0;
//
// LongFxCheckBox
//
this.LongFxCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.LongFxCheckBox.AutoSize = true;
this.LongFxCheckBox.Checked = true;
this.LongFxCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
this.LongFxCheckBox.Location = new System.Drawing.Point(157, 114);
this.LongFxCheckBox.Name = "LongFxCheckBox";
this.LongFxCheckBox.Size = new System.Drawing.Size(64, 17);
this.LongFxCheckBox.TabIndex = 4;
this.LongFxCheckBox.Text = "Long Fx";
this.LongFxCheckBox.UseVisualStyleBackColor = true;
//
// ChipFxCheckBox
//
this.ChipFxCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.ChipFxCheckBox.AutoSize = true;
this.ChipFxCheckBox.Checked = true;
this.ChipFxCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
this.ChipFxCheckBox.Location = new System.Drawing.Point(90, 114);
this.ChipFxCheckBox.Name = "ChipFxCheckBox";
this.ChipFxCheckBox.Size = new System.Drawing.Size(61, 17);
this.ChipFxCheckBox.TabIndex = 3;
this.ChipFxCheckBox.Text = "Chip Fx";
this.ChipFxCheckBox.UseVisualStyleBackColor = true;
//
// MusicCodeLabel
//
this.MusicCodeLabel.Location = new System.Drawing.Point(10, 19);
this.MusicCodeLabel.Name = "MusicCodeLabel";
this.MusicCodeLabel.Size = new System.Drawing.Size(74, 20);
this.MusicCodeLabel.TabIndex = 0;
this.MusicCodeLabel.Text = "Music Code";
this.MusicCodeLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// CancelConvertButton
//
this.CancelConvertButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.CancelConvertButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.CancelConvertButton.Location = new System.Drawing.Point(343, 401);
this.CancelConvertButton.Name = "CancelConvertButton";
this.CancelConvertButton.Size = new System.Drawing.Size(116, 28);
this.CancelConvertButton.TabIndex = 20;
this.CancelConvertButton.Text = "Cancel";
this.CancelConvertButton.UseVisualStyleBackColor = true;
this.CancelConvertButton.Click += new System.EventHandler(this.OnCancelConvertButtonClick);
//
// ProcessConvertButton
//
this.ProcessConvertButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.ProcessConvertButton.Location = new System.Drawing.Point(221, 401);
this.ProcessConvertButton.Name = "ProcessConvertButton";
this.ProcessConvertButton.Size = new System.Drawing.Size(116, 28);
this.ProcessConvertButton.TabIndex = 19;
this.ProcessConvertButton.Text = "Continue";
this.ProcessConvertButton.UseVisualStyleBackColor = true;
this.ProcessConvertButton.Click += new System.EventHandler(this.OnProcessConvertButtonClick);
//
// TargetLabel
//
this.TargetLabel.AutoSize = true;
this.TargetLabel.Location = new System.Drawing.Point(9, 20);
this.TargetLabel.Name = "TargetLabel";
this.TargetLabel.Size = new System.Drawing.Size(38, 13);
this.TargetLabel.TabIndex = 21;
this.TargetLabel.Text = "Target";
this.TargetLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// ConverterForm
//
this.AcceptButton = this.ProcessConvertButton;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.CancelConvertButton;
this.ClientSize = new System.Drawing.Size(471, 441);
this.Controls.Add(this.TargetLabel);
this.Controls.Add(this.CancelConvertButton);
this.Controls.Add(this.ProcessConvertButton);
this.Controls.Add(this.OptionsGroupBox);
this.Controls.Add(this.PathTextBox);
this.Controls.Add(this.LevelGroupBox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.HelpButton = true;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "ConverterForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Add Music";
this.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(this.OnHelpButtonClicked);
this.Load += new System.EventHandler(this.OnConverterFormLoad);
this.LevelGroupBox.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.JacketInfPictureBox)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.JacketExhPictureBox)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.JacketAdvPictureBox)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.JacketNovPictureBox)).EndInit();
this.OptionsGroupBox.ResumeLayout(false);
this.OptionsGroupBox.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.GroupBox LevelGroupBox;
private System.Windows.Forms.Button InfEditButton;
private System.Windows.Forms.Button ExhEditButton;
private System.Windows.Forms.Button AdvEditButton;
private System.Windows.Forms.Button NovEditButton;
private System.Windows.Forms.PictureBox JacketInfPictureBox;
private System.Windows.Forms.PictureBox JacketExhPictureBox;
private System.Windows.Forms.PictureBox JacketAdvPictureBox;
private System.Windows.Forms.PictureBox JacketNovPictureBox;
private System.Windows.Forms.TextBox PathTextBox;
private System.Windows.Forms.GroupBox OptionsGroupBox;
private System.Windows.Forms.CheckBox LongFxCheckBox;
private System.Windows.Forms.CheckBox ChipFxCheckBox;
private System.Windows.Forms.Label MusicCodeLabel;
private System.Windows.Forms.Label MeasureLabel;
private System.Windows.Forms.TextBox AsciiTextBox;
private System.Windows.Forms.CheckBox CameraCheckBox;
private System.Windows.Forms.CheckBox TrackButtonCheckBox;
private System.Windows.Forms.CheckBox TrackLaserCheckBox;
private System.Windows.Forms.Label TrackLabel;
private System.Windows.Forms.Label EffectsLabel;
private System.Windows.Forms.Label InfVerLabel;
private System.Windows.Forms.CheckBox AsciiAutoCheckBox;
private System.Windows.Forms.Button CancelConvertButton;
private System.Windows.Forms.Button ProcessConvertButton;
private System.Windows.Forms.CheckBox RealignOffsetCheckBox;
private System.Windows.Forms.Label BackgroundLabel;
private System.Windows.Forms.ComboBox BackgroundDropDown;
private System.Windows.Forms.ComboBox InfVerDropDown;
private System.Windows.Forms.ComboBox VersionDropDown;
private System.Windows.Forms.Label TargetLabel;
private System.Windows.Forms.CheckBox SlamImpactCheckBox;
}
}

View File

@ -0,0 +1,698 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Diagnostics;
namespace VoxCharger
{
public partial class ConverterForm : Form
{
private class ChartInfo
{
public Ksh Source { get; set; }
public VoxLevelHeader Header { get; set; }
public string FileName { get; set; }
public string MusicFileName { get; set; }
public string JacketFileName { get; set; }
public ChartInfo(Ksh source, VoxLevelHeader header, string fileName)
{
Source = source;
Header = header;
FileName = fileName;
}
}
private readonly Image DummyJacket = VoxCharger.Properties.Resources.jk_dummy_s;
public static string LastBackground { get; private set; } = "63";
private string target;
private string defaultAscii;
private bool converter;
private Dictionary<Difficulty, ChartInfo> charts = new Dictionary<Difficulty, ChartInfo>();
public VoxHeader Header { get; private set; } = null;
public Action Action { get; private set; } = null;
public ConverterForm(string path, bool asConverter = false)
{
InitializeComponent();
target = path;
converter = asConverter;
if (asConverter)
{
MusicCodeLabel.Visible = false;
InfVerLabel.Visible = false;
BackgroundLabel.Visible = false;
LevelGroupBox.Enabled = LevelGroupBox.Visible = false;
AsciiTextBox.Enabled = AsciiTextBox.Visible = false;
AsciiAutoCheckBox.Enabled = AsciiAutoCheckBox.Visible = false;
VersionDropDown.Enabled = VersionDropDown.Visible = false;
InfVerDropDown.Enabled = InfVerDropDown.Visible = false;
BackgroundDropDown.Enabled = BackgroundDropDown.Visible = false;
int componentHeight = AsciiTextBox.Height + VersionDropDown.Height + BackgroundDropDown.Height;
OptionsGroupBox.Location = LevelGroupBox.Location;
OptionsGroupBox.Height -= componentHeight;
Height -= LevelGroupBox.Height + componentHeight;
ProcessConvertButton.Text = "Convert";
}
else
ProcessConvertButton.Text = "Add";
PathTextBox.Text = target;
}
private void OnConverterFormLoad(object sender, EventArgs e)
{
var main = new Ksh();
if (converter)
return;
try
{
main.Parse(target);
Header = ToHeader(main);
Header.Ascii = new DirectoryInfo(Path.GetDirectoryName(target)).Name;
defaultAscii = AsciiTextBox.Text = Header.Ascii;
for (int i = 1; Directory.Exists(AssetManager.GetMusicPath(Header)); i++)
{
if (i >= 100)
break; // seriously? stupid input get stupid output
Header.Ascii = $"{defaultAscii}{i:D2}";
}
defaultAscii = AsciiTextBox.Text = Header.Ascii;
BackgroundDropDown.SelectedItem = LastBackground;
VersionDropDown.SelectedIndex = 4;
InfVerDropDown.SelectedIndex = 0;
charts[main.Difficulty] = new ChartInfo(main, ToLevelHeader(main), target);
LoadJacket(charts[main.Difficulty]);
}
catch (Exception ex)
{
MessageBox.Show(
$"Failed to load ksh chart.\n{ex.Message}",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
CancelButton.PerformClick();
}
// Try to locate another difficulty
string dir = Path.GetDirectoryName(target);
foreach (string fn in Directory.GetFiles(dir, "*.ksh"))
{
try
{
var chart = new Ksh();
chart.Parse(fn);
// Different chart
if (chart.Title != main.Title)
continue;
charts[chart.Difficulty] = new ChartInfo(chart, ToLevelHeader(chart), fn);
}
catch (Exception ex)
{
Debug.WriteLine("Failed attempt to parse ksh file: {0} ({1})", fn, ex.Message);
}
}
UpdateUI();
}
private void OnAsciiAutoCheckBoxCheckedChanged(object sender, EventArgs e)
{
AsciiTextBox.ReadOnly = AsciiAutoCheckBox.Checked;
if (AsciiTextBox.ReadOnly)
AsciiTextBox.Text = defaultAscii;
}
private void OnBackgroundDropDownSelectedIndexChanged(object sender, EventArgs e)
{
LastBackground = BackgroundDropDown.SelectedItem.ToString();
}
private void OnInfVerDropDownSelectedIndexChanged(object sender, EventArgs e)
{
if (!charts.ContainsKey(Difficulty.Infinite))
InfEditButton.Text = "--";
else
InfEditButton.Text = InfVerDropDown.SelectedItem.ToString();
}
private void OnLevelEditButtonClick(object sender, EventArgs e)
{
using (var browser = new OpenFileDialog())
{
browser.Filter = "KShoot Mania Chart|*.ksh";
browser.CheckFileExists = true;
if (browser.ShowDialog() != DialogResult.OK)
return;
var control = (Button)sender;
if (!Enum.TryParse(control.Tag.ToString(), out Difficulty diff))
return;
try
{
var chart = new Ksh();
chart.Parse(browser.FileName);
chart.Difficulty = diff; // make sure to replace diff
charts[diff] = new ChartInfo(chart, ToLevelHeader(chart), browser.FileName);
UpdateUI();
}
catch (Exception ex)
{
MessageBox.Show(
$"Failed to load ksh chart.\n{ex.Message}",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
}
}
private void OnHelpButtonClicked(object sender, CancelEventArgs e)
{
e.Cancel = true;
using (var help = new HelpForm())
help.ShowDialog();
}
private void OnCancelConvertButtonClick(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
private void OnProcessConvertButtonClick(object sender, EventArgs e)
{
bool warned = false;
var options = new Ksh.ParseOption()
{
RealignOffset = RealignOffsetCheckBox.Checked,
EnableChipFx = ChipFxCheckBox.Checked,
EnableLongFx = LongFxCheckBox.Checked,
EnableCamera = CameraCheckBox.Checked,
EnableSlamImpact = SlamImpactCheckBox.Checked,
EnableLaserTrack = TrackLaserCheckBox.Checked,
EnableButtonTrack = TrackButtonCheckBox.Checked
};
// Act as converter
if (converter)
{
try
{
if (File.Exists(target) || Directory.Exists(target))
{
if (File.Exists(target))
SingleConvert(options);
else if (Directory.Exists(target))
BulkConvert(options);
}
else
{
MessageBox.Show(
"Target path not found",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
Close();
}
}
catch (Exception ex)
{
MessageBox.Show(
$"Failed to convert ksh chart.\n{ex.Message}",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
return;
}
// Again, stupid input get stupid output
foreach (var header in AssetManager.Headers)
{
if (Header.Ascii == header.Ascii)
{
MessageBox.Show(
$"Music Code is already exists.\n{AssetManager.GetMusicPath(header)}",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
return;
}
}
// Assign metadata
Header.Ascii = AsciiTextBox.Text;
Header.BackgroundId = short.Parse((BackgroundDropDown.SelectedItem ?? "0").ToString().Split(' ')[0]);
Header.Version = (GameVersion)(VersionDropDown.SelectedIndex + 1);
Header.InfVersion = InfVerDropDown.SelectedIndex == 0 ? InfiniteVersion.MXM : (InfiniteVersion)(InfVerDropDown.SelectedIndex + 1);
Header.GenreId = 16;
Header.Levels = new Dictionary<Difficulty, VoxLevelHeader>();
// Again, stupid input get stupid output
if (Directory.Exists(AssetManager.GetMusicPath(Header)))
{
MessageBox.Show(
$"Music asset for {Header.CodeName} is already exists.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
return;
}
// Uhh, remove empty level?
var entries = charts.Where(x => x.Value != null);
charts = new Dictionary<Difficulty, ChartInfo>();
foreach (var entry in entries)
charts[entry.Key] = entry.Value;
// Assign level info and charts
foreach (var chart in charts)
{
var info = chart.Value;
if (!File.Exists(info.FileName))
{
MessageBox.Show(
$"Chart file was moved or deleted\n{info.FileName}",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
charts[chart.Key] = null;
UpdateUI();
return;
}
// If you happen to read the source, this is probably what you're looking for
var ksh = new Ksh();
ksh.Parse(info.FileName, options);
var bpmCount = ksh.Events.Count(ev => ev is Event.BPM);
if (!warned && bpmCount > 1 && ksh.MusicOffset % 48 != 0 && options.RealignOffset)
{
// You've been warned!
var prompt = MessageBox.Show(
"Chart contains multiple bpm with music offset that non multiple of 48.\n" +
"Adapting music offset could break the chart.\n\n" +
"Do you want to continue?",
"Warning",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning
);
warned = true;
if (prompt == DialogResult.No)
return;
}
var vox = new VoxChart();
vox.Import(ksh);
var level = ToLevelHeader(ksh);
level.Chart = vox;
info.Source = ksh;
Header.Levels[chart.Key] = charts[chart.Key].Header = level;
}
// Prevent resource files being moved or deleted, copy them into temporary storage
string musicFile = string.Empty;
string tmpFile = string.Empty;
foreach (var chart in charts)
{
var info = chart.Value;
// Make sure to reuse 2dx file for music that share same file
if (string.IsNullOrEmpty(musicFile) || chart.Value.MusicFileName != musicFile)
{
string music = Path.Combine(Path.GetDirectoryName(info.FileName), info.Source.MusicFileName);
if (File.Exists(music))
{
string tmp = Path.Combine(
Path.GetTempPath(),
$"{Path.GetRandomFileName()}{new FileInfo(info.Source.MusicFileName).Extension}"
);
musicFile = music;
info.MusicFileName = tmpFile = tmp;
File.Copy(music, tmp);
}
else
info.MusicFileName = string.Empty;
}
else
info.MusicFileName = tmpFile;
string jacket = Path.Combine(Path.GetDirectoryName(info.FileName), info.Source.JacketFileName);
if (File.Exists(jacket))
{
string tmp = Path.Combine(
Path.GetTempPath(),
$"{Path.GetRandomFileName()}{new FileInfo(info.Source.JacketFileName).Extension}"
);
try
{
using (var image = Image.FromFile(jacket))
info.Header.Jacket = new Bitmap(image);
}
catch (Exception)
{
info.Header.Jacket = null;
}
info.JacketFileName = tmp;
File.Copy(jacket, tmp);
}
}
Action = new Action(() =>
{
bool unique = false;
musicFile = charts.Values.First().MusicFileName;
foreach (var chart in charts)
{
if (chart.Value.MusicFileName != musicFile)
{
unique = true;
break;
}
}
// Import all music assets
AssetManager.ImportVox(Header);
// Make sure to use single asset for music for shared music file
if (!unique)
{
AssetManager.Import2DX(musicFile, Header);
AssetManager.Import2DX(musicFile, Header, true);
}
foreach (var chart in charts.Values)
{
if (unique && File.Exists(chart.MusicFileName))
{
AssetManager.Import2DX(chart.MusicFileName, Header, chart.Header.Difficulty);
AssetManager.Import2DX(chart.MusicFileName, Header, chart.Header.Difficulty, true);
}
if (File.Exists(chart.JacketFileName))
{
using (var image = Image.FromFile(chart.JacketFileName))
AssetManager.ImportJacket(Header, chart.Header.Difficulty, new Bitmap(image));
}
}
});
DialogResult = DialogResult.OK;
Close();
}
private VoxHeader ToHeader(Ksh chart)
{
return new VoxHeader()
{
ID = AssetManager.Headers.LastID + 1,
Title = chart.Title,
Artist = chart.Artist,
BpmMin = chart.BpmMin,
BpmMax = chart.BpmMax,
Volume = chart.Volume > 0 ? (short)chart.Volume : (short)91,
DistributionDate = DateTime.Now,
BackgroundId = 63,
GenreId = 16,
};
}
private VoxLevelHeader ToLevelHeader(Ksh chart)
{
return new VoxLevelHeader
{
Difficulty = chart.Difficulty,
Illustrator = chart.Illustrator,
Effector = chart.Effector,
Level = chart.Level
};
}
private void LoadJacket(ChartInfo info)
{
var chart = info.Source;
if (chart == null)
return;
PictureBox pictureBox = null;
string tag = ((int)chart.Difficulty).ToString();
foreach (var control in LevelGroupBox.Controls)
{
if (control is PictureBox p && p.Tag.ToString() == tag)
{
pictureBox = p;
break;
}
}
if (pictureBox == null)
return;
string filename = Path.Combine(Path.GetDirectoryName(info.FileName), chart.JacketFileName);
if (!File.Exists(filename))
{
pictureBox.Image = DummyJacket;
return;
}
try
{
using (var image = Image.FromFile(filename))
pictureBox.Image = new Bitmap(image);
}
catch (Exception ex)
{
pictureBox.Image = DummyJacket;
Debug.WriteLine("Failed load ksh jacket: {0} ({1})", filename, ex.Message);
}
}
private void UpdateUI()
{
var buttons = new List<Button>();
foreach (var control in LevelGroupBox.Controls)
{
if (control is Button button)
buttons.Add(button);
}
foreach (Difficulty diff in Enum.GetValues(typeof(Difficulty)))
{
Button button = null;
string tag = ((int)diff).ToString();
foreach (var bt in buttons)
{
if (bt.Tag.ToString() == tag)
{
button = bt;
break;
}
}
if (!charts.ContainsKey(diff) || charts[diff] == null)
{
charts.Remove(diff); // should be UpdateUiButAlterMapCharts, ya whatever
if (button != null)
button.Text = "--";
foreach (var control in LevelGroupBox.Controls)
{
if (control is PictureBox picture && picture.Tag.ToString() == tag)
{
picture.Image = DummyJacket;
break;
}
}
}
else
{
if (button != null)
{
switch (diff)
{
case Difficulty.Novice: button.Text = "NOV"; break;
case Difficulty.Advanced: button.Text = "ADV"; break;
case Difficulty.Exhaust: button.Text = "EXH"; break;
default:
button.Text = InfVerDropDown.SelectedItem.ToString();
break;
}
}
LoadJacket(charts[diff]);
}
}
}
private void SingleConvert(Ksh.ParseOption options)
{
// Single convert
using (var browser = new SaveFileDialog())
{
browser.Filter = "Sound Voltex Chart File|*.vox|All Files|*.*";
if (browser.ShowDialog() != DialogResult.OK)
return;
var ksh = new Ksh();
ksh.Parse(target, options);
var bpmCount = ksh.Events.Count(ev => ev is Event.BPM);
if (bpmCount > 1 && ksh.MusicOffset % 48 != 0 && options.RealignOffset)
{
// You've been warned!
var prompt = MessageBox.Show(
"Chart contains multiple bpm with music offset that non multiple of 48.\n" +
"Adapting music offset could break the chart.\n\n" +
"Do you want to continue?",
"Warning",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning
);
if (prompt == DialogResult.No)
return;
}
var vox = new VoxChart();
vox.Import(ksh);
vox.Serialize(browser.FileName);
MessageBox.Show(
"Chart has been converted successfully",
"Information",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
Close();
}
}
private void BulkConvert(Ksh.ParseOption options)
{
bool warned = false;
using (var browser = new FolderBrowserDialog())
{
browser.Description = "Select output directory";
if (browser.ShowDialog() != DialogResult.OK)
return;
using (var loader = new LoadingForm())
{
var action = new Action(() =>
{
var directories = Directory.GetDirectories(target);
int progress = 0;
foreach (string dir in directories)
{
loader.SetStatus($"Processing {Path.GetFileName(dir)}..");
loader.SetProgress((progress++ / (float)directories.Length) * 100f);
foreach (var fn in Directory.GetFiles(dir, "*.ksh"))
{
try
{
var ksh = new Ksh();
ksh.Parse(fn, options);
var bpmCount = ksh.Events.Count(ev => ev is Event.BPM);
if (!warned && bpmCount > 1 && ksh.MusicOffset % 48 != 0 && options.RealignOffset)
{
// You've been warned!
var prompt = MessageBox.Show(
"Chart contains multiple bpm with music offset that non multiple of 48.\n" +
"Adapting music offset could break the chart.\n\n" +
"Do you want to continue?",
"Warning",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning
);
warned = true;
if (prompt == DialogResult.No)
return;
}
var vox = new VoxChart();
vox.Import(ksh);
string path = Path.Combine(
$"{browser.SelectedPath}",
$"{Path.GetFileName(dir)}\\"
);
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
vox.Serialize($"{Path.Combine(path, Path.GetFileName(fn.Replace(".ksh", ".vox")))}");
}
catch (Exception ex)
{
Debug.WriteLine("Failed attempt to convert ksh file: {0} ({1})", fn, ex.Message);
continue;
}
}
}
loader.Complete();
});
loader.SetAction(action);
loader.ShowDialog();
}
}
MessageBox.Show(
"Chart has been converted successfully",
"Information",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
Close();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

256
Sources/Forms/HelpForm.Designer.cs generated Normal file
View File

@ -0,0 +1,256 @@
namespace VoxCharger
{
partial class HelpForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.panel1 = new System.Windows.Forms.Panel();
this.OkButton = new System.Windows.Forms.Button();
this.panel2 = new System.Windows.Forms.Panel();
this.label11 = new System.Windows.Forms.Label();
this.label12 = new System.Windows.Forms.Label();
this.label9 = new System.Windows.Forms.Label();
this.label10 = new System.Windows.Forms.Label();
this.label7 = new System.Windows.Forms.Label();
this.label8 = new System.Windows.Forms.Label();
this.label5 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.panel1.SuspendLayout();
this.panel2.SuspendLayout();
this.SuspendLayout();
//
// panel1
//
this.panel1.Controls.Add(this.OkButton);
this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panel1.Location = new System.Drawing.Point(0, 322);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(603, 40);
this.panel1.TabIndex = 0;
//
// OkButton
//
this.OkButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Right)));
this.OkButton.Location = new System.Drawing.Point(516, 8);
this.OkButton.Name = "OkButton";
this.OkButton.Size = new System.Drawing.Size(75, 22);
this.OkButton.TabIndex = 0;
this.OkButton.Text = "OK";
this.OkButton.UseVisualStyleBackColor = true;
this.OkButton.Click += new System.EventHandler(this.OnOkButtonClick);
//
// panel2
//
this.panel2.BackColor = System.Drawing.Color.White;
this.panel2.Controls.Add(this.label11);
this.panel2.Controls.Add(this.label12);
this.panel2.Controls.Add(this.label9);
this.panel2.Controls.Add(this.label10);
this.panel2.Controls.Add(this.label7);
this.panel2.Controls.Add(this.label8);
this.panel2.Controls.Add(this.label5);
this.panel2.Controls.Add(this.label6);
this.panel2.Controls.Add(this.label3);
this.panel2.Controls.Add(this.label4);
this.panel2.Controls.Add(this.label2);
this.panel2.Controls.Add(this.label1);
this.panel2.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel2.Location = new System.Drawing.Point(0, 0);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(603, 322);
this.panel2.TabIndex = 1;
//
// label11
//
this.label11.AutoSize = true;
this.label11.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label11.Location = new System.Drawing.Point(15, 109);
this.label11.Name = "label11";
this.label11.Size = new System.Drawing.Size(185, 13);
this.label11.TabIndex = 11;
this.label11.Text = "ID of gameplay background theme";
//
// label12
//
this.label12.AutoSize = true;
this.label12.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.label12.Location = new System.Drawing.Point(10, 92);
this.label12.Name = "label12";
this.label12.Size = new System.Drawing.Size(71, 15);
this.label12.TabIndex = 10;
this.label12.Text = "Background";
//
// label9
//
this.label9.AutoSize = true;
this.label9.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label9.Location = new System.Drawing.Point(15, 253);
this.label9.Name = "label9";
this.label9.Size = new System.Drawing.Size(302, 52);
this.label9.TabIndex = 9;
this.label9.Text = "Only include selected tracks to the output\r\nFor example, laser can be disabled by" +
" deselect \"Laser\" box\r\n\r\nUseful for experimental or debugging purpose.";
//
// label10
//
this.label10.AutoSize = true;
this.label10.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.label10.Location = new System.Drawing.Point(10, 236);
this.label10.Name = "label10";
this.label10.Size = new System.Drawing.Size(40, 15);
this.label10.TabIndex = 8;
this.label10.Text = "Tracks";
//
// label7
//
this.label7.AutoSize = true;
this.label7.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label7.Location = new System.Drawing.Point(15, 192);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(531, 39);
this.label7.TabIndex = 7;
this.label7.Text = "Attempt to convert selected events into Vox format.\r\nAll of these effects are map" +
"ped, there\'s chance some effects to misaligned and even cease to function.\r\nNote" +
" that user defined Fx\'s are excluded";
//
// label8
//
this.label8.AutoSize = true;
this.label8.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.label8.Location = new System.Drawing.Point(10, 175);
this.label8.Name = "label8";
this.label8.Size = new System.Drawing.Size(42, 15);
this.label8.TabIndex = 6;
this.label8.Text = "Effects";
//
// label5
//
this.label5.AutoSize = true;
this.label5.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label5.Location = new System.Drawing.Point(15, 144);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(530, 26);
this.label5.TabIndex = 5;
this.label5.Text = "Trim or add additional measures to the beginning of chart. Useful when Ksh rely o" +
"n start music offset.\r\nHowever, chart can be broken when signature / bpm change " +
"occurs";
//
// label6
//
this.label6.AutoSize = true;
this.label6.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.label6.Location = new System.Drawing.Point(10, 127);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(44, 15);
this.label6.TabIndex = 4;
this.label6.Text = "Offsets";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label3.Location = new System.Drawing.Point(15, 74);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(365, 13);
this.label3.TabIndex = 3;
this.label3.Text = "Assigned game version and 4th Difficulty that will appear in the game";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.label4.Location = new System.Drawing.Point(10, 57);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(46, 15);
this.label4.TabIndex = 2;
this.label4.Text = "Version";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label2.Location = new System.Drawing.Point(17, 26);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(488, 26);
this.label2.TabIndex = 1;
this.label2.Text = "Music identifier that contains only valid ascii.\r\nThis will determine asset file " +
"location (data_mods\\\\<mix>\\\\music\\\\<music_id>_<music_code>)";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.label1.Location = new System.Drawing.Point(10, 9);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(69, 15);
this.label1.TabIndex = 0;
this.label1.Text = "Music Code";
//
// HelpForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(603, 362);
this.Controls.Add(this.panel2);
this.Controls.Add(this.panel1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "HelpForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Converter";
this.panel1.ResumeLayout(false);
this.panel2.ResumeLayout(false);
this.panel2.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Panel panel2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.Label label8;
private System.Windows.Forms.Button OkButton;
private System.Windows.Forms.Label label9;
private System.Windows.Forms.Label label10;
private System.Windows.Forms.Label label11;
private System.Windows.Forms.Label label12;
}
}

25
Sources/Forms/HelpForm.cs Normal file
View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace VoxCharger
{
public partial class HelpForm : Form
{
public HelpForm()
{
InitializeComponent();
}
private void OnOkButtonClick(object sender, EventArgs e)
{
Close();
}
}
}

120
Sources/Forms/HelpForm.resx Normal file
View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,66 @@
namespace VoxCharger
{
partial class JacketViewerForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.JacketPictureBox = new System.Windows.Forms.PictureBox();
((System.ComponentModel.ISupportInitialize)(this.JacketPictureBox)).BeginInit();
this.SuspendLayout();
//
// JacketPictureBox
//
this.JacketPictureBox.Location = new System.Drawing.Point(0, 0);
this.JacketPictureBox.Name = "JacketPictureBox";
this.JacketPictureBox.Size = new System.Drawing.Size(676, 676);
this.JacketPictureBox.TabIndex = 0;
this.JacketPictureBox.TabStop = false;
this.JacketPictureBox.MouseClick += new System.Windows.Forms.MouseEventHandler(this.JacketPictureBox_MouseClick);
//
// JacketViewerForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(676, 676);
this.Controls.Add(this.JacketPictureBox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "JacketViewerForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Jacket Preview";
this.Load += new System.EventHandler(this.OnJacketFormLoad);
((System.ComponentModel.ISupportInitialize)(this.JacketPictureBox)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.PictureBox JacketPictureBox;
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace VoxCharger
{
public partial class JacketViewerForm : Form
{
public JacketViewerForm(Image image)
{
InitializeComponent();
JacketPictureBox.Size = Size = image.Size;
JacketPictureBox.Image = image;
}
private void OnJacketFormLoad(object sender, EventArgs e)
{
}
private void JacketPictureBox_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
using (var browser = new SaveFileDialog())
{
browser.Filter = "Image Files|*.png;*.jpg;*.bmp";
if (browser.ShowDialog() == DialogResult.OK)
JacketPictureBox.Image.Save(browser.FileName);
}
}
else
Close();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

261
Sources/Forms/LevelEditorForm.Designer.cs generated Normal file
View File

@ -0,0 +1,261 @@
namespace VoxCharger
{
partial class LevelEditorForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.LevelLabel = new System.Windows.Forms.Label();
this.LevelNumericBox = new System.Windows.Forms.NumericUpDown();
this.EffectorTextBox = new System.Windows.Forms.TextBox();
this.EffectorLabel = new System.Windows.Forms.Label();
this.IllustratorLabel = new System.Windows.Forms.Label();
this.IllustratorTextBox = new System.Windows.Forms.TextBox();
this.CancelEditButton = new System.Windows.Forms.Button();
this.JacketButton = new System.Windows.Forms.Button();
this.SaveEditButton = new System.Windows.Forms.Button();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.Preview2DX = new System.Windows.Forms.Button();
this.MainDxButton = new System.Windows.Forms.Button();
this.VoxButton = new System.Windows.Forms.Button();
this.JacketPictureBox = new System.Windows.Forms.PictureBox();
((System.ComponentModel.ISupportInitialize)(this.LevelNumericBox)).BeginInit();
this.groupBox1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.JacketPictureBox)).BeginInit();
this.SuspendLayout();
//
// LevelLabel
//
this.LevelLabel.AutoSize = true;
this.LevelLabel.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.LevelLabel.Location = new System.Drawing.Point(11, 27);
this.LevelLabel.Name = "LevelLabel";
this.LevelLabel.Size = new System.Drawing.Size(32, 13);
this.LevelLabel.TabIndex = 13;
this.LevelLabel.Text = "Level";
this.LevelLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// LevelNumericBox
//
this.LevelNumericBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.LevelNumericBox.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.LevelNumericBox.Location = new System.Drawing.Point(72, 24);
this.LevelNumericBox.Maximum = new decimal(new int[] {
20,
0,
0,
0});
this.LevelNumericBox.Name = "LevelNumericBox";
this.LevelNumericBox.Size = new System.Drawing.Size(214, 21);
this.LevelNumericBox.TabIndex = 14;
this.LevelNumericBox.Value = new decimal(new int[] {
1,
0,
0,
0});
//
// EffectorTextBox
//
this.EffectorTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.EffectorTextBox.Location = new System.Drawing.Point(72, 51);
this.EffectorTextBox.Name = "EffectorTextBox";
this.EffectorTextBox.Size = new System.Drawing.Size(214, 20);
this.EffectorTextBox.TabIndex = 15;
//
// EffectorLabel
//
this.EffectorLabel.AutoSize = true;
this.EffectorLabel.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.EffectorLabel.Location = new System.Drawing.Point(12, 54);
this.EffectorLabel.Name = "EffectorLabel";
this.EffectorLabel.Size = new System.Drawing.Size(46, 13);
this.EffectorLabel.TabIndex = 16;
this.EffectorLabel.Text = "Effector";
this.EffectorLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// IllustratorLabel
//
this.IllustratorLabel.AutoSize = true;
this.IllustratorLabel.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.IllustratorLabel.Location = new System.Drawing.Point(12, 80);
this.IllustratorLabel.Name = "IllustratorLabel";
this.IllustratorLabel.Size = new System.Drawing.Size(54, 13);
this.IllustratorLabel.TabIndex = 18;
this.IllustratorLabel.Text = "Illustrator";
this.IllustratorLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// IllustratorTextBox
//
this.IllustratorTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.IllustratorTextBox.Location = new System.Drawing.Point(72, 77);
this.IllustratorTextBox.Name = "IllustratorTextBox";
this.IllustratorTextBox.Size = new System.Drawing.Size(214, 20);
this.IllustratorTextBox.TabIndex = 17;
//
// CancelEditButton
//
this.CancelEditButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.CancelEditButton.Location = new System.Drawing.Point(343, 180);
this.CancelEditButton.Name = "CancelEditButton";
this.CancelEditButton.Size = new System.Drawing.Size(80, 28);
this.CancelEditButton.TabIndex = 19;
this.CancelEditButton.Text = "Cancel";
this.CancelEditButton.UseVisualStyleBackColor = true;
this.CancelEditButton.Click += new System.EventHandler(this.OnCancelEditButtonClick);
//
// JacketButton
//
this.JacketButton.Location = new System.Drawing.Point(12, 126);
this.JacketButton.Name = "JacketButton";
this.JacketButton.Size = new System.Drawing.Size(108, 22);
this.JacketButton.TabIndex = 3;
this.JacketButton.Text = "Jacket File";
this.JacketButton.UseVisualStyleBackColor = true;
this.JacketButton.Click += new System.EventHandler(this.OnJacketButtonClick);
//
// SaveEditButton
//
this.SaveEditButton.Location = new System.Drawing.Point(257, 180);
this.SaveEditButton.Name = "SaveEditButton";
this.SaveEditButton.Size = new System.Drawing.Size(80, 28);
this.SaveEditButton.TabIndex = 23;
this.SaveEditButton.Text = "Save";
this.SaveEditButton.UseVisualStyleBackColor = true;
this.SaveEditButton.Click += new System.EventHandler(this.OnSaveEditButtonClick);
//
// groupBox1
//
this.groupBox1.Controls.Add(this.Preview2DX);
this.groupBox1.Controls.Add(this.MainDxButton);
this.groupBox1.Controls.Add(this.VoxButton);
this.groupBox1.Controls.Add(this.IllustratorTextBox);
this.groupBox1.Controls.Add(this.LevelNumericBox);
this.groupBox1.Controls.Add(this.LevelLabel);
this.groupBox1.Controls.Add(this.IllustratorLabel);
this.groupBox1.Controls.Add(this.EffectorTextBox);
this.groupBox1.Controls.Add(this.EffectorLabel);
this.groupBox1.Location = new System.Drawing.Point(126, 12);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(297, 162);
this.groupBox1.TabIndex = 21;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Level Data";
//
// Preview2DX
//
this.Preview2DX.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.Preview2DX.Location = new System.Drawing.Point(181, 129);
this.Preview2DX.Name = "Preview2DX";
this.Preview2DX.Size = new System.Drawing.Size(105, 22);
this.Preview2DX.TabIndex = 23;
this.Preview2DX.Text = "2DX Preview";
this.Preview2DX.UseVisualStyleBackColor = true;
this.Preview2DX.Click += new System.EventHandler(this.OnPreview2DXClick);
//
// MainDxButton
//
this.MainDxButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.MainDxButton.Location = new System.Drawing.Point(72, 129);
this.MainDxButton.Name = "MainDxButton";
this.MainDxButton.Size = new System.Drawing.Size(105, 22);
this.MainDxButton.TabIndex = 22;
this.MainDxButton.Text = "2DX Music";
this.MainDxButton.UseVisualStyleBackColor = true;
this.MainDxButton.Click += new System.EventHandler(this.OnMainDxButtonClick);
//
// VoxButton
//
this.VoxButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.VoxButton.Location = new System.Drawing.Point(72, 101);
this.VoxButton.Name = "VoxButton";
this.VoxButton.Size = new System.Drawing.Size(214, 22);
this.VoxButton.TabIndex = 21;
this.VoxButton.Text = "Vox / Ksh File";
this.VoxButton.UseVisualStyleBackColor = true;
this.VoxButton.Click += new System.EventHandler(this.OnVoxButtonClick);
//
// JacketPictureBox
//
this.JacketPictureBox.Image = global::VoxCharger.Properties.Resources.jk_dummy;
this.JacketPictureBox.Location = new System.Drawing.Point(12, 12);
this.JacketPictureBox.Name = "JacketPictureBox";
this.JacketPictureBox.Size = new System.Drawing.Size(108, 108);
this.JacketPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.JacketPictureBox.TabIndex = 1;
this.JacketPictureBox.TabStop = false;
this.JacketPictureBox.Click += new System.EventHandler(this.OnJacketPictureBoxClick);
//
// LevelEditorForm
//
this.AcceptButton = this.SaveEditButton;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.CancelEditButton;
this.ClientSize = new System.Drawing.Size(435, 220);
this.Controls.Add(this.SaveEditButton);
this.Controls.Add(this.JacketPictureBox);
this.Controls.Add(this.JacketButton);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.CancelEditButton);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "LevelEditorForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Level Editor - MXM";
this.Load += new System.EventHandler(this.OnLevelEditorFormLoad);
((System.ComponentModel.ISupportInitialize)(this.LevelNumericBox)).EndInit();
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.JacketPictureBox)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.PictureBox JacketPictureBox;
private System.Windows.Forms.Label LevelLabel;
private System.Windows.Forms.NumericUpDown LevelNumericBox;
private System.Windows.Forms.TextBox EffectorTextBox;
private System.Windows.Forms.Label EffectorLabel;
private System.Windows.Forms.Label IllustratorLabel;
private System.Windows.Forms.TextBox IllustratorTextBox;
private System.Windows.Forms.Button CancelEditButton;
private System.Windows.Forms.Button JacketButton;
private System.Windows.Forms.Button SaveEditButton;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Button VoxButton;
private System.Windows.Forms.Button MainDxButton;
private System.Windows.Forms.Button Preview2DX;
}
}

View File

@ -0,0 +1,304 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
namespace VoxCharger
{
public partial class LevelEditorForm : Form
{
private readonly Image DummyJacket = VoxCharger.Properties.Resources.jk_dummy_s;
private VoxHeader header;
private Ksh kshUpdate = null;
private string voxUpdatePath = string.Empty;
private string dxMainUpdatePath = string.Empty;
private string dxPreviewUpdatePath = string.Empty;
public VoxLevelHeader Result { get; private set; }
public Action Action { get; private set; } = null;
public LevelEditorForm(VoxHeader header, Difficulty difficulty)
{
this.header = header;
VoxLevelHeader level;
if (!header.Levels.TryGetValue(difficulty, out level))
Result = new VoxLevelHeader();
else
Result = level;
InitializeComponent();
}
private void OnLevelEditorFormLoad(object sender, EventArgs e)
{
switch (Result.Difficulty)
{
case Difficulty.Novice: Text = "Level Editor - NOV"; break;
case Difficulty.Advanced: Text = "Level Editor - ADV"; break;
case Difficulty.Exhaust: Text = "Level Editor - EXH"; break;
default:
switch (header.InfVersion)
{
case InfiniteVersion.INF: Text = "Level Editor - INF"; break;
case InfiniteVersion.GRV: Text = "Level Editor - GRV"; break;
case InfiniteVersion.HVN: Text = "Level Editor - HVN"; break;
case InfiniteVersion.VVD: Text = "Level Editor - VVD"; break;
default: Text = "Level Editor - MXM"; break;
}
break;
}
LevelNumericBox.Value = Result.Level;
EffectorTextBox.Text = Result.Effector;
IllustratorTextBox.Text = Result.Illustrator;
LoadJacket();
}
private void OnJacketButtonClick(object sender, EventArgs e)
{
using (var browser = new OpenFileDialog())
{
browser.Filter = "Portable Network Graphic|*.png";
browser.CheckFileExists = true;
if (browser.ShowDialog() == DialogResult.OK)
{
try
{
using (var image = Image.FromFile(browser.FileName))
JacketPictureBox.Image = Result.Jacket = new Bitmap(image);
}
catch (Exception)
{
Result.Jacket = null;
}
}
}
}
private void OnVoxButtonClick(object sender, EventArgs e)
{
using (var browser = new OpenFileDialog())
{
browser.Filter = "All supported types|*.vox;*.ksh|Sound Voltex Chart File|*.vox|Kshoot Chart File|*.ksh";
browser.CheckFileExists = true;
if (browser.ShowDialog() == DialogResult.OK)
{
try
{
// Shoving vox file while it's not actually vox? well.. stupid input get stupid output
string filename = browser.FileName;
if (filename.EndsWith(".vox"))
{
var vox = new VoxChart();
vox.Parse(filename);
voxUpdatePath = filename;
kshUpdate = null;
}
else if (filename.EndsWith(".ksh"))
{
var ksh = new Ksh();
ksh.Parse(filename);
kshUpdate = ksh;
voxUpdatePath = string.Empty;
}
else
MessageBox.Show("Warning! Stupid input, get stupid output :)", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
catch (Exception ex)
{
voxUpdatePath = null;
MessageBox.Show(
$"Failed to load chart.\n{ex.Message}",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
}
}
}
private void OnMainDxButtonClick(object sender, EventArgs e)
{
Load2DX();
}
private void OnPreview2DXClick(object sender, EventArgs e)
{
Load2DX(true);
}
private void Load2DX(bool preview = false)
{
using (var browser = new OpenFileDialog())
{
browser.Filter = "All supported formats|*.2dx;*.s3v;*.wav;*.ogg;*.mp3;*.flac|2DX Music File|*.2dx;*.s3v|Music Files|*.wav;*.ogg;*.mp3;*.flac"; ;
browser.CheckFileExists = true;
if (browser.ShowDialog() == DialogResult.OK)
{
if (!preview)
dxMainUpdatePath = browser.FileName;
else
dxPreviewUpdatePath = browser.FileName;
}
}
}
private void OnJacketPictureBoxClick(object sender, EventArgs e)
{
if (Result.Jacket == null)
{
string jacket = $"{AssetManager.GetJacketPath(header, Result.Difficulty)}_b.png";
if (!File.Exists(jacket))
return;
using (var image = Image.FromFile(jacket))
using (var viewer = new JacketViewerForm(image))
viewer.ShowDialog();
}
else
{
using (var viewer = new JacketViewerForm(Result.Jacket))
viewer.ShowDialog();
}
}
private void OnCancelEditButtonClick(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
private void OnSaveEditButtonClick(object sender, EventArgs e)
{
try
{
string voxData = string.Empty;
if (!string.IsNullOrEmpty(voxUpdatePath))
{
if (File.Exists(voxUpdatePath))
voxData = File.ReadAllText(voxUpdatePath, Encoding.GetEncoding("Shift_JIS"));
else
throw new FileNotFoundException("Vox file not found", voxUpdatePath);
}
VoxChart voxChart = null;
if (kshUpdate != null)
{
voxChart = new VoxChart();
voxChart.Import(kshUpdate);
}
if (!string.IsNullOrEmpty(dxMainUpdatePath))
{
if (File.Exists(dxMainUpdatePath))
{
string tmp = Path.Combine(
Path.GetTempPath(),
$"{Path.GetRandomFileName()}{new FileInfo(dxMainUpdatePath).Extension}"
);
File.Copy(dxMainUpdatePath, tmp);
dxMainUpdatePath = tmp;
}
else
throw new FileNotFoundException("Music file not found", dxMainUpdatePath);
}
if (!string.IsNullOrEmpty(dxPreviewUpdatePath))
{
if (File.Exists(dxPreviewUpdatePath))
{
string tmp = Path.Combine(
Path.GetTempPath(),
$"{Path.GetRandomFileName()}{new FileInfo(dxPreviewUpdatePath).Extension}"
);
File.Copy(dxPreviewUpdatePath, tmp);
dxPreviewUpdatePath = tmp;
}
else
throw new FileNotFoundException("Preview file not found", dxPreviewUpdatePath);
}
if (Result.Jacket != null || voxChart != null || !string.IsNullOrEmpty(voxData) || !string.IsNullOrEmpty(dxMainUpdatePath) || !string.IsNullOrEmpty(dxPreviewUpdatePath))
{
Action = new Action(() =>
{
if (Result.Jacket != null)
AssetManager.ImportJacket(header, Result.Difficulty, Result.Jacket);
if (!string.IsNullOrEmpty(voxData))
AssetManager.ImportVox(header, Result.Difficulty, voxUpdatePath);
else if (voxChart != null)
AssetManager.ImportVox(header, Result.Difficulty, voxChart);
if (File.Exists(dxMainUpdatePath))
AssetManager.Import2DX(dxMainUpdatePath, header, Result.Difficulty);
if (File.Exists(dxPreviewUpdatePath))
AssetManager.Import2DX(dxPreviewUpdatePath, header, Result.Difficulty, true);
});
}
Result.Level = (int)LevelNumericBox.Value;
Result.Effector = EffectorTextBox.Text;
Result.Illustrator = IllustratorTextBox.Text;
DialogResult = DialogResult.OK;
Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void LoadJacket()
{
if (Result.Jacket != null)
{
JacketPictureBox.Image = Result.Jacket;
return;
}
try
{
string currentJacket = $"{AssetManager.GetJacketPath(header, Result.Difficulty)}_s.png";
string defaultJacket = $"{AssetManager.GetDefaultJacketPath(header)}_s.png";
if (File.Exists(currentJacket))
{
using (var image = Image.FromFile(currentJacket))
JacketPictureBox.Image = new Bitmap(image);
}
else
{
if (File.Exists(defaultJacket))
{
using (var image = Image.FromFile(defaultJacket))
JacketPictureBox.Image = new Bitmap(image);
}
else
JacketPictureBox.Image = DummyJacket;
}
}
catch (Exception)
{
JacketPictureBox.Image = DummyJacket;
}
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace VoxCharger
{
public partial class LoadingForm : Form
{
private Action action;
private bool completed = false;
public LoadingForm()
{
InitializeComponent();
}
private void OnLoadingForm(object sender, EventArgs e)
{
}
private void OnLoadingFormShown(object sender, EventArgs e)
{
this.action();
}
public void SetAction(Action action)
{
this.action = action;
}
public void SetStatus(string text)
{
if (StatusLabel.InvokeRequired)
{
StatusLabel.Invoke(new Action(() => SetStatus(text)));
return;
}
StatusLabel.Text = text;
StatusLabel.Update();
}
public void SetProgress(float percentage)
{
if (ProgressBar.InvokeRequired)
{
ProgressBar.Invoke(new Action<float>(SetProgress), percentage);
return;
}
percentage = percentage > 100f ? 100f : percentage < 0f ? 0f : percentage;
ProgressBar.Value = (int)percentage;
ProgressBar.Update();
}
public void Complete()
{
completed = true;
if (InvokeRequired)
Invoke(new Action(Close));
else
Close();
}
private void OnLoadingFormFormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = !completed;
}
}
}

75
Sources/Forms/LoadingForm.designer.cs generated Normal file
View File

@ -0,0 +1,75 @@
namespace VoxCharger
{
partial class LoadingForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.ProgressBar = new System.Windows.Forms.ProgressBar();
this.StatusLabel = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// ProgressBar
//
this.ProgressBar.Location = new System.Drawing.Point(12, 12);
this.ProgressBar.Name = "ProgressBar";
this.ProgressBar.Size = new System.Drawing.Size(335, 28);
this.ProgressBar.TabIndex = 0;
//
// StatusLabel
//
this.StatusLabel.Location = new System.Drawing.Point(9, 43);
this.StatusLabel.Name = "StatusLabel";
this.StatusLabel.Size = new System.Drawing.Size(338, 13);
this.StatusLabel.TabIndex = 1;
this.StatusLabel.Text = "Loading";
this.StatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// LoadingForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(359, 70);
this.ControlBox = false;
this.Controls.Add(this.StatusLabel);
this.Controls.Add(this.ProgressBar);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Name = "LoadingForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Processing";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OnLoadingFormFormClosing);
this.Load += new System.EventHandler(this.OnLoadingForm);
this.Shown += new System.EventHandler(this.OnLoadingFormShown);
this.ResumeLayout(false);
}
#endregion
public System.Windows.Forms.ProgressBar ProgressBar;
public System.Windows.Forms.Label StatusLabel;
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

1084
Sources/Forms/MainForm.Designer.cs generated Normal file

File diff suppressed because it is too large Load Diff

928
Sources/Forms/MainForm.cs Normal file
View File

@ -0,0 +1,928 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace VoxCharger
{
public partial class MainForm : Form
{
#region -- Variables --
private const int DefaultVolume = 91;
private readonly Image DummyJacket = VoxCharger.Properties.Resources.jk_dummy_s;
private readonly Dictionary<string, Queue<Action>> actions = new Dictionary<string, Queue<Action>>();
private bool Pristine = true;
private bool Autosave = true;
#endregion
#region --- Form ---
public MainForm()
{
InitializeComponent();
}
private void OnMainFormLoad(object sender, EventArgs e)
{
}
private void OnMainFormFormClosing(object sender, FormClosingEventArgs e)
{
if (!Pristine && SaveFileMenu.Enabled)
{
var response = MessageBox.Show(
"Save file before exit the program?",
"Quit",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question
);
if (response == DialogResult.Yes)
SaveFileMenu.PerformClick();
else if (response == DialogResult.Cancel)
e.Cancel = true;
}
}
#endregion
#region --- Menu ---
private void OnNewFileMenuClick(object sender, EventArgs e)
{
if (!Pristine && SaveFileMenu.Enabled)
{
var response = MessageBox.Show(
"Save file before open another mix?",
"Create Mix",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question
);
if (response == DialogResult.Yes)
SaveFileMenu.PerformClick();
else if (response == DialogResult.Cancel)
return;
Pristine = true;
actions.Clear();
}
string gamePath = AssetManager.GamePath;
if (string.IsNullOrEmpty(AssetManager.MixPath) || !Directory.Exists(AssetManager.MixPath))
{
using (var browser = new FolderBrowserDialog())
{
browser.ShowNewFolderButton = true;
browser.Description = "Select KFC Content Root";
if (browser.ShowDialog() == DialogResult.OK)
{
gamePath = browser.SelectedPath;
PathTextBox.Text = string.Empty;
MusicListBox.Items.Clear();
ResetEditor();
}
}
}
using (var mixSelector = new MixSelectorForm(createMode: true))
{
AssetManager.Initialize(gamePath);
if (mixSelector.ShowDialog() == DialogResult.OK)
Reload();
}
}
private void OnSaveFileMenuClick(object sender, EventArgs e)
{
try
{
Save(AssetManager.MdbFilename);
MessageBox.Show(
"Mix has been saved successfully",
"Information",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
}
catch (Exception ex)
{
MessageBox.Show(
ex.Message,
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
}
private void OnSaveAsFileMenuClick(object sender, EventArgs e)
{
try
{
using (var exporter = new SaveFileDialog())
{
exporter.Filter = "Music DB|*.xml|All Files|*.*";
exporter.FileName = new FileInfo(AssetManager.MdbFilename).Name;
if (exporter.ShowDialog() != DialogResult.OK)
return;
Save(exporter.FileName);
MessageBox.Show(
"Mix has been saved successfully",
"Information",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
}
}
catch (Exception ex)
{
MessageBox.Show(
ex.Message,
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
}
private void OnChangeMixFileMenuClick(object sender, EventArgs e)
{
if (!Pristine && SaveFileMenu.Enabled)
{
var response = MessageBox.Show(
"Save file before open another mix?",
"Change Mix",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question
);
if (response == DialogResult.Yes)
SaveFileMenu.PerformClick();
else if (response == DialogResult.Cancel)
return;
Pristine = true;
actions.Clear();
}
using (var mixSelector = new MixSelectorForm())
{
AssetManager.Initialize(AssetManager.GamePath);
if (mixSelector.ShowDialog() == DialogResult.OK)
Reload();
}
}
private void OnDeleteMixFileMenuClick(object sender, EventArgs e)
{
var prompt = MessageBox.Show(
$"Are you sure want to delete \"{AssetManager.MixName}\"?",
"Delete Mix",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning
);
if (prompt == DialogResult.Yes)
{
try
{
FileMenu.Enabled = OpenButton.Enabled = false;
Directory.Delete(AssetManager.MixPath, true);
PathTextBox.Text = "";
MetadataGroupBox.Enabled = false;
MusicListBox.Items.Clear();
DisableUI();
ResetEditor();
AssetManager.Initialize(AssetManager.GamePath);
OnChangeMixFileMenuClick(sender, e);
}
catch (Exception ex)
{
MessageBox.Show(
ex.Message,
"Delete Mix",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
finally
{
FileMenu.Enabled = OpenButton.Enabled = true;
}
}
}
private void OnAutosaveEditMenuClick(object sender, EventArgs e)
{
Autosave = !Autosave;
AutosaveEditMenu.Checked = Autosave;
}
private void OnExplorerEditMenuClick(object sender, EventArgs e)
{
var header = MusicListBox.SelectedItem as VoxHeader;
if (header == null)
return;
string path = AssetManager.GetMusicPath(header);
Process.Start("explorer.exe", path);
}
private void OnSingleConvertToolsMenuClick(object sender, EventArgs e)
{
using (var browser = new OpenFileDialog())
{
browser.Filter = "Kshoot Chart File|*.ksh";
browser.CheckFileExists = true;
if (browser.ShowDialog() != DialogResult.OK)
return;
using (var converter = new ConverterForm(browser.FileName, true))
converter.ShowDialog();
}
}
private void OnBulkConvertToolsMenuClick(object sender, EventArgs e)
{
using (var browser = new FolderBrowserDialog())
{
browser.Description = "Select Kshoot chart repository";
if (browser.ShowDialog() != DialogResult.OK)
return;
using (var converter = new ConverterForm(browser.SelectedPath, true))
converter.ShowDialog();
}
}
private void OnMusicFileBuilderClick(object sender, EventArgs e)
{
using (var browser = new OpenFileDialog())
using (var exporter = new SaveFileDialog())
{
browser.Filter = "Audio Files|*.wav;*.ogg;*.mp3;*.flac|2DX Music File|*.2dx;*.s3v|Music Files|*.wav;*.ogg;*.mp3;*.flac";
browser.CheckFileExists = true;
if (browser.ShowDialog() != DialogResult.OK)
return;
exporter.Filter = "2DX File|*.2dx";
if (exporter.ShowDialog() != DialogResult.OK)
return;
string error = string.Empty;
using (var loader = new LoadingForm())
{
loader.SetAction(() =>
{
try
{
loader.SetStatus("Processing assets..");
string source = browser.FileName;
string tmp = DxTool.ConvertToWave(source, false);
DxTool.Build(tmp, exporter.FileName);
Directory.Delete(tmp, true);
loader.SetProgress(100);
loader.DialogResult = DialogResult.OK;
loader.Complete();
}
catch (Exception ex)
{
error = ex.Message;
loader.DialogResult = DialogResult.Abort;
loader.Complete();
}
});
if (loader.ShowDialog() == DialogResult.OK)
{
MessageBox.Show(
"Audio file has been converted successfully",
"2DX Builder",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
}
else
{
MessageBox.Show(
$"Failed to convert audio file.\n{error}",
"2DX Builder",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
}
}
}
private void OnAboutHelpMenuClick(object sender, EventArgs e)
{
using (var about = new AboutForm())
about.ShowDialog();
}
private void OnExitFileMenuClick(object sender, EventArgs e)
{
Application.Exit();
}
#endregion
#region -- Editor ---
private void OnOpenButtonClick(object sender, EventArgs e)
{
try
{
if (!Pristine && SaveFileMenu.Enabled)
{
var response = MessageBox.Show(
"Save file before open another mix?",
"Open Mix",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question
);
if (response == DialogResult.Yes)
SaveFileMenu.PerformClick();
else if (response == DialogResult.Cancel)
return;
Pristine = true;
actions.Clear();
}
using (var browser = new FolderBrowserDialog())
using (var mixSelector = new MixSelectorForm())
{
browser.ShowNewFolderButton = true;
browser.Description = "Select KFC Content Root";
if (browser.ShowDialog() == DialogResult.OK)
{
AssetManager.Initialize(browser.SelectedPath);
PathTextBox.Text = string.Empty;
MusicListBox.Items.Clear();
ResetEditor();
if (mixSelector.ShowDialog() == DialogResult.OK)
Reload();
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void OnAddNewMenuClick(object sender, EventArgs e)
{
string defaultAscii = AssetManager.MixName.ToLower().Replace(" ", string.Empty);
var duplicates = new List<VoxHeader>();
foreach (var h in AssetManager.Headers)
{
if (h.Ascii.StartsWith(defaultAscii))
duplicates.Add(h);
}
if (duplicates.Count > 0)
{
duplicates.Sort((a, b) => a.Ascii.CompareTo(b.Ascii));
string ascii = $"{defaultAscii}_01";
int counter = 1;
foreach (var h in duplicates)
{
if (h.Ascii == ascii)
ascii = $"{defaultAscii}_{++counter:D2}";
else
break;
}
defaultAscii = ascii;
}
var header = new VoxHeader()
{
Title = "Untitled",
Ascii = defaultAscii,
Artist = "Unknown",
Version = GameVersion.VividWave,
InfVersion = InfiniteVersion.MXM,
BackgroundId = short.Parse(ConverterForm.LastBackground),
GenreId = 16,
BpmMin = 1,
BpmMax = 1,
Volume = 91,
DistributionDate = DateTime.Now,
Levels = new Dictionary<Difficulty, VoxLevelHeader>()
{
{ Difficulty.Infinite, new VoxLevelHeader() {} }
}
};
Pristine = false;
AssetManager.Headers.Add(header);
MusicListBox.Items.Add(header);
}
private void OnImportKshMenuClick(object sender, EventArgs e)
{
using (var browser = new OpenFileDialog())
{
browser.Filter = "KShoot Mania Chart|*.ksh";
browser.CheckFileExists = true;
if (browser.ShowDialog() != DialogResult.OK)
return;
using (var converter = new ConverterForm(browser.FileName))
{
if (converter.ShowDialog() != DialogResult.OK)
return;
var header = converter.Header;
if (!actions.ContainsKey(header.Ascii))
actions[header.Ascii] = new Queue<Action>();
Pristine = false;
AssetManager.Headers.Add(header);
MusicListBox.Items.Add(header);
actions[header.Ascii].Enqueue(converter.Action);
if (Autosave)
Save(AssetManager.MdbFilename);
}
}
}
private void OnMetadataChanged(object sender, EventArgs e)
{
if (sender is TextBox textBox && !textBox.Modified)
return;
if (sender is Control control && !(control is ComboBox) && !control.ContainsFocus)
return;
if (!(MusicListBox.SelectedItem is VoxHeader header))
return;
if (int.TryParse(IdTextBox.Text, out int id))
header.ID = id;
else
IdTextBox.Text = header.ID.ToString();
double min = (double)Math.Min(BpmMinNumericBox.Value, BpmMaxNumericBox.Value);
double max = (double)Math.Max(BpmMinNumericBox.Value, BpmMaxNumericBox.Value);
BpmMinNumericBox.Value = (decimal)min;
BpmMaxNumericBox.Value = (decimal)max;
header.Title = TitleTextBox.Text;
header.Artist = ArtistTextBox.Text;
header.BpmMin = min;
header.BpmMax = max;
header.Version = (GameVersion)(VersionDropDown.SelectedIndex + 1);
header.InfVersion = InfVerDropDown.SelectedIndex == 0 ? InfiniteVersion.MXM : (InfiniteVersion)(InfVerDropDown.SelectedIndex + 1);
header.DistributionDate = DistributionPicker.Value;
header.BackgroundId = short.Parse((BackgroundDropDown.SelectedItem ?? "0").ToString().Split(' ')[0]);
header.Volume = (short)VolumeTrackBar.Value;
VolumeIndicatorLabel.Text = $"{VolumeTrackBar.Value:#00}%";
Pristine = false;
}
private void OnLevelEditButtonClick(object sender, EventArgs e)
{
if (MusicListBox.SelectedItem == null)
return;
var button = (Button)sender;
var difficulty = (Difficulty)int.Parse(button.Tag.ToString());
var header = MusicListBox.SelectedItem as VoxHeader;
using (var editor = new LevelEditorForm(header, difficulty))
{
if (editor.ShowDialog() == DialogResult.OK)
{
// Update the levels in case it's newly added
header.Levels[difficulty] = editor.Result;
if (editor.Action != null)
{
if (!actions.ContainsKey(header.Ascii))
actions[header.Ascii] = new Queue<Action>();
actions[header.Ascii].Enqueue(editor.Action);
}
if (header.Levels.ContainsKey(Difficulty.Infinite))
InfVerDropDown.Items[0] = "MXM";
else
InfVerDropDown.Items[0] = "--";
OnInfVerDropDownSelectedIndexChanged(sender, e);
Pristine = false;
LoadJacket(header);
if (Autosave)
Save(AssetManager.MdbFilename);
}
}
}
private void OnInfVerDropDownSelectedIndexChanged(object sender, EventArgs e)
{
var header = MusicListBox.SelectedItem as VoxHeader;
if (header == null || InfVerDropDown.SelectedItem == null)
InfEditButton.Text = "--";
else if (header.Levels.ContainsKey(Difficulty.Infinite))
InfEditButton.Text = InfVerDropDown.SelectedItem.ToString();
}
private void OnImport2DXMusicFileButtonClick(object sender, EventArgs e)
{
Import2DX();
if (Autosave)
Save(AssetManager.MdbFilename);
}
private void OnImport2DXPreviewFileButtonClick(object sender, EventArgs e)
{
Import2DX(true);
if (Autosave)
Save(AssetManager.MdbFilename);
}
private void OnJacketPictureBoxClick(object sender, EventArgs e)
{
var header = MusicListBox.SelectedItem as VoxHeader;
if (header == null)
return;
var control = (Control)sender;
var difficulty = (Difficulty)int.Parse(control.Tag.ToString());
if (!header.Levels.ContainsKey(difficulty))
return;
string jacket = $"{AssetManager.GetJacketPath(header, difficulty)}_b.png";
if (!File.Exists(jacket))
{
jacket = $"{AssetManager.GetDefaultJacketPath(header)}_b.png";
if (!File.Exists(jacket))
return;
}
using (var image = Image.FromFile(jacket))
using (var viewer = new JacketViewerForm(image))
viewer.ShowDialog();
}
#endregion
#region --- Mix List Management ---
private void OnMusicListBoxSelectedIndexChanged(object sender, EventArgs e)
{
var header = MusicListBox.SelectedItem as VoxHeader;
if (header == null)
{
MetadataGroupBox.Enabled = false;
ResetEditor();
return;
}
IdTextBox.Text = header.ID.ToString();
TitleTextBox.Text = VoxHeader.FixMappedChars(header.Title);
ArtistTextBox.Text = VoxHeader.FixMappedChars(header.Artist);
BpmMinNumericBox.Value = (decimal)header.BpmMin;
BpmMaxNumericBox.Value = (decimal)header.BpmMax;
VersionDropDown.SelectedIndex = (int)(header.Version) - 1;
InfVerDropDown.SelectedIndex = header.InfVersion == InfiniteVersion.MXM ? 0 : (int)(header.InfVersion) - 1;
DistributionPicker.Value = header.DistributionDate;
VolumeTrackBar.Value = header.Volume;
BackgroundDropDown.SelectedItem = $"{header.BackgroundId:D2}";
VolumeIndicatorLabel.Text = $"{VolumeTrackBar.Value:#00}%";
if (header.Levels.ContainsKey(Difficulty.Infinite))
InfVerDropDown.Items[0] = "MXM";
else
InfVerDropDown.Items[0] = "--";
bool safe = !string.IsNullOrEmpty(AssetManager.MixName);
AddButton.Enabled = safe;
AddEditMenu.Enabled = safe;
RemoveButton.Enabled = safe;
RemoveEditMenu.Enabled = safe;
Import2DXEditMenu.Enabled = safe;
Import2DXPreviewEditMenu.Enabled = safe;
ExplorerEditMenu.Enabled = true;
EditMenu.Enabled = true;
MetadataGroupBox.Enabled = true;
InfEditButton.Text = InfVerDropDown.SelectedItem.ToString();
LoadJacket(header);
}
private void OnRemoveButtonClick(object sender, EventArgs e)
{
var header = MusicListBox.SelectedItem as VoxHeader;
if (header == null)
return;
var response = MessageBox.Show(
$"Are you sure want to delete selected song?",
"Delete",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning
);
if (response == DialogResult.No)
return;
AssetManager.Headers.Remove(header);
MusicListBox.Items.Remove(header);
// Clear pending modification, since this asset will be deleted anyway
actions[header.Ascii] = new Queue<Action>();
actions[header.Ascii].Enqueue(() => AssetManager.DeleteAssets(header));
if (Autosave)
Save(AssetManager.MdbFilename);
}
private void OnPathTextBoxTextChanged(object sender, EventArgs e)
{
MusicGroupBox.Enabled = !string.IsNullOrEmpty(PathTextBox.Text);
}
#endregion
#region --- Functions ---
private void LoadMusicDb()
{
using (var loader = new LoadingForm())
{
loader.SetAction(() =>
{
MusicListBox.Items.Clear();
foreach (var header in AssetManager.Headers)
{
MusicListBox.Items.Add(header);
loader.SetStatus(header.Title);
loader.SetProgress(((float)MusicListBox.Items.Count / AssetManager.Headers.Count) * 100f);
}
loader.Complete();
});
loader.ShowDialog();
}
}
private void Save(string dbFilename)
{
using (var loader = new LoadingForm())
{
var proc = new Action(() =>
{
int max = actions.Count + 1;
foreach (var queue in actions.Values)
{
float progress = ((float)(max - actions.Count) / max) * 100f;
loader.SetStatus($"[{progress:00}%] - Processing assets..");
loader.SetProgress(progress);
while (queue.Count > 0)
{
try
{
queue.Dequeue()?.Invoke();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
loader.SetStatus("[100%] - Processing Music DB..");
loader.SetProgress(100f);
AssetManager.Headers.Save(dbFilename);
loader.Complete();
});
loader.SetAction(() => new Thread(() => proc()).Start());
loader.ShowDialog();
}
actions.Clear();
Pristine = true;
}
private void Import2DX(bool preview = false)
{
var header = MusicListBox.SelectedItem as VoxHeader;
if (header == null)
return;
using (var browser = new OpenFileDialog())
{
browser.Filter = "All supported format|*.2dx;*.s3v;*.wav;*.ogg;*.mp3;*.flac|2DX Music File|*.2dx;*.s3v|Music Files|*.wav;*.ogg;*.mp3;*.flac";
browser.CheckFileExists = true;
if (browser.ShowDialog() != DialogResult.OK)
return;
string source = browser.FileName;
string tmp = Path.Combine(
Path.GetTempPath(),
$"{Path.GetRandomFileName()}{new FileInfo(source).Extension}"
);
File.Copy(source, tmp);
if (!actions.ContainsKey(header.Ascii))
actions[header.Ascii] = new Queue<Action>();
actions[header.Ascii].Enqueue(() => AssetManager.Import2DX(tmp, header, preview));
}
}
private void LoadJacket(VoxHeader header)
{
string defaultJacket = $"{AssetManager.GetDefaultJacketPath(header)}_s.png";
foreach (Difficulty diff in Enum.GetValues(typeof(Difficulty)))
{
PictureBox picture;
switch (diff)
{
case Difficulty.Novice: picture = JacketNovPictureBox; break;
case Difficulty.Advanced: picture = JacketAdvPictureBox; break;
case Difficulty.Exhaust: picture = JacketExhPictureBox; break;
default: picture = JacketInfPictureBox; break;
}
if (!header.Levels.ContainsKey(diff))
{
picture.Image = DummyJacket;
continue;
}
try
{
string filename = $"{AssetManager.GetJacketPath(header, diff)}_s.png";
if (File.Exists(filename) && (Pristine || header.Levels[diff].Jacket == null))
{
using (var image = Image.FromFile(filename))
picture.Image = new Bitmap(image);
if (header.Levels[diff].Jacket != null) // clear cache
{
header.Levels[diff].Jacket.Dispose();
header.Levels[diff].Jacket = null;
}
}
else if (header.Levels[diff].Jacket != null)
{
// use cache for new + unsaved header
picture.Image = header.Levels[diff].Jacket;
}
else if (File.Exists(defaultJacket))
{
using (var image = Image.FromFile(defaultJacket))
picture.Image = new Bitmap(image);
}
else
picture.Image = DummyJacket;
}
catch (Exception)
{
picture.Image = DummyJacket;
}
}
}
private void Reload()
{
PathTextBox.Text = AssetManager.MixPath;
MetadataGroupBox.Enabled = false;
ResetEditor();
DisableUI();
LoadMusicDb();
EnableUI();
}
private void EnableUI()
{
UpdateUI(true);
}
private void DisableUI()
{
UpdateUI(false);
ResetEditor();
}
private void UpdateUI(bool state)
{
// Dont break your goddamn kfc
bool safe = !string.IsNullOrEmpty(AssetManager.MixName);
// Container
MusicGroupBox.Enabled = state;
// Menu
SaveFileMenu.Enabled = state && safe;
SaveAsFileMenu.Enabled = state;
ChangeMixFileMenu.Enabled = state;
DeleteMixFileMenu.Enabled = state && safe;
AddButton.Enabled = state && safe;
AddEditMenu.Enabled = state && safe;
RemoveButton.Enabled = state && safe;
RemoveEditMenu.Enabled = state && safe;
EditMenu.Enabled = state && safe;
Import2DXEditMenu.Enabled = state && safe;
Import2DXPreviewEditMenu.Enabled = state && safe;
foreach (Control control in MetadataGroupBox.Controls)
{
if (control is TextBox textBox)
textBox.ReadOnly = !safe;
else if (control is NumericUpDown numeric)
numeric.Enabled = safe;
else if (!(control is PictureBox))
control.Enabled = safe;
}
LevelGroupBox.Enabled = true;
foreach (Control control in LevelGroupBox.Controls)
{
if (!(control is PictureBox))
control.Enabled = safe;
else
control.Enabled = true;
}
}
private void ResetEditor()
{
foreach (Control control in MetadataGroupBox.Controls)
{
if (control is TextBox)
control.Text = "";
else if (control is NumericUpDown)
(control as NumericUpDown).Value = 1;
else if (control is ComboBox)
(control as ComboBox).SelectedItem = null;
}
foreach (Control control in LevelGroupBox.Controls)
{
if (control is PictureBox)
{
var pictureBox = control as PictureBox;
pictureBox.Image = DummyJacket;
}
else if (control is Button && control.Tag.ToString() == "4")
{
control.Text = "--";
}
}
DistributionPicker.Value = DateTime.Now;
VolumeTrackBar.Value = DefaultVolume;
EditMenu.Enabled = false;
Import2DXEditMenu.Enabled = false;
Import2DXPreviewEditMenu.Enabled = false;
ExplorerEditMenu.Enabled = false;
}
#endregion
}
}

3086
Sources/Forms/MainForm.resx Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class MenuButton : Button
{
[DefaultValue(null), Browsable(true),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public ContextMenu Menu { get; set; }
[DefaultValue(20), Browsable(true),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int SplitWidth { get; set; }
[DefaultValue(true), Browsable(true),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public bool FullMenu { get; set; } = true;
public MenuButton()
{
SplitWidth = 20;
}
protected override void OnMouseDown(MouseEventArgs mevent)
{
if (FullMenu)
{
Menu.Show(this, new Point(0, Height));
base.OnMouseDown(mevent);
}
else
{
var splitRect = new Rectangle(Width - SplitWidth, 0, SplitWidth, Height);
// Figure out if the button click was on the button itself or the menu split
if (Menu != null &&
mevent.Button == MouseButtons.Left &&
splitRect.Contains(mevent.Location))
{
Menu.Show(this, new Point(0, Height)); // Shows menu under button
}
else
{
base.OnMouseDown(mevent);
}
}
}
protected override void OnPaint(PaintEventArgs pevent)
{
base.OnPaint(pevent);
if (Menu != null && SplitWidth > 0)
{
// Draw the arrow glyph on the right side of the button
int arrowX = ClientRectangle.Width - 14;
int arrowY = ClientRectangle.Height / 2 - 1;
var arrowBrush = Enabled ? SystemBrushes.ControlText : SystemBrushes.ButtonShadow;
var arrows = new[] { new Point(arrowX, arrowY), new Point(arrowX + 7, arrowY), new Point(arrowX + 3, arrowY + 4) };
pevent.Graphics.FillPolygon(arrowBrush, arrows);
// Draw a dashed separator on the left of the arrow
int lineX = ClientRectangle.Width - SplitWidth - 5;
int lineYFrom = arrowY - 6;
int lineYTo = arrowY + 10;
using (var separatorPen = new Pen(Brushes.DarkGray) { DashStyle = DashStyle.Dot })
{
pevent.Graphics.DrawLine(separatorPen, lineX, lineYFrom, lineX, lineYTo);
}
}
}
}

111
Sources/Forms/MixSelectorForm.Designer.cs generated Normal file
View File

@ -0,0 +1,111 @@
namespace VoxCharger
{
partial class MixSelectorForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.ModSelectorLabel = new System.Windows.Forms.Label();
this.MixSelectorDropDown = new System.Windows.Forms.ComboBox();
this.ContinueButton = new System.Windows.Forms.Button();
this.NameTextBox = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// ModSelectorLabel
//
this.ModSelectorLabel.AutoSize = true;
this.ModSelectorLabel.Location = new System.Drawing.Point(10, 14);
this.ModSelectorLabel.Name = "ModSelectorLabel";
this.ModSelectorLabel.Size = new System.Drawing.Size(59, 13);
this.ModSelectorLabel.TabIndex = 0;
this.ModSelectorLabel.Text = "Select Mix:";
//
// MixSelectorDropDown
//
this.MixSelectorDropDown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.MixSelectorDropDown.FormattingEnabled = true;
this.MixSelectorDropDown.Items.AddRange(new object[] {
"-- Create New Mix --",
"original"});
this.MixSelectorDropDown.Location = new System.Drawing.Point(12, 30);
this.MixSelectorDropDown.Name = "MixSelectorDropDown";
this.MixSelectorDropDown.Size = new System.Drawing.Size(274, 21);
this.MixSelectorDropDown.TabIndex = 1;
this.MixSelectorDropDown.SelectedIndexChanged += new System.EventHandler(this.OnModSelectorDropDownSelectedIndexChanged);
//
// ContinueButton
//
this.ContinueButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.ContinueButton.Enabled = false;
this.ContinueButton.Location = new System.Drawing.Point(12, 62);
this.ContinueButton.Name = "ContinueButton";
this.ContinueButton.Size = new System.Drawing.Size(274, 27);
this.ContinueButton.TabIndex = 2;
this.ContinueButton.Text = "Continue";
this.ContinueButton.UseVisualStyleBackColor = true;
this.ContinueButton.Click += new System.EventHandler(this.OnContinueButtonClick);
//
// NameTextBox
//
this.NameTextBox.Location = new System.Drawing.Point(12, 57);
this.NameTextBox.Name = "NameTextBox";
this.NameTextBox.Size = new System.Drawing.Size(274, 20);
this.NameTextBox.TabIndex = 3;
this.NameTextBox.Text = "custom";
this.NameTextBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.NameTextBox.Visible = false;
//
// MixSelectorForm
//
this.AcceptButton = this.ContinueButton;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(298, 101);
this.Controls.Add(this.ContinueButton);
this.Controls.Add(this.MixSelectorDropDown);
this.Controls.Add(this.ModSelectorLabel);
this.Controls.Add(this.NameTextBox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "MixSelectorForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Mix Selection";
this.Load += new System.EventHandler(this.OnModSelectorFormLoad);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label ModSelectorLabel;
private System.Windows.Forms.ComboBox MixSelectorDropDown;
private System.Windows.Forms.Button ContinueButton;
private System.Windows.Forms.TextBox NameTextBox;
}
}

View File

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace VoxCharger
{
public partial class MixSelectorForm : Form
{
public int EXPANDED_HEIGHT = 165;
public int COLLAPSED_HEIGHT = 140;
public MixSelectorForm(bool createMode = false)
{
InitializeComponent();
if (createMode)
{
Text = "Create Mix";
MixSelectorDropDown.SelectedIndex = 0;
NameTextBox.Location = MixSelectorDropDown.Location;
MixSelectorDropDown.Visible = MixSelectorDropDown.Enabled = false;
ModSelectorLabel.Text = "Enter mix name:";
Size = new Size(Size.Width, COLLAPSED_HEIGHT);
}
}
private void OnModSelectorFormLoad(object sender, EventArgs e)
{
foreach (string mix in AssetManager.GetMixes())
MixSelectorDropDown.Items.Add(mix);
}
private void OnModSelectorDropDownSelectedIndexChanged(object sender, EventArgs e)
{
if (MixSelectorDropDown.SelectedIndex == 0)
Size = new Size(Size.Width, EXPANDED_HEIGHT);
else
Size = new Size(Size.Width, COLLAPSED_HEIGHT);
NameTextBox.Visible = MixSelectorDropDown.SelectedIndex == 0;
ContinueButton.Enabled = true;
}
private void OnContinueButtonClick(object sender, EventArgs e)
{
if (MixSelectorDropDown.SelectedIndex == 0)
{
if (string.IsNullOrEmpty(NameTextBox.Text))
{
MessageBox.Show(
"Mix name cannot be empty",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Warning
);
return;
}
else if (AssetManager.GetMixes().Contains(NameTextBox.Text) || NameTextBox.Text.ToLower() == "original")
{
MessageBox.Show(
"Mix name is already exists",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Warning
);
return;
}
AssetManager.CreateMix(NameTextBox.Text);
}
else if (MixSelectorDropDown.SelectedIndex == 1)
AssetManager.LoadMix(string.Empty);
else
AssetManager.LoadMix(MixSelectorDropDown.SelectedItem.ToString());
DialogResult = DialogResult.OK;
Close();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

760
Sources/Ksh/Ksh.cs Normal file
View File

@ -0,0 +1,760 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
namespace VoxCharger
{
public partial class Ksh
{
private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("Shift_JIS");
private const string VolPositions = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmno";
public string Title { get; set; }
public string Artist { get; set; }
public string Effector { get; set; }
public string Illustrator { get; set; }
public string JacketFileName { get; set; }
public string MusicFileName { get; set; }
public int Volume { get; set; }
public Difficulty Difficulty { get; set; }
public int Level { get; set; }
public int MusicOffset { get; set; }
public int PreviewOffset { get; set; }
public float BpmMin { get; set; }
public float BpmMax { get; set; }
public string Background { get; set; }
public EventCollection Events { get; private set; } = new EventCollection();
public class ParseOption
{
public bool RealignOffset { get; set; } = false;
public bool EnableChipFx { get; set; } = true;
public bool EnableLongFx { get; set; } = true;
public bool EnableCamera { get; set; } = true;
public bool EnableSlamImpact { get; set; } = true;
public bool EnableLaserTrack { get; set; } = true;
public bool EnableButtonTrack { get; set; } = true;
}
public Ksh()
{
}
public void Parse(string fileName, ParseOption opt = null)
{
// I pulled all nighters for few days, all for this piece of trash codes :)
// Reminder: there's lot of "offset" that can be ambigous between many contexts everywhere
var lines = File.ReadAllLines(fileName, DefaultEncoding);
if (opt == null)
opt = new ParseOption();
Time time = new Time(1, 1, 0);
var signature = new Event.TimeSignature(time, 4, 4);
var filter = Event.LaserFilter.Peak;
int rangeLeft = 0;
int rangeRight = 0;
var cameras = new Dictionary<Camera.WorkType, Camera>();
var lastCamera = new Dictionary<Camera.WorkType, Camera>();
var hitFx = new Dictionary<Event.ButtonTrack, KshSoundEffect>();
var holdFx = new Dictionary<Event.ButtonTrack, Effect>();
var longNotes = new Dictionary<Event.ButtonTrack, Event.Button>();
var lasers = new Dictionary<Event.LaserTrack, List<Event.Laser>>();
int offset = 0;
int measure = 0;
int measureCount = lines.Count(l => l.Trim() == "--");
int fxCount = 0;
int noteCount = 0;
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i].Trim();
if (line.StartsWith("//"))
continue;
// First measure is 1,1,0 but initial counter start from 0 until actual measure started
if (measure > 0)
{
/** Time
* Behold! time conversion between ksh format to vox
*
* Vox Time Format:
* Timestamp value has beat to be matched with current signature
* However, the offset is base of 48, this might be lead into confusion in non standard signature
*
* For example:
* In 8 / 4 signature the maximum timestamp that means:
* - Maximum timestamp is 999 / 8 / 48 for each measure, beat and cells respectively
* - Value of beat adapt to the signature beat
* - May only contain 4 notes per beat in normal behavior since signature note is 4
*
* And since offset need to be base of 48 (not 192) this value need to be converted
* for example the 3rd note in the beat should be 36 and not 24
*
* Ksh Time Format:
* This too, depends on beat but the number of actual event definitions can be much greater than the beat itself
* for example, measure that has 8/4 signature may contains 441 events, though much of them are just padding
* this padding required since the event didn't define timestamp in each line (easier to debug with larger file size tradeoff)
*/
float position = (measure * 192f) + ((offset / (float)noteCount) * 192f);
if (opt.RealignOffset)
position -= MusicOffset; // Attempt to align
time = Time.FromOffset(position, signature);
//int p = time.AsAbsoluteOffset(signature);
//if ((int)Math.Round(position) != p)
// Debug.WriteLine("");
// Magic happens here!
if (time.Measure < 0)
continue; // Might happen when try to realign music offset
if (time.Beat > signature.Beat)
Debug.WriteLine($"[KSH] {i + 1:D4}: Measure has more than {signature.Beat} beats");
}
if (line.Contains("="))
{
// Might just pull it to first bar
if (time.Measure == 0)
time = new Time(1, 1, 0);
var data = line.Split('=');
if (data.Length < 2)
{
Debug.WriteLine($"[KSH] {i+1:D4}: Invalid attribute");
continue;
}
string prop = data[0].Trim().ToLower();
string value = data[1].Trim();
var param = value.Split(';');
if (string.IsNullOrEmpty(value))
Debug.WriteLine($"[KSH] {i + 1:D4}: {prop} value is empty (reset value)");
/** WARNING !!!
* Cancer code ahead, proceed with caution!
* Well.. the rest of codes aren't any better either lol
*/
int maxDuration = (measureCount - measure) * 192;
switch (prop) // At first i just want try to use switch-case pattern, turns out became cancerous (indent and) code lol
{
#region --- Metadata ---
case "title": Title = value; break;
case "artist": Artist = value; break;
case "effect": Effector = value; break;
case "illustrator": Illustrator = value; break;
case "jacket": JacketFileName = value; break;
case "m": MusicFileName = value; break;
case "po" when int.TryParse(value, out int po): PreviewOffset = po; break;
case "mvol" when int.TryParse(value, out int vol): Volume = vol; break;
case "level" when int.TryParse(value, out int lv): Level = lv; break;
case "difficulty" when value == "light": Difficulty = Difficulty.Novice; break;
case "difficulty" when value == "challenge": Difficulty = Difficulty.Advanced; break;
case "difficulty" when value == "extended": Difficulty = Difficulty.Exhaust; break;
case "difficulty" when value == "infinite": Difficulty = Difficulty.Infinite; break;
case "bg" when string.IsNullOrEmpty(Background):
case "layer" when string.IsNullOrEmpty(Background):
Background = value;
break;
#endregion
#region --- Event Info ---
case "o" when int.TryParse(value, out int o):
MusicOffset = o;
// Should be fine if there's no bpm change
if (MusicOffset % 48 != 0 && opt.RealignOffset)
Debug.WriteLine($"[KSH] Music Offset is not base of 48 signature (Offset: {MusicOffset})");
break;
case "t":
foreach (string ts in value.Split('-'))
{
if (!float.TryParse(ts, out float t))
break;
if (BpmMin == 0f || t < BpmMin)
BpmMin = t;
if (BpmMax == 0f || t > BpmMax)
BpmMax = t;
if (!value.Contains("-"))
{
var bpm = new Event.BPM(new Time(time.Measure, time.Beat, 0), t); // beat still acceptable
Events.Add(bpm);
}
}
// BPM change with unaligned music offset, proceed with caution!
if (BpmMin != BpmMax && MusicOffset % 48 != 0 && opt.RealignOffset)
Debug.WriteLine($"[KSH] BPM change with unaligned music offset");
break;
case "stop" when float.TryParse(value, out float duration):
Event.BPM start = null;
time = new Time(time.Measure, time.Beat, 0);
foreach (var ev in Events[time])
{
if (ev is Event.BPM x)
{
start = x;
start.IsStop = true;
break;
}
}
if (start == null)
{
var last = Events.GetBPM(time);
if (last == null)
break;
start = new Event.BPM(time, last.Value);
start.IsStop = true;
Events.Add(start);
}
Event.BPM end = null;
var target = Time.FromOffset((int)duration, signature);
foreach (var ev in Events[target])
{
if (ev is Event.BPM x)
{
end = x;
end.IsStop = false;
break;
}
}
if (end == null)
{
var last = Events.GetBPM(target);
if (last == null)
break;
end = new Event.BPM(target, last.Value);
end.IsStop = false;
Events.Add(end);
}
// Stop with unaligned music offset can break other stuffs too
if (MusicOffset % 48 != 0 && opt.RealignOffset)
Debug.WriteLine($"[KSH] Stop event unaligned music offset");
break;
case "beat":
var sig = value.Split('/');
if (sig.Length != 2 || !int.TryParse(sig[0], out int b) || !int.TryParse(sig[1], out int n))
break;
signature = new Event.TimeSignature(new Time(time.Measure, 1, 0), b, n); // beat unacceptable, only at start measure
Events.Add(signature);
break;
case "fx-l":
case "fx-r":
if (!opt.EnableLongFx)
break;
var htrack = prop == "fx-l" ? Event.ButtonTrack.FxL : Event.ButtonTrack.FxR;
holdFx[htrack] = new Effect();
var fx = Effect.FromKsh(value);
if (fx != null)
{
fx.Id = ++fxCount;
holdFx[htrack] = fx;
}
break;
case "fx-l_se":
case "fx-r_se":
if (!opt.EnableChipFx)
break;
var fxTrack = prop == "fx-l_se" ? Event.ButtonTrack.FxL : Event.ButtonTrack.FxR;
hitFx[fxTrack] = new KshSoundEffect();
if (Enum.TryParse(param[0], true, out Event.ChipFx hit))
hitFx[fxTrack].Effect = hit;
if (param.Length >= 2 && int.TryParse(param[1], out int hitCount))
hitFx[fxTrack].HitCount = hitCount + 1;
else
hitFx[fxTrack].HitCount = 1;
break;
case "filtertype":
switch (value.Trim())
{
case "peak": filter = Event.LaserFilter.Peak; break;
case "hpf1": filter = Event.LaserFilter.HighPass; break;
case "lpf1": filter = Event.LaserFilter.LowPass; break;
case var f when f.Contains("bitc"):
filter = Event.LaserFilter.BitCrusher;
break;
default:
filter = (Event.LaserFilter)6;
break;
}
break;
case "laserrange_l" when int.TryParse(value.Replace("x", ""), out int r): rangeLeft = r; break;
case "laserrange_r" when int.TryParse(value.Replace("x", ""), out int r): rangeRight = r; break;
#endregion
#region --- Camera ---
case "tilt" when !float.TryParse(value.Trim(), out float _):
if (!opt.EnableCamera)
break;
switch (value.Trim()) // everything untested
{
case "normal":
Events.Add(new Event.TiltMode(time, Event.TiltType.Normal));
break;
case "bigger":
Events.Add(new Event.TiltMode(time, Event.TiltType.Large));
break;
case "keep_bigger":
Events.Add(new Event.TiltMode(time, Event.TiltType.Incremental));
break;
}
break;
case "tilt": // Tilt
case "zoom_top": // CAM_RotX
case "zoom_bottom": // CAM_Radi
case "lane_toggle": // LaneY
if (!opt.EnableCamera)
break;
if (!float.TryParse(value, out float cameraOffset))
break;
Camera.WorkType work = Camera.WorkType.None;
switch (prop)
{
case "zoom_top":
work = Camera.WorkType.Rotation;
cameraOffset /= 150.0f;
break;
case "zoom_bottom":
work = Camera.WorkType.Radian;
cameraOffset /= -150.0f;
break;
case "tilt":
work = Camera.WorkType.Tilt;
cameraOffset /= 1.0f; // untested
break;
case "lane_toggle":
work = Camera.WorkType.LaneClear; // untested too
break;
}
Camera camera = null;
switch (work)
{
case Camera.WorkType.Rotation:
case Camera.WorkType.Radian:
case Camera.WorkType.Tilt:
camera = Camera.Create(work, time, maxDuration, cameraOffset, cameraOffset);
break;
case Camera.WorkType.LaneClear:
camera = new Camera.LaneClear(time, (int)cameraOffset, 0.00f, 1024.00f);
break;
}
if (!cameras.ContainsKey(work))
{
cameras[work] = null;
lastCamera[work] = camera;
Events.Add(camera);
continue;
}
var pairCamera = cameras[work];
if (pairCamera == null)
{
switch (work)
{
case Camera.WorkType.Rotation:
case Camera.WorkType.Radian:
case Camera.WorkType.Tilt:
cameras[work] = Camera.Create(work, time, -1, cameraOffset, cameraOffset);
break;
case Camera.WorkType.LaneClear:
var ev = new Camera.LaneClear(time, (int)cameraOffset, 1024.00f, 0.00f);
cameras[work] = ev;
Events.Add(ev);
break;
}
}
else
{
// Can't really use Time.Difference since time signature can be different across different measures
float curOffset = time.GetAbsoluteOffset(signature);
float pairOffset = pairCamera.Time.GetAbsoluteOffset(Events.GetTimeSignature(pairCamera.Time));
if (work == Camera.WorkType.LaneClear)
{
camera.Start = pairCamera.End;
camera.End = pairCamera.Start;
if ((int)Math.Abs(curOffset - pairOffset) != pairCamera.Duration)
{
var fill = new Camera.LaneClear(null, 0, pairCamera.End, camera.Start);
var fillOffset = pairOffset + pairCamera.Duration;
var timeSig = Events.GetTimeSignature((int)(fillOffset / 192f));
fill.Time = Time.FromOffset(fillOffset, timeSig);
fill.Duration = (int)Math.Abs(curOffset - fill.Time.GetAbsoluteOffset(timeSig));
Events.Add(fill);
}
cameras[work] = camera;
Events.Add(camera);
break;
}
// Assign camera properties
camera.Time = pairCamera.Time;
camera.Duration = (int)Math.Abs(curOffset - pairOffset);
camera.Start = pairCamera.Start;
// Find gap between last camera event
var prevCamera = lastCamera[work];
float lastOffset = prevCamera.Time.GetAbsoluteOffset(Events.GetTimeSignature(prevCamera.Time));
int diff = (int)Math.Abs(pairOffset - lastOffset);
// There's seems to be gap
if (prevCamera != null && prevCamera.Duration != diff)
{
// Check gap, is indeed something that we can fill
if (prevCamera.Duration < diff)
{
// Adjust time and duration to fill the gap, make sure to use correct time signature!
var fill = Camera.Create(work, time, 0, prevCamera.End, pairCamera.Start);
var fillOffset = lastOffset + prevCamera.Duration;
var timeSig = Events.GetTimeSignature((int)(fillOffset / 192f));
fill.Time = Time.FromOffset(fillOffset, timeSig);
fill.Duration = (int)Math.Abs(pairOffset - fill.Time.GetAbsoluteOffset(timeSig));
Events.Add(fill);
}
// The previous duration is overlap with current time, readjust previous event duration
else
{
prevCamera.Duration = diff;
prevCamera.End = camera.Start;
}
}
cameras[work] = null;
lastCamera[work] = camera;
Events.Add(camera);
}
break;
#endregion
}
}
else if (line == "--")
{
// Reset time counter
measure += 1;
offset = 0;
noteCount = 0;
// Reset laser range, unless there's active laser
if (!lasers.ContainsKey(Event.LaserTrack.Left))
rangeLeft = 1;
if (!lasers.ContainsKey(Event.LaserTrack.Right))
rangeRight = 1;
float position = measure * 192f;
if (measure > 1 && opt.RealignOffset)
position -= MusicOffset;
time = Time.FromOffset(position, signature);
for (int j = i + 1; j < lines.Length; j++)
{
string ln = lines[j];
if (char.IsDigit(ln[0]))
noteCount++;
else if (ln == "--")
break;
}
}
else if (char.IsDigit(line[0]))
{
// Increment offset when current line is event
offset++;
#region --- BT & FX ---
for (int channel = 0; channel < 7; channel++)
{
if (!opt.EnableButtonTrack)
break;
Event.ButtonTrack track;
switch(channel)
{
case 0: track = Event.ButtonTrack.A; break;
case 1: track = Event.ButtonTrack.B; break;
case 2: track = Event.ButtonTrack.C; break;
case 3: track = Event.ButtonTrack.D; break;
case 5: track = Event.ButtonTrack.FxL; break;
case 6: track = Event.ButtonTrack.FxR; break;
default: continue;
}
// FxL and FxR accept any char for long note
if (!int.TryParse(line[channel].ToString(), out int flag) && (track != Event.ButtonTrack.FxL && track != Event.ButtonTrack.FxR))
continue;
// Who's right in the mind to split fx and bt event logic just because the flag is opposite? lol
bool isFx = track == Event.ButtonTrack.FxL || track == Event.ButtonTrack.FxR;
bool isChip = ((isFx && flag == 2) || (!isFx && flag == 1)) && flag != 0;
bool isHold = ((isFx && flag != 2) || (!isFx && flag == 2)) && flag != 0;
if (isChip)
{
var hit = Event.ChipFx.None;
if (isFx)
{
if (!hitFx.ContainsKey(track))
hitFx[track] = new KshSoundEffect();
var fx = hitFx[track];
hit = fx.HitCount > fx.Used ? fx.Effect : Event.ChipFx.None;
fx.Used++;
}
// Overlapping long notes
if (longNotes.ContainsKey(track))
{
var ev = longNotes[track];
var timeSig = Events.GetTimeSignature(ev.Time.Measure);
ev.HoldLength = time.Difference(ev.Time, timeSig);
Events.Add(ev);
}
longNotes.Remove(track);
Events.Add(new Event.Button(time, track, 0, null, hit));
}
else if (isHold)
{
if (!longNotes.ContainsKey(track))
{
Effect fx = null;
if (isFx)
{
if (!holdFx.ContainsKey(track))
holdFx[track] = null;
if (holdFx[track] != null && holdFx[track].Type != FxType.None)
fx = holdFx[track];
}
longNotes[track] = new Event.Button(time, track, 0, fx, Event.ChipFx.None);
}
else
{
var ev = longNotes[track];
ev.HoldLength = Math.Abs(time.Difference(ev.Time, signature));
}
}
else
{
// Empty event (Event flag: 0)
if (longNotes.ContainsKey(track))
{
var ev = longNotes[track];
var timeSig = Events.GetTimeSignature(ev.Time.Measure);
ev.HoldLength = time.Difference(ev.Time, timeSig);
Events.Add(longNotes[track]);
}
longNotes.Remove(track);
}
}
#endregion
#region --- Laser ---
foreach (var track in new[] { Event.LaserTrack.Left, Event.LaserTrack.Right })
{
if (!opt.EnableLaserTrack)
break;
int channel = track == Event.LaserTrack.Left ? 8 : 9;
int range = track == Event.LaserTrack.Left ? rangeLeft : rangeRight;
char flag = line[channel];
var impact = Event.SlamImpact.None;
// Ignore tick
if (flag == ':')
continue;
if (line.Length >= 13 && opt.EnableSlamImpact)
{
// Ignore direction, use slam offset instead
char f = line[10];
if (f == '@')
{
// Map the impact types
char spin = line[11];
if (spin == '(' || spin == ')')
impact = Event.SlamImpact.Measure;
else if (spin == '<' || spin == '>')
impact = Event.SlamImpact.HalfMeasure;
// Try to realign with impact length, seriously, why would impact has length in kshoot?
if (int.TryParse(line.Substring(12), out int length))
{
// This mapping may incorrect.. and stupid
if ((impact == Event.SlamImpact.Measure || impact == Event.SlamImpact.HalfMeasure) && length < 96)
impact = Event.SlamImpact.Swing;
else if (impact == Event.SlamImpact.Measure && length <= 144)
impact = Event.SlamImpact.ThreeBeat;
}
}
else if (f == 'S')
impact = Event.SlamImpact.Swing;
}
var laser = new Event.Laser(
time,
track,
0,
Event.LaserFlag.Start,
impact,
filter,
range
);
laser.Slam = noteCount >= 32;
if (flag != '-')
{
// Determine laser offset
for (int x = 0; x < VolPositions.Length; x++)
{
if (VolPositions[x] == flag)
laser.Offset = (int)((x / 51f) * 127f);
}
if (lasers.ContainsKey(track))
laser.Flag = Event.LaserFlag.Tick;
else
lasers[track] = new List<Event.Laser>();
lasers[track].Add(laser);
}
else if (lasers.ContainsKey(track))
{
var nodes = lasers[track];
lasers.Remove(track);
// There suppose to be 2 point of laser, no?
if (nodes.Count < 2)
continue;
/** Slam Laser
* Every lasers inside 1/32 that 6 cells apart or less are slam in kshoot
* However, Vox may produce bug when slam defined more than 2 laser events within certain amount of distance
* It seems the threshold determined based on current active bpm, the exact formula is still unclear
* Therefore, eliminate the duplicate / unnecessary ticks
*
* For example, this code will transform following events:
*
* 001,01,00 25 1 0 0 2 0
* 001,01,00 95 0 0 0 2 0 // -> Unnecessary event, since the next event is the same offset
* 001,01,06 95 0 0 0 2 0 // -> This cause bug because same offset and located within distance threshold
* 002,01,00 95 0 0 0 2 0
* 002,01,06 50 2 0 0 2 0
*
* To this:
*
* 001,01,00 25 1 0 0 2 0
* 001,01,06 95 0 0 0 2 0 // -> Keep 001,01,06 and throw one of the duplicate
* 002,01,00 95 0 0 0 2 0
* 002,01,00 50 2 0 0 2 0
*
*/
int counter = 0;
Event.Laser last = null;
foreach (var node in nodes.ToArray())
{
if (last == null)
{
last = node;
continue;
}
if (node.Offset == last.Offset)
counter++;
else
counter = 0;
if (counter >= 2)
nodes.Remove(last);
last = node;
}
// TODO: As inefficient as it gets lol, merge into previous loop if possible
for (int n = 0; n < nodes.Count; n++)
{
if (n + 1 >= nodes.Count)
break;
var start = nodes[n];
var end = nodes[n + 1];
var timeSig = Events.GetTimeSignature(start.Time);
// Due to terrible ksh format for slam that appear in 1/32 or shorter
// the output of conversion may have 6 cells gap apart each other
// TODO: Use offset instead of Time.Difference, cuz it might start and finish in different measure
if (start.Slam && end.Slam && end.Time.Difference(start.Time, timeSig) <= 6)
{
// Pull one of the offset
// In some rare cases, not pulling the offset will ended up normal laser instead of slam, especially in slow bpm
int min = Math.Min(start.Time.Offset, end.Time.Offset);
start.Time.Offset = end.Time.Offset = min;
n += 1;
}
}
// Don't forget to set the flag
nodes[0].Flag = Event.LaserFlag.Start;
last.Flag = Event.LaserFlag.End;
// TODO: Reset laser range when it's no longer active in current measure(?)
Events.Add(nodes.ToArray());
}
}
#endregion
}
}
}
}
}

View File

@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VoxCharger
{
public class KshDefinition
{
private Dictionary<string, string> definitions;
public bool IsNormalized { get; set; } = true;
public string Value { get; private set; }
public KshDefinition(string data)
{
definitions = new Dictionary<string, string>();
foreach (string p in data.Split(';'))
{
var prop = p.Split('=');
if (prop.Length != 2)
{
if (Value != null)
Value = prop[1].Trim();
continue;
}
definitions.Add(
prop[0].Trim(),
prop[1].Trim()
);
}
}
public bool GetString(string pname, out string result)
{
result = string.Empty;
if (definitions.ContainsKey(pname))
{
result = definitions[pname];
return true;
}
return false;
}
public bool GetValue(string pname, out int result)
{
result = 0;
if (definitions.ContainsKey(pname))
{
string str = GetDominantValue(definitions[pname]);
if (str.Contains('/'))
return GetFraction(pname, out result);
else if (str.Contains('%'))
return GetPercentage(pname, out result);
else if (!char.IsDigit(str.Last()))
str = new string(str.Select(c => char.IsDigit(c) ? c : '\0').ToArray());
return int.TryParse(str.Trim(), out result);
}
return false;
}
public bool GetValue(string pname, out float result)
{
result = 0;
if (definitions.ContainsKey(pname))
{
string str = GetDominantValue(definitions[pname]);
if (str.Contains('/'))
return GetFraction(pname, out result);
else if (str.Contains('%'))
return GetPercentage(pname, out result);
else if (!char.IsDigit(str.Last()))
str = new string(str.Select(c => char.IsDigit(c) ? c : '\0').ToArray());
return float.TryParse(str.Trim(), out result);
}
return false;
}
public bool GetFraction(string pname, out int result)
{
result = 0;
if (GetFraction(pname, out int numerator, out int denominator))
{
result = IsNormalized ? numerator * denominator : numerator / denominator;
return true;
}
return false;
}
public bool GetFraction(string pname, out float result)
{
result = 0;
if (GetFraction(pname, out float numerator, out float denominator))
{
result = IsNormalized ? numerator * denominator : numerator / denominator;
return true;
}
return false;
}
public bool GetFraction(string pname, out int numerator, out int denominator)
{
denominator = numerator = 0;
if (definitions.ContainsKey(pname))
{
var data = definitions[pname].Split('/');
return data.Length == 2 && int.TryParse(data[0], out numerator) && int.TryParse(data[1], out denominator);
}
return false;
}
public bool GetFraction(string pname, out float numerator, out float denominator)
{
denominator = numerator = 0f;
if (definitions.ContainsKey(pname))
{
var data = definitions[pname].Split('/');
return data.Length == 2 && float.TryParse(data[0], out numerator) && float.TryParse(data[1], out denominator);
}
return false;
}
public bool GetPercentage(string pname, out int result)
{
result = 0;
if (definitions.ContainsKey(pname))
{
string str = definitions[pname];
if (float.TryParse(str.Replace("%", string.Empty), out float percentage))
{
result = IsNormalized ? (int)(percentage / 100f) : (int)percentage;
return true;
}
}
return false;
}
public bool GetPercentage(string pname, out float result)
{
result = 0f;
if (definitions.ContainsKey(pname))
{
string str = definitions[pname];
if (float.TryParse(str.Replace("%", string.Empty), out result))
{
result = IsNormalized ? result / 100f : result;
return true;
}
}
return false;
}
private string GetDominantValue(string data)
{
foreach (var separator in new char[] { '-', '>' })
{
if (data.Contains(separator))
{
var values = data.Split('-');
if (values.Length < 2)
continue;
return values[0];
}
}
return data;
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VoxCharger
{
public partial class Ksh
{
public class KshSoundEffect
{
public Event.ButtonTrack Track { get; set; }
public Event.ChipFx Effect { get; set; } = Event.ChipFx.None;
public int HitCount { get; set; }
public int Used { get; set; }
}
}
}

12
Sources/Vox/Difficulty.cs Normal file
View File

@ -0,0 +1,12 @@
using System;
namespace VoxCharger
{
public enum Difficulty
{
Novice = 1,
Advanced = 2,
Exhaust = 3,
Infinite = 4
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace VoxCharger
{
public enum GameVersion
{
Booth = 1,
InfiniteInfection = 2,
GravityWars = 3,
HeavenlyHaven = 4,
VividWave = 5
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VoxCharger
{
public enum InfiniteVersion
{
MXM = 0,
INF = 2,
GRV = 3,
HVN = 4,
VVD = 5
}
}

215
Sources/Vox/MusicDb.cs Normal file
View File

@ -0,0 +1,215 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Xml;
using System.Globalization;
namespace VoxCharger
{
public class MusicDb : IEnumerable<VoxHeader>
{
private const string DummyYomigana = "ダミー";
private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("Shift_JIS");
private Dictionary<int, VoxHeader> headers;
public int LastID => headers.Count > 0 ? headers.Values.Max(h => h.ID) : 0;
public int Count => headers.Count;
public MusicDb()
{
headers = new Dictionary<int, VoxHeader>();
}
public void Load(string path, bool append = false)
{
headers = append ? headers : new Dictionary<int, VoxHeader>();
var doc = new XmlDocument();
using (var reader = new StreamReader(path, DefaultEncoding))
{
// Load via reader to ensure correct encoding is used
doc.Load(reader);
}
foreach (XmlNode music in doc.SelectSingleNode("mdb"))
{
int id = int.Parse(music.Attributes["id"].Value);
if (append && headers.ContainsKey(id))
continue;
var info = music.SelectSingleNode("info");
var header = new VoxHeader
{
ID = id,
Title = info.SelectSingleNode("title_name").InnerText,
Artist = info.SelectSingleNode("artist_name").InnerText,
Ascii = info.SelectSingleNode("ascii").InnerText,
DistributionDate = DateTime.ParseExact(info.SelectSingleNode("distribution_date").InnerText, "yyyyMMdd", CultureInfo.InvariantCulture),
Volume = short.Parse(info.SelectSingleNode("volume").InnerText),
BackgroundId = short.Parse(info.SelectSingleNode("bg_no").InnerText),
GenreId = int.Parse(info.SelectSingleNode("genre").InnerText),
Version = (GameVersion)int.Parse(info.SelectSingleNode("version").InnerText),
InfVersion = (InfiniteVersion)int.Parse(info.SelectSingleNode("inf_ver").InnerText),
};
string bpmMax = info.SelectSingleNode("bpm_max").InnerText;
header.BpmMax = double.Parse($"{bpmMax.Substring(0, bpmMax.Length - 2)}.{bpmMax.Substring(bpmMax.Length - 2)}");
string bpmMin = info.SelectSingleNode("bpm_min").InnerText;
header.BpmMin = double.Parse($"{bpmMin.Substring(0, bpmMin.Length - 2)}.{bpmMin.Substring(bpmMin.Length - 2)}");
header.Levels = new Dictionary<Difficulty, VoxLevelHeader>();
foreach (XmlNode diffInfo in music.SelectSingleNode("difficulty"))
{
Difficulty current;
if (diffInfo.Name == "novice")
current = Difficulty.Novice;
else if (diffInfo.Name == "advanced")
current = Difficulty.Advanced;
else if (diffInfo.Name == "exhaust")
current = Difficulty.Exhaust;
else if (diffInfo.Name == "infinite" || diffInfo.Name == "maximum")
current = Difficulty.Infinite;
else
continue; // Unknown difficulty,
var lvHeader = new VoxLevelHeader
{
Difficulty = current,
Level = int.Parse(diffInfo.SelectSingleNode("difnum").InnerText),
Effector = diffInfo.SelectSingleNode("effected_by").InnerText,
Illustrator = diffInfo.SelectSingleNode("illustrator").InnerText,
Limited = int.Parse(diffInfo.SelectSingleNode("limited").InnerText),
Price = int.Parse(diffInfo.SelectSingleNode("price").InnerText),
};
if (lvHeader.Level <= 0)
continue; // Invalid level?
header.Levels[current] = lvHeader;
}
Add(header);
}
}
public void Save(string filename)
{
var doc = new XmlDocument();
var master = doc.CreateElement("mdb");
var CreateMetaElement = new Func<XmlDocument, string, string, string, XmlElement>((d, n, v, t) =>
{
var prop = doc.CreateElement(n);
var value = doc.CreateTextNode(v);
prop.AppendChild(value);
if (!string.IsNullOrEmpty(t))
prop.SetAttribute("__type", t);
return prop;
});
foreach (var header in headers.Values)
{
if (header == null)
continue;
var music = doc.CreateElement("music");
music.SetAttribute("id", header.ID.ToString());
var info = doc.CreateElement("info");
info.AppendChild(CreateMetaElement(doc, "label", header.ID.ToString(), string.Empty));
info.AppendChild(CreateMetaElement(doc, "title_name", header.Title, string.Empty));
info.AppendChild(CreateMetaElement(doc, "title_yomigana", DummyYomigana, string.Empty));
info.AppendChild(CreateMetaElement(doc, "artist_name", header.Artist, string.Empty));
info.AppendChild(CreateMetaElement(doc, "artist_yomigana", DummyYomigana, string.Empty));
info.AppendChild(CreateMetaElement(doc, "ascii", header.Ascii, string.Empty));
info.AppendChild(CreateMetaElement(doc, "bpm_max", header.BpmMax.ToString("000.00").Replace(".", string.Empty), "u32"));
info.AppendChild(CreateMetaElement(doc, "bpm_min", header.BpmMin.ToString("000.00").Replace(".", string.Empty), "u32"));
info.AppendChild(CreateMetaElement(doc, "distribution_date", header.DistributionDate.ToString("yyyyMMdd"), "u32"));
info.AppendChild(CreateMetaElement(doc, "volume", header.Volume.ToString(), "u16"));
info.AppendChild(CreateMetaElement(doc, "bg_no", header.BackgroundId.ToString(), "u16"));
info.AppendChild(CreateMetaElement(doc, "genre", header.GenreId.ToString(), "u8"));
info.AppendChild(CreateMetaElement(doc, "is_fixed", "1", "u8"));
info.AppendChild(CreateMetaElement(doc, "version", ((int)header.Version).ToString(), "u8"));
info.AppendChild(CreateMetaElement(doc, "demo_pri", "0", "s8"));
info.AppendChild(CreateMetaElement(doc, "inf_ver", ((int)header.InfVersion).ToString(), "u8"));
var diffInfo = doc.CreateElement("difficulty");
foreach (Difficulty difficulty in Enum.GetValues(typeof(Difficulty)))
{
string diffName = Enum.GetName(typeof(Difficulty), difficulty).ToLower();
if (difficulty == Difficulty.Infinite && header.InfVersion == InfiniteVersion.MXM)
diffName = "maximum";
var detail = doc.CreateElement(diffName);
var lvHeader = new VoxLevelHeader() { Level = 0 };
if (header.Levels.ContainsKey(difficulty))
lvHeader = header.Levels[difficulty];
detail.AppendChild(CreateMetaElement(doc, "difnum", lvHeader.Level.ToString(), "u8"));
detail.AppendChild(CreateMetaElement(doc, "illustrator", lvHeader.Illustrator, string.Empty));
detail.AppendChild(CreateMetaElement(doc, "effected_by", lvHeader.Effector, string.Empty));
detail.AppendChild(CreateMetaElement(doc, "price", lvHeader.Price.ToString(), "s32"));
detail.AppendChild(CreateMetaElement(doc, "limited", lvHeader.Limited.ToString(), "u8"));
diffInfo.AppendChild(detail);
}
music.AppendChild(info);
music.AppendChild(diffInfo);
master.AppendChild(music);
}
var settings = new XmlWriterSettings { Indent = true };
using (var output = new StreamWriter(filename, false, DefaultEncoding))
using (var writer = XmlWriter.Create(output, settings))
{
doc.AppendChild(master);
doc.Save(writer);
}
}
public void Add(VoxHeader header)
{
headers[header.ID] = header;
}
public void Remove(int id)
{
headers.Remove(id);
}
public void Remove(VoxHeader header)
{
headers.Remove(header.ID);
}
public VoxHeader GetHeader(int id)
{
return headers[id];
}
public void Clear()
{
// Clear the list but not the last id
headers.Clear();
}
public IEnumerator<VoxHeader> GetEnumerator()
{
return headers.Values.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return headers.Values.GetEnumerator();
}
}
}

45
Sources/Vox/Section.cs Normal file
View File

@ -0,0 +1,45 @@
using System;
namespace VoxCharger
{
public partial class VoxChart
{
public enum Section
{
NO_STATE = -1,
FORMAT_VERSION = 0,
BEAT_INFO = 1,
BPM_INFO = 2,
TILT = 3,
LYRIC = 4,
END_POSITION = 5,
TAB_EFFECT = 6,
FXBUTTON_EFFECT = 7,
TAB_PARAM = 8,
REVERB = 9,
TRACK1 = 10,
TRACK2 = 11,
TRACK3 = 12,
TRACK4 = 13,
TRACK5 = 14,
TRACK6 = 15,
TRACK7 = 16,
TRACK8 = 17,
TRACK_AUTO = 18,
SPCONTROLER = 19,
SOUND_ID = 20,
BPM = 21
}
public static bool IsTrackSection(Section section)
{
int value = (int)section;
return value >= (int)Section.TRACK1 && value <= (int)Section.TRACK_AUTO;
}
public static int GetTrackNumber(Section section)
{
return IsTrackSection(section) ? ((int)section + 1) - (int)Section.TRACK1 : -1;
}
}
}

692
Sources/Vox/VoxChart.cs Normal file
View File

@ -0,0 +1,692 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
namespace VoxCharger
{
public partial class VoxChart
{
private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("shift_jis");
private const string VolCodes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmno";
private readonly string[] Filters = new string[] { "peak", "lpf1", "lpf1", "hpf1", "hpf1", "lbic", "nof" };
public int Version { get; private set; } = 10;
public string Lyric { get; private set; }
public EventCollection Events { get; private set; } = new EventCollection();
public List<Effect> Effects { get; private set; } = new List<Effect>();
public Time EndPosition { get; private set; }
public VoxChart()
{
}
public void Import(Ksh chart)
{
// Ksh events is already adjusted to be compatible with vox upon parsing
Events = chart.Events;
}
public void Parse(string fileName)
{
var current = Section.NO_STATE;
var lines = File.ReadAllLines(fileName, DefaultEncoding);
Event.Stop stop = null;
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i].Trim().Trim('\t');
if (string.IsNullOrEmpty(line) || line == "//")
continue;
// Identify current section
if (line.StartsWith("#"))
{
if (current == Section.NO_STATE)
{
var candidate = Section.NO_STATE;
string header = line.Substring(1).Trim().Replace(' ', '_');
foreach (Section section in Enum.GetValues(typeof(Section)))
{
// e.g there's BPM and BPM_INFO, make sure simple BPM isn't registered as BPM_INFO
string name = Enum.GetName(typeof(Section), section);
if (header == name)
{
current = section;
break;
}
else if (header.StartsWith(name))
candidate = section;
}
if (current == Section.NO_STATE)
current = candidate;
}
else
{
if (line == "#END")
current = Section.NO_STATE;
}
continue;
}
#region --- TIME ---
/**
* Legends:
* mmm = measure
* bbb = beat
* ccc = cell
*/
var data = line.Trim().Split('\t').Select(p => p.Trim()).ToArray();
var time = Time.FromString(data[0]);
#endregion
#region --- FORMAT VERSION ---
if (current == Section.FORMAT_VERSION)
{
/*
* Format Version
* Parse version of Vox
*/
if (int.TryParse(line, out int ver))
Version = ver;
else
Debug.WriteLine($"[FORMAT] {i+1:D4}: Invalid version format");
}
#endregion
#region --- BEAT INFO ---
else if (current == Section.BEAT_INFO)
{
/*
* Beat Info (a.k.a Signature)
* Parse signature changes throughout the music, first signature change may referred as main signature
*
* Format:
* mmm,bbb,ccc b n
*
* * Arguments:
* b = beat
* n = note
*
* Note:
* signature = beat/note (e.g: 1/4)
*/
if (data.Length != 3 || time == null)
{
Debug.WriteLine($"[BEAT_INFO] {i+1:D4}: Invalid event format");
continue;
}
if (!int.TryParse(data[1], out int measure) || !int.TryParse(data[2], out int beat))
{
Debug.WriteLine($"[BEAT_INFO] {i+1:D4}: Invalid signature format");
continue;
}
if (time.Beat != 1 || time.Offset != 0)
{
Debug.WriteLine($"[BEAT_INFO] {i+1:D4}: Invalid Signature change position");
continue;
}
var signature = new Event.TimeSignature(time, measure, beat);
Events.Add(signature);
}
#endregion
#region --- BPM (Simple) ---
else if (current == Section.BPM)
{
/*
* BPM (Simple)
* Parse intial BPM of music
*
* Format:
* ttt.tt
*
* Arguments:
* t = bpm
*
* Note:
* - BPM time offset is always positioned at 1st measure and 1st beat (001,001,000)
* - BPM Format is {000.####}
*/
// TODO: Process certain instructions
if (line.Contains("BAROFF"))
{
Debug.WriteLine($"[BPM_INFO] {i+1:D4}: \"BAROFF\" instruction skipped");
continue;
}
if (!float.TryParse(line, out float value))
Debug.WriteLine($"[BPM] {i:D4}: Invalid BPM value");
else
Events.Add(new Event.BPM(new Time(1, 1, 0), value));
}
#endregion
#region --- BPM (Extended) ---
else if (current == Section.BPM_INFO)
{
/*
* BPM (Extended)
* Parse intial BPM of music
*
* Format:
* mmm,bbb,ccc ttt.tt s(-)
*
* Arguments:
* t = bpm
* s = signature
*
* Note:
* - signature can be ended with minus sign (-) which indicate bpm stop
* - stop event duration can be obtained by calculating difference of time offset of the next bpm event occurence
* - signature most of the time is always 4
* - BPM Format is {000.####}
*/
if (data.Length != 3 || time == null)
{
Debug.WriteLine($"[BPM_INFO] {i+1:D4}: Invalid event format");
continue;
}
if (data[1] == "BAROFF")
{
Debug.WriteLine($"[BPM_INFO] {i+1:D4}: \"BAR\" instruction skipped"); // TODO: Process certain instructions
continue;
}
if (!float.TryParse(data[1], out float value))
{
Debug.WriteLine($"[BPM_INFO] {i+1:D4}: Invalid BPM value");
continue;
}
if (!int.TryParse(data[2].Replace("-", string.Empty), out int beat))
Debug.WriteLine($"[BPM_INFO] {i+1:D4}: Invalid beat division");
if (beat != 4)
Debug.WriteLine($"[BPM_INFO] {i+1:D4}: Non-4 beat division ({beat})");
// Handle stop event
if (data[2].EndsWith("-"))
{
if (stop != null)
Debug.WriteLine($"[BPM_INFO] {i+1:D4}: Duplicate stop event");
stop = new Event.Stop(time);
}
else
{
// Handle stop event if previously assigned
if (stop != null)
{
// Retrieve last signature for current measure
var signature = Events.GetTimeSignature(stop.Time);
if (signature == null)
{
Debug.WriteLine($"[BPM_INFO] {i+1:D4}: No valid signature for stop event");
stop = null;
continue;
}
stop.Duration = time.Difference(stop.Time, signature);
Events.Add(stop);
stop = null;
}
}
Events.Add(new Event.BPM(time, value));
}
#endregion
#region --- TILT MODE INFO ---
else if (current == Section.TILT)
{
/*
* Tilt Mode
* Parse camera tilt event
*
* Format:
* mmm,bbb,ccc m
*
* Arguments:
* m = mode
*
* Note:
* Refer to TiltMode for value mapping
*/
if (data.Length != 2 && time == null)
{
Debug.WriteLine($"[TILT_MODE_INFO] {i+1:D4}: Invalid event format");
continue;
}
if (!int.TryParse(data[1], out int tilt))
{
Debug.WriteLine($"[TILT_MODE_INFO] {i+1:D4}: Invalid tilt code format");
continue;
}
if (!Enum.IsDefined(typeof(Event.TiltType), tilt))
{
Debug.WriteLine($"[TILT_MODE_INFO] {i+1:D4}: Invalid tilt mode value");
continue;
}
Events.Add(new Event.TiltMode(time, (Event.TiltType)tilt));
}
#endregion
#region --- LYRIC INFO ---
else if (current == Section.LYRIC)
{
/*
* Lyric Info
* Parse Lyric information
*
* Format:
* ???
*
* Note:
* Assumed has no format and lyric under plain text
*/
if (!string.IsNullOrEmpty(line))
Lyric += line + '\n';
}
#endregion
#region --- END POSITION ---
else if (current == Section.END_POSITION)
{
/*
* End Position
* Parse end position; the last measure of music
*
* Format:
* mmm,bbb,ccc
*/
if (time == null)
Debug.WriteLine($"[END_POSITION] {i+1:D4}: Invalid event format");
else
EndPosition = time;
}
#endregion
#region --- TAB EFFECT INFO ---
else if (current == Section.TAB_EFFECT)
{
/*
* Tab Effect Info
* Parse Tab Effect info
*
* Format:
* ?, ???.??, ???.??, ??????.??, ?.??
*/
Debug.WriteLine($"[TAB_EFFECT] {i+1:D4}: {line}");
}
#endregion
#region --- FXBUTTON EFFECT INFO ---
else if (current == Section.FXBUTTON_EFFECT)
{
/*
* FxButton Effect Info
* Parse FxButton Effect Mapping
*
* Format:
* Depends on Fx, check implementation class for details
*/
if (line.StartsWith("0"))
continue; // Skip padding
var fx = Effect.FromVox(line);
if (fx == null || fx.Type == FxType.None)
Debug.WriteLine($"[FXBUTTON_EFFECT] {i+1:D4}: Invalid FX data");
else
{
fx.Id = Effects.Count;
Effects.Add(fx);
}
}
#endregion
#region --- TAB PARAM ASSIGN INFO ---
else if (current == Section.TAB_PARAM)
{
/*
* Tab Param Info
* Parse Tab Param Assign Info
*
* Format:
* ?, ?, ?.??, ?.??
*/
Debug.WriteLine($"[TAB_PARAM] {i+1:D4}: {line}");
}
#endregion
#region --- REVERB EFFECT PARAM ---
else if (current == Section.REVERB)
{
/*
* Reverb Effect
* Parse Reverb Effect
*/
Debug.WriteLine($"[REVERB_EFFECT] {i+1:D4}: {line}");
}
#endregion
#region --- SPCONTROLLER INFO ---
else if (current == Section.SPCONTROLER)
{
/*
* SP Controller Info
* Parse camera works
*
* Format:
* Depends on type, refer to Camera
*/
Debug.WriteLine($"[SPCONTROLLER_INFO] {i+1:D4}: {line}");
}
#endregion
#region --- TRACK ---
else if (IsTrackSection(current))
{
int trackId = GetTrackNumber(current);
if (trackId <= 0)
{
Debug.WriteLine($"[TRACK] {i+1:D4}: Invalid track id");
continue;
}
// Laser Channel
if (trackId == (int)Event.LaserTrack.Left || trackId == (int)Event.LaserTrack.Right)
{
/*
* Track - Laser
* Parse Laser Info
*
* Format:
* mmm,bbb,ccc o f t x r ?
*
* Arguments:
* o = Offset
* f = Flag
* t = Tick Tempo
* x = Filter
* r = Range
*
* Note:
* Slam defined as 2 laser events with same time and side, the offset of start laser must be less than offset of end laser
*/
if (data.Length < 5 || time == null)
{
Debug.WriteLine($"[TRACK_{trackId}] {i+1:D4}: Invalid event format");
continue;
}
if (!int.TryParse(data[1], out int offset))
{
Debug.WriteLine($"[TRACK_{trackId}] {i+1:D4}: Invalid laser offset");
continue;
}
if (!Enum.TryParse(data[2], out Event.LaserFlag flag))
{
Debug.WriteLine($"[TRACK_{trackId}] {i+1:D4}: Invalid laser flag");
continue;
}
var impact = Event.SlamImpact.None;
if (data.Length > 5)
{
if (!Enum.TryParse(data[3], out impact))
Debug.WriteLine($"[TRACK_{trackId}] {i + 1:D4}: Invalid laser tick tempo");
}
var laser = new Event.Laser(time, (Event.LaserTrack)trackId, offset, flag, impact);
string filterStr = data.Length > 5 ? data[4] : data[3];
string rangeStr = data.Length > 5 ? data[5] : data[4];
if (int.TryParse(filterStr, out int filterId))
{
switch (filterId)
{
case 1:
case 2: laser.Filter = Event.LaserFilter.LowPass; break;
case 3:
case 4: laser.Filter = Event.LaserFilter.HighPass; break;
case 5: laser.Filter = Event.LaserFilter.BitCrusher; break;
default: laser.Filter = Event.LaserFilter.Peak; break;
}
}
else
Debug.WriteLine($"[TRACK_{trackId}] {i + 1:D4}: Invalid laser filter");
if (!int.TryParse(rangeStr, out int range))
Debug.WriteLine($"[TRACK_{trackId}] {i + 1:D4}: Invalid laser range");
else
laser.Range = range;
// Locate slam
var slam = Events[time].FirstOrDefault(ev =>
(ev is Event.Laser l && l.Track == laser.Track) ||
(ev is Event.Slam s && s.Track == laser.Track)
);
if (slam != null)
{
// Duplicate slam, handle gracefully
if (slam is Event.Slam s)
slam = s.End;
var start = slam as Event.Laser;
var end = laser;
if (start.Offset == end.Offset)
{
Debug.WriteLine($"[TRACK_{trackId}] {i+1:D4}: Invalid slam offset");
continue;
}
if (start.Track != end.Track)
{
Debug.WriteLine($"[TRACK_{trackId}] {i+1:D4}: Invalid slam track");
continue;
}
Events.Add(new Event.Slam(time, start, end));
}
else
Events.Add(laser);
}
else
{
var track = (Event.ButtonTrack)trackId;
if (!int.TryParse(data[1], out int holdLength))
Debug.WriteLine($"[TRACK_{trackId}] {i+1:D4}: Invalid chip hold length");
var chip = new Event.Button(time, track, holdLength, null);
if (chip.IsFx)
{
// Hold Fx
if (holdLength > 0)
{
if (int.TryParse(data[2], out int fxIndex) && fxIndex - 2 < Effects.Count)
chip.HoldFx = Effects[fxIndex - 2];
else
Debug.WriteLine($"[TRACK_{trackId}] {i+1:D4}: Invalid chip hold effect index");
}
else
{
// Clap, snare, etc
if (Enum.TryParse(data[2], out Event.ChipFx hitFx))
chip.HitFx = hitFx;
else
Debug.WriteLine($"[TRACK_{trackId}] {i+1:D4}: Invalid chip hit effect value");
}
}
Events.Add(chip);
}
}
#endregion
}
}
public void Serialize(string filename)
{
string result = string.Empty;
result += "//====================================\n";
result += "// SOUND VOLTEX OUTPUT TEXT FILE\n";
result += "//====================================\n\n";
result += "#FORMAT VERSION\n";
result += $"{Version}\n";
result += "#END\n\n";
result += "#BEAT INFO\n";
foreach (var ev in Events)
{
if (ev is Event.TimeSignature signature)
result += signature.ToString() + "\n";
}
result += "#END\n\n";
result += "#BPM INFO\n";
Event.BPM lastBpm = null;
foreach (var ev in Events)
{
if (ev is Event.BPM bpm)
{
lastBpm = bpm;
result += bpm.ToString() + "\n";
}
}
result += "#END\n\n";
result += "#TILT MODE INFO\n";
foreach (var ev in Events)
{
if (ev is Event.TiltMode tilt)
result += tilt.ToString() + "\n";
}
result += "#END\n\n";
result += "#LYRIC INFO\n";
result += Lyric;
result += "#END\n\n";
result += "#END POSITION\n";
if (EndPosition == null)
EndPosition = new Time(Events.Max(ev => ev.Time.Measure) + 1, 1, 0);
result += EndPosition + "\n";
result += "#END\n\n";
result += "#TAB EFFECT INFO\n";
result += "1,\t90.00,\t400.00,\t18000.00,\t0.70\n";
result += "1,\t90.00,\t600.00,\t15000.00,\t5.00\n";
result += "2,\t50.00,\t40.00,\t5000.00,\t0.70\n";
result += "2,\t90.00,\t40.00,\t2000.00,\t3.00\n";
result += "3,\t100.00,\t30\n";
result += "#END\n\n";
var fxInfo = new List<Effect>();
var padding = new Effect();
foreach (var ev in Events)
{
if (ev is Event.Button bt && bt.HoldFx != null && fxInfo.Find(f => f.Id == bt.HoldFx.Id) == null)
fxInfo.Add(bt.HoldFx);
}
result += "#FXBUTTON EFFECT INFO\n";
fxInfo.Sort((a, b) => a.Id.CompareTo(b.Id));
foreach (var fx in fxInfo)
{
result += fx.ToString() + "\n";
result += padding.ToString() + "\n\n";
}
result += "#END\n\n";
result += "#TAB PARAM ASSIGN INFO\n";
for (int i = 0; i < 12; i++)
{
for (int j = 0; j < 2; j++)
result += $"{i},\t0,\t0.00,\t0.00\n";
}
result += "#END\n\n";
result += "#REVERB EFFECT PARAM\n";
result += "#END\n\n";
result += "//====================================\n";
result += "// TRACK INFO\n";
result += "//====================================\n\n";
for (int trackId = 1; trackId <= 8; trackId++)
{
result += $"#TRACK{trackId}\n";
if (trackId == (int)Event.LaserTrack.Left || trackId == (int)Event.LaserTrack.Right)
{
var track = (Event.LaserTrack)trackId;
foreach (var ev in Events)
{
if (ev is Event.Laser laser && laser.Track == track)
result += laser.ToString() + "\n";
}
}
else
{
var track = (Event.ButtonTrack)trackId;
foreach (var ev in Events)
{
if (ev is Event.Button button && button.Track == track)
result += button.ToString() + "\n";
}
}
result += "#END\n\n";
result += "//====================================\n\n";
}
result += "#TRACK AUTO TAB\n";
result += "#END\n\n";
result += "//====================================\n\n";
result += "#SPCONTROLER\n";
result += "001,01,00\tRealize\t3\t0\t36.12\t60.12\t110.12\t0.00\n";
result += "001,01,00\tRealize\t4\t0\t0.62\t0.72\t1.03\t0.00\n";
result += "001,01,00\tAIRL_ScaX\t1\t0\t0.00\t1.00\t0.00\t0.00\n";
result += "001,01,00\tAIRR_ScaX\t1\t0\t0.00\t2.00\t0.00\t0.00\n";
foreach (Camera.WorkType work in Enum.GetValues(typeof(Camera.WorkType)))
{
foreach (var ev in Events)
{
if (ev is Camera camera && camera.Work == work)
result += camera.ToString() + "\n";
}
}
result += "#END\n\n";
result += "//====================================\n\n";
File.WriteAllText(filename, result, DefaultEncoding);
}
}
}

73
Sources/Vox/VoxHeader.cs Normal file
View File

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
namespace VoxCharger
{
public class VoxHeader
{
public int ID { get; set; }
public string Title { get; set; }
public string Artist { get; set; }
public string Ascii { get; set; }
public double BpmMin { get; set; }
public double BpmMax { get; set; }
public DateTime DistributionDate { get; set; }
public short Volume { get; set; }
public short BackgroundId { get; set; }
public int GenreId { get; set; }
public GameVersion Version { get; set; } = GameVersion.VividWave;
public InfiniteVersion InfVersion { get; set; } = InfiniteVersion.MXM;
public Dictionary<Difficulty, VoxLevelHeader> Levels { get; set; }
public string CodeName => $"{ID:D4}_{Ascii}";
public VoxHeader()
{
}
public override string ToString()
{
return FixMappedChars(Title);
}
public static string FixMappedChars(string input)
{
if (string.IsNullOrEmpty(input))
return input;
var map = new Dictionary<string, string>
{
{"\u203E", "~"},
{"\u301C", ""},
{"\u49FA", "ê"},
{"\u5F5C", "ū"},
{"\u66E6", "à"},
{"\u66E9", "è"},
{"\u7F47", "ê"},
{"\u8E94", "🐾"},
{"\u9A2B", "á"},
{"\u9A69", "Ø"},
{"\u9A6B", "ā"},
{"\u9A6A", "ō"},
{"\u9AAD", "ü"},
{"\u9B2F", "ī"},
{"\u9EF7", "ē"},
{"\u9F63", "Ú"},
{"\u9F67", "Ä"},
{"\u973B", "♠"},
{"\u9F6A", "♣"},
{"\u9448", "♦"},
{"\u9F72", "♥"},
{"\u9F76", "♡"},
{"\u9F77", "é"},
{"?壬", "êp"}
};
string result = input;
foreach (var c in map)
result = result.Replace(c.Key, c.Value);
return result;
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Drawing;
namespace VoxCharger
{
public class VoxLevelHeader
{
public Difficulty Difficulty { get; set; }
public int Level { get; set; } = 1;
public string Illustrator { get; set; } = "dummy";
public string Effector { get; set; } = "dummy";
public int Price { get; set; } = -1;
public int Limited { get; set; } = 3;
public VoxChart Chart { get; set; }
public Image Jacket { get; set; }
}
}

210
VoxCharger.csproj Normal file
View File

@ -0,0 +1,210 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<IntermediateOutputPath>.\Temp</IntermediateOutputPath>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B049DC1D-7404-4437-AE8C-1FF6A0A321DC}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>VoxCharger</RootNamespace>
<AssemblyName>VoxCharger</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<IntermediateOutputPath>.\Temp</IntermediateOutputPath>
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>Build\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<IntermediateOutputPath>.\Temp</IntermediateOutputPath>
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>Build\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup />
<PropertyGroup />
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Sources\Forms\MenuButton.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Sources\Forms\ConverterForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Sources\Forms\ConverterForm.Designer.cs">
<DependentUpon>ConverterForm.cs</DependentUpon>
</Compile>
<Compile Include="Sources\Forms\HelpForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Sources\Forms\HelpForm.Designer.cs">
<DependentUpon>HelpForm.cs</DependentUpon>
</Compile>
<Compile Include="Sources\2dx\DxTool.cs" />
<Compile Include="Sources\AssetManager.cs" />
<Compile Include="Sources\Events\Controller\Camera.cs" />
<Compile Include="Sources\Events\Controller\Radian.cs" />
<Compile Include="Sources\Events\Controller\LaneClear.cs" />
<Compile Include="Sources\Events\Controller\Tilt.cs" />
<Compile Include="Sources\Events\Controller\WorkType.cs" />
<Compile Include="Sources\Events\Controller\Rotation.cs" />
<Compile Include="Sources\Effects\BitCrusher.cs" />
<Compile Include="Sources\Effects\Flanger.cs" />
<Compile Include="Sources\Effects\PitchShift.cs" />
<Compile Include="Sources\Effects\LowPass.cs" />
<Compile Include="Sources\Effects\Effect.cs" />
<Compile Include="Sources\Effects\FxType.cs" />
<Compile Include="Sources\Effects\Gate.cs" />
<Compile Include="Sources\Effects\Phaser.cs" />
<Compile Include="Sources\Effects\Retrigger.cs" />
<Compile Include="Sources\Effects\SideChain.cs" />
<Compile Include="Sources\Effects\TapeStop.cs" />
<Compile Include="Sources\Effects\Wobble.cs" />
<Compile Include="Sources\Events\BPM.cs" />
<Compile Include="Sources\Events\Button.cs" />
<Compile Include="Sources\Events\Laser.cs" />
<Compile Include="Sources\Events\Signature.cs" />
<Compile Include="Sources\Events\Stop.cs" />
<Compile Include="Sources\Events\TiltMode.cs" />
<Compile Include="Sources\Ksh\Ksh.cs" />
<Compile Include="Sources\Events\EventCollection.cs" />
<Compile Include="Sources\Events\Event.cs" />
<Compile Include="Sources\Ksh\KshDefinition.cs" />
<Compile Include="Sources\Ksh\KshSoundEffect.cs" />
<Compile Include="Sources\Events\Time.cs" />
<Compile Include="Sources\Vox\GameVersion.cs" />
<Compile Include="Sources\Vox\InfiniteVersion.cs" />
<Compile Include="Sources\Vox\Difficulty.cs" />
<Compile Include="Sources\Vox\MusicDb.cs" />
<Compile Include="Sources\Vox\Section.cs" />
<Compile Include="Sources\Vox\VoxChart.cs" />
<Compile Include="Sources\Vox\VoxLevelHeader.cs" />
<Compile Include="Sources\Forms\AboutForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Sources\Forms\AboutForm.designer.cs">
<DependentUpon>AboutForm.cs</DependentUpon>
</Compile>
<Compile Include="Sources\Forms\LoadingForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Sources\Forms\LoadingForm.designer.cs">
<DependentUpon>LoadingForm.cs</DependentUpon>
</Compile>
<Compile Include="Sources\Forms\MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Sources\Forms\MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Sources\Forms\LevelEditorForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Sources\Forms\LevelEditorForm.Designer.cs">
<DependentUpon>LevelEditorForm.cs</DependentUpon>
</Compile>
<Compile Include="Sources\Forms\MixSelectorForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Sources\Forms\MixSelectorForm.Designer.cs">
<DependentUpon>MixSelectorForm.cs</DependentUpon>
</Compile>
<Compile Include="Sources\Vox\VoxHeader.cs" />
<Compile Include="Sources\Forms\JacketViewerForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Sources\Forms\JacketViewerForm.Designer.cs">
<DependentUpon>JacketViewerForm.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Sources\Forms\AboutForm.resx">
<DependentUpon>AboutForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Sources\Forms\LoadingForm.resx">
<DependentUpon>LoadingForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Sources\Forms\MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Sources\Forms\LevelEditorForm.resx">
<DependentUpon>LevelEditorForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Sources\Forms\MixSelectorForm.resx">
<DependentUpon>MixSelectorForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Sources\Forms\JacketViewerForm.resx">
<DependentUpon>JacketViewerForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Sources\Forms\ConverterForm.resx">
<DependentUpon>ConverterForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Sources\Forms\HelpForm.resx">
<DependentUpon>HelpForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="2dxbuild.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="2dxwavconvert.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="Resources\icon.ico" />
<None Include="Resources\jk_dummy_s.png" />
<None Include="Resources\jk_dummy_b.png" />
<None Include="Resources\jk_dummy.png" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

25
VoxCharger.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29806.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoxCharger", "VoxCharger.csproj", "{B049DC1D-7404-4437-AE8C-1FF6A0A321DC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B049DC1D-7404-4437-AE8C-1FF6A0A321DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B049DC1D-7404-4437-AE8C-1FF6A0A321DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B049DC1D-7404-4437-AE8C-1FF6A0A321DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B049DC1D-7404-4437-AE8C-1FF6A0A321DC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DB7D65B1-75C0-461D-B75E-417DBEBC586A}
EndGlobalSection
EndGlobal