fix(instrument_drop_win): UTF-8 temp path, PID collision guard, embed-strip excluded from drop hotspot

This commit is contained in:
2026-07-28 06:37:42 -04:00
parent b4dd6dc9f1
commit 66f2e402b0
4 changed files with 78 additions and 25 deletions
+47 -20
View File
@@ -31,27 +31,49 @@ namespace reasampler {
namespace {
// Write `bytes` to a fresh uniquely-named .vstpreset in the OS temp dir and return its
// absolute path; empty string on any failure. The .vstpreset extension is load-bearing —
// Write `bytes` to a fresh uniquely-named .vstpreset in the OS temp dir and return its path;
// returns an empty path on any failure. The .vstpreset extension is load-bearing —
// TrackFX_SetPreset's full-path form is documented for .vstpreset files (VST3). The file is
// transient: the caller deletes it right after the SetPreset call.
std::string writeTempPreset(const std::vector<std::uint8_t>& bytes) {
static std::atomic<unsigned> counter{0};
std::error_code ec;
const std::filesystem::path dir = std::filesystem::temp_directory_path(ec);
if (ec) return {};
const std::filesystem::path path =
dir / ("reasampler_drop_" + std::to_string(counter.fetch_add(1)) + ".vstpreset");
std::ofstream out(path, std::ios::binary | std::ios::trunc);
if (!out) return {};
out.write(reinterpret_cast<const char*>(bytes.data()),
static_cast<std::streamsize>(bytes.size()));
out.close();
if (!out) { // short write / flush failure -> don't hand REAPER a truncated preset
std::filesystem::remove(path, ec);
//
// The temp filename embeds the process ID so two concurrent REAPER instances (e.g. stable +
// beta) cannot collide in the shared OS temp dir, and one instance's cleanup cannot
// accidentally delete another's in-flight file.
//
// Non-throwing: every std::filesystem call uses the error_code overload. The whole body is
// wrapped in try/catch to guarantee no exception crosses the REAPER C callback boundary
// (the same discipline persist.cpp uses — see its non-throwing scanPruneOrphans comment).
//
// Returns the path object (not a narrow string) so the caller can:
// (a) pass path.u8string() to TrackFX_SetPreset — UTF-8 on MSVC, not ACP-converted,
// so a temp dir with accented or CJK user-name bytes is handled correctly;
// (b) delete via the retained path object — not via re-parsing the narrow string —
// so the cleanup cannot leak if the conversion above were to round-trip incorrectly.
std::filesystem::path writeTempPreset(const std::vector<std::uint8_t>& bytes) {
try {
static std::atomic<unsigned> counter{0};
std::error_code ec;
const std::filesystem::path dir = std::filesystem::temp_directory_path(ec);
if (ec) return {};
// PID in the name keeps files from distinct REAPER instances distinct in the shared
// temp dir — prevents cross-instance collisions and spurious post-apply deletions.
const std::string name =
"reasampler_drop_" + std::to_string(GetCurrentProcessId()) +
"_" + std::to_string(counter.fetch_add(1)) + ".vstpreset";
const std::filesystem::path path = dir / name;
std::ofstream out(path, std::ios::binary | std::ios::trunc);
if (!out) return {};
out.write(reinterpret_cast<const char*>(bytes.data()),
static_cast<std::streamsize>(bytes.size()));
out.close();
if (!out) { // short write / flush failure -> don't hand REAPER a truncated preset
std::filesystem::remove(path, ec);
return {};
}
return path;
} catch (...) {
return {};
}
return path.string();
}
} // namespace
@@ -77,7 +99,7 @@ bool loadInstrumentOntoTrack(MediaTrack* track, const std::vector<std::uint8_t>&
// Materialize the .vstpreset FIRST so an I/O failure leaves the track untouched (no FX
// added yet — nothing to roll back).
const std::string presetPath = writeTempPreset(presetBytes);
const std::filesystem::path presetPath = writeTempPreset(presetBytes);
if (presetPath.empty()) return false;
// The CHANNEL-correct FX name: "VST3:ReaSampler 9000" on stable, "VST3:ReaSampler 9000
@@ -100,9 +122,14 @@ bool loadInstrumentOntoTrack(MediaTrack* track, const std::vector<std::uint8_t>&
// serializer produced (instrument_drop::buildInstrumentDropPreset ->
// sample_map::serializeComponentState). Unlike the former "vst_chunk" named-config-parm
// write, a failure here is REPORTED (false), not silently ignored.
if (ok) ok = TrackFX_SetPreset(track, fxIndex, presetPath.c_str());
//
// u8string() gives UTF-8 bytes on MSVC (not ACP-converted), so a temp dir under an
// accented or CJK user-name is handled correctly by REAPER's path APIs.
if (ok) ok = TrackFX_SetPreset(track, fxIndex, presetPath.u8string().c_str());
// The preset file is transient regardless of outcome; best-effort cleanup (temp dir).
// The preset file is transient regardless of outcome; delete via the retained path object
// (not a re-parsed narrow string) so cleanup cannot leak even if the UTF-8 conversion
// round-trip were incorrect.
std::error_code ec;
std::filesystem::remove(presetPath, ec);