diff --git a/Documentation/images/editors/epicumx.jpg b/Documentation/images/editors/epicumx.jpg new file mode 100644 index 00000000..c8f1ea8a --- /dev/null +++ b/Documentation/images/editors/epicumx.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91a288801c3eaab1fcfcc55f65c73303bf72d7d5303bd9456141141063cf0ed8 +size 31943 diff --git a/Documentation/images/editors/epicumx_t.png b/Documentation/images/editors/epicumx_t.png new file mode 100644 index 00000000..28c4c6f2 --- /dev/null +++ b/Documentation/images/editors/epicumx_t.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1065ae642b5d5043520fcee9043d1e124cf1fca76c23e7a12300974a75b43a7 +size 8610 diff --git a/Documentation/images/editors/unreal.png b/Documentation/images/editors/unreal.png deleted file mode 100644 index b29b7e0b..00000000 --- a/Documentation/images/editors/unreal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:56a56a2b261bd5cfc0a8d17373b3f2ce7c5d702f9b6f14b8439bd2211c1b4b6b -size 7356 diff --git a/Documentation/images/editors/unreal_t.png b/Documentation/images/editors/unreal_t.png deleted file mode 100644 index c8cd6300..00000000 --- a/Documentation/images/editors/unreal_t.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ca5858bcef7bd48f7d801f44af64395f23b59ef097708d6be86ddf6d7803aa1 -size 5709 diff --git a/Documentation/moduletypes.html b/Documentation/moduletypes.html index c46e6b5f..ea5f692b 100644 --- a/Documentation/moduletypes.html +++ b/Documentation/moduletypes.html @@ -483,6 +483,22 @@ + + + + + + +

Epic Games UMX - This converter recognizes the modules in “umx” files from games like “Unreal”, “DeusEx”, etc. To NostalgicPlayer, UMX is just a container and the real music format may be one of “ScreamTracker 3”, “Impulse Tracker”, “FastTracker 2”, or possibly a “ProTracker” compatible one.

+

File extension

+

.umx

+

Converter

+

Module Converter

+

Player

+

ModTracker / Mpg123 / Xmp

+
+ + @@ -2262,22 +2278,6 @@ - - - - - - -

Unreal Music File - This loader recognizes the modules in “umx” files from games like “Unreal”, “DeusEx”, etc. To NostalgicPlayer, UMX is just a container and the real music format may be one of “ScreamTracker 3”, “Impulse Tracker”, “FastTracker 2”, or possibly a “Protracker” compatible one.

-

File extension

-

.umx

-

Converter

-

MikMod Converter

-

Player

-

MikMod

