mirror of
https://github.com/SirusDoma/VoxCharger.git
synced 2024-11-28 01:10:49 +01:00
Fix Music ID Handling
Also, Music ID field is now disabled
This commit is contained in:
parent
e3564e690d
commit
2d6eb38dff
@ -12,6 +12,8 @@ namespace VoxCharger
|
|||||||
{
|
{
|
||||||
#region --- Properties ---
|
#region --- Properties ---
|
||||||
private static List<string> MixList = new List<string>();
|
private static List<string> MixList = new List<string>();
|
||||||
|
private static MusicDb InternalHeaders = null;
|
||||||
|
private static int LastOriginalID = 0;
|
||||||
|
|
||||||
public static string MixName { get; private set; }
|
public static string MixName { get; private set; }
|
||||||
|
|
||||||
@ -32,13 +34,6 @@ namespace VoxCharger
|
|||||||
if (!File.Exists(dbFilename))
|
if (!File.Exists(dbFilename))
|
||||||
throw new FormatException("Invalid Game Directory");
|
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
|
// Look for other mixes
|
||||||
MixList.Clear();
|
MixList.Clear();
|
||||||
string modsPath = Path.Combine(gamePath, @"data_mods\");
|
string modsPath = Path.Combine(gamePath, @"data_mods\");
|
||||||
@ -59,10 +54,10 @@ namespace VoxCharger
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Confirmed mod path, append into music db, ignore cache to avoid uncached mix being excluded
|
// Confirmed mod path, append into music db, ignore cache to avoid uncached mix being excluded
|
||||||
Headers.Load(dbFilename, true);
|
|
||||||
MixList.Add(modName);
|
MixList.Add(modName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LastOriginalID = 0;
|
||||||
GamePath = gamePath;
|
GamePath = gamePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +72,9 @@ namespace VoxCharger
|
|||||||
Directory.CreateDirectory(Path.Combine(mixPath, @"music\"));
|
Directory.CreateDirectory(Path.Combine(mixPath, @"music\"));
|
||||||
Directory.CreateDirectory(Path.Combine(mixPath, @"others\"));
|
Directory.CreateDirectory(Path.Combine(mixPath, @"others\"));
|
||||||
|
|
||||||
|
// Load Existing song DB to avoid duplicate id
|
||||||
|
LoadInternalDb(mixName);
|
||||||
|
|
||||||
// Create empty db
|
// Create empty db
|
||||||
MdbFilename = Path.Combine(mixPath, @"others\music_db.merged.xml");
|
MdbFilename = Path.Combine(mixPath, @"others\music_db.merged.xml");
|
||||||
File.WriteAllText(MdbFilename, "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?><mdb></mdb>");
|
File.WriteAllText(MdbFilename, "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?><mdb></mdb>");
|
||||||
@ -93,7 +91,8 @@ namespace VoxCharger
|
|||||||
if (!Directory.Exists(mixPath))
|
if (!Directory.Exists(mixPath))
|
||||||
throw new DirectoryNotFoundException("Mix directory missing");
|
throw new DirectoryNotFoundException("Mix directory missing");
|
||||||
|
|
||||||
// No way it happen since combo box is dropdownlist, but well.. :v
|
// Load Existing song DB to avoid duplicate id
|
||||||
|
LoadInternalDb(mixName);
|
||||||
if (!string.IsNullOrEmpty(mixName) && !MixList.Contains(mixName))
|
if (!string.IsNullOrEmpty(mixName) && !MixList.Contains(mixName))
|
||||||
MixList.Add(mixName);
|
MixList.Add(mixName);
|
||||||
|
|
||||||
@ -110,6 +109,38 @@ namespace VoxCharger
|
|||||||
return MixList.ToArray();
|
return MixList.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void LoadInternalDb(string mixName)
|
||||||
|
{
|
||||||
|
// First, we need to populate available ids outside selected mix
|
||||||
|
// This will prevent duplicate ID not only with originals but with other mixes too
|
||||||
|
|
||||||
|
// Load original music, ignore cache to avoid uncached mix being excluded or included
|
||||||
|
string dbFilename = Path.Combine(GamePath, @"data\others\music_db.xml");
|
||||||
|
string modsPath = Path.Combine(GamePath, @"data_mods\");
|
||||||
|
|
||||||
|
// Load original headers data
|
||||||
|
InternalHeaders = new MusicDb();
|
||||||
|
InternalHeaders.Load(dbFilename);
|
||||||
|
LastOriginalID = InternalHeaders.LastID;
|
||||||
|
|
||||||
|
// Load other music db
|
||||||
|
foreach (var modDir in Directory.GetDirectories(modsPath))
|
||||||
|
{
|
||||||
|
// Get directory name, exclude selected mix
|
||||||
|
string modName = new DirectoryInfo(modDir).Name;
|
||||||
|
if (modName == "_cache" || modName == mixName)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Validate whether the mod is a mix mod
|
||||||
|
// Do not skip unsupported mods, just read the db and ignore the rest of assets
|
||||||
|
dbFilename = Path.Combine(modDir, @"others\music_db.merged.xml");
|
||||||
|
if (!File.Exists(dbFilename))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Confirmed mod path, append into music db
|
||||||
|
InternalHeaders.Load(dbFilename, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region --- Asset Management ---
|
#region --- Asset Management ---
|
||||||
@ -217,6 +248,23 @@ namespace VoxCharger
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region --- Asset Identifier ---
|
#region --- Asset Identifier ---
|
||||||
|
public static int GetNextMusicID()
|
||||||
|
{
|
||||||
|
// TODO: This probably inefficient in scenario where one or more mix has gap between each ids
|
||||||
|
// This could be waste to those gaps, and eat up our precious limited id
|
||||||
|
// However, these gaps may indicate deleted song, which should be taken by omnimix
|
||||||
|
|
||||||
|
int id = InternalHeaders.LastID + 1;
|
||||||
|
while (InternalHeaders.Contains(id) || Headers.Contains(id)) // Contains is O(1) so its should be fine
|
||||||
|
id++;
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ValidateMusicID(int id)
|
||||||
|
{
|
||||||
|
return !InternalHeaders.Contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetDifficultyCodes(VoxHeader header, Difficulty difficulty)
|
public static string GetDifficultyCodes(VoxHeader header, Difficulty difficulty)
|
||||||
{
|
{
|
||||||
|
@ -37,8 +37,9 @@ namespace VoxCharger
|
|||||||
private bool converter;
|
private bool converter;
|
||||||
private Dictionary<Difficulty, ChartInfo> charts = new Dictionary<Difficulty, ChartInfo>();
|
private Dictionary<Difficulty, ChartInfo> charts = new Dictionary<Difficulty, ChartInfo>();
|
||||||
|
|
||||||
public VoxHeader Header { get; private set; } = null;
|
public VoxHeader Header { get; private set; } = null;
|
||||||
public Action Action { get; private set; } = null;
|
public Action Action { get; private set; } = null;
|
||||||
|
public Ksh.ParseOption Options { get; private set; } = new Ksh.ParseOption();
|
||||||
|
|
||||||
public ConverterForm(string path, bool asConverter = false)
|
public ConverterForm(string path, bool asConverter = false)
|
||||||
{
|
{
|
||||||
@ -94,9 +95,9 @@ namespace VoxCharger
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultAscii = AsciiTextBox.Text = Header.Ascii;
|
defaultAscii = AsciiTextBox.Text = Header.Ascii;
|
||||||
BackgroundDropDown.SelectedItem = LastBackground;
|
BackgroundDropDown.SelectedItem = LastBackground;
|
||||||
VersionDropDown.SelectedIndex = 4;
|
VersionDropDown.SelectedIndex = 4;
|
||||||
InfVerDropDown.SelectedIndex = 0;
|
InfVerDropDown.SelectedIndex = 0;
|
||||||
|
|
||||||
charts[main.Difficulty] = new ChartInfo(main, ToLevelHeader(main), target);
|
charts[main.Difficulty] = new ChartInfo(main, ToLevelHeader(main), target);
|
||||||
LoadJacket(charts[main.Difficulty]);
|
LoadJacket(charts[main.Difficulty]);
|
||||||
@ -110,30 +111,12 @@ namespace VoxCharger
|
|||||||
MessageBoxIcon.Error
|
MessageBoxIcon.Error
|
||||||
);
|
);
|
||||||
|
|
||||||
CancelButton.PerformClick();
|
Close();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to locate another difficulty
|
// Try to locate another difficulty
|
||||||
string dir = Path.GetDirectoryName(target);
|
charts = GetCharts(target, main);
|
||||||
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();
|
UpdateUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,8 +190,7 @@ namespace VoxCharger
|
|||||||
|
|
||||||
private void OnProcessConvertButtonClick(object sender, EventArgs e)
|
private void OnProcessConvertButtonClick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
bool warned = false;
|
Options = new Ksh.ParseOption()
|
||||||
var options = new Ksh.ParseOption()
|
|
||||||
{
|
{
|
||||||
RealignOffset = RealignOffsetCheckBox.Checked,
|
RealignOffset = RealignOffsetCheckBox.Checked,
|
||||||
EnableChipFx = ChipFxCheckBox.Checked,
|
EnableChipFx = ChipFxCheckBox.Checked,
|
||||||
@ -221,235 +203,16 @@ namespace VoxCharger
|
|||||||
|
|
||||||
// Act as converter
|
// Act as converter
|
||||||
if (converter)
|
if (converter)
|
||||||
{
|
Convert();
|
||||||
try
|
else
|
||||||
{
|
Process();
|
||||||
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)
|
private VoxHeader ToHeader(Ksh chart)
|
||||||
{
|
{
|
||||||
return new VoxHeader()
|
return new VoxHeader()
|
||||||
{
|
{
|
||||||
ID = AssetManager.Headers.LastID + 1,
|
ID = AssetManager.GetNextMusicID(),
|
||||||
Title = chart.Title,
|
Title = chart.Title,
|
||||||
Artist = chart.Artist,
|
Artist = chart.Artist,
|
||||||
BpmMin = chart.BpmMin,
|
BpmMin = chart.BpmMin,
|
||||||
@ -472,6 +235,279 @@ namespace VoxCharger
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Process()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool warned = false;
|
||||||
|
foreach (var header in AssetManager.Headers)
|
||||||
|
{
|
||||||
|
if (Header.Ascii == header.Ascii)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
$"Music Code is already exists.\n{AssetManager.GetMusicPath(header).Replace(AssetManager.GamePath, "")}",
|
||||||
|
"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>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversion is actually boring because its already "pre-converted"
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
$"Failed to import ksh chart.\n{ex.Message}",
|
||||||
|
"Error",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Convert()
|
||||||
|
{
|
||||||
|
// Only serve as options dialog
|
||||||
|
if (string.IsNullOrEmpty(target))
|
||||||
|
{
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
DialogResult = DialogResult.Cancel;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
$"Failed to convert ksh chart.\n{ex.Message}",
|
||||||
|
"Error",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<Difficulty, ChartInfo> GetCharts(string target, Ksh main)
|
||||||
|
{
|
||||||
|
// Try to locate another difficulty
|
||||||
|
string dir = Path.GetDirectoryName(target);
|
||||||
|
var charts = new Dictionary<Difficulty, ChartInfo>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return charts;
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadJacket(ChartInfo info)
|
private void LoadJacket(ChartInfo info)
|
||||||
{
|
{
|
||||||
var chart = info.Source;
|
var chart = info.Source;
|
||||||
@ -608,6 +644,7 @@ namespace VoxCharger
|
|||||||
MessageBoxIcon.Information
|
MessageBoxIcon.Information
|
||||||
);
|
);
|
||||||
|
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -692,6 +729,7 @@ namespace VoxCharger
|
|||||||
MessageBoxIcon.Information
|
MessageBoxIcon.Information
|
||||||
);
|
);
|
||||||
|
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
Sources/Forms/MainForm.Designer.cs
generated
2
Sources/Forms/MainForm.Designer.cs
generated
@ -858,9 +858,9 @@
|
|||||||
this.IdTextBox.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
this.IdTextBox.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||||
this.IdTextBox.Location = new System.Drawing.Point(83, 23);
|
this.IdTextBox.Location = new System.Drawing.Point(83, 23);
|
||||||
this.IdTextBox.Name = "IdTextBox";
|
this.IdTextBox.Name = "IdTextBox";
|
||||||
|
this.IdTextBox.ReadOnly = true;
|
||||||
this.IdTextBox.Size = new System.Drawing.Size(383, 21);
|
this.IdTextBox.Size = new System.Drawing.Size(383, 21);
|
||||||
this.IdTextBox.TabIndex = 1;
|
this.IdTextBox.TabIndex = 1;
|
||||||
this.IdTextBox.TextChanged += new System.EventHandler(this.OnMetadataChanged);
|
|
||||||
//
|
//
|
||||||
// IdLabel
|
// IdLabel
|
||||||
//
|
//
|
||||||
|
@ -107,13 +107,15 @@ namespace VoxCharger
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Save(AssetManager.MdbFilename);
|
if (Save(AssetManager.MdbFilename))
|
||||||
MessageBox.Show(
|
{
|
||||||
"Mix has been saved successfully",
|
MessageBox.Show(
|
||||||
"Information",
|
"Mix has been saved successfully",
|
||||||
MessageBoxButtons.OK,
|
"Information",
|
||||||
MessageBoxIcon.Information
|
MessageBoxButtons.OK,
|
||||||
);
|
MessageBoxIcon.Information
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -139,13 +141,15 @@ namespace VoxCharger
|
|||||||
if (exporter.ShowDialog() != DialogResult.OK)
|
if (exporter.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Save(exporter.FileName);
|
if (Save(exporter.FileName))
|
||||||
MessageBox.Show(
|
{
|
||||||
"Mix has been saved successfully",
|
MessageBox.Show(
|
||||||
"Information",
|
"Mix has been saved successfully",
|
||||||
MessageBoxButtons.OK,
|
"Information",
|
||||||
MessageBoxIcon.Information
|
MessageBoxButtons.OK,
|
||||||
);
|
MessageBoxIcon.Information
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -491,7 +495,16 @@ namespace VoxCharger
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (int.TryParse(IdTextBox.Text, out int id))
|
if (int.TryParse(IdTextBox.Text, out int id))
|
||||||
header.ID = id;
|
{
|
||||||
|
// Validate ID
|
||||||
|
if (!AssetManager.ValidateMusicID(id))
|
||||||
|
{
|
||||||
|
IdTextBox.Text = header.ID.ToString();
|
||||||
|
MessageBox.Show("Music ID is already taken", "Duplicate", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
header.ID = id;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
IdTextBox.Text = header.ID.ToString();
|
IdTextBox.Text = header.ID.ToString();
|
||||||
|
|
||||||
@ -703,19 +716,21 @@ namespace VoxCharger
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Save(string dbFilename)
|
private bool Save(string dbFilename)
|
||||||
{
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
using (var loader = new LoadingForm())
|
using (var loader = new LoadingForm())
|
||||||
{
|
{
|
||||||
var proc = new Action(() =>
|
var proc = new Action(() =>
|
||||||
{
|
{
|
||||||
int max = actions.Count + 1;
|
int max = actions.Count + 1;
|
||||||
foreach (var queue in actions.Values)
|
foreach (var action in actions)
|
||||||
{
|
{
|
||||||
float progress = ((float)(max - actions.Count) / max) * 100f;
|
float progress = ((float)(max - actions.Count) / max) * 100f;
|
||||||
loader.SetStatus($"[{progress:00}%] - Processing assets..");
|
loader.SetStatus($"[{progress:00}%] - Processing assets..");
|
||||||
loader.SetProgress(progress);
|
loader.SetProgress(progress);
|
||||||
|
|
||||||
|
var queue = action.Value;
|
||||||
while (queue.Count > 0)
|
while (queue.Count > 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -724,6 +739,7 @@ namespace VoxCharger
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
errors.Add($"{action.Key}: {ex.Message}");
|
||||||
Debug.WriteLine(ex.Message);
|
Debug.WriteLine(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -740,8 +756,19 @@ namespace VoxCharger
|
|||||||
loader.ShowDialog();
|
loader.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errors.Count > 0)
|
||||||
|
{
|
||||||
|
string message = "Error occured when processing following assets:\n";
|
||||||
|
foreach (var err in errors)
|
||||||
|
message += $"\n{err}";
|
||||||
|
|
||||||
|
MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
actions.Clear();
|
actions.Clear();
|
||||||
Pristine = true;
|
Pristine = true;
|
||||||
|
|
||||||
|
return errors.Count == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Import2DX(bool preview = false)
|
private void Import2DX(bool preview = false)
|
||||||
@ -880,6 +907,8 @@ namespace VoxCharger
|
|||||||
control.Enabled = safe;
|
control.Enabled = safe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modifying this could lead into disaster, must be left untouched
|
||||||
|
IdTextBox.ReadOnly = true;
|
||||||
LevelGroupBox.Enabled = true;
|
LevelGroupBox.Enabled = true;
|
||||||
foreach (Control control in LevelGroupBox.Controls)
|
foreach (Control control in LevelGroupBox.Controls)
|
||||||
{
|
{
|
||||||
|
@ -15,10 +15,11 @@ namespace VoxCharger
|
|||||||
private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("Shift_JIS");
|
private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("Shift_JIS");
|
||||||
|
|
||||||
private Dictionary<int, VoxHeader> headers;
|
private Dictionary<int, VoxHeader> headers;
|
||||||
|
private int max = 0;
|
||||||
|
|
||||||
public int LastID => headers.Count > 0 ? headers.Values.Max(h => h.ID) : 0;
|
public int Count => headers.Count;
|
||||||
|
|
||||||
public int Count => headers.Count;
|
public int LastID => headers.Values.Max(h => h.ID);
|
||||||
|
|
||||||
public MusicDb()
|
public MusicDb()
|
||||||
{
|
{
|
||||||
@ -178,17 +179,33 @@ namespace VoxCharger
|
|||||||
|
|
||||||
public void Add(VoxHeader header)
|
public void Add(VoxHeader header)
|
||||||
{
|
{
|
||||||
|
if (max < header.ID)
|
||||||
|
max = header.ID;
|
||||||
|
|
||||||
headers[header.ID] = header;
|
headers[header.ID] = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(int id)
|
public void Remove(int id)
|
||||||
{
|
{
|
||||||
|
if (max == id)
|
||||||
|
max = 0;
|
||||||
|
|
||||||
headers.Remove(id);
|
headers.Remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(VoxHeader header)
|
public void Remove(VoxHeader header)
|
||||||
{
|
{
|
||||||
headers.Remove(header.ID);
|
Remove(header.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(int id)
|
||||||
|
{
|
||||||
|
return headers.ContainsKey(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(VoxHeader header)
|
||||||
|
{
|
||||||
|
return Contains(header.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VoxHeader GetHeader(int id)
|
public VoxHeader GetHeader(int id)
|
||||||
|
Loading…
Reference in New Issue
Block a user