commit 174bf2f357aa345e5ca541870debe50662dc34ec Author: Rikux3 Date: Sun Jul 19 00:05:11 2020 +0200 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e645270 --- /dev/null +++ b/.gitignore @@ -0,0 +1,353 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# 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 + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# 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 +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# 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/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# 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 +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9976aa0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Rikux3 + +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. diff --git a/UsmToolkit.sln b/UsmToolkit.sln new file mode 100644 index 0000000..fdc05a9 --- /dev/null +++ b/UsmToolkit.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30225.117 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UsmToolkit", "UsmToolkit\UsmToolkit.csproj", "{1C7F9474-2096-4039-9F6F-9B9940717D06}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1C7F9474-2096-4039-9F6F-9B9940717D06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C7F9474-2096-4039-9F6F-9B9940717D06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C7F9474-2096-4039-9F6F-9B9940717D06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C7F9474-2096-4039-9F6F-9B9940717D06}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C8EB6C12-4B73-41DD-9F88-BDB76671892C} + EndGlobalSection +EndGlobal diff --git a/UsmToolkit/Helpers.cs b/UsmToolkit/Helpers.cs new file mode 100644 index 0000000..f40fbf4 --- /dev/null +++ b/UsmToolkit/Helpers.cs @@ -0,0 +1,24 @@ +using System.Diagnostics; + +namespace UsmToolkit +{ + public static class Helpers + { + public static void ExecuteProcess(string fileName, string arguments) + { + ProcessStartInfo startInfo = new ProcessStartInfo() + { + FileName = fileName, + Arguments = arguments, + RedirectStandardOutput = true, + UseShellExecute = false, + }; + + Process process = new Process(); + process.StartInfo = startInfo; + + process.Start(); + process.WaitForExit(); + } + } +} diff --git a/UsmToolkit/Program.cs b/UsmToolkit/Program.cs new file mode 100644 index 0000000..61104ea --- /dev/null +++ b/UsmToolkit/Program.cs @@ -0,0 +1,197 @@ +using McMaster.Extensions.CommandLineUtils; +using Newtonsoft.Json; +using System; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using VGMToolbox.format; + +namespace UsmToolkit +{ + [Command("UsmToolkit")] + [VersionOptionFromMember("--version", MemberName = nameof(GetVersion))] + [Subcommand(typeof(ExtractCommand), typeof(GetDependenciesCommand))] + class Program + { + + static int Main(string[] args) + { + try + { + return CommandLineApplication.Execute(args); + } + catch (FileNotFoundException e) + { + Console.WriteLine($"The file {e.FileName} cannot be found. The program will now exit."); + return 2; + } + catch (Exception e) + { + Console.WriteLine($"FATAL ERROR: {e.Message}\n{e.StackTrace}"); + return -1; + } + } + + protected int OnExecute(CommandLineApplication app) + { + app.ShowHelp(); + return 1; + } + + private static string GetVersion() + => typeof(Program).Assembly.GetCustomAttribute().InformationalVersion; + + private class ExtractCommand + { + private class JoinConfig + { + public string VideoParameter { get; set; } + public string AudioParameter { get; set; } + public string OutputFormat { get; set; } + } + + [Required] + [FileOrDirectoryExists] + [Argument(0, Description = "File or folder containing usm files")] + public string InputPath { get; set; } + + [Option(CommandOptionType.NoValue, Description = "Join files after extraction.", ShortName = "j", LongName = "join")] + public bool JoinOutput { get; set; } + + [Option(CommandOptionType.NoValue, Description = "Remove temporary m2v and audio after joining.", ShortName = "c", LongName = "clean")] + public bool CleanTempFiles { get; set; } + + protected int OnExecute(CommandLineApplication app) + { + FileAttributes attr = File.GetAttributes(InputPath); + if (attr.HasFlag(FileAttributes.Directory)) + { + foreach (var file in Directory.GetFiles(InputPath, "*.usm")) + { + Convert(file); + } + } + else + Convert(InputPath); + + return 0; + } + + private void Convert(string fileName) + { + Console.WriteLine($"File: {fileName}"); + var usmStream = new CriUsmStream(fileName); + + usmStream.DemultiplexStreams(new MpegStream.DemuxOptionsStruct() + { + AddHeader = false, + AddPlaybackHacks = false, + ExtractAudio = true, + ExtractVideo = true, + SplitAudioStreams = false + }); + + if (JoinOutput) + { + JoinOutputFile(usmStream); + } + } + + private void JoinOutputFile(CriUsmStream usmStream) + { + var audioFormat = usmStream.FinalAudioExtension; + var pureFileName = Path.GetFileNameWithoutExtension(usmStream.FilePath); + + if (audioFormat == ".adx") + { + //ffmpeg can not handle .adx from 0.2 for whatever reason + //need vgmstream to format that to wav + if (!Directory.Exists("vgmstream")) + { + Console.WriteLine("WARNING: vgmstream folder not found!"); + } + + Helpers.ExecuteProcess("vgmstream/test.exe", $"\"{Path.ChangeExtension(usmStream.FilePath, usmStream.FinalAudioExtension)}\" -o \"{Path.ChangeExtension(usmStream.FilePath, "wav")}\""); + + usmStream.FinalAudioExtension = ".wav"; + } + + if (!File.Exists("config.json")) + { + Console.WriteLine("ERROR: config.json not found!"); + return; + } + + Helpers.ExecuteProcess("ffmpeg", CreateFFmpegParameters(usmStream, pureFileName)); + + if (CleanTempFiles) + { + File.Delete(Path.ChangeExtension(usmStream.FilePath, "wav")); + File.Delete(Path.ChangeExtension(usmStream.FilePath, "adx")); + File.Delete(Path.ChangeExtension(usmStream.FilePath, "hca")); + File.Delete(Path.ChangeExtension(usmStream.FilePath, "m2v")); + } + } + + private string CreateFFmpegParameters(CriUsmStream usmStream, string pureFileName) + { + JoinConfig conf = JsonConvert.DeserializeObject(File.ReadAllText("config.json")); + + StringBuilder sb = new StringBuilder(); + sb.Append($"-i \"{Path.ChangeExtension(usmStream.FilePath, usmStream.FileExtensionVideo)}\" "); + + if (usmStream.HasAudio) + sb.Append($"-i \"{Path.ChangeExtension(usmStream.FilePath, usmStream.FinalAudioExtension)}\" "); + + sb.Append($"{conf.VideoParameter} "); + + if (usmStream.HasAudio) + sb.Append($"{conf.AudioParameter} "); + + sb.Append($"{pureFileName}.{conf.OutputFormat}"); + + return sb.ToString(); + } + } + + private class GetDependenciesCommand + { + private class DepsConfig + { + public string Vgmstream { get; set; } + public string FFmpeg { get; set; } + } + + protected int OnExecute(CommandLineApplication app) + { + DepsConfig conf = JsonConvert.DeserializeObject(File.ReadAllText("deps.json")); + WebClient client = new WebClient(); + + //ffmpeg + client.DownloadFile(conf.FFmpeg, "ffmpeg.zip"); + + using (ZipArchive archive = ZipFile.OpenRead("ffmpeg.zip")) + { + var ent = archive.Entries.FirstOrDefault(x => x.Name == "ffmpeg.exe"); + if (ent != null) + { + ent.ExtractToFile("ffmpeg.exe", true); + } + } + + File.Delete("ffmpeg.zip"); + + //vgmstream + client.DownloadFile(conf.Vgmstream, "vgmstream.zip"); + ZipFile.ExtractToDirectory("vgmstream.zip", "vgmstream"); + File.Delete("vgmstream.zip"); + + return 0; + } + } + } +} diff --git a/UsmToolkit/Properties/launchSettings.json b/UsmToolkit/Properties/launchSettings.json new file mode 100644 index 0000000..348f2c0 --- /dev/null +++ b/UsmToolkit/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "UsmToolkit": { + "commandName": "Project", + "commandLineArgs": "extract \"F:\\CUSA05787\\tresgame\\content\\crimovie\\rebellis02\\en\\cs_dw009_mv.usm\" --join" + } + } +} \ No newline at end of file diff --git a/UsmToolkit/UsmToolkit.csproj b/UsmToolkit/UsmToolkit.csproj new file mode 100644 index 0000000..10aacbb --- /dev/null +++ b/UsmToolkit/UsmToolkit.csproj @@ -0,0 +1,28 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + deps\vgmtutil.dll + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/UsmToolkit/VGMToolbox/CriUsmStream.cs b/UsmToolkit/VGMToolbox/CriUsmStream.cs new file mode 100644 index 0000000..840216f --- /dev/null +++ b/UsmToolkit/VGMToolbox/CriUsmStream.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using VGMToolbox.util; + +namespace VGMToolbox.format +{ + public class CriUsmStream : MpegStream + { + public const string DefaultAudioExtension = ".adx"; + public const string DefaultVideoExtension = ".m2v"; + public const string HcaAudioExtension = ".hca"; + + static readonly byte[] HCA_SIG_BYTES = new byte[] { 0x48, 0x43, 0x41, 0x00 }; + + protected static readonly byte[] ALP_BYTES = new byte[] { 0x40, 0x41, 0x4C, 0x50 }; + protected static readonly byte[] CRID_BYTES = new byte[] { 0x43, 0x52, 0x49, 0x44 }; + protected static readonly byte[] SFV_BYTES = new byte[] { 0x40, 0x53, 0x46, 0x56 }; + protected static readonly byte[] SFA_BYTES = new byte[] { 0x40, 0x53, 0x46, 0x41 }; + protected static readonly byte[] SBT_BYTES = new byte[] { 0x40, 0x53, 0x42, 0x54 }; + protected static readonly byte[] CUE_BYTES = new byte[] { 0x40, 0x43, 0x55, 0x45 }; + + protected static readonly byte[] UTF_BYTES = new byte[] { 0x40, 0x55, 0x54, 0x46 }; + + protected static readonly byte[] HEADER_END_BYTES = + new byte[] { 0x23, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, + 0x45, 0x4E, 0x44, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, + 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x00 }; + + protected static readonly byte[] METADATA_END_BYTES = + new byte[] { 0x23, 0x4D, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, + 0x41, 0x20, 0x45, 0x4E, 0x44, 0x20, 0x20, 0x20, + 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, + 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x00 }; + + protected static readonly byte[] CONTENTS_END_BYTES = + new byte[] { 0x23, 0x43, 0x4F, 0x4E, 0x54, 0x45, 0x4E, 0x54, + 0x53, 0x20, 0x45, 0x4E, 0x44, 0x20, 0x20, 0x20, + 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, + 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x00 }; + + public CriUsmStream(string path) + : base(path) + { + this.UsesSameIdForMultipleAudioTracks = true; + this.FileExtensionAudio = DefaultAudioExtension; + this.FileExtensionVideo = DefaultVideoExtension; + + base.BlockIdDictionary.Clear(); + base.BlockIdDictionary[BitConverter.ToUInt32(ALP_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @ALP + base.BlockIdDictionary[BitConverter.ToUInt32(CRID_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // CRID + base.BlockIdDictionary[BitConverter.ToUInt32(SFV_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @SFV + base.BlockIdDictionary[BitConverter.ToUInt32(SFA_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @SFA + base.BlockIdDictionary[BitConverter.ToUInt32(SBT_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @SBT + base.BlockIdDictionary[BitConverter.ToUInt32(CUE_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @CUE + } + + protected override byte[] GetPacketStartBytes() { return CRID_BYTES; } + + protected override int GetAudioPacketHeaderSize(Stream readStream, long currentOffset) + { + UInt16 checkBytes; + OffsetDescription od = new OffsetDescription(); + + od.OffsetByteOrder = Constants.BigEndianByteOrder; + od.OffsetSize = "2"; + od.OffsetValue = "8"; + + checkBytes = (UInt16)ParseFile.GetVaryingByteValueAtRelativeOffset(readStream, od, currentOffset); + + return checkBytes; + } + protected override int GetVideoPacketHeaderSize(Stream readStream, long currentOffset) + { + UInt16 checkBytes; + OffsetDescription od = new OffsetDescription(); + + od.OffsetByteOrder = Constants.BigEndianByteOrder; + od.OffsetSize = "2"; + od.OffsetValue = "8"; + + checkBytes = (UInt16)ParseFile.GetVaryingByteValueAtRelativeOffset(readStream, od, currentOffset); + + return checkBytes; + } + + protected override bool IsThisAnAudioBlock(byte[] blockToCheck) + { + return ParseFile.CompareSegment(blockToCheck, 0, SFA_BYTES); + } + protected override bool IsThisAVideoBlock(byte[] blockToCheck) + { + return ParseFile.CompareSegment(blockToCheck, 0, SFV_BYTES); + } + + protected override byte GetStreamId(Stream readStream, long currentOffset) + { + byte streamId; + + streamId = ParseFile.ParseSimpleOffset(readStream, currentOffset + 0xC, 1)[0]; + + return streamId; + } + + protected override int GetAudioPacketFooterSize(Stream readStream, long currentOffset) + { + UInt16 checkBytes; + OffsetDescription od = new OffsetDescription(); + + od.OffsetByteOrder = Constants.BigEndianByteOrder; + od.OffsetSize = "2"; + od.OffsetValue = "0xA"; + + checkBytes = (UInt16)ParseFile.GetVaryingByteValueAtRelativeOffset(readStream, od, currentOffset); + + return checkBytes; + } + + protected override int GetVideoPacketFooterSize(Stream readStream, long currentOffset) + { + UInt16 checkBytes; + OffsetDescription od = new OffsetDescription(); + + od.OffsetByteOrder = Constants.BigEndianByteOrder; + od.OffsetSize = "2"; + od.OffsetValue = "0xA"; + + checkBytes = (UInt16)ParseFile.GetVaryingByteValueAtRelativeOffset(readStream, od, currentOffset); + + return checkBytes; + } + + protected override void DoFinalTasks(FileStream sourceFileStream, Dictionary outputFiles, bool addHeader) + { + long headerEndOffset; + long metadataEndOffset; + long headerSize; + + long footerOffset; + long footerSize; + + string sourceFileName; + string workingFile; + string fileExtension; + string destinationFileName; + + foreach (uint streamId in outputFiles.Keys) + { + sourceFileName = outputFiles[streamId].Name; + + //-------------------------- + // get header size + //-------------------------- + headerEndOffset = ParseFile.GetNextOffset(outputFiles[streamId], 0, HEADER_END_BYTES); + metadataEndOffset = ParseFile.GetNextOffset(outputFiles[streamId], 0, METADATA_END_BYTES); + + if (metadataEndOffset > headerEndOffset) + { + headerSize = metadataEndOffset + METADATA_END_BYTES.Length; + } + else + { + headerSize = headerEndOffset + METADATA_END_BYTES.Length; + } + + //----------------- + // get footer size + //----------------- + footerOffset = ParseFile.GetNextOffset(outputFiles[streamId], 0, CONTENTS_END_BYTES) - headerSize; + footerSize = outputFiles[streamId].Length - footerOffset; + + //------------------------------------------ + // check data to adjust extension if needed + //------------------------------------------ + if (this.IsThisAnAudioBlock(BitConverter.GetBytes(streamId & 0xFFFFFFF0))) // may need to change mask if more than 0xF streams + { + byte[] checkBytes = ParseFile.ParseSimpleOffset(outputFiles[streamId], headerSize, 4); + + if (ParseFile.CompareSegment(checkBytes, 0, SofdecStream.AixSignatureBytes)) + { + fileExtension = SofdecStream.AixAudioExtension; + } + else if (checkBytes[0] == 0x80) + { + fileExtension = SofdecStream.AdxAudioExtension; + } + else if (ParseFile.CompareSegment(checkBytes, 0, HCA_SIG_BYTES)) + { + fileExtension = HcaAudioExtension; + } + else + { + fileExtension = ".bin"; + } + + this.FinalAudioExtension = fileExtension; + this.HasAudio = true; + } + else + { + fileExtension = Path.GetExtension(sourceFileName); + } + + outputFiles[streamId].Close(); + outputFiles[streamId].Dispose(); + + workingFile = FileUtil.RemoveChunkFromFile(sourceFileName, 0, headerSize); + File.Copy(workingFile, sourceFileName, true); + File.Delete(workingFile); + + workingFile = FileUtil.RemoveChunkFromFile(sourceFileName, footerOffset, footerSize); + destinationFileName = Path.ChangeExtension(sourceFileName, fileExtension); + destinationFileName = destinationFileName.Substring(0, destinationFileName.LastIndexOf("_"))+fileExtension; + File.Copy(workingFile, destinationFileName, true); + File.Delete(workingFile); + + if ((sourceFileName != destinationFileName) && (File.Exists(sourceFileName))) + { + File.Delete(sourceFileName); + } + } + } + } +} diff --git a/UsmToolkit/VGMToolbox/Mpeg1Stream.cs b/UsmToolkit/VGMToolbox/Mpeg1Stream.cs new file mode 100644 index 0000000..18e33fb --- /dev/null +++ b/UsmToolkit/VGMToolbox/Mpeg1Stream.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; + +namespace VGMToolbox.format +{ + public class Mpeg1Stream : MpegStream + { + public const string DefaultAudioExtension = ".mp2"; + public const string DefaultVideoExtension = ".m1v"; + + public Mpeg1Stream(string path) + : base(path) + { + this.FileExtensionAudio = DefaultAudioExtension; + this.FileExtensionVideo = DefaultVideoExtension; + + base.BlockIdDictionary[BitConverter.ToUInt32(MpegStream.PacketStartBytes, 0)] = new BlockSizeStruct(PacketSizeType.Static, 0xC); // Pack Header + } + + protected override int GetAudioPacketHeaderSize(Stream readStream, long currentOffset) + { + int paddingByteCount = 0; + readStream.Position = currentOffset + 6; + + // skip stuffing bytes + while (readStream.ReadByte() == 0xFF) + { + paddingByteCount++; + } + + return paddingByteCount + 7; + } + + protected override int GetVideoPacketHeaderSize(Stream readStream, long currentOffset) + { + return 0xC; + } + } +} diff --git a/UsmToolkit/VGMToolbox/MpegStream.cs b/UsmToolkit/VGMToolbox/MpegStream.cs new file mode 100644 index 0000000..80c4da1 --- /dev/null +++ b/UsmToolkit/VGMToolbox/MpegStream.cs @@ -0,0 +1,506 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using VGMToolbox.util; + +namespace VGMToolbox.format +{ + public abstract class MpegStream + { + protected static readonly byte[] PacketStartBytes = new byte[] { 0x00, 0x00, 0x01, 0xBA }; + protected static readonly byte[] PacketEndBytes = new byte[] { 0x00, 0x00, 0x01, 0xB9 }; + + public MpegStream(string path) + { + this.FilePath = path; + this.UsesSameIdForMultipleAudioTracks = false; + this.SubTitleExtractionSupported = false; + this.BlockSizeIsLittleEndian = false; + + //******************** + // Add Slice Packets + //******************** + byte[] sliceBytes; + uint sliceBytesValue; + BlockSizeStruct blockSize = new BlockSizeStruct(PacketSizeType.Static, 0xE); + + for (byte i = 0; i <= 0xAF; i++) + { + sliceBytes = new byte[] { 0x00, 0x00, 0x01, i }; + sliceBytesValue = BitConverter.ToUInt32(sliceBytes, 0); + this.BlockIdDictionary.Add(sliceBytesValue, blockSize); + } + } + + public enum PacketSizeType + { + Static, + SizeBytes, + Eof + } + + public struct MpegDemuxOptions + { + public bool AddHeader { set; get; } + } + + public struct BlockSizeStruct + { + public PacketSizeType SizeType; + public int Size; + + public BlockSizeStruct(PacketSizeType sizeTypeValue, int sizeValue) + { + this.SizeType = sizeTypeValue; + this.Size = sizeValue; + } + } + + public struct DemuxOptionsStruct + { + public bool ExtractVideo { set; get; } + public bool ExtractAudio { set; get; } + + public bool AddHeader { set; get; } + public bool SplitAudioStreams { set; get; } + public bool AddPlaybackHacks { set; get; } + } + + #region Dictionary Initialization + + protected Dictionary BlockIdDictionary = + new Dictionary + { + //******************** + // System Packets + //******************** + {BitConverter.ToUInt32(MpegStream.PacketEndBytes, 0), new BlockSizeStruct(PacketSizeType.Eof, -1)}, // Program End + {BitConverter.ToUInt32(MpegStream.PacketStartBytes, 0), new BlockSizeStruct(PacketSizeType.Static, 0xE)}, // Pack Header + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xBB }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // System Header, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xBD }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Private Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xBE }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Padding Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xBF }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Private Stream, two bytes following equal length (Big Endian) + + //**************************** + // Audio Streams + //**************************** + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC0 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC1 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC2 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC3 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC4 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC5 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC6 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC7 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC8 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xC9 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xCA }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xCB }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xCC }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xCD }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xCE }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xCF }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD0 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD1 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD2 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD3 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD4 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD5 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD6 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD7 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD8 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xD9 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xDA }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xDB }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xDC }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xDD }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xDE }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xDF }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Audio Stream, two bytes following equal length (Big Endian) + + //**************************** + // Video Streams + //**************************** + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE0 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE1 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE2 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE3 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE4 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE5 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE6 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE7 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE8 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xE9 }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xEA }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xEB }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xEC }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xED }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xEE }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + {BitConverter.ToUInt32(new byte[] { 0x00, 0x00, 0x01, 0xEF }, 0), new BlockSizeStruct(PacketSizeType.SizeBytes, 2)}, // Video Stream, two bytes following equal length (Big Endian) + }; + #endregion + + public string FilePath { get; set; } + public string FileExtensionAudio { get; set; } + public string FileExtensionVideo { get; set; } + + public bool HasAudio { get; set; } + public string FinalAudioExtension { get; set; } + + protected Dictionary StreamIdFileType = new Dictionary(); + + public bool UsesSameIdForMultipleAudioTracks { set; get; } // for PMF/PAM/DVD, who use 000001BD for all audio tracks + public bool SubTitleExtractionSupported { set; get; } // assume not supported. + + public bool BlockSizeIsLittleEndian { set; get; } + + protected virtual byte[] GetPacketStartBytes() { return MpegStream.PacketStartBytes; } + + protected virtual byte[] GetPacketEndBytes() { return MpegStream.PacketEndBytes; } + + protected abstract int GetAudioPacketHeaderSize(Stream readStream, long currentOffset); + + protected virtual int GetAudioPacketSubHeaderSize(Stream readStream, long currentOffset, byte streamId) { return 0; } + + protected abstract int GetVideoPacketHeaderSize(Stream readStream, long currentOffset); + + protected virtual int GetAudioPacketFooterSize(Stream readStream, long currentOffset) { return 0; } + + protected virtual int GetVideoPacketFooterSize(Stream readStream, long currentOffset) { return 0; } + + protected virtual bool IsThisAnAudioBlock(byte[] blockToCheck) + { + return ((blockToCheck[3] >= 0xC0) && + (blockToCheck[3] <= 0xDF)); + } + + protected virtual bool IsThisAVideoBlock(byte[] blockToCheck) + { + return ((blockToCheck[3] >= 0xE0) && (blockToCheck[3] <= 0xEF)); + } + + protected virtual bool IsThisASubPictureBlock(byte[] blockToCheck) + { + return ((blockToCheck[3] >= 0xE0) && (blockToCheck[3] <= 0xEF)); + } + + protected virtual string GetAudioFileExtension(Stream readStream, long currentOffset) + { + return this.FileExtensionAudio; + } + + protected virtual string GetVideoFileExtension(Stream readStream, long currentOffset) + { + return this.FileExtensionVideo; + } + + protected virtual byte GetStreamId(Stream readStream, long currentOffset) { return 0; } + + protected virtual long GetStartOffset(Stream readStream, long currentOffset) { return 0; } + + protected virtual void DoFinalTasks(FileStream sourceFileStream, Dictionary outputFiles, bool addHeader) + { + + } + + public virtual void DemultiplexStreams(DemuxOptionsStruct demuxOptions) + { + using (FileStream fs = File.OpenRead(this.FilePath)) + { + long fileSize = fs.Length; + long currentOffset = 0; + + byte[] currentBlockId; + uint currentBlockIdVal; + byte[] currentBlockIdNaming; + + BlockSizeStruct blockStruct = new BlockSizeStruct(); + byte[] blockSizeArray; + uint blockSize; + + int audioBlockSkipSize; + int videoBlockSkipSize; + + int audioBlockFooterSize; + int videoBlockFooterSize; + + int cutSize; + + bool eofFlagFound = false; + + Dictionary streamOutputWriters = new Dictionary(); + string outputFileName; + + byte streamId = 0; // for types that have multiple streams in the same block ID + uint currentStreamKey; // hash key for each file + bool isAudioBlock; + string audioFileExtension; + + // look for first packet + currentOffset = this.GetStartOffset(fs, currentOffset); + currentOffset = ParseFile.GetNextOffset(fs, currentOffset, this.GetPacketStartBytes()); + + if (currentOffset != -1) + { + while (currentOffset < fileSize) + { +#if DEBUG + //if (currentOffset == 0x414080e) + //{ + // int gggg = 1; + //} + + //// hack for bad data (ni no kuni s09.pam) + //if ((currentOffset & 1) == 1) + //{ + // currentOffset = MathUtil.RoundUpToByteAlignment(currentOffset, 0x800); + //} +#endif + + try + { + // get the current block + currentBlockId = ParseFile.ParseSimpleOffset(fs, currentOffset, 4); + + // get value to use as key to hash table + currentBlockIdVal = BitConverter.ToUInt32(currentBlockId, 0); + + if (BlockIdDictionary.ContainsKey(currentBlockIdVal)) + { + // get info about this block type + blockStruct = BlockIdDictionary[currentBlockIdVal]; + + switch (blockStruct.SizeType) + { + ///////////////////// + // Static Block Size + ///////////////////// + case PacketSizeType.Static: + currentOffset += blockStruct.Size; // skip this block + break; + + ////////////////// + // End of Stream + ////////////////// + case PacketSizeType.Eof: + eofFlagFound = true; // set EOF block found so we can exit the loop + break; + + ////////////////////// + // Varying Block Size + ////////////////////// + case PacketSizeType.SizeBytes: + + // Get the block size + blockSizeArray = ParseFile.ParseSimpleOffset(fs, currentOffset + currentBlockId.Length, blockStruct.Size); + + if (!this.BlockSizeIsLittleEndian) + { + Array.Reverse(blockSizeArray); + } + + switch (blockStruct.Size) + { + case 4: + blockSize = (uint)BitConverter.ToUInt32(blockSizeArray, 0); + break; + case 2: + blockSize = (uint)BitConverter.ToUInt16(blockSizeArray, 0); + break; + case 1: + blockSize = (uint)blockSizeArray[0]; + break; + default: + throw new ArgumentOutOfRangeException(String.Format("Unhandled size block size.{0}", Environment.NewLine)); + } + + + // if block type is audio or video, extract it + isAudioBlock = this.IsThisAnAudioBlock(currentBlockId); + + if ((demuxOptions.ExtractAudio && isAudioBlock) || + (demuxOptions.ExtractVideo && this.IsThisAVideoBlock(currentBlockId))) + { + // reset stream id + streamId = 0; + + // if audio block, get the stream number from the queue + if (isAudioBlock && this.UsesSameIdForMultipleAudioTracks) + { + streamId = this.GetStreamId(fs, currentOffset); + currentStreamKey = (streamId | currentBlockIdVal); + } + else + { + currentStreamKey = currentBlockIdVal; + } + + // check if we've already started parsing this stream + if (!streamOutputWriters.ContainsKey(currentStreamKey)) + { + // convert block id to little endian for naming + currentBlockIdNaming = BitConverter.GetBytes(currentStreamKey); + Array.Reverse(currentBlockIdNaming); + + // build output file name + outputFileName = Path.GetFileNameWithoutExtension(this.FilePath); + outputFileName = outputFileName + "_" + BitConverter.ToUInt32(currentBlockIdNaming, 0).ToString("X8"); + + // add proper extension + if (this.IsThisAnAudioBlock(currentBlockId)) + { + audioFileExtension = this.GetAudioFileExtension(fs, currentOffset); + outputFileName += audioFileExtension; + + if (!this.StreamIdFileType.ContainsKey(streamId)) + { + this.StreamIdFileType.Add(streamId, audioFileExtension); + } + } + else + { + this.FileExtensionVideo = this.GetVideoFileExtension(fs, currentOffset); + outputFileName += this.FileExtensionVideo; + } + + // add output directory + outputFileName = Path.Combine(Path.GetDirectoryName(this.FilePath), outputFileName); + + // add an output stream for writing + streamOutputWriters[currentStreamKey] = new FileStream(outputFileName, FileMode.Create, FileAccess.ReadWrite); + } + + // write the block + if (this.IsThisAnAudioBlock(currentBlockId)) + { + // write audio + audioBlockSkipSize = this.GetAudioPacketHeaderSize(fs, currentOffset) + GetAudioPacketSubHeaderSize(fs, currentOffset, streamId); + audioBlockFooterSize = this.GetAudioPacketFooterSize(fs, currentOffset); + cutSize = (int)(blockSize - audioBlockSkipSize - audioBlockFooterSize); + if (cutSize > 0) + { + streamOutputWriters[currentStreamKey].Write(ParseFile.ParseSimpleOffset(fs, currentOffset + currentBlockId.Length + blockSizeArray.Length + audioBlockSkipSize, (int)(blockSize - audioBlockSkipSize)), 0, cutSize); + } +#if DEBUG + //else + //{ + // int aaa = 1; + //} +#endif + } + else + { + // write video + videoBlockSkipSize = this.GetVideoPacketHeaderSize(fs, currentOffset); + videoBlockFooterSize = this.GetVideoPacketFooterSize(fs, currentOffset); + cutSize = (int)(blockSize - videoBlockSkipSize - videoBlockFooterSize); + if (cutSize > 0) + { + streamOutputWriters[currentStreamKey].Write(ParseFile.ParseSimpleOffset(fs, currentOffset + currentBlockId.Length + blockSizeArray.Length + videoBlockSkipSize, (int)(blockSize - videoBlockSkipSize)), 0, cutSize); + } +#if DEBUG + //else + //{ + // int vvv = 1; + //} +#endif + } + } + + // move to next block + currentOffset += currentBlockId.Length + blockSizeArray.Length + blockSize; + blockSizeArray = new byte[] { }; + break; + default: + break; + } + } + else // this is an undexpected block type + { + this.closeAllWriters(streamOutputWriters); + Array.Reverse(currentBlockId); + throw new FormatException(String.Format("Block ID at 0x{0} not found in table: 0x{1}", currentOffset.ToString("X8"), BitConverter.ToUInt32(currentBlockId, 0).ToString("X8"))); + } + + // exit loop if EOF block found + if (eofFlagFound) + { + break; + } + } + catch (Exception _ex) + { + this.closeAllWriters(streamOutputWriters); + throw new Exception(String.Format("Error parsing file at offset {0), '{1}'", currentOffset.ToString("X8"), _ex.Message), _ex); + } + } // while (currentOffset < fileSize) + } + else + { + this.closeAllWriters(streamOutputWriters); + throw new FormatException(String.Format("Cannot find Pack Header for file: {0}{1}", Path.GetFileName(this.FilePath), Environment.NewLine)); + } + + /////////////////////////////////// + // Perform any final tasks needed + /////////////////////////////////// + this.DoFinalTasks(fs, streamOutputWriters, demuxOptions.AddHeader); + + ////////////////////////// + // close all open writers + ////////////////////////// + this.closeAllWriters(streamOutputWriters); + + } // using (FileStream fs = File.OpenRead(path)) + } + + private void closeAllWriters(Dictionary writers) + { + ////////////////////////// + // close all open writers + ////////////////////////// + foreach (uint b in writers.Keys) + { + if (writers[b].CanRead) + { + writers[b].Close(); + writers[b].Dispose(); + } + } + } + + public static int GetMpegStreamType(string path) + { + int mpegType = -1; + + using (FileStream fs = File.OpenRead(path)) + { + // look for first packet + long currentOffset = ParseFile.GetNextOffset(fs, 0, MpegStream.PacketStartBytes); + + if (currentOffset != -1) + { + currentOffset += 4; + fs.Position = currentOffset; + byte idByte = (byte)fs.ReadByte(); + + if ((int)ByteConversion.GetHighNibble(idByte) == 2) + { + mpegType = 1; + } + else if ((int)ByteConversion.GetHighNibble(idByte) == 4) + { + mpegType = 2; + } + } + else + { + throw new FormatException(String.Format("Cannot find Pack Header for file: {0}{1}", Path.GetFileName(path), Environment.NewLine)); + } + } + + return mpegType; + } + } +} diff --git a/UsmToolkit/VGMToolbox/SofdecStream.cs b/UsmToolkit/VGMToolbox/SofdecStream.cs new file mode 100644 index 0000000..3f92928 --- /dev/null +++ b/UsmToolkit/VGMToolbox/SofdecStream.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using VGMToolbox.util; + +namespace VGMToolbox.format +{ + public class SofdecStream : Mpeg1Stream + { + new public const string DefaultVideoExtension = ".m2v"; + + public const string AdxAudioExtension = ".adx"; + public const string AixAudioExtension = ".aix"; + public const string Ac3AudioExtension = ".ac3"; + + public static readonly byte[] AixSignatureBytes = new byte[] { 0x41, 0x49, 0x58, 0x46 }; + public static readonly byte[] Ac3SignatureBytes = new byte[] { 0x0B, 0x77 }; + + public SofdecStream(string path): base(path) + { + this.FileExtensionAudio = AdxAudioExtension; + this.FileExtensionVideo = DefaultVideoExtension; + } + + protected override string GetAudioFileExtension(Stream readStream, long currentOffset) + { + string fileExtension; + byte[] checkBytes, checkBytesAc3; + + int headerSize = this.GetAudioPacketHeaderSize(readStream, currentOffset); + checkBytes = ParseFile.ParseSimpleOffset(readStream, (currentOffset + 6 + headerSize), 4); + + if (ParseFile.CompareSegment(checkBytes, 0, AixSignatureBytes)) + { + fileExtension = AixAudioExtension; + } + else if (checkBytes[0] == 0x80) + { + fileExtension = AdxAudioExtension; + } + else + { + checkBytesAc3 = ParseFile.ParseSimpleOffset(readStream, (currentOffset + 6 + headerSize), 2); + + if (ParseFile.CompareSegment(checkBytesAc3, 0, Ac3SignatureBytes)) + { + fileExtension = Ac3AudioExtension; + } + else + { + fileExtension = ".bin"; + } + } + + return fileExtension; + } + } +} diff --git a/UsmToolkit/build.bat b/UsmToolkit/build.bat new file mode 100644 index 0000000..cd802ee --- /dev/null +++ b/UsmToolkit/build.bat @@ -0,0 +1 @@ +dotnet publish --configuration Release --framework netcoreapp3.1 --output publish /p:DebugType=None /p:DebugSymbols=false \ No newline at end of file diff --git a/UsmToolkit/config.json b/UsmToolkit/config.json new file mode 100644 index 0000000..b2972f1 --- /dev/null +++ b/UsmToolkit/config.json @@ -0,0 +1,5 @@ +{ + "VideoParameter" : "-c:v copy", + "AudioParameter" : "-c:a ac3 -b:a 640k -af pan='stereo|FL=FL+FC+0.5*BL+BR|FR=FR+LFE+0.5*BL+BR'", + "OutputFormat" : "mp4" +} \ No newline at end of file diff --git a/UsmToolkit/deps.json b/UsmToolkit/deps.json new file mode 100644 index 0000000..3d3bafd --- /dev/null +++ b/UsmToolkit/deps.json @@ -0,0 +1,4 @@ +{ + "Vgmstream" : "https://github.com/losnoco/vgmstream/releases/latest/download/test.zip", + "FFmpeg" : "https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.zip" +} \ No newline at end of file diff --git a/UsmToolkit/deps/vgmtutil.dll b/UsmToolkit/deps/vgmtutil.dll new file mode 100644 index 0000000..019909a Binary files /dev/null and b/UsmToolkit/deps/vgmtutil.dll differ