From 0ceb836317af3174f21d23722d65fb3d3294821a Mon Sep 17 00:00:00 2001 From: maxton Date: Thu, 22 Mar 2018 23:28:17 -0400 Subject: [PATCH] Add LibForge --- .gitattributes | 63 + .gitignore | 261 +++++ Dependencies/GameArchives.XML | 1010 +++++++++++++++++ Dependencies/GameArchives.dll | Bin 0 -> 56832 bytes Dependencies/LICENSE | 166 +++ Dependencies/MidiCS.dll | Bin 0 -> 29184 bytes LibForge/ForgeTool/ForgeTool.csproj | 56 + LibForge/ForgeTool/Program.cs | 43 + LibForge/ForgeTool/Properties/AssemblyInfo.cs | 36 + LibForge/LibForge.sln | 31 + LibForge/LibForge/LibForge.csproj | 55 + LibForge/LibForge/Midi/RBMid.cs | 262 +++++ LibForge/LibForge/Midi/RBMidReader.cs | 332 ++++++ LibForge/LibForge/Properties/AssemblyInfo.cs | 36 + LibForge/LibForge/ReaderBase.cs | 55 + LibForge/LibForge/StreamExtensions.cs | 372 ++++++ 16 files changed, 2778 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Dependencies/GameArchives.XML create mode 100644 Dependencies/GameArchives.dll create mode 100644 Dependencies/LICENSE create mode 100644 Dependencies/MidiCS.dll create mode 100644 LibForge/ForgeTool/ForgeTool.csproj create mode 100644 LibForge/ForgeTool/Program.cs create mode 100644 LibForge/ForgeTool/Properties/AssemblyInfo.cs create mode 100644 LibForge/LibForge.sln create mode 100644 LibForge/LibForge/LibForge.csproj create mode 100644 LibForge/LibForge/Midi/RBMid.cs create mode 100644 LibForge/LibForge/Midi/RBMidReader.cs create mode 100644 LibForge/LibForge/Properties/AssemblyInfo.cs create mode 100644 LibForge/LibForge/ReaderBase.cs create mode 100644 LibForge/LibForge/StreamExtensions.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f297d06 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +#* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c4efe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,261 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: 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 +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# 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 +node_modules/ +orleans.codegen.cs + +# 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 + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/Dependencies/GameArchives.XML b/Dependencies/GameArchives.XML new file mode 100644 index 0000000..2c3850c --- /dev/null +++ b/Dependencies/GameArchives.XML @@ -0,0 +1,1010 @@ + + + + GameArchives + + + + + Represent an element of a filesystem, usually directories and files. + + + + + The name of this node. + + + + + The folder where this node resides. + For the root directory, this is null. + + + + + Represents a single file in a filesystem. + + + + + The size of this file. + + + + + Indicates whether this file is compressed in the archive. + + + + + The size of this file, as it is in the archive. + + + + + A collection of extended information about the file. The values in the collection + depend on the type of package the file is from. Modifying this dictionary results in + undefined behavior. + + + + + Get a byte-array in memory containing all the data of this file. + + + + + + Gets a stream that allows access to this file. + + + + + Get a stream (either memory-backed or disk-based) that allows access to this file. + + + + + + Represents a directory within some file system. + + + + + Tries to get the named file. If it is not found, returns false. + + + + + + + + Get the file in this directory with the given name. Throws exception if not found. + + + + Thrown when the file could not be found. + + + + Tries to get the named directory. If it is not found, returns false. + + + + + + + + Get the directory in this directory with the given name. Throws exception if not found. + + + + Thrown when the directory could not be found. + + + + Tries to get the file at the given path, which is relative to this directory. + + + + Thrown when a directory in the path could not be found. + Thrown when the file could not be found. + + + + A collection of all files in this directory. + + + + + A collection of all the directories in this directory. + + + + + Represents some content package which contains a single filesystem. + + + + + The name of this package. + + + + + The root directory of this filesystem. + + + + + The size of this package's data files. For packages with unified header and data, + this is just the size of the package file. + + + + + Indicates whether this package can be modified. + + + + + Implementation of the IDisposable interface. + + + + + Separates elements in a file path. + + + + + The name of the root directory. Never used in paths, though. + + + + + The .NET type of the file objects in this package. + + + + + Get the file at the given path. Path separator is '/'. + Files in the root directory have no path separator. + + + The file at the given path. + + + + Returns a list containing all the logical files of the specified type in this archive. + + + + + + Returns a list containing all the logical files in this archive. + + + + + + Checks if a replacement operation is possible on the given source and target files. + + The file to be overwritten. + The file to read from. + True if the replacement is possible. + + + + Replace the given target file with the given source file. + This modifies the archive file permanently! + + The file to be overwritten. + The file to read from. + True if the replacement is successful. + + + + Ark Package + + + + + Instantiate ark package file from input .hdr file. + Note: will check for data files and throw exception if they're not found. + + Full path to .hdr file + + + + Read the filename table, which is a blob of strings, + then read the filename pointer table which links files to filenames + + + + + Reads the new file table format in v9 and v10 + + + + + Get the directory at the end of this path, or make it (and all + intermediate dirs) if it doesn't exist. + + + + + + + A "protected file" wrapper. + + + + + Constructs a new protected file stream from the given base stream. + + The base stream + + + + A default implementation of a directory. + Useful for archives where directories are implicit. + Important: File and directory names are case-insensitive. + + + + + An uncompressed file which is simply a number of bytes at a certain offset in a stream. + + + + + Constructs a new OffsetFile + + The name of the file, including extension + The directory in which this file resides + Stream which contains this file + Offset into the stream at which the file starts + Length in bytes of the file + + + + A single stream which is actually made up of a number of streams in sequence. + + + + + Denotes whether the stream can be read from. + + + + + Denotes whether the user can seek this stream. + + + + + Denotes whether the user can write to this stream. + + + + + The total length of this file. + + + + + The current position the stream points to within the file. + + + + + Not implemented; read-only stream. + + + + + Reads `count` bytes into `buffer` at offset `offset`. + + + + + + + + + Get the correct stream and offset. + Returns the offset into that stream. + + + + + + + + Seek the stream to given position within the file relative to given origin. + + + + + + + + Not implemented; read-only stream. + + + + + + Not implemented; read-only stream. + + + + + + + + A stream based on another stream, useful for representing + file streams within simple archive packages. + + + + + Constructs a new offset stream on the given base stream with the given offset and length. + + The base stream + Offset into the base stream where this stream starts + Number of bytes in this stream + + + + Represents a directory in the local file system. + All files in the directory are loaded by default, while subdirectories + are loaded on-demand (although this may change in the future). + + + + + Make a shallow instance of the given local directory. + + Location of the directory. + + + + Represents a file on the local filesystem. + + + + + The result of a package test. + + + + + Definitely not an instance of the package type. + + + + + Possibly an instance of the package type, but a more in-depth analysis would be needed. + + + + + Definitely an instance of the package type. + + + + + Collection of methods for reading packages. + + + + + Attempts to read the file as a supported archive package. + If the file is not of a supported format, throws an exception. + + + + Thrown when an unsupported file type is given. + + + + Attempts to read the file as a supported archive package. + If the file is not of a supported format, throws an exception. + + An IFile referring to the archive package. + The package, if it could be opened. + Thrown when an unsupported file type is given. + + + + Tries to read a package given only a stream. This makes a dummy file which works with the package reader. + + Stream to read from. This must support the Length property. + (Optional) the filename to give the dummy file. + + + + + A list of supported file formats and their extensions, presented + in a format that an OpenFileDialog supports. + + + + + Open the .far archive which is the given file. + + + + + + Get the directory at the end of this path, or make it (and all + intermediate dirs) if it doesn't exist. + + + + + + + Parse a directory for its contents. + + The name of this directory. + Location of its filename infos. + File descriptor dictionary + + + + + Hashes a path with a broken fnv132 hashing algorithm + + + + + + + The common name of this package type. + + + + + The common file extensions of this package type. + + + These should be of the format: + *.ext + As expected by an OpenFileDialog filter list. + + + + + Given a file, determines whether the file is + of this package type. + + + + + Given a file which is a valid package, opens it as this + package type, returning the package instance. + + + + + Add an archive package type to the supported types. + + Friendly name for the package type + String-array of typical file extensions, formatted + as *.ext + Function which, given a file, returns a PackageTestResult + which tells if the file is of that package type. + Function which loads the package. + + + + Playstation File System Directory + + + + + Represents a PFS image. + + + + + Open the .psarc archive which is the given file. + + + + + + Get the directory at the end of this path, or make it (and all + intermediate dirs) if it doesn't exist. + + Path from PSARC path file, including filename. + + + + + + Represents a Directory within an STFS package. + + + + + Represents a file within an STFS package. + + + + + The name of this file. + + + + + + Gets up to the first 2GiB of a file. For larger accesses use STFSFileStream. + + + + + + Returns a Stream that allows access to the file's bytes. + + + + + + A stream for accessing file data from within an STFS package. + + + + + Denotes whether the stream can be read from. + + + + + Denotes whether the user can seek this stream. + + + + + Denotes whether the user can write to this stream. + + + + + The total length of this file. + + + + + The current position the stream points to within the file. + + + + + Not implemented; read-only stream. + + + + + Reads `count` bytes into `buffer` at offset `offset`. + + + + + + + + + Seek the stream to given position within the file relative to given origin. + + + + + + + + Not implemented; read-only stream. + + + + + + Not implemented; read-only stream. + + + + + + + + Represents the two supported types of STFS packages (CON and LIVE). + + + + + Package signed with console key. + + + + + Package signed for XBOX Live. + + + + + Package used in system files and on-disc content. + + + + + The ordinal index of this block. + + + + + SHA-1 hash of the block. + + + + + Ordinal number of next block. + + + + + Status byte: + Value Meaning + 0x00 Unused Block + 0x40 Free Block (previously used) + 0x80 Used Block + 0xC0 Newly Allocated Block + + + + + Represents an STFS package. Allows read-only access to files. + + + + + Checks if the given file is an STFS file (LIVE/CON). + + Absolute path to file. + Is the file an STFS? + + + + The stream used to access this STFS package. Typically, this is a FileStream. + + + + + The type of this STFS package (LIVE and CON are supported). + + + + + The directory under which all files in this STFS package live. + + + + + The total size of this STFS package. + + + + + Has this package been disposed? + + + + + The content thumbnail of this package. + + + + + The title thumbnail for this package. + + + + + The filename of this package. + + + + + Holds the block we're currently working on. + + + + + Opens up an STFS file at the given absolute path. + + Path to the STFS file. + New STFS instance which refers to given file. + Thrown if file is not valid STFS package. + + + + Ensure that we dispose upon garbage collection. + + + + + Turns a given block number to an offset within the STFS package. + + + + + + + Returns an array of the file's block numbers in order. + + The starting block index + How many blocks the file has + Do we know the blocks are sequential? (speedup) + + + + + Cache the block at the given byte offset. + Does not check that you're aligned to a block boundary. + + + + + + Dispose this object. + + + + + Read a signed 8-bit integer from the stream. + + + + + + + Read an unsigned 8-bit integer from the stream. + + + + + + + Read an unsigned 16-bit little-endian integer from the stream. + + + + + + + Read a signed 16-bit little-endian integer from the stream. + + + + + + + Read an unsigned 16-bit Big-endian integer from the stream. + + + + + + + Read a signed 16-bit Big-endian integer from the stream. + + + + + + + Read an unsigned 24-bit little-endian integer from the stream. + + + + + + + Read a signed 24-bit little-endian integer from the stream. + + + + + + + Read an unsigned 24-bit Big-endian integer from the stream. + + + + + + + Read a signed 24-bit Big-endian integer from the stream. + + + + + + + Read an unsigned 32-bit little-endian integer from the stream. + + + + + + + Read a signed 32-bit little-endian integer from the stream. + + + + + + + Read an unsigned 32-bit Big-endian integer from the stream. + + + + + + + Read a signed 32-bit Big-endian integer from the stream. + + + + + + + Read an unsigned 64-bit little-endian integer from the stream. + + + + + + + Read a signed 64-bit little-endian integer from the stream. + + + + + + + Read an unsigned 64-bit big-endian integer from the stream. + + + + + + + Read a signed 64-bit big-endian integer from the stream. + + + + + + + Reads a multibyte value of the specified length from the stream. + + The stream + Must be less than or equal to 8 + + + + + Read a single-precision (4-byte) floating-point value from the stream. + + + + + + + Read a null-terminated ASCII string from the given stream. + + + + + + + Read a length-prefixed string of the specified encoding type from the file. + The length is a 32-bit little endian integer. + + + The encoding to use to decode the string. + + + + + Read a length-prefixed UTF-8 string from the given stream. + + + + + + + Read a given number of bytes from a stream into a new byte array. + + + Number of bytes to read (maximum) + New byte array of size <=count. + + + + Read a variable-length integral value as found in MIDI messages. + + + + + + + Open the .far archive which is the given file. + + + + + + Opens a directory from the local filesystem as an IDirectory + + Path to the directory. + An IDirectory representing the local directory. + + + + Create an instance of an IFile from the given local path. + Note that this creates a new LocalDirectory object each time it is + called. If you are opening a lot of files from one directory, it's more + efficient to grab the directory with Util.LocalDir(), then get each + file from there. + + + + + + + Returns the last element of this array. + + The type of the array. + + The last element of the array. + + + + Saves this file to the given path. Overwrites existing files. + + + + + + + Copies one stream to the other. + + The source stream. + The destination stream. + + + + Xbox (360) ISO Directory + + + + + Represents an element of an XISO file system. + + + + + The location of the filesystem entry node in the ISO. + + + + + Represents an Xbox/Xbox 360 disc image. + + + + diff --git a/Dependencies/GameArchives.dll b/Dependencies/GameArchives.dll new file mode 100644 index 0000000000000000000000000000000000000000..198bfca86d8fe49c7bb9f552420c09656331134e GIT binary patch literal 56832 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 literal 0 HcmV?d00001 diff --git a/Dependencies/LICENSE b/Dependencies/LICENSE new file mode 100644 index 0000000..341c30b --- /dev/null +++ b/Dependencies/LICENSE @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/Dependencies/MidiCS.dll b/Dependencies/MidiCS.dll new file mode 100644 index 0000000000000000000000000000000000000000..f36ad00431173f4d3be907fe808ad117deb18432 GIT binary patch literal 29184 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 + + + + Debug + AnyCPU + {CB848B8C-A1B6-4777-A674-D3E5C255AAC1} + Exe + ForgeTool + ForgeTool + v4.0 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\Dependencies\MidiCS.dll + + + + + + + + + + + + + + + + {3684b7e6-0978-487a-895c-d0ed8f6b7b9a} + LibForge + + + + \ No newline at end of file diff --git a/LibForge/ForgeTool/Program.cs b/LibForge/ForgeTool/Program.cs new file mode 100644 index 0000000..13a6718 --- /dev/null +++ b/LibForge/ForgeTool/Program.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; +using LibForge.Midi; + +namespace ForgeTool +{ + class Program + { + static void Main(string[] args) + { + if(args.Length < 1) + { + Usage(); + return; + } + switch (args[0]) + { + case "rbmid2mid": + var input = args[1]; + var output = args[2]; + using (var fi = File.OpenRead(input)) + using (var fo = File.OpenWrite(output)) + { + var rbmid = RBMidReader.ReadStream(fi); + var midi = rbmid.ToMidiFile(); + MidiCS.MidiFileWriter.WriteSMF(midi, fo); + } + break; + default: + Usage(); + break; + } + } + + static void Usage() + { + Console.WriteLine("Usage: ForgeTool.exe [options]"); + Console.WriteLine("Verbs: "); + Console.WriteLine(" rbmid2mid "); + Console.WriteLine(" - converts a Forge midi to a Standard Midi File"); + } + } +} diff --git a/LibForge/ForgeTool/Properties/AssemblyInfo.cs b/LibForge/ForgeTool/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..20ff57f --- /dev/null +++ b/LibForge/ForgeTool/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ForgeTool")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ForgeTool")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cb848b8c-a1b6-4777-a674-d3e5c255aac1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/LibForge/LibForge.sln b/LibForge/LibForge.sln new file mode 100644 index 0000000..2490e98 --- /dev/null +++ b/LibForge/LibForge.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibForge", "LibForge\LibForge.csproj", "{3684B7E6-0978-487A-895C-D0ED8F6B7B9A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ForgeTool", "ForgeTool\ForgeTool.csproj", "{CB848B8C-A1B6-4777-A674-D3E5C255AAC1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3684B7E6-0978-487A-895C-D0ED8F6B7B9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3684B7E6-0978-487A-895C-D0ED8F6B7B9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3684B7E6-0978-487A-895C-D0ED8F6B7B9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3684B7E6-0978-487A-895C-D0ED8F6B7B9A}.Release|Any CPU.Build.0 = Release|Any CPU + {CB848B8C-A1B6-4777-A674-D3E5C255AAC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB848B8C-A1B6-4777-A674-D3E5C255AAC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB848B8C-A1B6-4777-A674-D3E5C255AAC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB848B8C-A1B6-4777-A674-D3E5C255AAC1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {361789AF-FA0C-458A-8AE3-2AC3F559CBC0} + EndGlobalSection +EndGlobal diff --git a/LibForge/LibForge/LibForge.csproj b/LibForge/LibForge/LibForge.csproj new file mode 100644 index 0000000..4ad8ebc --- /dev/null +++ b/LibForge/LibForge/LibForge.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {3684B7E6-0978-487A-895C-D0ED8F6B7B9A} + Library + Properties + LibForge + LibForge + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\Dependencies\GameArchives.dll + + + ..\..\Dependencies\MidiCS.dll + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LibForge/LibForge/Midi/RBMid.cs b/LibForge/LibForge/Midi/RBMid.cs new file mode 100644 index 0000000..cae1442 --- /dev/null +++ b/LibForge/LibForge/Midi/RBMid.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using LibForge.Extensions; +using GameArchives.Common; +using MidiCS; + + +namespace LibForge.Midi +{ + public class RBMid + { + public MidiFile ToMidiFile() => + new MidiFile(MidiFormat.MultiTrack, new List(MidiTracks), 480); + + public struct TICKTEXT + { + public uint Tick; + public string Text; + } + public struct LYRICS + { + public string TrackName; + public TICKTEXT[] Lyrics; + public int Unknown1; + public int Unknown2; + public byte Unknown3; + } + + public struct DRUMFILLS + { + public struct FILLS_UNK + { + public uint Tick; + public uint Unknown; + } + public struct DRUMFILL + { + public uint StartTick; + public uint EndTick; + public byte Unknown; + } + public FILLS_UNK[] Unknown; + public DRUMFILL[] Fills; + } + public struct ANIM + { + public struct EVENT + { + public float Time; + public uint Tick; + public int Unknown1; + public int KeyBitfield; + public int Unknown2; + public short Unknown3; + } + public string TrackName; + public int Unknown1; + public int Unknown2; + public EVENT[] Events; + public int Unknown3; + } + + public struct MARKERS + { + public struct MARKER + { + public uint Tick; + [Flags] + public enum FLAGS : int { + Unk = 1, + Unk2 = 2, + ProYellow = 4, + ProBlue = 8, + ProGreen = 16 + } + public FLAGS Flags; + } + public MARKER[] Markers; + public int Unknown1; + public int Unknown2; + } + + public struct UNKTRACK + { + // TODO + } + + public struct UNKTRACK2 + { + public struct DATA + { + public uint Tick1; + public uint Tick2; + public int Unknown1; + public int Unknown2; + } + public DATA[][] Data; + } + + public struct DRUMMIXES + { + // 1st dimension: difficulties + public TICKTEXT[][] Mixes; + } + + public struct GEMTRACK + { + public struct GEM + { + public float StartMillis; + public uint StartTicks; + public short Unknown; + public short Unknown2; + public byte Unknown3; + public int Unknown4; + public byte Unknown5; + public int Unknown6; + } + public GEM[][] Gems; + public int Unknown; + } + public struct UNKTRACK3 + { + public struct EVENT + { + public uint Tick1; + public uint Tick2; + } + public EVENT[][][] Unknown; + } + public struct VOCALTRACK + { + public struct PHRASE_MARKER + { + public float StartMillis; + public float Length; + public int Unknown1; + public int Unknown2; + public int Unknown3; + public int Unknown4; + public int Unknown5; + public byte[] Unknown6; + } + public struct VOCAL_NOTE + { + public int Type; + public int MidiNote; + public int MidiNote2; + public float StartMillis; + public uint StartTick; + public float Length; + public short Unknown1; + public string Lyric; + public byte[] Unknown2; + } + public struct UNKNOWN + { + public float Unknown1; + public float Unknown2; + } + public PHRASE_MARKER[] PhraseMarkers; + public PHRASE_MARKER[] PhraseMarkers2; + public VOCAL_NOTE[] Notes; + public int[] Unknown1; + public UNKNOWN[] Unknown2; + } + public struct HANDMAP + { + public struct MAP + { + public float StartMillis; + public int Map; + } + public MAP[] Maps; + } + public struct HANDPOS + { + public struct POS + { + public float StartMillis; + public float EndMillis; + public int Position; + public byte Unknown; + } + public POS[] Events; + } + public struct UNKTRACK4 + { + public float[] Unknown; + } + public struct UNKSTRUCT + { + public uint StartTick; + public uint EndTick; + public int Unknown; + } + public struct UNKMARKUP { + public uint StartTick; + public uint EndTick; + public int[] Unknown; + } + public struct TEMPO + { + public float StartMillis; + public uint StartTick; + public int Tempo; + } + public struct TIMESIG + { + public int Unknown; + public uint Tick; + public short Numerator; + public short Denominator; + } + public struct BEAT + { + public uint Tick; + public bool Downbeat; + } + public int Format; + public LYRICS[] Lyrics; + public DRUMFILLS[] DrumFills; + public ANIM[] Anims; + public MARKERS[] ProMarkers; + public UNKTRACK[] UnkTrack; + public UNKTRACK2[] UnkTrack2; + public DRUMMIXES[] DrumMixes; + public GEMTRACK[] GemTracks; + public UNKTRACK3[] UnkTrack3; + public VOCALTRACK[] VocalTracks; + public int Unknown1; + public int Unknown2; + public float Unknown3; + public int Unknown4; + public int Unknown5; + public int Unknown6; + public int Unknown7; + public float Unknown8; + public float Unknown9; + public int Unknown10; + public int Unknown11; + public int Unknown12; + public byte Unknown13; + public float Unknown14; + public float Unknown15; + public HANDMAP[] GuitarHandmap; + public HANDPOS[] GuitarLeftHandPos; + public UNKTRACK4[] Unktrack4; + public UNKSTRUCT[] Unkstruct; + public UNKMARKUP[] UnkMarkup; + public UNKSTRUCT[] Unkstruct2; + public MidiCS.MidiTrack[] MidiTracks; + public TEMPO[] Tempos; + public TIMESIG[] TimeSigs; + public BEAT[] Beats; + public string[] MidiTrackNames; + } + +} diff --git a/LibForge/LibForge/Midi/RBMidReader.cs b/LibForge/LibForge/Midi/RBMidReader.cs new file mode 100644 index 0000000..a4c790e --- /dev/null +++ b/LibForge/LibForge/Midi/RBMidReader.cs @@ -0,0 +1,332 @@ +using System; +using System.Collections.Generic; +using System.IO; +using LibForge.Util; +using MidiCS; +using MidiCS.Events; + +namespace LibForge.Midi +{ + public class RBMidReader : ReaderBase + { + public static RBMid ReadStream(Stream s) + { + return new RBMidReader(s).Read(); + } + public RBMidReader(Stream s) : base(s) { } + + private RBMid Read() + { + return new RBMid + { + Format = Int(), + Lyrics = Arr(ReadLyrics), + DrumFills = Arr(ReadDrumFills), + Anims = Arr(ReadAnims), + ProMarkers = Arr(ReadMarkers), + UnkTrack = Arr(ReadUnktrack), + UnkTrack2 = Arr(ReadUnktrack2), + DrumMixes = Arr(ReadDrumMixes), + GemTracks = Arr(ReadGemTracks), + UnkTrack3 = Arr(ReadUnktrack3), + VocalTracks = Arr(ReadVocalTrack), + Unknown1 = Int(), + Unknown2 = Int(), + Unknown3 = Float(), + Unknown4 = Int(), + Unknown5 = Int(), + Unknown6 = Int(), + Unknown7 = Int(), + Unknown8 = Float(), + Unknown9 = Float(), + Unknown10 = Int(), + Unknown11 = Int(), + Unknown12 = Int(), + Unknown13 = Byte(), + Unknown14 = Float(), + Unknown15 = Float(), + GuitarHandmap = Arr(ReadHandMap), + GuitarLeftHandPos = Arr(ReadHandPos), + Unktrack4 = Arr(ReadUnktrack4), + Unkstruct = Arr(ReadUnkstruct).Then(Skip(4)), + UnkMarkup = Arr(ReadUnkmarkup).Then(Skip(4)), + Unkstruct2 = Arr(ReadUnkstruct).Then(Skip(12)), + MidiTracks = Arr(ReadMidiTrack).Then(Skip(13*4)), + Tempos = Arr(ReadTempo), + TimeSigs = Arr(ReadTimesig), + Beats = Arr(ReadBeat).Then(Skip(4)), + MidiTrackNames = Arr(String) + }; + } + private RBMid.TICKTEXT ReadTickText() => new RBMid.TICKTEXT + { + Tick = UInt(), + Text = String() + }; + private RBMid.LYRICS ReadLyrics() => new RBMid.LYRICS + { + TrackName = String(), + Lyrics = Arr(ReadTickText), + Unknown1 = Int(), + Unknown2 = Int(), + Unknown3 = Byte() + }; + private RBMid.DRUMFILLS ReadDrumFills() => new RBMid.DRUMFILLS + { + Unknown = Arr(() => new RBMid.DRUMFILLS.FILLS_UNK + { + Tick = UInt(), + Unknown = UInt() + }), + Fills = Arr(() => new RBMid.DRUMFILLS.DRUMFILL + { + StartTick = UInt(), + EndTick = UInt(), + Unknown = Byte() + }) + }; + private RBMid.ANIM ReadAnims() => new RBMid.ANIM + { + TrackName = String(), + Unknown1 = Int(), + Unknown2 = Int(), + Events = Arr(() => new RBMid.ANIM.EVENT + { + Time = Float(), + Tick = UInt(), + Unknown1 = Int(), + KeyBitfield = Int(), + Unknown2 = Int(), + Unknown3 = Short() + }), + Unknown3 = Int() + }; + private RBMid.MARKERS ReadMarkers() => new RBMid.MARKERS + { + Markers = Arr(() => new RBMid.MARKERS.MARKER + { + Tick = UInt(), + Flags = (RBMid.MARKERS.MARKER.FLAGS)Int() + }), + Unknown1 = Int(), + Unknown2 = Int() + }; + private RBMid.UNKTRACK ReadUnktrack() + { + var ret = default(RBMid.UNKTRACK); + var num = Int(); + if(num > 0) + { + for (var i = 0; i < num; i++) { + var data = Int(); + if (data > 0) + { + Skip(8); + } + } + Skip(4)(); + } + return ret; + } + private RBMid.UNKTRACK2 ReadUnktrack2() => new RBMid.UNKTRACK2 + { + Data = Arr(() => Arr(() => new RBMid.UNKTRACK2.DATA + { + Tick1 = UInt(), + Tick2 = UInt(), + Unknown1 = Int(), + Unknown2 = Int() + })) + }; + private RBMid.DRUMMIXES ReadDrumMixes() => new RBMid.DRUMMIXES + { + Mixes = Arr(() => Arr(ReadTickText)) + }; + private RBMid.GEMTRACK ReadGemTracks() => new RBMid.GEMTRACK + { + Gems = Arr(Seq(Skip(4), () => Arr(() => new RBMid.GEMTRACK.GEM + { + StartMillis = Float(), + StartTicks = UInt(), + Unknown = Short(), + Unknown2 = Short(), + Unknown3 = Byte(), + Unknown4 = Int(), + Unknown5 = Byte(), + Unknown6 = Int() + }))), + Unknown = Int() + }; + private RBMid.UNKTRACK3 ReadUnktrack3() => new RBMid.UNKTRACK3 + { + Unknown = Arr(() => Arr(() => Arr(() => new RBMid.UNKTRACK3.EVENT + { + Tick1 = UInt(), + Tick2 = UInt() + }))) + }; + private RBMid.VOCALTRACK ReadVocalTrack() => new RBMid.VOCALTRACK + { + PhraseMarkers = Arr(ReadPhraseMarker), + PhraseMarkers2 = Arr(ReadPhraseMarker), + Notes = Arr(() => new RBMid.VOCALTRACK.VOCAL_NOTE + { + Type = Int(), + MidiNote = Int(), + MidiNote2 = Int(), + StartMillis = Float(), + StartTick = UInt(), + Length = Float(), + Unknown1 = Short(), + Lyric = String(), + Unknown2 = Arr(Byte, 9) + }), + Unknown1 = Arr(Int), + Unknown2 = Arr(() => new RBMid.VOCALTRACK.UNKNOWN + { + Unknown1 = Float(), + Unknown2 = Float() + }) + }; + private RBMid.VOCALTRACK.PHRASE_MARKER ReadPhraseMarker() => + new RBMid.VOCALTRACK.PHRASE_MARKER + { + StartMillis = Float(), + Length = Float(), + Unknown1 = Int(), + Unknown2 = Int(), + Unknown3 = Int(), + Unknown4 = Int(), + Unknown5 = Int(), + Unknown6 = Arr(Byte, 25) + }; + private RBMid.HANDMAP ReadHandMap() => new RBMid.HANDMAP + { + Maps = Arr(() => new RBMid.HANDMAP.MAP + { + StartMillis = Float(), + Map = Int() + }) + }; + private RBMid.HANDPOS ReadHandPos() => new RBMid.HANDPOS + { + Events = Arr(() => new RBMid.HANDPOS.POS + { + StartMillis = Float(), + EndMillis = Float(), + Position = Int(), + Unknown = Byte() + }) + }; + private RBMid.UNKTRACK4 ReadUnktrack4() => new RBMid.UNKTRACK4 + { + Unknown = Arr(Float) + }; + private RBMid.UNKSTRUCT ReadUnkstruct() => new RBMid.UNKSTRUCT + { + StartTick = UInt(), + EndTick = UInt(), + Unknown = Int() + }; + private RBMid.UNKMARKUP ReadUnkmarkup() => new RBMid.UNKMARKUP + { + StartTick = UInt(), + EndTick = UInt(), + Unknown = Arr(Int) + }; + private uint midiTick; + private string trackName; + private string[] trackStrings; + private MidiTrack ReadMidiTrack() + { + var unk = Byte(); + var unk2 = Int(); + var num_events = Int(); + midiTick = 0; + trackName = ""; + var start = s.Position; + s.Position += num_events * 8; + trackStrings = Arr(String); + var end = s.Position; + s.Position = start; + var msgs = new List(Arr(ReadMessage, num_events)); + msgs.Add(new EndOfTrackEvent(0)); + s.Position = end; + return new MidiTrack(msgs, midiTick, trackName); + } + private IMidiMessage ReadMessage() + { + var tick = UInt(); + var deltaTime = tick - midiTick; + midiTick = tick; + var kind = Byte(); + switch (kind) + { + // Midi Messages + case 1: + var tc = Byte(); + var channel = (byte)(tc & 0xF); + var type = tc >> 4; + var note = Byte(); + var velocity = Byte(); + switch (type) + { + case 8: + return new NoteOffEvent(deltaTime, channel, note, velocity); + case 9: + return new NoteOnEvent(deltaTime, channel, note, velocity); + default: + throw new NotImplementedException($"Message type {type}"); + } + // Tempo + case 2: + var tempo_msb = (uint)Byte(); + var tempo_lsb = UShort(); + return new TempoEvent(deltaTime, tempo_msb << 16 | tempo_lsb); + // Time Signature + case 4: + var num = Byte(); + var denom = Byte(); + var denom_pow2 = (byte)Math.Log(denom, 2); + Skip(1)(); + return new TimeSignature(deltaTime, num, denom_pow2, 24, 8); + // Text + case 8: + var ttype = Byte(); + var txt = trackStrings[Short()]; + switch (ttype) + { + case 1: + return new TextEvent(deltaTime, txt); + case 3: + trackName = txt; + return new TrackName(deltaTime, txt); + case 5: + return new Lyric(deltaTime, txt); + default: + throw new NotImplementedException($"Text event {ttype} not implemented"); + } + default: + throw new NotImplementedException($"Message kind {kind} not yet known"); + } + } + private RBMid.TEMPO ReadTempo() => new RBMid.TEMPO + { + StartMillis = Float(), + StartTick = UInt(), + Tempo = Int() + }; + private RBMid.TIMESIG ReadTimesig() => new RBMid.TIMESIG + { + Unknown = Int(), + Tick = UInt(), + Numerator = Short(), + Denominator = Short() + }; + private RBMid.BEAT ReadBeat() => new RBMid.BEAT + { + Tick = UInt(), + Downbeat = Int() != 0 + }; + } +} diff --git a/LibForge/LibForge/Properties/AssemblyInfo.cs b/LibForge/LibForge/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f425682 --- /dev/null +++ b/LibForge/LibForge/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LibForge")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LibForge")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3684b7e6-0978-487a-895c-d0ed8f6b7b9a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/LibForge/LibForge/ReaderBase.cs b/LibForge/LibForge/ReaderBase.cs new file mode 100644 index 0000000..9f0079c --- /dev/null +++ b/LibForge/LibForge/ReaderBase.cs @@ -0,0 +1,55 @@ +using System; +using System.Text; +using LibForge.Extensions; + +namespace LibForge.Util +{ + static class ReaderExtensions + { + // For doing side effects else fluently in an expression + public static T Then(this T v, Action a) + { + a(); + return v; + } + } + public abstract class ReaderBase + { + protected System.IO.Stream s; + protected ReaderBase(System.IO.Stream s) + { + this.s = s; + } + protected static Func Seq(Action a, Func v) + { + return () => + { + a(); + return v(); + }; + } + + // For reading a fixed size array of something + protected T[] Arr(Func constructor, int size) + { + var arr = new T[size]; + for (var i = 0; i < size; i++) + arr[i] = constructor(); + + return arr; + } + // For reading a length-prefixed array of something + protected T[] Arr(Func constructor) => Arr(constructor, Int()); + // For skipping unknown data + protected Action Skip(int count) => () => s.Position += count; + // For reading simple types + protected int Int() => s.ReadInt32LE(); + protected uint UInt() => s.ReadUInt32LE(); + protected float Float() => s.ReadFloat(); + protected short Short() => s.ReadInt16LE(); + protected ushort UShort() => s.ReadUInt16LE(); + protected byte Byte() => (byte)s.ReadByte(); + protected string String() => s.ReadLengthPrefixedString(Encoding.ASCII); + protected uint UInt24() => s.ReadUInt24LE(); + } +} diff --git a/LibForge/LibForge/StreamExtensions.cs b/LibForge/LibForge/StreamExtensions.cs new file mode 100644 index 0000000..9cb5d12 --- /dev/null +++ b/LibForge/LibForge/StreamExtensions.cs @@ -0,0 +1,372 @@ +using System; +using System.IO; +using System.Text; + +namespace LibForge.Extensions +{ + internal static class StreamExtensions + { + /// + /// Read a signed 8-bit integer from the stream. + /// + /// + /// + public static sbyte ReadInt8(this Stream s) => unchecked((sbyte)s.ReadUInt8()); + + /// + /// Read an unsigned 8-bit integer from the stream. + /// + /// + /// + public static byte ReadUInt8(this Stream s) + { + byte ret; + byte[] tmp = new byte[1]; + s.Read(tmp, 0, 1); + ret = tmp[0]; + return ret; + } + + /// + /// Read an unsigned 16-bit little-endian integer from the stream. + /// + /// + /// + public static ushort ReadUInt16LE(this Stream s) => unchecked((ushort)s.ReadInt16LE()); + + /// + /// Read a signed 16-bit little-endian integer from the stream. + /// + /// + /// + public static short ReadInt16LE(this Stream s) + { + int ret; + byte[] tmp = new byte[2]; + s.Read(tmp, 0, 2); + ret = tmp[0] & 0x00FF; + ret |= (tmp[1] << 8) & 0xFF00; + return (short)ret; + } + + /// + /// Read an unsigned 16-bit Big-endian integer from the stream. + /// + /// + /// + public static ushort ReadUInt16BE(this Stream s) => unchecked((ushort)s.ReadInt16BE()); + + /// + /// Read a signed 16-bit Big-endian integer from the stream. + /// + /// + /// + public static short ReadInt16BE(this Stream s) + { + int ret; + byte[] tmp = new byte[2]; + s.Read(tmp, 0, 2); + ret = (tmp[0] << 8) & 0xFF00; + ret |= tmp[1] & 0x00FF; + return (short)ret; + } + + /// + /// Read an unsigned 24-bit little-endian integer from the stream. + /// + /// + /// + public static uint ReadUInt24LE(this Stream s) + { + int ret; + byte[] tmp = new byte[3]; + s.Read(tmp, 0, 3); + ret = tmp[0] & 0x0000FF; + ret |= (tmp[1] << 8) & 0x00FF00; + ret |= (tmp[2] << 16) & 0xFF0000; + return unchecked((uint)ret); + } + + /// + /// Read a signed 24-bit little-endian integer from the stream. + /// + /// + /// + public static int ReadInt24LE(this Stream s) + { + int ret; + byte[] tmp = new byte[3]; + s.Read(tmp, 0, 3); + ret = tmp[0] & 0x0000FF; + ret |= (tmp[1] << 8) & 0x00FF00; + ret |= (tmp[2] << 16) & 0xFF0000; + if ((tmp[2] & 0x80) == 0x80) + { + ret |= 0xFF << 24; + } + return ret; + } + + /// + /// Read an unsigned 24-bit Big-endian integer from the stream. + /// + /// + /// + public static uint ReadUInt24BE(this Stream s) + { + int ret; + byte[] tmp = new byte[3]; + s.Read(tmp, 0, 3); + ret = tmp[2] & 0x0000FF; + ret |= (tmp[1] << 8) & 0x00FF00; + ret |= (tmp[0] << 16) & 0xFF0000; + return (uint)ret; + } + + /// + /// Read a signed 24-bit Big-endian integer from the stream. + /// + /// + /// + public static int ReadInt24BE(this Stream s) + { + int ret; + byte[] tmp = new byte[3]; + s.Read(tmp, 0, 3); + ret = tmp[2] & 0x0000FF; + ret |= (tmp[1] << 8) & 0x00FF00; + ret |= (tmp[0] << 16) & 0xFF0000; + if ((tmp[0] & 0x80) == 0x80) + { + ret |= 0xFF << 24; // sign-extend + } + return ret; + } + + /// + /// Read an unsigned 32-bit little-endian integer from the stream. + /// + /// + /// + public static uint ReadUInt32LE(this Stream s) => unchecked((uint)s.ReadInt32LE()); + + /// + /// Read a signed 32-bit little-endian integer from the stream. + /// + /// + /// + public static int ReadInt32LE(this Stream s) + { + int ret; + byte[] tmp = new byte[4]; + s.Read(tmp, 0, 4); + ret = tmp[0] & 0x000000FF; + ret |= (tmp[1] << 8) & 0x0000FF00; + ret |= (tmp[2] << 16) & 0x00FF0000; + ret |= (tmp[3] << 24); + return ret; + } + + /// + /// Read an unsigned 32-bit Big-endian integer from the stream. + /// + /// + /// + public static uint ReadUInt32BE(this Stream s) => unchecked((uint)s.ReadInt32BE()); + + /// + /// Read a signed 32-bit Big-endian integer from the stream. + /// + /// + /// + public static int ReadInt32BE(this Stream s) + { + int ret; + byte[] tmp = new byte[4]; + s.Read(tmp, 0, 4); + ret = (tmp[0] << 24); + ret |= (tmp[1] << 16) & 0x00FF0000; + ret |= (tmp[2] << 8) & 0x0000FF00; + ret |= tmp[3] & 0x000000FF; + return ret; + } + + /// + /// Read an unsigned 64-bit little-endian integer from the stream. + /// + /// + /// + public static ulong ReadUInt64LE(this Stream s) => unchecked((ulong)s.ReadInt64LE()); + + /// + /// Read a signed 64-bit little-endian integer from the stream. + /// + /// + /// + public static long ReadInt64LE(this Stream s) + { + long ret; + byte[] tmp = new byte[8]; + s.Read(tmp, 0, 8); + ret = tmp[4] & 0x000000FFL; + ret |= (tmp[5] << 8) & 0x0000FF00L; + ret |= (tmp[6] << 16) & 0x00FF0000L; + ret |= (tmp[7] << 24) & 0xFF000000L; + ret <<= 32; + ret |= tmp[0] & 0x000000FFL; + ret |= (tmp[1] << 8) & 0x0000FF00L; + ret |= (tmp[2] << 16) & 0x00FF0000L; + ret |= (tmp[3] << 24) & 0xFF000000L; + return ret; + } + + /// + /// Read an unsigned 64-bit big-endian integer from the stream. + /// + /// + /// + public static ulong ReadUInt64BE(this Stream s) => unchecked((ulong)s.ReadInt64BE()); + + /// + /// Read a signed 64-bit big-endian integer from the stream. + /// + /// + /// + public static long ReadInt64BE(this Stream s) + { + long ret; + byte[] tmp = new byte[8]; + s.Read(tmp, 0, 8); + ret = tmp[3] & 0x000000FFL; + ret |= (tmp[2] << 8) & 0x0000FF00L; + ret |= (tmp[1] << 16) & 0x00FF0000L; + ret |= (tmp[0] << 24) & 0xFF000000L; + ret <<= 32; + ret |= tmp[7] & 0x000000FFL; + ret |= (tmp[6] << 8) & 0x0000FF00L; + ret |= (tmp[5] << 16) & 0x00FF0000L; + ret |= (tmp[4] << 24) & 0xFF000000L; + return ret; + } + + /// + /// Reads a multibyte value of the specified length from the stream. + /// + /// The stream + /// Must be less than or equal to 8 + /// + public static long ReadMultibyteBE(this Stream s, byte bytes) + { + if (bytes > 8) return 0; + long ret = 0; + var b = s.ReadBytes(bytes); + for (uint i = 0; i < b.Length; i++) + { + ret <<= 8; + ret |= b[i]; + } + return ret; + } + + /// + /// Read a single-precision (4-byte) floating-point value from the stream. + /// + /// + /// + public static float ReadFloat(this Stream s) + { + byte[] tmp = new byte[4]; + s.Read(tmp, 0, 4); + return BitConverter.ToSingle(tmp, 0); + } + + /// + /// Read a null-terminated ASCII string from the given stream. + /// + /// + /// + public static string ReadASCIINullTerminated(this Stream s, int limit = -1) + { + StringBuilder sb = new StringBuilder(255); + char cur; + while ((limit == -1 || sb.Length < limit) && (cur = (char)s.ReadByte()) != 0) + { + sb.Append(cur); + } + return sb.ToString(); + } + + /// + /// Read a length-prefixed string of the specified encoding type from the file. + /// The length is a 32-bit little endian integer. + /// + /// + /// The encoding to use to decode the string. + /// + public static string ReadLengthPrefixedString(this Stream s, Encoding e) + { + int length = s.ReadInt32LE(); + byte[] chars = new byte[length]; + s.Read(chars, 0, length); + return e.GetString(chars); + } + + /// + /// Read a length-prefixed UTF-8 string from the given stream. + /// + /// + /// + public static string ReadLengthUTF8(this Stream s) + { + return s.ReadLengthPrefixedString(Encoding.UTF8); + } + + /// + /// Read a given number of bytes from a stream into a new byte array. + /// + /// + /// Number of bytes to read (maximum) + /// New byte array of size <=count. + public static byte[] ReadBytes(this Stream s, int count) + { + // Size of returned array at most count, at least difference between position and length. + int realCount = (int)((s.Position + count > s.Length) ? (s.Length - s.Position) : count); + byte[] ret = new byte[realCount]; + s.Read(ret, 0, realCount); + return ret; + } + + /// + /// Read a variable-length integral value as found in MIDI messages. + /// + /// + /// + public static int ReadMidiMultiByte(this Stream s) + { + int ret = 0; + byte b = (byte)(s.ReadByte()); + ret += b & 0x7f; + if (0x80 == (b & 0x80)) + { + ret <<= 7; + b = (byte)(s.ReadByte()); + ret += b & 0x7f; + if (0x80 == (b & 0x80)) + { + ret <<= 7; + b = (byte)(s.ReadByte()); + ret += b & 0x7f; + if (0x80 == (b & 0x80)) + { + ret <<= 7; + b = (byte)(s.ReadByte()); + ret += b & 0x7f; + if (0x80 == (b & 0x80)) + throw new InvalidDataException("Variable-length MIDI number > 4 bytes"); + } + } + } + return ret; + } + } +}