mirror of
https://github.com/djhackersdev/legacy-tools.git
synced 2026-10-08 13:37:55 +03:00
Add more legacy tools that I found while cleaning up my server
This commit is contained in:
Executable
+580
@@ -0,0 +1,580 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using NAudio;
|
||||
using NAudio.WindowsMediaFormat;
|
||||
using NAudio.Wave;
|
||||
using NAudio.Wave.SampleProviders;
|
||||
using NAudio.Lame;
|
||||
using System.IO;
|
||||
|
||||
using CommandLine;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace _2dxrender
|
||||
{
|
||||
class Program
|
||||
{
|
||||
enum ChartCommands
|
||||
{
|
||||
KeyP1 = 0,
|
||||
KeyP2 = 1,
|
||||
LoadSampleP1 = 2,
|
||||
LoadSampleP2 = 3,
|
||||
End = 6,
|
||||
BgmNote = 7,
|
||||
}
|
||||
|
||||
class KeyPosition
|
||||
{
|
||||
public int offset;
|
||||
public int keysoundId;
|
||||
public int key;
|
||||
public int player;
|
||||
|
||||
public KeyPosition(int offset, int keysoundId, int key, int player)
|
||||
{
|
||||
this.offset = offset;
|
||||
this.keysoundId = keysoundId;
|
||||
this.key = key;
|
||||
this.player = player;
|
||||
}
|
||||
}
|
||||
|
||||
static Options options = new Options();
|
||||
static int assistClapIdx = -1;
|
||||
|
||||
private static string GetTempFileName()
|
||||
{
|
||||
return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
static List<string> getAudioSamplesFromS3p(BinaryReader reader)
|
||||
{
|
||||
var samples = new List<string>();
|
||||
|
||||
var fileCount = reader.ReadUInt32();
|
||||
|
||||
var tempPath = GetTempFileName();
|
||||
|
||||
if (!Directory.Exists(tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(tempPath);
|
||||
}
|
||||
|
||||
for (var i = 0; i < fileCount; i++)
|
||||
{
|
||||
reader.BaseStream.Seek(i * 8 + 8, SeekOrigin.Begin);
|
||||
|
||||
var offset = reader.ReadUInt32();
|
||||
var size = reader.ReadInt32();
|
||||
|
||||
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "S3V0")
|
||||
{
|
||||
Console.WriteLine("Not a valid S3V audio file");
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
var dataOffset = reader.ReadUInt32();
|
||||
var dataSize = reader.ReadInt32();
|
||||
|
||||
reader.BaseStream.Seek(offset + dataOffset, SeekOrigin.Begin);
|
||||
|
||||
var audioBytes = reader.ReadBytes(dataSize);
|
||||
var tempFilename = Path.Combine(tempPath, String.Format("{0:d4}.wma", i + 1));
|
||||
|
||||
samples.Add(tempFilename);
|
||||
|
||||
File.WriteAllBytes(tempFilename, audioBytes);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
static List<string> getAudioSamplesFrom2dx(BinaryReader reader)
|
||||
{
|
||||
var samples = new List<string>();
|
||||
|
||||
reader.BaseStream.Seek(0x14, SeekOrigin.Begin);
|
||||
|
||||
var fileCount = reader.ReadUInt32();
|
||||
|
||||
reader.BaseStream.Seek(0x48, SeekOrigin.Begin);
|
||||
|
||||
var tempPath = Path.GetTempPath();
|
||||
for (var i = 0; i < fileCount; i++)
|
||||
{
|
||||
reader.BaseStream.Seek(0x48 + i * 4, SeekOrigin.Begin);
|
||||
|
||||
var offset = reader.ReadUInt32();
|
||||
|
||||
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "2DX9")
|
||||
{
|
||||
Console.WriteLine("Not a valid 2DX audio file @ {0:x8}", reader.BaseStream.Position - 4);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
var dataOffset = reader.ReadUInt32();
|
||||
var dataSize = reader.ReadInt32();
|
||||
|
||||
reader.BaseStream.Seek(offset + dataOffset, SeekOrigin.Begin);
|
||||
|
||||
var audioBytes = reader.ReadBytes(dataSize);
|
||||
var tempFilename = Path.Combine(tempPath, String.Format("{0:d4}.wav", i + 1));
|
||||
|
||||
samples.Add(tempFilename);
|
||||
|
||||
File.WriteAllBytes(tempFilename, audioBytes);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
static List<string> getAudioSamples(string inputFilename)
|
||||
{
|
||||
var samples = new List<string>();
|
||||
|
||||
FileAttributes attr = File.GetAttributes(inputFilename);
|
||||
|
||||
if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
samples = Directory.GetFiles(inputFilename).OrderBy(x => x).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var reader = new BinaryReader(File.Open(inputFilename, FileMode.Open)))
|
||||
{
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) == "S3P0")
|
||||
{
|
||||
samples = getAudioSamplesFromS3p(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try parsing as .2dx
|
||||
samples = getAudioSamplesFrom2dx(reader);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Couldn't find parser for input audio file: {0}", inputFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.AssistClap)
|
||||
{
|
||||
if (File.Exists(options.AssistClapSound))
|
||||
{
|
||||
var assistClapTempFilename = GetTempFileName();
|
||||
assistClapIdx = samples.Count;
|
||||
samples.Add(assistClapTempFilename);
|
||||
File.Copy(options.AssistClapSound, assistClapTempFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Couldn't find clap file \"{0}\", skipping...", options.AssistClapSound);
|
||||
options.AssistClap = false;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
private static int findSampleById(List<string> samples, int value, string prefix)
|
||||
{
|
||||
for (int idx = 0; idx < samples.Count; idx++)
|
||||
{
|
||||
var sample = samples[idx];
|
||||
|
||||
if (sample == Path.Combine(prefix, String.Format("{0:d4}.wav", value)) ||
|
||||
sample == Path.Combine(prefix, String.Format("{0:d4}.wma", value)))
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static List<KeyPosition> parseJsonChartData(string filename, List<string> samples, int chartId)
|
||||
{
|
||||
var sounds = new List<KeyPosition>();
|
||||
var loaded_samples = new List<Dictionary<short, int>>(){
|
||||
new Dictionary<short, int>(),
|
||||
new Dictionary<short, int>()
|
||||
};
|
||||
|
||||
var chartIdLookup = new Dictionary<int, string>()
|
||||
{
|
||||
{ 0, "input_sp_normal" },
|
||||
{ 1, "input_sp_hyper" },
|
||||
{ 2, "input_sp_another" },
|
||||
{ 3, "input_sp_beginner" },
|
||||
{ 4, "input_sp_black" },
|
||||
{ 6, "input_dp_normal" },
|
||||
{ 7, "input_dp_hyper" },
|
||||
{ 8, "input_dp_another" },
|
||||
{ 9, "input_dp_beginner" },
|
||||
{ 10, "input_dp_black" },
|
||||
};
|
||||
|
||||
var json = File.ReadAllText(filename);
|
||||
dynamic _chartData = JsonConvert.DeserializeObject(json);
|
||||
|
||||
int chartIdx = -1;
|
||||
foreach (var chart in _chartData["charts"])
|
||||
{
|
||||
chartIdx++;
|
||||
|
||||
if (chart["chart_type"] == chartIdLookup[chartId])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (chartIdx == -1)
|
||||
{
|
||||
Console.WriteLine("Couldn't find selected chart: {0}", chartId);
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
dynamic chartData = _chartData["charts"][chartIdx];
|
||||
|
||||
var soundFolder = Path.GetDirectoryName(samples[0]);
|
||||
|
||||
bool isFloat = false;
|
||||
var dicts = chartData["events"].ToObject<Dictionary<string, object>>();
|
||||
var keys = new List<string>();
|
||||
foreach (var dict in dicts)
|
||||
{
|
||||
keys.Add(dict.Key);
|
||||
}
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
string k = key.ToString();
|
||||
|
||||
if (isFloat)
|
||||
{
|
||||
k = String.Format("{0:f}", key);
|
||||
}
|
||||
var _ev = chartData["events"][k];
|
||||
for (int eventidx = 0; eventidx < _ev.Count; eventidx++)
|
||||
{
|
||||
var ev = _ev[eventidx];
|
||||
|
||||
int offset = ev["offset"];
|
||||
|
||||
if ((ev["event"] == "note_p1" || ev["event"] == "note_p2") && offset != 0)
|
||||
{
|
||||
var playerId = ev["event"] == "note_p1" ? 0 : 1;
|
||||
int value = ev["value"];
|
||||
short param = ev["slot"];
|
||||
|
||||
if (value != 0 && param == 7)
|
||||
{
|
||||
sounds.Add(new KeyPosition(offset + value, loaded_samples[playerId][param], param, playerId));
|
||||
|
||||
if (options.AssistClap)
|
||||
{
|
||||
sounds.Add(new KeyPosition(offset + value, assistClapIdx, -1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.AssistClap && param == 7)
|
||||
{
|
||||
sounds.Add(new KeyPosition(offset, assistClapIdx, -1, 0));
|
||||
}
|
||||
|
||||
sounds.Add(new KeyPosition(offset, loaded_samples[playerId][param], param, playerId));
|
||||
}
|
||||
else if (ev["event"] == "sample_p1" || ev["event"] == "sample_p2")
|
||||
{
|
||||
var playerId = ev["event"] == "sample_p1" ? 0 : 1;
|
||||
int value = ev["sound_id"];
|
||||
short param = ev["slot"];
|
||||
|
||||
loaded_samples[playerId][param] = findSampleById(samples, value, soundFolder);
|
||||
|
||||
// To simulate the engine loading a new sample before it's played, loop through and update everything
|
||||
// for that specific key from the current offset onward
|
||||
for (int i = 0; i < sounds.Count; i++)
|
||||
{
|
||||
if (sounds[i].player == playerId && sounds[i].key == param && sounds[i].offset >= offset)
|
||||
{
|
||||
sounds[i].keysoundId = loaded_samples[playerId][param];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ev["event"] == "auto")
|
||||
{
|
||||
int value = ev["sound_id"];
|
||||
int sample_idx = findSampleById(samples, value, soundFolder);
|
||||
|
||||
if (sample_idx == 0 && options.NoBgm)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sounds.Add(new KeyPosition(offset, sample_idx, -1, 0));
|
||||
}
|
||||
else if (ev["event"] == "end")
|
||||
{
|
||||
if (ev["player"] == null)
|
||||
{
|
||||
sounds.Add(new KeyPosition(offset, -1, -1, 0));
|
||||
sounds.Add(new KeyPosition(offset, -1, -1, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
int value = ev["player"];
|
||||
sounds.Add(new KeyPosition(offset, -1, -1, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sounds;
|
||||
}
|
||||
|
||||
private static List<KeyPosition> parseChartData(BinaryReader reader, List<string> samples)
|
||||
{
|
||||
var sounds = new List<KeyPosition>();
|
||||
var loaded_samples = new List<Dictionary<short, int>>(){
|
||||
new Dictionary<short, int>(),
|
||||
new Dictionary<short, int>()
|
||||
};
|
||||
|
||||
while (true)
|
||||
{
|
||||
var offset = reader.ReadInt32();
|
||||
var command = reader.ReadByte();
|
||||
var param = reader.ReadByte();
|
||||
var value = reader.ReadInt16();
|
||||
|
||||
if (offset == 0x7fffffff)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case (byte)ChartCommands.KeyP1:
|
||||
case (byte)ChartCommands.KeyP2:
|
||||
{
|
||||
var playerId = command - (byte)ChartCommands.KeyP1;
|
||||
|
||||
if (value != 0 && param == 7)
|
||||
{
|
||||
sounds.Add(new KeyPosition(offset + value, loaded_samples[playerId][param], param, playerId));
|
||||
|
||||
if (options.AssistClap)
|
||||
{
|
||||
sounds.Add(new KeyPosition(offset + value, assistClapIdx, -1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.AssistClap && param == 7)
|
||||
{
|
||||
sounds.Add(new KeyPosition(offset, assistClapIdx, -1, 0));
|
||||
}
|
||||
|
||||
sounds.Add(new KeyPosition(offset, loaded_samples[playerId][param], param, playerId));
|
||||
}
|
||||
break;
|
||||
|
||||
case (byte)ChartCommands.LoadSampleP1:
|
||||
case (byte)ChartCommands.LoadSampleP2:
|
||||
{
|
||||
var playerId = command - (byte)ChartCommands.LoadSampleP1;
|
||||
|
||||
loaded_samples[playerId][param] = value - 1;
|
||||
|
||||
// To simulate the engine loading a new sample before it's played, loop through and update everything
|
||||
// for that specific key from the current offset onward
|
||||
for (int i = 0; i < sounds.Count; i++)
|
||||
{
|
||||
if (sounds[i].player == playerId && sounds[i].key == param && sounds[i].offset >= offset)
|
||||
{
|
||||
sounds[i].keysoundId = loaded_samples[playerId][param];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case (byte)ChartCommands.BgmNote:
|
||||
{
|
||||
if (value - 1 == 0 && options.NoBgm)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sounds.Add(new KeyPosition(offset, value - 1, -1, 0));
|
||||
}
|
||||
break;
|
||||
|
||||
case (byte)ChartCommands.End:
|
||||
{
|
||||
sounds.Add(new KeyPosition(offset, -1, -1, param));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sounds;
|
||||
}
|
||||
|
||||
static void parseChart(string filename, int chartId, string outputFilename, List<string> samples)
|
||||
{
|
||||
if (chartId < 0 || chartId > 0x60 / 8)
|
||||
{
|
||||
Console.WriteLine("Invalid chart ID");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (filename.EndsWith(".json"))
|
||||
{
|
||||
var sounds = parseJsonChartData(filename, samples, chartId);
|
||||
mixFinalAudio(outputFilename, sounds, samples);
|
||||
return;
|
||||
}
|
||||
|
||||
using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open)))
|
||||
{
|
||||
reader.BaseStream.Seek(chartId * 8, SeekOrigin.Begin);
|
||||
|
||||
var offset = reader.ReadUInt32();
|
||||
var filesize = reader.ReadInt32();
|
||||
|
||||
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
var sounds = parseChartData(reader, samples);
|
||||
mixFinalAudio(outputFilename, sounds, samples);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
foreach (var sampleFilename in samples)
|
||||
{
|
||||
File.Delete(sampleFilename);
|
||||
}
|
||||
}
|
||||
|
||||
private static void mixFinalAudio(string outputFilename, List<KeyPosition> sounds, List<string> samples)
|
||||
{
|
||||
var mixedSamples = new List<OffsetSampleProvider>();
|
||||
|
||||
// Find P1 and P2 ends
|
||||
int[] playerEnd = { -1, -1 };
|
||||
foreach (var sound in sounds)
|
||||
{
|
||||
if (sound.keysoundId == -1 && sound.key == -1)
|
||||
{
|
||||
playerEnd[sound.player] = sound.offset;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sound in sounds)
|
||||
{
|
||||
if (sound.keysoundId == -1)
|
||||
continue;
|
||||
|
||||
var audioFile = new AudioFileReader(samples[sound.keysoundId]);
|
||||
var volSample = new VolumeSampleProvider(audioFile);
|
||||
|
||||
if (volSample.WaveFormat.Channels == 1)
|
||||
{
|
||||
volSample = new VolumeSampleProvider(volSample.ToStereo());
|
||||
}
|
||||
|
||||
if (volSample.WaveFormat.SampleRate != 44100)
|
||||
{
|
||||
// Causes pop sound at end of audio
|
||||
volSample = new VolumeSampleProvider(
|
||||
new WaveToSampleProvider(
|
||||
new MediaFoundationResampler(
|
||||
new SampleToWaveProvider(volSample),
|
||||
WaveFormat.CreateIeeeFloatWaveFormat(44100, 2)
|
||||
) {
|
||||
ResamplerQuality = 60
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (options.AssistClap && sound.keysoundId == assistClapIdx)
|
||||
{
|
||||
volSample.Volume = options.AssistClapVolume;
|
||||
}
|
||||
else
|
||||
{
|
||||
volSample.Volume = options.RenderVolume;
|
||||
}
|
||||
|
||||
var sample = new OffsetSampleProvider(volSample);
|
||||
sample.DelayBy = TimeSpan.FromMilliseconds(sound.offset);
|
||||
|
||||
if (sound.player >= 0 && sound.player <= 1 && playerEnd[sound.player] != -1 && sound.offset + audioFile.TotalTime.TotalMilliseconds > playerEnd[sound.player])
|
||||
{
|
||||
sample.Take = TimeSpan.FromMilliseconds(playerEnd[sound.player] - sound.offset);
|
||||
}
|
||||
|
||||
mixedSamples.Add(sample);
|
||||
}
|
||||
|
||||
var mixers = new List<MixingSampleProvider>();
|
||||
for (int i = 0; i < mixedSamples.Count; i += 128)
|
||||
{
|
||||
var arr = mixedSamples.Skip(i).Take(128).ToArray();
|
||||
mixers.Add(new MixingSampleProvider(arr));
|
||||
}
|
||||
|
||||
var mixer = new MixingSampleProvider(mixers);
|
||||
|
||||
if (options.OutputFormat.ToLower() == "wav")
|
||||
{
|
||||
WaveFileWriter.CreateWaveFile16(outputFilename, mixer);
|
||||
}
|
||||
else if (options.OutputFormat.ToLower() == "mp3")
|
||||
{
|
||||
var tempFilename = GetTempFileName();
|
||||
|
||||
WaveFileWriter.CreateWaveFile16(tempFilename, mixer);
|
||||
|
||||
ID3TagData id3 = new ID3TagData();
|
||||
id3.Album = options.Id3Album;
|
||||
id3.AlbumArtist = options.Id3AlbumArtist;
|
||||
id3.Title = options.Id3Title;
|
||||
id3.Artist = options.Id3Artist;
|
||||
id3.Genre = options.Id3Genre;
|
||||
id3.Track = options.Id3Track;
|
||||
id3.Year = options.Id3Year;
|
||||
|
||||
using (var reader = new AudioFileReader(tempFilename))
|
||||
using (var writer = new LameMP3FileWriter(outputFilename, reader.WaveFormat, 320, id3))
|
||||
{
|
||||
reader.CopyTo(writer);
|
||||
}
|
||||
|
||||
File.Delete(tempFilename);
|
||||
}
|
||||
}
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Parser.Default.ParseArguments<Options>(args).WithParsed<Options>(opts => options = opts).WithNotParsed(errors => Environment.Exit(1));
|
||||
|
||||
var samples = getAudioSamples(options.InputAudio);
|
||||
parseChart(options.InputChart, options.ChartId, options.OutputFile, samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user