Mercurial > web
changeset 118:503e22dd6cf5
blog: add (unfinished) series on OMS
I'll update this as I do more research into the inner workings of
OMS. It's much more interesting (and more convoluted) than ASIO
is unfortunately, but it means the blog posts will probably be
more interesting
| author | Paper <paper@tflc.us> |
|---|---|
| date | Sun, 19 Oct 2025 23:15:02 -0400 |
| parents | 6ca8af53d424 |
| children | 3d2803ed2c6a |
| files | _posts/2025-10-19-oms-part-0.html _posts/2025-10-19-oms-part-1.html |
| diffstat | 2 files changed, 109 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/_posts/2025-10-19-oms-part-0.html Sun Oct 19 23:15:02 2025 -0400 @@ -0,0 +1,25 @@ +--- +layout: post +author: Paper +title: 'The Open Music System, part 0 - preface' +nowplaying: 'Midnight Star - Midas Touch (Hell Interface remix)' +--- +<span> + The primary method of organizing (and interacting with) MIDI devices + on Mac OS 9 is through the Open Music System, developed by Opcode. + However, OMS in itself isn't very "open" at all, as are most + standards for music software. Steinberg's ASIO and VST + are prime examples of this phenomena. +</span> +<br><br> +<span> + In this series of blog posts I will be digging into how applications + interact with the OMS system extension, in an effort to allow other + programs to (legally) interact with OMS without using the proprietary + OMS SDK. +</span> +<br><br> +<span> + Find the next post in this series + <a class="prettylink" href="oms-part-1.html">here</a>. +</span> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/_posts/2025-10-19-oms-part-1.html Sun Oct 19 23:15:02 2025 -0400 @@ -0,0 +1,84 @@ +--- +layout: post +author: Paper +title: 'The Open Music System, part 1 - initialization' +nowplaying: 'The Radio Dept. - Brobygatan' +--- +<span> + Applications wanting to interact with OMS must first call into + <code>Gestalt</code> with the first parameter being the magic + bytes <code>" OMS"</code> (<code>0x204F3D53</code>) and the + second parameter being a pointer to a 32-bit variable that + receives the magic pointer. +</span> +{% comment %} + is there was a way to do this with newlines? this is ugly +{% endcomment %} +<figure><pre class="code-block"><code>UniversalProcPtr OMS_GetGestaltPtr(void) +{ + OSErr err; + uint32_t x; + + err = Gestalt(0x204F3D53 /* " OMS" */, &x); + if (err != noErr) + return NULL; + + return (UniversalProcPtr)x; +}</code></pre></figure> +<span> + From here, applications must retrieve an array of functions + through calling that magic pointer. Note that users cannot + simply call this pointer directly, unless they are specifically + only targeting 68k. You must use the <code>CallUniversalProc</code> + function to safely call into 68k code. +</span> +<figure><pre class="code-block"><code>UniversalProcPtr oms_table[0x5e]; + +#define OMS_GESTALT_PROC_TYPE \ + (kThinkCStackBased \ + | RESULT_SIZE(kFourByteCode) \ + | STACK_ROUTINE_PARAMETER(1, kTwoByteCode) \ + | STACK_ROUTINE_PARAMETER(2, kFourByteCode)) + +int32_t OMS_Init(void) +{ + uint32_t functable; + UniversalProcPtr omsptr; + + omsptr = OMS_GetGestaltPtr(); + if (!omsptr) + return -1; + + functable = CallUniversalProc(omsptr, OMS_GESTALT_PROC_TYPE, 4, 0); + if (functable != (uint32_t)-1) { + memcpy(oms_table, (const void *)functable, sizeof(oms_table)); + } else { + /* very old OMS */ + uint32_t i; + + functable = CallUniversalProc(omsptr, OMS_GESTALT_PROC_TYPE, 1, 0); + if (!functable) + return -1; + + for (i = 0; i < ARRAY_SIZE(oms_table); i++, functable += 4) + oms_table[i] = (UniversalProcPtr)functable; + } + + return 0; +}</code></pre></figure> +<span> + At this point, you can now call into any of the pointers in + <code>oms_table</code>, provided that you know what the parameters + are. The easiest way I've found to find the parameters is to open an + OMS-capable program in a reverse-engineering tool, search for uses of + <code>CallUniversalProc</code> that look roughly like the initialization + code above, and mark the offset they are copied to as the OMS table with + the correct size and type. You can then find other uses of + <code>CallUniversalProc</code> that use the pointers in the OMS table, + and mark down the procedure type (the second parameter to + <code>CallUniversalProc</code>). To decode the procedure type, I've + written a simple utility to do the heavy lifting + <a class="prettylink" href="https://hg.tflc.us/codedump/file/tip/decode-mixed-mode.c">here</a>. + Do take note that it does not support register-based calling yet. + Feel free to send me any patches if you add support for it ;) +</span>
