2

This is a niche question, I hope you can help me with

I have a VERY old project (2010 or so - running on XCODE 3.2) It is written in a combination of C++ and also some programming using the JUCE library for Audio Plugin development - it compiles as an Audio Unit and a VST

The problem I've got is that since OS X Catalina came out, the directory lookup in OS X has become corrupted.

Instead of it pointing to

/Volumes/Macintosh HD/Library/Application Support/Company Name/Product Name/Presets/

its started pointing to

 /Volumes/Macintosh HD///Macintosh HD/Library/Application Support/Company Name/Product Name/Presets/

When it does this, the plugin just crashes - and it doesn't pass validation in any music production DAW.

I've had a look in the project and have identified the following area

#ifdef _Mac
  tchar psz[1024]
  IFile::GetSystemDirectory(IFile::SystemDirApplicationSupport, psz);
  sPathName = std::string(psz);
  sPathName += msCompanyName;
  sPathName += ":";
  sPathName += msProductName;
  sPathName += ":Presets:";
#else
  //windows stuff
#endif

return sPathName:

seems to be something to do with the IFile::SystemDirApplicationSupport ? for whatever reason, that's messed up with Catalina now, but I'm unsure how to get around it

Any help would be greatly appreciated - EDIT

So i found some of the internal Libraries

below you can see the GetSystemDirectory etc.

IFile.h

    /*! \class IFile
 * \brief Interface for accessing files
 *
 * Note that a file cannot be opened for simultaneous reading and writing
*/

class IFile : public virtual IDestructable

