diff --git a/Makefile b/Makefile index dd1472a0..f9fe73f0 100644 --- a/Makefile +++ b/Makefile @@ -169,7 +169,7 @@ SUPPORT_SRCS += third_party/ucl/src/n2e_99.c third_party/ucl/src/alloc.c SUPPORT_SRCS += $(wildcard third_party/iec-60908b/*.c) OBJECTS := third_party/luajit/src/libluajit.a -TOOLS = exe2elf exe2iso ps1-packer psyq-obj-parser +TOOLS = exe2elf exe2iso modconv ps1-packer psyq-obj-parser ############################################################################## diff --git a/azure-pipelines-cli.yml b/azure-pipelines-cli.yml index 18a020d1..98af628d 100644 --- a/azure-pipelines-cli.yml +++ b/azure-pipelines-cli.yml @@ -64,8 +64,9 @@ steps: vsprojects/x64/ReleaseCLI/crashpad_handler.exe vsprojects/x64/ReleaseCLI/exe2elf.exe vsprojects/x64/ReleaseCLI/exe2iso.exe - vsprojects/x64/ReleaseCLI/psyq-obj-parser.exe + vsprojects/x64/ReleaseCLI/modconv.exe vsprojects/x64/ReleaseCLI/ps1-packer.exe + vsprojects/x64/ReleaseCLI/psyq-obj-parser.exe vsprojects/x64/ReleaseCLI/*.dll TargetFolder: '$(build.artifactStagingDirectory)/binaries' @@ -83,11 +84,12 @@ steps: !**\crashpad_handler.exe !**\exe2elf.exe !**\exe2iso.exe - !**\pcsxrunner.exe - !**\pcsx-wrapper.exe + !**\modconv.exe !**\pcsx-redux.exe - !**\psyq-obj-parser.exe + !**\pcsx-wrapper.exe + !**\pcsxrunner.exe !**\ps1-packer.exe + !**\psyq-obj-parser.exe !third_party\**\*.exe searchFolder: '$(System.DefaultWorkingDirectory)' pathtoCustomTestAdapters: 'GoogleTestAdapter' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3750db51..f56a5140 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -72,8 +72,9 @@ steps: vsprojects/x64/ReleaseWithClangCL/crashpad_handler.exe vsprojects/x64/ReleaseWithClangCL/exe2elf.exe vsprojects/x64/ReleaseWithClangCL/exe2iso.exe - vsprojects/x64/ReleaseWithClangCL/psyq-obj-parser.exe + vsprojects/x64/ReleaseWithClangCL/modconv.exe vsprojects/x64/ReleaseWithClangCL/ps1-packer.exe + vsprojects/x64/ReleaseWithClangCL/psyq-obj-parser.exe vsprojects/x64/ReleaseWithClangCL/*.dll TargetFolder: '$(build.artifactStagingDirectory)/binaries' @@ -91,11 +92,12 @@ steps: !**\crashpad_handler.exe !**\exe2elf.exe !**\exe2iso.exe - !**\pcsxrunner.exe + !**\modconv.exe !**\pcsx-redux.main !**\pcsx-redux.exe - !**\psyq-obj-parser.exe + !**\pcsxrunner.exe !**\ps1-packer.exe + !**\psyq-obj-parser.exe !third_party\**\*.exe searchFolder: '$(System.DefaultWorkingDirectory)' pathtoCustomTestAdapters: 'GoogleTestAdapter' diff --git a/src/mips/modplayer/README.md b/src/mips/modplayer/README.md index 3f5e50e7..23ce3c38 100644 --- a/src/mips/modplayer/README.md +++ b/src/mips/modplayer/README.md @@ -5,3 +5,5 @@ This code is a reverse engineering of the file MODPLAY.BIN, located in the zip f The current API behaves roughly the same the original one. The code should provide information about the alterations made to it. The demo song, "timewarped", written by [Jesster](https://modarchive.org/index.php?request=view_profile&query=69138), comes from [the modarchive](https://modarchive.org/index.php?request=view_by_moduleid&query=106481) with a [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/) license, allowing non-commercial adaptations. The file has been converted from MOD to HIT using the MODCONV.EXE software provided by Hitmen. + +A modern recreation of the MODCONV.EXE software is available within the PCSX-Redux project itself. See the [modconv](https://github.com/grumpycoders/pcsx-redux/tree/main/tools/modconv) folder for more information. diff --git a/src/mips/modplayer/modplayer.c b/src/mips/modplayer/modplayer.c index 131bbae7..af3050ba 100644 --- a/src/mips/modplayer/modplayer.c +++ b/src/mips/modplayer/modplayer.c @@ -218,7 +218,7 @@ static void MOD_SetBPM(unsigned bpm) { static struct SPUChannelData s_channelData[24]; -uint32_t MOD_Load(const struct MODFileFormat* module) { +static uint32_t loadInternal(const struct MODFileFormat* module, const uint8_t* sampleData) { SPUInit(); MOD_Channels = MOD_Check(module); @@ -241,8 +241,12 @@ uint32_t MOD_Load(const struct MODFileFormat* module) { MOD_ModuleData = (const uint8_t*)&module->patternTable[0]; - SPUUploadInstruments(0x1010, MOD_ModuleData + 4 + 128 + MOD_Channels * 0x100 * (maxPatternID + 1), - currentSpuAddress - 0x1010); + if (sampleData) { + SPUUploadInstruments(0x1010, sampleData, currentSpuAddress - 0x1010); + } else { + SPUUploadInstruments(0x1010, MOD_ModuleData + 4 + 128 + MOD_Channels * 0x100 * (maxPatternID + 1), + currentSpuAddress - 0x1010); + } MOD_CurrentOrder = 0; MOD_CurrentPattern = module->patternTable[0]; @@ -274,6 +278,13 @@ uint32_t MOD_Load(const struct MODFileFormat* module) { return 4 + 128 + MOD_Channels * 0x100 * (maxPatternID + 1); } +uint32_t MOD_Load(const struct MODFileFormat* module) { return loadInternal(module, NULL); } + +unsigned MOD_LoadEx(const struct MODFileFormat* module, const uint8_t* sampleData) { + loadInternal(module, sampleData); + return MOD_Channels; +} + void MOD_Silence() { SPUInit(); for (unsigned i = 0; i < 24; i++) { diff --git a/src/mips/modplayer/modplayer.h b/src/mips/modplayer/modplayer.h index f112488e..d82b8f69 100644 --- a/src/mips/modplayer/modplayer.h +++ b/src/mips/modplayer/modplayer.h @@ -76,6 +76,17 @@ unsigned MOD_Check(const struct MODFileFormat* module); // the SPU. uint32_t MOD_Load(const struct MODFileFormat* module); +// Loads the specified module and gets it ready for +// playback. The pointers have to be aligned to a +// 4-bytes boundary. Will also setup the SPU. This +// call is meant to be used with the separate .smp +// file, which the new modconv.exe tool can generate. +// Returns the number of channels from the module, +// or 0 if the module is invalid. No relocation is +// needed, and the sampleData pointer can simply be +// freed after this call. +unsigned MOD_LoadEx(const struct MODFileFormat* module, const uint8_t* sampleData); + // Call this function periodically to play sound. The // frequency at which this is called will determine the // actual playback speed of the module. Most modules will diff --git a/src/support/binstruct.h b/src/support/binstruct.h index 0290afb4..3ca809f5 100644 --- a/src/support/binstruct.h +++ b/src/support/binstruct.h @@ -29,6 +29,7 @@ SOFTWARE. #include #include +#include #include #include #include @@ -91,10 +92,6 @@ struct UInt64 : public BasicFieldType { static constexpr char const typeName[] = "uint64_t"; }; -struct BEInt8 : public BasicFieldType { - static constexpr char const typeName[] = "int8_t"; -}; - struct BEInt16 : public BasicFieldType { static constexpr char const typeName[] = "int16_t"; }; @@ -107,10 +104,6 @@ struct BEInt64 : public BasicFieldType { static constexpr char const typeName[] = "int64_t"; }; -struct BEUInt8 : public BasicFieldType { - static constexpr char const typeName[] = "uint8_t"; -}; - struct BEUInt16 : public BasicFieldType { static constexpr char const typeName[] = "uint16_t"; }; @@ -160,11 +153,17 @@ struct CString { memcpy(value, v.data(), S); return *this; } - void set(const type &v) { memcpy(value, v.data(), S); } + void set(const type &v) { + value[S] = 0; + memcpy(value, v.data(), S); + } void serialize(IO f) const { f->write(value, S); } - void deserialize(IO f) { f->read(value, S); } - void reset() { memset(value, 0, S); } - char value[S]; + void deserialize(IO f) { + value[S] = 0; + f->read(value, S); + } + void reset() { memset(value, 0, S + 1); } + char value[S + 1]; }; template @@ -183,6 +182,70 @@ struct StructField> : public StructType { typedef irqus::typestring fieldName; }; +template +struct RepeatedField; +template +struct RepeatedField, N> { + RepeatedField() {} + typedef irqus::typestring fieldName; + FieldType value[N]; + FieldType &operator[](size_t i) { + if (i >= N) throw std::out_of_range("Index out of range"); + return value[i]; + } + const FieldType &operator[](size_t i) const { + if (i >= N) throw std::out_of_range("Index out of range"); + return value[i]; + } + void serialize(IO f) const { + for (size_t i = 0; i < N; i++) { + value[i].serialize(f); + } + } + void deserialize(IO f) { + for (size_t i = 0; i < N; i++) { + value[i].deserialize(f); + } + } + void reset() { + for (size_t i = 0; i < N; i++) { + value[i].reset(); + } + } +}; + +template +struct RepeatedStruct; +template +struct RepeatedStruct, N> { + RepeatedStruct() {} + typedef irqus::typestring fieldName; + FieldType value[N]; + FieldType &operator[](size_t i) { + if (i >= N) throw std::out_of_range("Index out of range"); + return value[i]; + } + const FieldType &operator[](size_t i) const { + if (i >= N) throw std::out_of_range("Index out of range"); + return value[i]; + } + void serialize(IO f) const { + for (size_t i = 0; i < N; i++) { + value[i].serialize(f); + } + } + void deserialize(IO f) { + for (size_t i = 0; i < N; i++) { + value[i].deserialize(f); + } + } + void reset() { + for (size_t i = 0; i < N; i++) { + value[i].reset(); + } + } +}; + template class Struct; template @@ -208,8 +271,8 @@ class Struct, fields...> : private std::tuple constexpr void reset() {} template constexpr void reset() { - FieldType &setting = std::get(*this); - setting.reset(); + FieldType &field = std::get(*this); + field.reset(); reset(); } template diff --git a/tools/modconv/README.md b/tools/modconv/README.md new file mode 100644 index 00000000..0ec4bf38 --- /dev/null +++ b/tools/modconv/README.md @@ -0,0 +1,22 @@ +# MODCONV +This folder contains a modern recreation of the MODCONV.EXE software, which is used to convert MOD files to HIT files. The original software was [provided by Hitmen](http://hitmen.c02.at/html/psx_tools.html), without source code. + +This version has been written from scratch, without reverse engineering, as the file format is fairly simple to understand. This means that the output files will be slightly different from the original software. + +Its purpose is to convert [MOD files](https://en.wikipedia.org/wiki/Module_file) to HIT files, which can then be played by the [modplayer library available](https://github.com/grumpycoders/pcsx-redux/tree/main/src/mips/modplayer) in the PCSX-Redux project. + +## Usage +```sh +modconv input.mod [-s output.smp] [-a amp] -o output.hit +``` + +## Arguments +| Argument | Type | Description | +|-|-|-| +| input.mod | mandatory | Specify the input mod file. | +| -o output.hit | mandatory | Name of the output file. | +| -s output.smp | optional | Name of the output samples data file. | +| -a amp | optional | Amplification factor. Default is 175 | +| -h | optional | Show help. | + +If the `-i` argument is not provided, the sample data will be written to the .hit file itself, and can be loaded with the `MOD_Load` function from the modplayer library or the old Hitmen implementation. If the `-i` argument is provided, the sample data will be written into a separate file. Both will need to be loaded with the `MOD_LoadEx` function from the modplayer library, and is not backwards compatible with the old Hitmen implementation. This allows the user to have a simple way to unload the samples data from the main ram after the call to `MOD_LoadEx`, only keeping the .hit file in memory. diff --git a/tools/modconv/modconv.cc b/tools/modconv/modconv.cc new file mode 100644 index 00000000..fc761dd7 --- /dev/null +++ b/tools/modconv/modconv.cc @@ -0,0 +1,260 @@ +/*************************************************************************** + * Copyright (C) 2024 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include +#include +#include +#include + +#include "flags.h" +#include "fmt/format.h" +#include "support/binstruct.h" +#include "support/file.h" +#include "support/typestring-wrapper.h" +#include "supportpsx/adpcm.h" + +typedef PCSX::BinStruct::Field, TYPESTRING("Title")> ModTitle; + +typedef PCSX::BinStruct::Field, TYPESTRING("Name")> SampleName; +typedef PCSX::BinStruct::Field SampleLength; +typedef PCSX::BinStruct::Field SampleFineTune; +typedef PCSX::BinStruct::Field SampleVolume; +typedef PCSX::BinStruct::Field SampleLoopStart; +typedef PCSX::BinStruct::Field SampleLoopLength; + +typedef PCSX::BinStruct::Struct + ModSample; +typedef PCSX::BinStruct::RepeatedStruct ModSamples; + +typedef PCSX::BinStruct::Field Positions; +typedef PCSX::BinStruct::Field RestartPosition; +typedef PCSX::BinStruct::RepeatedField PatternTable; + +typedef PCSX::BinStruct::Field, TYPESTRING("Signature")> Signature; + +typedef PCSX::BinStruct::Struct + ModFile; + +int main(int argc, char** argv) { + CommandLine::args args(argc, argv); + auto output = args.get("o"); + + fmt::print(R"( +modconv by Nicolas "Pixel" Noble +https://github.com/grumpycoders/pcsx-redux/tree/main/tools/modconv/ + +)"); + + auto inputs = args.positional(); + const bool asksForHelp = args.get("h").value_or(false); + const bool hasOutput = output.has_value(); + const bool oneInput = inputs.size() == 1; + auto samplesFile = args.get("s"); + auto amplification = args.get("a").value_or(175); + if (asksForHelp || !oneInput || !hasOutput) { + fmt::print(R"( +Usage: {} input.mod [-h] [-s output.ins] [-a amp] -o output.hit + input.mod mandatory: specify the input mod file + -o output.hit mandatory: name of the output hit file. + -h displays this help information and exit. + -s output.smp optional: name of the output sample file. + -a amplification optional: value of sample amplification. Defaults to 175. + +If the -s option is specified, the .hit file will only contain the pattern data, +and the .smp file will contain the sample data which can be loaded into the SPU +memory separately. If the -s option is not specified, the .hit file will contain +both the pattern and sample data. +)", + argv[0]); + return -1; + } + + auto& input = inputs[0]; + PCSX::IO file(new PCSX::PosixFile(input)); + if (file->failed()) { + fmt::print("Unable to open file: {}\n", input); + return -1; + } + + ModFile modFile; + modFile.deserialize(file); + + std::string_view signature(modFile.get().value, 4); + + unsigned channels = 0; + if (signature == "M.K." || signature == "M!K!") { + channels = 4; + } else if (std::isdigit(signature[0]) && (signature[1] == 'C') && (signature[2] == 'H') && (signature[3] == 'N')) { + channels = signature[0] - '0'; + } else if (std::isdigit(signature[0]) && std::isdigit(signature[1]) && (signature[2] == 'C') && + (signature[3] == 'H')) { + channels = (signature[0] - '0') * 10 + signature[1] - '0'; + } + + if (channels == 0) { + fmt::print("{} doesn't have a recognized MOD file format.\n", input); + return -1; + } + + if (channels > 24) { + fmt::print("{} has too many channels ({}). The maximum is 24.\n", input, channels); + return -1; + } + + unsigned maxPatternID = 0; + for (unsigned i = 0; i < 128; i++) { + maxPatternID = std::max(maxPatternID, unsigned(modFile.get()[i])); + } + + auto patternData = file->read(channels * (maxPatternID + 1) * 256); + + fmt::print("Title: {}\n", modFile.get().value); + fmt::print("Positions: {}\n", modFile.get().value); + fmt::print("Patterns: {}\n", maxPatternID + 1); + fmt::print("Converting samples...\n"); + + PCSX::IO encodedSamples = + samplesFile.has_value() + ? reinterpret_cast(new PCSX::PosixFile(samplesFile.value().c_str(), PCSX::FileOps::TRUNCATE)) + : reinterpret_cast(new PCSX::BufferFile(PCSX::FileOps::READWRITE)); + + constexpr uint8_t silentLoopBlock[16] = {0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + std::unique_ptr encoder(new PCSX::ADPCM::Encoder); + for (unsigned i = 0; i < 31; i++) { + encoder->reset(); + auto& sample = modFile.get()[i]; + fmt::print("Sample {:2} [{:22}] - ", i + 1, sample.get().value); + auto length = sample.get().value; + auto loopStart = sample.get().value; + auto loopLength = sample.get().value; + bool hasLoop = (loopStart > 0) && (loopLength > 1); + if (length == 0) { + fmt::print("Empty\n"); + continue; + } + int16_t input[28]; + uint8_t spuBlock[16]; + file->skip(); + length--; + length *= 2; + unsigned position = 2; + loopStart *= 2; + unsigned loopEnd = loopStart + loopLength * 2; + unsigned encodedLength = 0; + while (length >= 28) { + for (unsigned j = 0; j < 28; j++) { + input[j] = int16_t(file->read()) * amplification; + } + length -= 28; + encoder->processSPUBlock(input, spuBlock, PCSX::ADPCM::Encoder::BlockAttribute::OneShot); + uint8_t blockAttribute = 0; + if (length == 0) { + blockAttribute |= 1; + } + if (hasLoop && (loopStart <= position)) { + blockAttribute |= 2; + if (position < (loopStart + 28)) { + blockAttribute |= 4; + } + } + spuBlock[1] = blockAttribute; + position += 28; + encodedSamples->write(spuBlock, 16); + encodedLength += 16; + } + if (length != 0) { + for (unsigned j = 0; j < length; j++) { + input[j] = int16_t(file->read()) * amplification; + } + for (unsigned j = length; j < 28; j++) { + input[j] = 0; + } + encoder->processSPUBlock(input, spuBlock, PCSX::ADPCM::Encoder::BlockAttribute::OneShot); + uint8_t blockAttribute = 0; + if (hasLoop) { + blockAttribute = 3; + if (position < (loopStart + 28)) { + blockAttribute |= 4; + } + } + spuBlock[1] = blockAttribute; + position += 28; + encodedSamples->write(spuBlock, 16); + encodedLength += 16; + } + if (!hasLoop) { + encodedSamples->write(silentLoopBlock, 16); + encodedLength += 16; + } + fmt::print("Size {} -> {}\n", sample.get().value * 2 - 2, encodedLength); + sample.get().value = encodedLength; + if (encodedLength >= 65536) { + fmt::print("Sample too big.\n"); + return -1; + } + } + + if (channels >= 10) { + modFile.get().value[0] = 'H'; + modFile.get().value[1] = 'M'; + modFile.get().value[2] = (channels / 10) + '0'; + modFile.get().value[3] = (channels % 10) + '0'; + } else { + modFile.get().value[0] = 'H'; + modFile.get().value[1] = 'I'; + modFile.get().value[2] = 'T'; + modFile.get().value[3] = channels + '0'; + } + + unsigned fullLength = 0; + for (unsigned i = 0; i < 31; i++) { + auto& sample = modFile.get()[i]; + fullLength += sample.get().value; + } + + constexpr unsigned spuMemory = 512 * 1024 - 0x1010; + + if (fullLength >= spuMemory) { + fmt::print("Not enough SPU memory to store all samples; {} bytes required but only {} available.\n", fullLength, + spuMemory); + return -1; + } else { + fmt::print("Used {} bytes of SPU memory, {} still available.\n", fullLength, spuMemory - fullLength); + } + + PCSX::IO out(new PCSX::PosixFile(output.value().c_str(), PCSX::FileOps::TRUNCATE)); + modFile.serialize(out); + out->write(std::move(patternData)); + if (!samplesFile.has_value()) { + out->write(std::move(encodedSamples.asA()->borrow())); + } + + out->close(); + encodedSamples->close(); + if (samplesFile.has_value()) { + fmt::print("All done, files {} and {} written out.\n", output.value(), args.get("s").value()); + } else { + fmt::print("All done, file {} written out.\n", output.value()); + } + + return 0; +} diff --git a/vsprojects/modconv/modconv.vcxproj b/vsprojects/modconv/modconv.vcxproj new file mode 100644 index 00000000..b72949d5 --- /dev/null +++ b/vsprojects/modconv/modconv.vcxproj @@ -0,0 +1,124 @@ + + + + + ReleaseWithClangCL + x64 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {74f6a549-ab14-4369-a382-c31c0ed97a92} + modconv + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + {71772007-5110-418d-be9c-fb102b6eaabf} + + + {b2e2ad84-9d7f-4976-9572-e415819ffd7f} + + + {0e621321-093c-4d60-bd8b-027fdc2b0f63} + + + + + + + + + \ No newline at end of file diff --git a/vsprojects/modconv/modconv.vcxproj.filters b/vsprojects/modconv/modconv.vcxproj.filters new file mode 100644 index 00000000..241ad19e --- /dev/null +++ b/vsprojects/modconv/modconv.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/vsprojects/pcsx-redux.sln b/vsprojects/pcsx-redux.sln index cd433adc..343e28be 100644 --- a/vsprojects/pcsx-redux.sln +++ b/vsprojects/pcsx-redux.sln @@ -103,6 +103,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lua", "Lua", "{394627A0-57E ..\tests\lua\file.lua = ..\tests\lua\file.lua EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "modconv", "modconv\modconv.vcxproj", "{74F6A549-AB14-4369-A382-C31C0ED97A92}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -466,8 +468,8 @@ Global {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.Debug|x64.Build.0 = Debug|x64 {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.Release|x64.ActiveCfg = Release|x64 {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.Release|x64.Build.0 = Release|x64 - {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.ReleaseCLI|x64.ActiveCfg = Release|x64 - {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.ReleaseCLI|x64.Build.0 = Release|x64 + {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.ReleaseCLI|x64.ActiveCfg = ReleaseWithClangCL|x64 + {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.ReleaseCLI|x64.Build.0 = ReleaseWithClangCL|x64 {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.ReleaseWithClangCL|x64.ActiveCfg = ReleaseWithClangCL|x64 {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.ReleaseWithClangCL|x64.Build.0 = ReleaseWithClangCL|x64 {CDED480F-14EE-475E-97F5-97F2B62DB3CE}.ReleaseWithTracy|x64.ActiveCfg = Release|x64 @@ -492,6 +494,16 @@ Global {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.ReleaseWithClangCL|x64.Build.0 = ReleaseWithClangCL|x64 {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.ReleaseWithTracy|x64.ActiveCfg = ReleaseWithTracy|x64 {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.ReleaseWithTracy|x64.Build.0 = ReleaseWithTracy|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.Debug|x64.ActiveCfg = Debug|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.Debug|x64.Build.0 = Debug|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.Release|x64.ActiveCfg = Release|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.Release|x64.Build.0 = Release|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.ReleaseCLI|x64.ActiveCfg = ReleaseWithClangCL|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.ReleaseCLI|x64.Build.0 = ReleaseWithClangCL|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.ReleaseWithClangCL|x64.ActiveCfg = ReleaseWithClangCL|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.ReleaseWithClangCL|x64.Build.0 = ReleaseWithClangCL|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.ReleaseWithTracy|x64.ActiveCfg = Release|x64 + {74F6A549-AB14-4369-A382-C31C0ED97A92}.ReleaseWithTracy|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -537,9 +549,10 @@ Global {B2E2AD84-9D7F-4976-9572-E415819FFD7F} = {008A2872-432F-480B-828D-FF9AAA4846BC} {CE54ED92-4645-4AE9-BDC8-C0B9607765F8} = {64A05F50-3203-42CC-B632-09D6EE6EA856} {394627A0-57EB-46B1-B768-E02ACFC798A8} = {9D5A1DB2-E74D-4CDD-8377-9EA08CF4AADE} + {74F6A549-AB14-4369-A382-C31C0ED97A92} = {C6DD47BC-0C38-4AE6-B517-9675F3AC8A50} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {284E91C1-764E-441A-854D-CFD3623A6501} SolutionGuid = {AC54A867-F976-4B3D-A6EF-F57EB764DCD4} + SolutionGuid = {284E91C1-764E-441A-854D-CFD3623A6501} EndGlobalSection EndGlobal