-
- - diff --git a/README.md b/README.md index 21ad4138..d64a33a9 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Modules in all supported formats can be found on my homepage at https://nostalgi | D.O.C. SoundTracker II | .mod | | ModTracker | | D.O.C. SoundTracker VI | .mod | | ModTracker | | D.O.C. SoundTracker IX | .mod | | ModTracker | +| Epic Games UMX | .umx | Module Converter | ModTracker / Mpg123 / Xmp | | Eureka Packer | .eureka / .eu | ProWizard | ModTracker | | Farandole Composer | .far | | Xmp | | Fast/TakeTracker | .mod | | ModTracker | @@ -184,7 +185,6 @@ Modules in all supported formats can be found on my homepage at https://nostalgi | Unic Tracker | .unic | ProWizard | ModTracker | | UniMod | .uni | MikMod Converter | MikMod | | Unis 669 | .669 | | Xmp | -| Unreal Music File | .umx | MikMod Converter | MikMod | | Wanton Packer | .wnp | ProWizard | ModTracker | | Xann Packer | .xann | ProWizard | ModTracker | | Zen Packer | .zen | ProWizard | ModTracker | diff --git a/Source/Agents/ModuleConverters/MikModConverter/MikModConverter.cs b/Source/Agents/ModuleConverters/MikModConverter/MikModConverter.cs index d9b6792c..1363d3bf 100644 --- a/Source/Agents/ModuleConverters/MikModConverter/MikModConverter.cs +++ b/Source/Agents/ModuleConverters/MikModConverter/MikModConverter.cs @@ -81,7 +81,7 @@ public override AgentSupportInfo[] AgentInformation // new AgentSupportInfo(Resources.IDS_MIKCONV_NAME_AGENT12, Resources.IDS_MIKCONV_DESCRIPTION_AGENT12, agent12Id), // new AgentSupportInfo(Resources.IDS_MIKCONV_NAME_AGENT13, Resources.IDS_MIKCONV_DESCRIPTION_AGENT13, agent13Id), new AgentSupportInfo(Resources.IDS_MIKCONV_NAME_AGENT14, Resources.IDS_MIKCONV_DESCRIPTION_AGENT14, agent14Id), - new AgentSupportInfo(Resources.IDS_MIKCONV_NAME_AGENT15, Resources.IDS_MIKCONV_DESCRIPTION_AGENT15, agent15Id), +// new AgentSupportInfo(Resources.IDS_MIKCONV_NAME_AGENT15, Resources.IDS_MIKCONV_DESCRIPTION_AGENT15, agent15Id), // new AgentSupportInfo(Resources.IDS_MIKCONV_NAME_AGENT16, Resources.IDS_MIKCONV_DESCRIPTION_AGENT16, agent16Id) }; } diff --git a/Source/Agents/ModuleConverters/ModuleConverter/Formats/UmxFormat.cs b/Source/Agents/ModuleConverters/ModuleConverter/Formats/UmxFormat.cs new file mode 100644 index 00000000..323afca7 --- /dev/null +++ b/Source/Agents/ModuleConverters/ModuleConverter/Formats/UmxFormat.cs @@ -0,0 +1,492 @@ +/******************************************************************************/ +/* This source, or parts thereof, may be used in any software as long the */ +/* license of NostalgicPlayer is keep. See the LICENSE file for more */ +/* information. */ +/******************************************************************************/ +using System; +using System.IO; +using System.Text; +using Polycode.NostalgicPlayer.Kit; +using Polycode.NostalgicPlayer.Kit.Bases; +using Polycode.NostalgicPlayer.Kit.Containers; +using Polycode.NostalgicPlayer.Kit.Streams; +using Polycode.NostalgicPlayer.Kit.Utility; + +namespace Polycode.NostalgicPlayer.Agent.ModuleConverter.ModuleConverter.Formats +{ + /// + /// Can convert Epic Games UMX container format and extract the module inside + /// + internal class UmxFormat : ModuleConverterAgentBase + { + #pragma warning disable 649 + #region GenHist struct + /// + /// For UPkg versions >= 68 + /// + private struct GenHist + { + public int ExportCount; + public int NameCount; + } + #endregion + + #region UPkgHdr class + private class UPkgHdr + { + public uint Tag; // UPkgHdrTag + public int FileVersion; + public uint PkgFlags; + public int NameCount; // Number of names in name table (>= 0) + public int NameOffset; // Offset to name table (>= 0) + public int ExportCount; // Num. exports in export table (>= 0) + public int ExportOffset; // Offset to export table (>= 0) + public int ImportCount; // Num. imports in export table (>= 0) + public int ImportOffset; // Offset to import table (>= 0) + + // Number of GUIDs in heritage table (>= 1) and table's offset: + // only with versions < 68 + public int HeritageCount; + public int HeritageOffset; + + // With versions >= 68: a GUID, a dword for generation count + // and export_count and name_count dwords for each generation + public readonly uint[] Guid = new uint[4]; + public int GenerationCount; + + public GenHist[] Gen; + } + #endregion + + #region UmxInfo class + private class UmxInfo + { + public UMusic Type; + public int Ofs; + public int Size; + } + #endregion + #pragma warning restore 649 + + private enum UMusic + { + It = 0, + S3M, + Xm, + Mod, + Wav, + Mp2 + } + + private static readonly string[] musType = + { + "IT", "S3M", "XM", "MOD", + "WAV", "MP2", null + }; + + private const uint UPkgHdrTag = 0x9e2a83c1; + private const int UPkgHdrSize = 64; + + private UmxInfo umxData; + + #region IModuleConverterAgent implementation + /********************************************************************/ + /// + /// Test the file to see if it could be identified + /// + /********************************************************************/ + public override AgentResult Identify(PlayerFileInfo fileInfo) + { + ModuleStream moduleStream = fileInfo.ModuleStream; + + // First check the length + long fileSize = moduleStream.Length; + if (fileSize < UPkgHdrSize) // Size of header + return AgentResult.Unknown; + + // Parse the structure and find the type of the module + moduleStream.Seek(0, SeekOrigin.Begin); + + int type = ProcessUPkg(moduleStream, out int ofs, out int size); + if (type < 0) + return AgentResult.Unknown; + + umxData = new UmxInfo(); + + umxData.Type = (UMusic)type; + umxData.Ofs = ofs; + umxData.Size = size; + + return AgentResult.Ok; + } + + + + /********************************************************************/ + /// + /// Convert the module and store the result in the stream given + /// + /********************************************************************/ + public override AgentResult Convert(PlayerFileInfo fileInfo, ConverterStream converterStream, out string errorMessage) + { + ModuleStream moduleStream = fileInfo.ModuleStream; + + errorMessage = string.Empty; + + // Try to read the module + moduleStream.Seek(umxData.Ofs, SeekOrigin.Begin); + + // We just copy the whole module into the output stream + Helpers.CopyData(moduleStream, converterStream, umxData.Size); + + return AgentResult.Ok; + } + #endregion + + #region Private methods + /********************************************************************/ + /// + /// Decode an FCompactIndex. + /// + /// Original documentation by Tim Sweeney was at + /// http://unreal.epicgames.com/Packages.htm + /// + /// Also see Unreal Wiki: + /// http://wiki.beyondunreal.com/Legacy:Package_File_Format/Data_Details + /// + /********************************************************************/ + private int GetFci(Span inBuf, ref int pos) + { + int size = 1; + int a = inBuf[0] & 0x3f; + + if ((inBuf[0] & 0x40) != 0) + { + size++; + a |= (inBuf[1] & 0x7f) << 6; + + if ((inBuf[1] & 0x80) != 0) + { + size++; + a |= (inBuf[2] & 0x7f) << 13; + + if ((inBuf[2] & 0x80) != 0) + { + size++; + a |= (inBuf[3] & 0x7f) << 20; + + if ((inBuf[3] & 0x80) != 0) + { + size++; + a |= (inBuf[4] & 0x3f) << 27; + } + } + } + } + + if ((inBuf[0] & 0x80) != 0) + a = -a; + + pos += size; + + return a; + } + + + + /********************************************************************/ + /// + /// Return the object type + /// + /********************************************************************/ + private int GetObjType(ModuleStream moduleStream, int ofs, UMusic type) + { + byte[] sig = new byte[16]; + + retry: + Array.Clear(sig); + + moduleStream.Seek(ofs, SeekOrigin.Begin); + moduleStream.Read(sig, 0, 16); + + if (type == UMusic.It) + { + if (Encoding.ASCII.GetString(sig, 0, 4) == "IMPM") + return (int)UMusic.It; + + return -1; + } + + if (type == UMusic.Xm) + { + if (Encoding.ASCII.GetString(sig, 0, 16) != "Extended Module:") + return -1; + + moduleStream.Read(sig, 0, 16); + if (sig[0] != ' ') + return -1; + + moduleStream.Read(sig, 0, 16); + if (sig[5] != 0x1a) + return -1; + + return (int)UMusic.Xm; + } + + if (type == UMusic.Mp2) + { + ushort u = (ushort)(((sig[0] << 8) | sig[1]) & 0xfffe); + if ((u == 0xfffc) || (u == 0xfff4)) + return (int)UMusic.Mp2; + + return -1; + } + + if (type == UMusic.Wav) + { + if ((Encoding.ASCII.GetString(sig, 0, 4) == "RIFF") && (Encoding.ASCII.GetString(sig, 8, 4) == "WAVE")) + return (int)UMusic.Wav; + + return -1; + } + + moduleStream.Seek(ofs + 44, SeekOrigin.Begin); + moduleStream.Read(sig, 0, 4); + + if (type == UMusic.S3M) + { + if (Encoding.ASCII.GetString(sig, 0, 4) == "SCRM") + return (int)UMusic.S3M; + + // SpaceMarines.umx and Starseek.umx from Return to NaPali + // reports as "s3m" whereas the actual music format is "it" + type = UMusic.It; + goto retry; + } + + moduleStream.Seek(ofs + 1080, SeekOrigin.Begin); + moduleStream.Read(sig, 0, 4); + + if (type == UMusic.Mod) + { + if ((Encoding.ASCII.GetString(sig, 0, 4) == "M.K.") || (Encoding.ASCII.GetString(sig, 0, 4) == "M!K!")) + return (int)UMusic.Mod; + + return -1; + } + + return -1; + } + + + + /********************************************************************/ + /// + /// Read export table + /// + /********************************************************************/ + private int ReadExport(ModuleStream moduleStream, UPkgHdr hdr, ref int ofs, ref int objSize) + { + byte[] buf = new byte[40]; + + moduleStream.Seek(ofs, SeekOrigin.Begin); + if (moduleStream.Read(buf, 0, 40) < 40) + return -1; + + int idx = 0; + + if (hdr.FileVersion < 40) + idx += 8; // 00 00 00 00 00 00 00 00 + + if (hdr.FileVersion < 60) + idx += 16; // 81 00 00 00 00 00 FF FF FF FF FF FF FF FF 00 00 + + GetFci(buf.AsSpan(idx), ref idx); // Skip junk + int t = GetFci(buf.AsSpan(idx), ref idx); // Type name + + if (hdr.FileVersion > 61) + idx += 4; // Skip export size + + objSize = GetFci(buf.AsSpan(idx), ref idx); + ofs += idx; // Offset for real data + + return t; // Return type name index + } + + + + /********************************************************************/ + /// + /// Read type name + /// + /********************************************************************/ + private int ReadTypeName(ModuleStream moduleStream, UPkgHdr hdr, int idx, byte[] outBuf) + { + if (idx >= hdr.NameCount) + return -1; + + byte[] buf = new byte[64]; + + long l = 0; + for (int i = 0; i <= idx; i++) + { + moduleStream.Seek(hdr.NameOffset + l, SeekOrigin.Begin); + if (moduleStream.Read(buf, 0, 63) == 0) + return -1; + + if (hdr.FileVersion >= 64) + { + sbyte s = (sbyte)buf[0]; // numchars *including* terminator + if (s <= 0) + return -1; + + l += s + 5; // 1 for buf[0], 4 for int32 name_flags + } + else + { + l += EncoderCollection.Dos.GetCharCount(buf); + l += 5; // 1 for terminator, 4 for int32 name_flags + } + } + + int off = hdr.FileVersion >= 64 ? 1 : 0; + Array.Copy(buf, off, outBuf, 0, 64 - off); + + return 0; + } + + + + /********************************************************************/ + /// + /// Probe container + /// + /********************************************************************/ + private int ProbeUmx(ModuleStream moduleStream, UPkgHdr hdr, out int ofs, out int objSize) + { + int idx = 0; + long fSiz = moduleStream.Length; + + ofs = -1; + objSize = -1; + + if ((hdr.NameOffset >= fSiz) || (hdr.ExportOffset >= fSiz) || (hdr.ImportOffset >= fSiz)) + return -1; + + // Find the offset and size of the first IT, S3M or XM + // by parsing the exports table. The umx files should + // have only one export. Kran32.umx from Unreal has two, + // but both pointing to the same music + if (hdr.ExportOffset >= fSiz) + return -1; + + byte[] buf = new byte[64]; + + moduleStream.Seek(hdr.ExportOffset, SeekOrigin.Begin); + moduleStream.Read(buf, 0, 64); + + GetFci(buf.AsSpan(idx), ref idx); // Skip class index + GetFci(buf.AsSpan(idx), ref idx); // Skip super index + + if (hdr.FileVersion >= 60) + idx += 4; // Skip int32 package index + + GetFci(buf.AsSpan(idx), ref idx); // Skip object name + idx += 4; // Skip int32 object flags + + int s = GetFci(buf.AsSpan(idx), ref idx); // Get serial size + if (s <= 0) + return -1; + + int pos = GetFci(buf.AsSpan(idx), ref idx);// Get serial offset + if ((pos < 0) || (pos > (fSiz - 40))) + return -1; + + int t = ReadExport(moduleStream, hdr, ref pos, ref s); + if (t < 0) + return -1; + + if ((s <= 0) || (s > (fSiz - pos))) + return -1; + + if (ReadTypeName(moduleStream, hdr, t, buf) < 0) + return -1; + + int i; + for (i = 0; musType[i] != null; i++) + { + if (EncoderCollection.Dos.GetString(buf).ToUpper() == musType[i]) + { + t = i; + break; + } + } + + if (musType[i] == null) + return -1; + + t = GetObjType(moduleStream, pos, (UMusic)t); + if (t < 0) + return -1; + + ofs = pos; + objSize = s; + + return t; + } + + + + /********************************************************************/ + /// + /// Probe header + /// + /********************************************************************/ + private int ProbeHeader(ModuleStream moduleStream, UPkgHdr hdr) + { + hdr.Tag = moduleStream.Read_L_UINT32(); + hdr.FileVersion = (int)moduleStream.Read_L_UINT32(); + hdr.PkgFlags = moduleStream.Read_L_UINT32(); + hdr.NameCount = (int)moduleStream.Read_L_UINT32(); + hdr.NameOffset = (int)moduleStream.Read_L_UINT32(); + hdr.ExportCount = (int)moduleStream.Read_L_UINT32(); + hdr.ExportOffset = (int)moduleStream.Read_L_UINT32(); + hdr.ImportCount = (int)moduleStream.Read_L_UINT32(); + hdr.ImportOffset = (int)moduleStream.Read_L_UINT32(); + + if (moduleStream.EndOfStream) + return -1; + + if (hdr.Tag != UPkgHdrTag) + return -1; + + if ((hdr.NameCount < 0) || (hdr.ExportCount < 0) || (hdr.ImportCount < 0) || (hdr.NameOffset < 36) || (hdr.ExportOffset < 36) || (hdr.ImportOffset < 36)) + return -1; + + return 0; + } + + + + /********************************************************************/ + /// + /// Process the package + /// + /********************************************************************/ + private int ProcessUPkg(ModuleStream moduleStream, out int ofs, out int objSize) + { + UPkgHdr header = new UPkgHdr(); + + if (ProbeHeader(moduleStream, header) < 0) + { + ofs = -1; + objSize = -1; + + return -1; + } + + return ProbeUmx(moduleStream, header, out ofs, out objSize); + } + #endregion + } +} diff --git a/Source/Agents/ModuleConverters/ModuleConverter/ModuleConverter.cs b/Source/Agents/ModuleConverters/ModuleConverter/ModuleConverter.cs index d06cadcb..85dfef44 100644 --- a/Source/Agents/ModuleConverters/ModuleConverter/ModuleConverter.cs +++ b/Source/Agents/ModuleConverters/ModuleConverter/ModuleConverter.cs @@ -24,6 +24,7 @@ public class ModuleConverter : AgentBase private static readonly Guid agent2Id = Guid.Parse("0C8D0CEE-EA9D-4132-95ED-DFE72D5D8FB6"); private static readonly Guid agent3Id = Guid.Parse("44F2292A-BBA1-42C7-B9EC-1ACED9A42BF8"); private static readonly Guid agent4Id = Guid.Parse("CED4726A-EF3B-40C6-8F93-865F0CE5321E"); + private static readonly Guid agent5Id = Guid.Parse("E03F718C-8FA9-4843-9EBE-6D69EC3A421D"); #region IAgent implementation /********************************************************************/ @@ -58,7 +59,8 @@ public override AgentSupportInfo[] AgentInformation new AgentSupportInfo(Resources.IDS_MODCONV_NAME_AGENT1, Resources.IDS_MODCONV_DESCRIPTION_AGENT1, agent1Id), new AgentSupportInfo(Resources.IDS_MODCONV_NAME_AGENT2, Resources.IDS_MODCONV_DESCRIPTION_AGENT2, agent2Id), new AgentSupportInfo(Resources.IDS_MODCONV_NAME_AGENT3, Resources.IDS_MODCONV_DESCRIPTION_AGENT3, agent3Id), - new AgentSupportInfo(Resources.IDS_MODCONV_NAME_AGENT4, Resources.IDS_MODCONV_DESCRIPTION_AGENT4, agent4Id) + new AgentSupportInfo(Resources.IDS_MODCONV_NAME_AGENT4, Resources.IDS_MODCONV_DESCRIPTION_AGENT4, agent4Id), + new AgentSupportInfo(Resources.IDS_MODCONV_NAME_AGENT5, Resources.IDS_MODCONV_DESCRIPTION_AGENT5, agent5Id) }; } } @@ -84,6 +86,9 @@ public override IAgentWorker CreateInstance(Guid typeId) if (typeId == agent4Id) return new Med4Format(); + if (typeId == agent5Id) + return new UmxFormat(); + return null; } #endregion diff --git a/Source/Agents/ModuleConverters/ModuleConverter/ModuleConverter.csproj b/Source/Agents/ModuleConverters/ModuleConverter/ModuleConverter.csproj index 39d22443..d42fa151 100644 --- a/Source/Agents/ModuleConverters/ModuleConverter/ModuleConverter.csproj +++ b/Source/Agents/ModuleConverters/ModuleConverter/ModuleConverter.csproj @@ -7,7 +7,7 @@ Polycode NostalgicPlayer Can convert different module formats to other formats where a player is available - 2.1.0 + 2.2.0 diff --git a/Source/Agents/ModuleConverters/ModuleConverter/Resources.Designer.cs b/Source/Agents/ModuleConverters/ModuleConverter/Resources.Designer.cs index 0a3aa9d8..45b5f070 100644 --- a/Source/Agents/ModuleConverters/ModuleConverter/Resources.Designer.cs +++ b/Source/Agents/ModuleConverters/ModuleConverter/Resources.Designer.cs @@ -130,10 +130,11 @@ internal static string IDS_ERR_LOADING_TRACKS { /// ///Current version can convert these formats: /// - ///Fred Editor (Final) -> Fred Editor - ///Future Composer 1.0 - 1.3 -> Future Composer 1.4 - ///SoundFX 1.x > SoundFX 2.0 - ///MED 2.10 (MED4) -> MED 2.10 (MMD0). + ///Epic Games UMX ➜ Whatever format inside + ///Fred Editor (Final) ➜ Fred Editor + ///Future Composer 1.0 - 1.3 ➜ Future Composer 1.4 + ///MED 2.10 (MED4) ➜ MED 2.10 (MMD0) + ///SoundFX 1.x ➜ SoundFX 2.0. /// internal static string IDS_MODCONV_DESCRIPTION { get { @@ -191,6 +192,18 @@ internal static string IDS_MODCONV_DESCRIPTION_AGENT4 { } } + /// + /// Looks up a localized string similar to This converter is based on the code for LibXmp. + ///Converted to C# by Thomas Neumann. + /// + ///This converter recognizes the modules in “umx” files from games like “Unreal”, “DeusEx”, etc. To NostalgicPlayer, UMX is just a container and the real music format may be one of “ScreamTracker 3”, “Impulse Tracker”, “FastTracker 2”, or possibly a “ProTracker” compatible one.. + /// + internal static string IDS_MODCONV_DESCRIPTION_AGENT5 { + get { + return ResourceManager.GetString("IDS_MODCONV_DESCRIPTION_AGENT5", resourceCulture); + } + } + /// /// Looks up a localized string similar to Module Converter. /// @@ -235,5 +248,14 @@ internal static string IDS_MODCONV_NAME_AGENT4 { return ResourceManager.GetString("IDS_MODCONV_NAME_AGENT4", resourceCulture); } } + + /// + /// Looks up a localized string similar to Epic Games UMX. + /// + internal static string IDS_MODCONV_NAME_AGENT5 { + get { + return ResourceManager.GetString("IDS_MODCONV_NAME_AGENT5", resourceCulture); + } + } } } diff --git a/Source/Agents/ModuleConverters/ModuleConverter/Resources.resx b/Source/Agents/ModuleConverters/ModuleConverter/Resources.resx index 1b1d2508..07b79def 100644 --- a/Source/Agents/ModuleConverters/ModuleConverter/Resources.resx +++ b/Source/Agents/ModuleConverters/ModuleConverter/Resources.resx @@ -145,10 +145,11 @@ Converts different module formats (mostly Amiga formats) to another format Nosta Current version can convert these formats: -Fred Editor (Final) -> Fred Editor -Future Composer 1.0 - 1.3 -> Future Composer 1.4 -SoundFX 1.x > SoundFX 2.0 -MED 2.10 (MED4) -> MED 2.10 (MMD0) +Epic Games UMX ➜ Whatever format inside +Fred Editor (Final) ➜ Fred Editor +Future Composer 1.0 - 1.3 ➜ Future Composer 1.4 +MED 2.10 (MED4) ➜ MED 2.10 (MMD0) +SoundFX 1.x ➜ SoundFX 2.0 Original player by SuperSero. @@ -175,6 +176,12 @@ The modules contain the player in 68000 assembler in the beginning of the files, Converted to C# by Thomas Neumann. This player plays modules created with MED v2.10 to MED v3.22. This format have both a real module format, where song data and samples are combined into a single file and song+sample format. The player can load both types of files. For song+sample format, you need to have the samples beside the song files. The player will load the samples from a folder named "Instruments", synth sounds from a folder named "Synthsounds" and hybrid samples in a folder named "Hybrids". All folders should be placed in the same folder as the song files. + + + This converter is based on the code for LibXmp. +Converted to C# by Thomas Neumann. + +This converter recognizes the modules in “umx” files from games like “Unreal”, “DeusEx”, etc. To NostalgicPlayer, UMX is just a container and the real music format may be one of “ScreamTracker 3”, “Impulse Tracker”, “FastTracker 2”, or possibly a “ProTracker” compatible one. Module Converter @@ -191,4 +198,7 @@ This player plays modules created with MED v2.10 to MED v3.22. This format have MED 2.10 (MED4) + + Epic Games UMX + \ No newline at end of file diff --git a/Source/Ports/LibXmp/Player.cs b/Source/Ports/LibXmp/Player.cs index 8b8ecb1e..3b504c9b 100644 --- a/Source/Ports/LibXmp/Player.cs +++ b/Source/Ports/LibXmp/Player.cs @@ -574,7 +574,10 @@ public void Xmp_Get_Frame_Info(out Xmp_Frame_Info info) } } - Array.Copy(mod.Xxp[info.Pattern].Index, info.Playing_Tracks, chn); + if (info.Pattern < mod.Pat) + Array.Copy(mod.Xxp[info.Pattern].Index, info.Playing_Tracks, chn); + else + Array.Clear(info.Playing_Tracks, 0, chn); } #region Private methods diff --git a/Source/Ports/README.md b/Source/Ports/README.md index 0ab5a1dc..52443372 100644 --- a/Source/Ports/README.md +++ b/Source/Ports/README.md @@ -1,4 +1,4 @@ -# Clients +# Ports In this directory, all 3rd party ports I have made are stored. They can have different licenses, so look after the LICENSE file inside each directory. -Note that the port may not be a 100% port, because I have only added the stuff that I need in my player. Therefore you will see some API functions can be missing. +Note that the port may not be a 100% port, because I have only added the stuff that I need in my player. Therefore, you will see some API functions may be missing.