{
public:
//! Creates IFile
static IFile* Create();

//! Enum for defining file access (read / write / create)
enum EOpenFile {
    //! Open file for reading only
    FileRead = 0,
    //! Open file for writing only. File must already exist.
    FileWrite,
    //! Open file for writing only. File may or may not already exist. If already existing it will be deleted.
    FileCreate
};

//! Open file, given filename (full path)
/*!
    \param pszPathName [in]: File to open (full path name).
    \param OpenFile [in]: File access to open with
    \return bool: true if success, false otherwise
*/
virtual tbool Open(const tchar* pszPathName, EOpenFile OpenFile) = 0;

//! Close is automatically called when opening a new file, or when destroying object. However you can call it manually if desired
virtual void Close() = 0;

//! Read from file
/*!
    \param pch [out]: Buffer to be filled
    \param iSize [in]: Number of bytes to read
    \return tuint64: Number of bytes actually read
*/
virtual tuint64 Read(tchar* pch, tuint64 iSize) = 0;

//! Write to file
/*!
    \param pch [in]: Buffer to write
    \param iSize [in]: Number of bytes to write
    \return tuint64: Number of bytes actually written
*/
virtual tuint64 Write(const tchar* pch, tuint64 iSize) = 0;

//! Seek to new position (from start of file). After opening file the position is always 0.
/*!
    \param iPos [in]: Position to seek to (from start of file)
    \return tuint64: New position
*/
virtual tuint64 Seek(tuint64 iPos) = 0;

//! Returns the size of file when it was initially opened
/*!
    \return tuint64: Size of file when it was initially opened
*/
virtual tuint64 GetSizeWhenOpened() const = 0;

//! Returns current file position
/*!
    \return tuint64: Current file position
*/
virtual tuint64 GetCurrentFilePosition() const = 0;

//! Gets (full) path name used when opening file
/*!
    \param pszPathName [out]: Pointer to buffer of min. 513 characters to be filled with path name
*/
virtual void GetPathName(tchar* pszPathName) const = 0;

//! Reads tint32's with automatic crossplatform swapping
/*!
    \param p [in]: Buffer to read into
    \param iSize [in]: Number of tint32's to read
    \return tuint64: Number of tint32's actually read
*/
virtual tuint64 ReadCP(tint32* p, tuint64 iSize) = 0;

//! Writes tint32's with automatic crossplatform swapping
/*!
    \param p [in]: Buffer to write
    \param iSize [in]: Number of tint32's to write
    \return tuint64: Number of tint32's actually write
*/
virtual tuint64 WriteCP(tint32* p, tuint64 iSize) = 0;

//! Reads tfloat32's with automatic crossplatform swapping
/*!
    \param p [in]: Buffer to read into
    \param iSize [in]: Number of tfloat32's to read
    \return tuint64: Number of tfloat32's actually read
*/
virtual tuint64 ReadCP(tfloat32* p, tuint64 iSize) = 0;

//! Writes tfloat32's with automatic crossplatform swapping
/*!
    \param p [in]: Buffer to write
    \param iSize [in]: Number of tfloat32's to write
    \return tuint64: Number of tfloat32's actually write
*/
virtual tuint64 WriteCP(tfloat32* p, tuint64 iSize) = 0;

//! Static call to delete a file
/*!
    \param pszPathName [in]: Full path name of file to delete
    \return tbool: If success true, otherwise false
*/
static tbool DeleteFile(const tchar* pszPathName);

//! Static call to move a file
/*!
    \param pszPathNameDest [in]: Path name of destination directory
    \param pszPathNameSrc [in]: Path name of source directory
    \param pszName [in]: Name of file
    \return tbool: If success true, otherwise false
*/
static tbool MoveFile(const tchar* pszPathNameDest, const tchar* pszPathNameSrc, const tchar* pszName);

//! Static call to copy a file
/*!
    \param pszPathNameDest [in]: Path name of destination directory
    \param pszPathNameSrc [in]: Path name of source directory
    \param pszName [in]: Name of file
    \return tbool: If success true, otherwise false
*/
static tbool CopyFile(const tchar* pszPathNameDest, const tchar* pszPathNameSrc, const tchar* pszName);

static tbool CopyFile(const tchar* pszPathNameDest, const tchar* pszPathNameSrc);

//! Static call to create a directory
/*!
    \param pszPathName [in]: Pathname of directory to create. May or may not have ending deliminator ('\' or ':')
    \return tbool: If success true, otherwise false. Call may return false if the directory already exists.
*/
static tbool CreateDirectory(const tchar* pszPathName);

//! Enumeration of system directories
enum ESystemDir {
    //! OSX: Users Preferences directory. Win32: Not valid
    SystemDirPreferences = 0,
    //! OSX: Users desktop. Win32: Users desktop.
    SystemDirDesktop,
    //! OSX: Application directory. Win32: "Program files" directory (use with caution, since application may be installed in custom location!)
    SystemDirApplications,
    //! OSX: Not implemented (should be users documents directory). Win32: Users documents directory.
    SystemDirDocuments,
    //! OSX: /Library/Application Support. Win32: "Program Files\Common" directory
    SystemDirApplicationSupport,
    //! OSX: The 'Music' folder inside the users private folder. Win32: The 'My Music' folder inside the users Documents folder
    SystemDirMyMusic,
    //! OSX: "Chewable" folder that gets cleaned upon boot. Win32: Temporary folder (same as TEMP env-variable).
    SystemDirScratch,
    //! OSX: Not implemented. Win32: Common application data folder
    SystemDirAppData
};

//! Static call to get system directory
/*!
    \param SystemDir [in]: Directory to get.
    \param pszPathName [out]: Returned full path name. Must be preallocated with minimum 513 bytes.
*/
static void GetSystemDirectory(ESystemDir SystemDir, tchar* pszPathName);

//! Converts from OS specific path to internal path. Only works with full paths (not relative).
/*!
    \param pszPathName [in/out]: Path to be converted. Returns converted path. Note that returned path may be 1 byte longer than the input path.
*/
static void PathFromOS(tchar* pszPathName);

//! Converts from internal path to OS specific path. Only works with full paths (not relative).
/*!
    \param pszPathName [in/out]: Path to be converted. Returns converted path.
*/
static void PathToOS(tchar* pszPathName);

//! Converts an OS format path to internal format (':' separated)
/*!
 \param pszPathNameIn [in]: The path to convert. It can be relative or absolute path, may include filename or not, and it may already be in internal format (won't fail).
 \param pszPathNameOut [out]: The converted path (you can enter the same pointer for in and out to provide in-place convertion, it won't crash).
 \param bMakeAbsPath [in]: True: the converted path will be prepended the current working directory (but only if it is not already an absolute path).
 \param pbIsAbsPath [out]: True: the converted path is absolute, false: the converted path is relative (doesn't start with '/').
 \return tbool: True upon convertion success, false upon internal error. Will almost always return true, since fail-tolerance is high.
 */
static tbool PathFromOS2(const tchar* pszPathNameIn, tchar* pszPathNameOut, tbool bMakeAbsPath = true, tbool* pbIsAbsPath = NULL);

//! Converts any internal format path to OS format (i.e. for Mac OS X => POSIX format, for Windows => DOS format)
/*!
 \param pszPathNameIn [in]: The path to convert. It can be relative or absolute path, may include filename or not, and it may already be in OS format (won't fail).
 \param pszPathNameOut [out]: The converted path (you can enter the same pointer for in and out to provide in-place convertion, it won't crash).
 \param bMakeAbsPath [in]: True: the converted path will be prepended the current working directory (but only if it is not already an absolute path).
 \param pbIsAbsPath [out]: True: the converted path is absolute, false: the converted path is relative (doesn't start with '/').
 \return tbool: True upon convertion success, false upon internal error. Will almost always return true, since fail-tolerance is high.
 */
static tbool PathToOS2(const tchar* pszPathNameIn, tchar* pszPathNameOut, tbool bMakeAbsPath = true, tbool* pbIsAbsPath = NULL);

//! Checks if a string represents an absolute path
/*!
 \param pszPathName [in]: The path to check. It may be in OS or internal format
 \return tbool: True if path is absolute
 */
static tbool IsAbsPath2(const tchar* pszPathName);

//! Checks if a string points to an existing file or folder
/*!
    \param pszItem [in]: The item to check the existance of
    \param pbIsFolder [out]: True if existing item is a folder, False if not.<br>Omit parameter if you don't care
    \return tbool: True if item is an existing file or folder
*/
static tbool Exists(const tchar* pszItem, tbool* pbIsFolder = NULL);

//! Split a full path into a path-only and a filename-only part
/*!
    \param pszFullPath [in]: The full path to split
    \param pszPathOnly [out]: The path-only part. Should be preallocated with 512 or more bytes.
    \param pszNameOnly [out]: The name-only part. Should be preallocated with 512 or more bytes.
    \param bAcceptEmptyPath [in]: True = won't fail even if the "full path" input consisted of only a name part
    \param bAcceptEmptyName [in]: True = won't fail even if there was no filename in full path (it pointed to a path instead of a file)
    \return tbool: True = Success, the two output strings were updated
*/
static tbool SplitPathToPathAndName(const tchar* pszFullPath, tchar* pszPathOnly, tchar* pszNameOnly, tbool bAcceptEmptyPath = true, tbool bAcceptEmptyName = true);

//! Creates an enum string with the names of all valid disk drives
/*
    \param pszEnumNames [out]: Receives the drive letters (Windows) or names (OS X) as an enum string delimited by a char of your name
    \param iBuffSize [in]: Max number of chars to place in the buffer (including trailing zero)
    \param cDelimiter [in]: Character used for delimiting enum string
    \param bAddExtraInfo [in]: For Windows: Returns not only the drive letter but also the volume name. For OS X: Ignored.
    \return tbool: True upon success, False if insufficient buffer space (or other error)
*/
static tbool GetDriveNames(tchar* pszEnumNames, tint32 iBuffSize = -1, char cDelimiter = '@', tbool bAddExtraInfo = false);

virtual int GetLastError() = 0;
};
Bryan Spence
  • 133
  • 1
  • 8
  • Is `IFile` part of JUCE? Do you have the source for it? Can you show it? The use of colon (`:`) as a path separator suggests the code is using (or trying to use) old HFS-style paths. – Ken Thomases May 21 '20 at 14:14
  • @KenThomases so i thought IFile was a system library, but i've just found it - i'll include it above – Bryan Spence May 21 '20 at 15:07
  • That shows the declaration of the `GetSystemDirectory` static member function, but not its definition. – Ken Thomases May 21 '20 at 19:39

