My solution to the problem is:
- Create a new application in Xcode that contains the icon resources for the file type.
- Select that app as the default for the file type.
- Substitute the app's binary inside its bundle for a hard link to the binary of the app you want to open the file.
EDIT: This only appears to work in Mojave: in Catalina, there's some code-signing fatal crash. If anyone know how to fix it...
Create a new app in Xcode
This step requires no coding whatsoever. Open a new Project as a Document-based application, using the file extension of the file type you want to change.
Under 'Assets.xcassets' in the sidebar, click on the + icon and under App Icons & Launch Images, select create a New MacOS Generic Icon.
Add the correctly sized images to the empty squares.
Next, in Targets > Info, define the Document Types fields with the relevant data for your file type. Select the Icon resource, and you should get the icon in the empty square.
You can do the same for Imported UTIs, just to cover all bases.
Your app should build and run without any further effort. Quit it. Back in Xcode, right-click on the app product and select "Show in Finder". Move the app to the Applications folder.
The LaunchServices database may not immediately update with your new app and its icons. One tip to force it to update is to copy and paste an icon into the icon square in Get Info on any file. You can delete the change immediately.
Set your app to be the default app for the file type.
This is done in the usual way in Get Info window. The Finder might not update existing windows: close and reopen to see the icon change.
Substitute the binary
Right-click on your new app. Select Show Package Contents. Navigate to the
MacOS folder, and delete the Unix executable file inside, with the name of your app.
In another Finder window, navigate to the similar
MacOS folder inside the bundle of the application that you want to open the file. (In my case, BBEdit.)
In the Terminal, type
ln (that's lowercase LN) followed by a space. Then drag the Unix executable of the 'destination' app onto the Terminal window. Then drag the the empty Macos folder of your app onto the Terminal window. The completed command should look something like this:
ln /Applications/BBEdit.app/Contents/MacOS/BBEdit /Applications/PostScript.app/Contents/MacOS
You should now have a hard link to the destination app (BBEdit) in your dummy app.
One important thing left to do: rename this link to the name of your app. (In my case, 'PostScript'.) You could do this in one step as part of the Terminal command above, by appending the name.
ln /Applications/BBEdit.app/Contents/MacOS/BBEdit /Applications/PostScript.app/Contents/MacOS/PostScript
As long as your dummy app and the destination app are both installed, files will display the custom icons, and launch with the app of your choice, without modifying installed apps. There doesn't seem to be any speed penalty, either.