1 Answers1

1

The current correct way to obtain the path to shared application support directory requires the use of Objective-C. You can add a single Objective-C source file to your project and link it with the rest. The interface between that and the rest of the code can be plain C.

For example:

void GetLocalApplicationSupportDirectory(char *out, size_t capacity)
{
    if (!out || !capacity)
        return;

    NSArray<NSString*>* dirs = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSLocalDomainMask, YES);
    if (dirs.count == 0)
    {
        out[0] = 0;
        return;
    }

    const char *dir = dirs[0].fileSystemRepresentation;
    if (strlen(dir) >= capacity)
    {
        out[0] = 0;
        return;
    }

    strcpy(out, dir);
}

This will produce a POSIX-style path string containing something like "/Volumes/Macintosh HD/Library/Application Support". You could then append further directory names, but you should use the "/" character as the separator.

Note that this is not a drop-in replacement for the code you showed, because that code produced an HFS-style path of the form ":Macintosh HD:Library:Application Support:…". Presumably, the calling code is also expecting an HFS-style path, although I guess it gets converted to a POSIX-style path at some point, because that's what you claim it contains.

If you need to convert between those two path styles, you can use CFURLCreateWithFileSystemPath() and CFURLCopyFileSystemPath(), both from Core Foundation. Those are pure C. However, the bug you're seeing on Catalina may be a consequence of those routines ceasing to work properly.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • thank you Ken, that helped a lot - i managed to actually find a File lookup in JUCE that did the same thing using File::getSpecialLocation(File::commonApplicationDataDirectory); The directories do appear to now be accurate, but the crash remains so i think its something else, perhaps as you say related to the path styles or something... still investigating – Bryan Spence May 27 '20 at 13:02