diff --git a/.gitignore b/.gitignore index 72dba90..64f2448 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ docs/superpowers/ build/ drivers/ahi/package/ drivers/ahi/package-v2/ +drivers/mhi/build/ +drivers/mhi/package/ dist/ release/ tmp/ diff --git a/Makefile b/Makefile index 8dafc23..d5ecfbc 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ TOOLS = host-run host-multiview host-shell host-path host-reveal host-notify ho TEST_BINS = tests/test_host_common.out tests/test_host_command_builders.out tests/test_host_edit_command.out tests/test_host_download_command.out TEST_SCRIPTS = tests/test_package_layout.sh tests/test_ahi_driver_source.sh TESTS = $(TEST_BINS) $(TEST_SCRIPTS) -COMMON_HEADERS = src/host_common.h src/host_path.h src/host_capture.h src/host_base64.h src/host_clip_command.h src/host_download_command.h src/host_edit_command.h src/host_env_command.h src/host_info_command.h src/host_notify_command.h src/host_powershell.h src/host_reveal_command.h src/uae_pragmas.h +COMMON_HEADERS = src/host_common.h src/host_path.h src/host_capture.h src/host_base64.h src/host_clip_command.h src/host_download_command.h src/host_edit_command.h src/host_env_command.h src/host_info_command.h src/host_notify_command.h src/host_powershell.h src/host_reveal_command.h src/host_shell_command.h src/uae_pragmas.h PACKAGE = Host-Tools-$(VERSION).lha PACKAGE_ROOT = Host-Tools PACKAGE_DIR ?= build/package @@ -16,6 +16,8 @@ AHI_FILES = $(AHI_AUDIO) $(AHI_MODE) AHI_V2_AUDIO = drivers/ahi/package-v2/Devs/AHI/uaesnd.audio AHI_V2_MODE = drivers/ahi/package-v2/Devs/AudioModes/UAESND AHI_V2_FILES = $(AHI_V2_AUDIO) $(AHI_V2_MODE) +MHI_LIBRARY = drivers/mhi/package/Libs/MHI/mhiuae.library +MHI_FILES = $(MHI_LIBRARY) AHI_SOURCES = drivers/ahi/Makefile \ drivers/ahi/src/v1/uae.audio.asm \ drivers/ahi/src/v1/UAE.asm \ @@ -29,6 +31,11 @@ AHI_SOURCES = drivers/ahi/Makefile \ AHI_V2_SOURCES = drivers/ahi/Makefile \ drivers/ahi/src/v2/uaesnd.audio.asm \ drivers/ahi/src/v2/UAESND.asm +MHI_SOURCES = drivers/mhi/Makefile \ + drivers/mhi/src/mhiuae.h \ + drivers/mhi/src/mhiuae_startup.c \ + drivers/mhi/src/mhiuae.c \ + src/uae_pragmas.h HELP_GUIDE = package/Help/Host-Tools.guide DRAWER_ICON = package/icons/drawer.info HELP_ICON = package/icons/Help.info @@ -37,9 +44,9 @@ README_ICON = package/icons/readme.info GUIDE_ICON = package/icons/guide.info .SUFFIXES: -.PHONY: all test debug package package-dir ahi ahi-v2 clean +.PHONY: all test debug package package-dir ahi ahi-v2 mhi clean -all: $(TOOLS) $(AHI_FILES) $(AHI_V2_FILES) +all: $(TOOLS) $(AHI_FILES) $(AHI_V2_FILES) $(MHI_FILES) test: $(TESTS) @for test in $(TESTS); do \ case "$$test" in \ @@ -97,7 +104,7 @@ host-env: src/host-env.c $(COMMON_HEADERS) tests/test_host_common.out: tests/test_host_common.c src/host_common.h $(HOST_CC) $(HOST_NATIVE_FLAGS) $(HOST_CFLAGS) tests/test_host_common.c -o $@ -tests/test_host_command_builders.out: tests/test_host_command_builders.c src/host_base64.h src/host_clip_command.h src/host_common.h src/host_env_command.h src/host_info_command.h src/host_notify_command.h src/host_powershell.h src/host_reveal_command.h +tests/test_host_command_builders.out: tests/test_host_command_builders.c src/host_base64.h src/host_clip_command.h src/host_common.h src/host_env_command.h src/host_info_command.h src/host_notify_command.h src/host_powershell.h src/host_reveal_command.h src/host_shell_command.h $(HOST_CC) $(HOST_NATIVE_FLAGS) $(HOST_CFLAGS) tests/test_host_command_builders.c -o $@ tests/test_host_edit_command.out: tests/test_host_edit_command.c src/host_edit_command.h src/host_common.h @@ -113,12 +120,17 @@ ahi: $(AHI_FILES) ahi-v2: $(AHI_V2_FILES) +mhi: $(MHI_FILES) + $(AHI_FILES): $(AHI_SOURCES) $(MAKE) -C drivers/ahi VERSION=$(VERSION) DATE=$(DATE) $(AHI_V2_FILES): $(AHI_V2_SOURCES) $(MAKE) -C drivers/ahi VERSION=$(VERSION) DATE=$(DATE) ahi-v2 +$(MHI_FILES): $(MHI_SOURCES) + $(MAKE) -C drivers/mhi VERSION=$(VERSION) DATE=$(DATE) + package-dir: all package/Install $(HELP_GUIDE) $(DRAWER_ICON) $(HELP_ICON) $(INSTALL_ICON) $(README_ICON) $(GUIDE_ICON) rm -rf $(PACKAGE_STAGE) $(PACKAGE_DIR)/$(PACKAGE_ROOT).info mkdir -p $(PACKAGE_STAGE)/C @@ -142,6 +154,10 @@ package-dir: all package/Install $(HELP_GUIDE) $(DRAWER_ICON) $(HELP_ICON) $(INS cp $(AHI_V2_AUDIO) $(PACKAGE_STAGE)/Devs/AHI/uaesnd.audio; \ cp $(AHI_V2_MODE) $(PACKAGE_STAGE)/Devs/AudioModes/UAESND; \ fi + if [ -f $(MHI_LIBRARY) ]; then \ + mkdir -p $(PACKAGE_STAGE)/Libs/MHI; \ + cp $(MHI_LIBRARY) $(PACKAGE_STAGE)/Libs/MHI/mhiuae.library; \ + fi package: package-dir rm -f $(PACKAGE) @@ -150,3 +166,4 @@ package: package-dir clean: rm -f $(TOOLS) $(TEST_BINS) $(MAKE) -C drivers/ahi clean + $(MAKE) -C drivers/mhi clean diff --git a/README.md b/README.md index 9350709..d5293f5 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ host-multiview [filename2|URL2 ...] **Interactive Host Terminal.** Opens a fully interactive terminal session on the host system, right inside your Amiga CLI. - **Interactive**: Supports `vi`, `htop`, and other interactive TUI applications. -- **Shell Support**: Respects your host's default shell (Bash, Zsh, Fish, etc.). +- **Shell Support**: Starts your host user's login shell (Bash, Zsh, Fish, etc.) so profile files can set paths and environment variables. - **Raw Mode**: Provides a true terminal experience. - **Terminal Size**: The host terminal is sized to match the Amiga console window, so full-screen programs render correctly. @@ -150,9 +150,9 @@ All tools follow AmigaDOS conventions: `0` on success, `10` (`RETURN_ERROR`) whe 2. Extract the `Host-Tools-.lha` archive. 3. Open the `Host-Tools` drawer and run `Install`. -The installer shows a component checklist for the command tools, AmigaGuide documentation, and the UAE and UAESND AHI audio drivers. Before replacing an existing versioned file, it shows the installed and package versions and asks whether to replace or skip it. Files without version strings are still checked for existence and ask before overwrite. It does not edit startup files, system settings, AHI preferences, or existing driver configuration. +The installer shows a component checklist for the command tools, AmigaGuide documentation, the UAE and UAESND AHI audio drivers, and the UAE MHI MP3 decoder library. Before replacing an existing versioned file, it shows the installed and package versions and asks whether to replace or skip it. Files without version strings are still checked for existence and ask before overwrite. It does not edit startup files, system settings, AHI preferences, or existing driver configuration. -For manual installation, copy the binaries (`host-run`, `host-multiview`, `host-shell`, `host-path`, `host-reveal`, `host-notify`, `host-edit`, `host-clip`, `host-info`, `host-download`, `host-env`) from the package `C` drawer to `C:` or anywhere in your system path. To install the UAE AHI driver manually, copy `Devs/AHI/uae.audio` to `DEVS:AHI/` and `Devs/AudioModes/UAE` to `DEVS:AudioModes/`. To install the UAESND AHI driver manually, copy `Devs/AHI/uaesnd.audio` to `DEVS:AHI/` and `Devs/AudioModes/UAESND` to `DEVS:AudioModes/`. The UAESND driver plays each AHI channel through a hardware audio stream and requires the UAESND sound board to be enabled in the Amiberry configuration. +For manual installation, copy the binaries (`host-run`, `host-multiview`, `host-shell`, `host-path`, `host-reveal`, `host-notify`, `host-edit`, `host-clip`, `host-info`, `host-download`, `host-env`) from the package `C` drawer to `C:` or anywhere in your system path. To install the UAE AHI driver manually, copy `Devs/AHI/uae.audio` to `DEVS:AHI/` and `Devs/AudioModes/UAE` to `DEVS:AudioModes/`. To install the UAESND AHI driver manually, copy `Devs/AHI/uaesnd.audio` to `DEVS:AHI/` and `Devs/AudioModes/UAESND` to `DEVS:AudioModes/`. To install the UAE MHI MP3 decoder manually, copy `Libs/MHI/mhiuae.library` to `LIBS:MHI/`. The UAESND driver plays each AHI channel through a hardware audio stream and requires the UAESND sound board to be enabled in the Amiberry configuration. The MHI library is independent of the selected AHI output mode and forwards MP3 decoding to Amiberry through `uae.resource`. ## Examples @@ -213,7 +213,7 @@ To build a release archive: make package ``` -The package target creates `Host-Tools-.lha`, containing a structured `Host-Tools` drawer with the Installer script, command tools, README, AmigaGuide documentation, and the UAE and UAESND AHI driver files. +The package target creates `Host-Tools-.lha`, containing a structured `Host-Tools` drawer with the Installer script, command tools, README, AmigaGuide documentation, the UAE and UAESND AHI driver files, and the UAE MHI MP3 decoder library. Native package builds also require `lha`. The Docker build image contains this tool. diff --git a/drivers/ahi/Makefile b/drivers/ahi/Makefile index e0222be..762da87 100644 --- a/drivers/ahi/Makefile +++ b/drivers/ahi/Makefile @@ -4,10 +4,11 @@ VERSION ?= 2.4 DATE ?= 2026-06-10 -ifeq ($(origin VASMM68K),default) -VASMM68K = vasmm68k_mot +VASMM68K_FALLBACK := $(shell command -v vasmm68k_mot 2>/dev/null || { test -x /opt/m68k-amigaos/bin/vasmm68k_mot && printf /opt/m68k-amigaos/bin/vasmm68k_mot; } || printf vasmm68k_mot) +ifeq ($(origin VASMM68K),undefined) +VASMM68K := $(VASMM68K_FALLBACK) endif -VASMM68K_CMD = $(if $(VASMM68K),$(VASMM68K),vasmm68k_mot) +VASMM68K_CMD = $(if $(strip $(VASMM68K)),$(VASMM68K),$(VASMM68K_FALLBACK)) BUILD_DIR = build AHI_PACKAGE_DIR = package diff --git a/drivers/ahi/src/v2/uaesnd.audio.asm b/drivers/ahi/src/v2/uaesnd.audio.asm index 41acb75..6bf701b 100644 --- a/drivers/ahi/src/v2/uaesnd.audio.asm +++ b/drivers/ahi/src/v2/uaesnd.audio.asm @@ -26,6 +26,17 @@ DEBUG_ADDR EQU $bfff00 VERSION EQU 4 REVISION EQU 1 VERSION_STRUCT EQU 1 +UAESND_CAP_CAPTURE EQU 8 +UAESND_CAP_CAPTURE_BLOCK EQU 16 +UAESND_CAPTURE_CONTROL_ENABLE EQU 1 +UAESND_CAPTURE_CONTROL_IRQ_ENABLE EQU 2 +UAESND_CAPTURE_BLOCK_COMMAND_COPY EQU 1 +UAESND_RECORD_FRAMES EQU 2048 +UAESND_RECORD_BYTES EQU UAESND_RECORD_FRAMES*4 +base_capture_block_address EQU $900 +base_capture_block_frames EQU $904 +base_capture_block_done EQU $908 +base_capture_block_command EQU $90c call MACRO jsr _LVO\1(a6) @@ -42,7 +53,17 @@ call MACRO UBYTE base_pad1 ULONG base_ram_offset ULONG base_ram_size - STRUCT base_reserved1,$48 + ULONG base_capabilities + ULONG base_diag_version + STRUCT base_diagnostics,$20 + ULONG base_capture_data + ULONG base_capture_overruns + ULONG base_capture_intreq + ULONG base_capture_threshold + ULONG base_capture_control + ULONG base_capture_status + ULONG base_capture_frequency + ULONG base_capture_available ULONG base_stream_allocate ULONG base_stream_latch ULONG base_stream_unlatch @@ -146,7 +167,13 @@ STREAM_OFFSET = $100 UWORD p_StreamCnt UWORD p_Pad1 ULONG p_PendingStartMask + ULONG p_Capabilities + APTR p_RecordBuffer + ULONG p_RecordBufferSize + UWORD p_Input + UWORD p_RecordActive STRUCT p_Interrupt,IS_SIZE + STRUCT p_RecordMessage,AHIRecordMessage_SIZEOF LABEL UAESND_SIZEOF Start: @@ -269,7 +296,7 @@ update_channel_volumes ; ULONG Flags D3 AHIsub_SetVol - movem.l d2/a2,-(sp) + movem.l d2/d4/a2,-(sp) IFNE DEBUG lea DEBUG_ADDR,a0 @@ -283,6 +310,7 @@ AHIsub_SetVol move.l ahiac_DriverData(a2),a2 + move.l d2,d4 move.w d0,d2 mulu #channel_SIZEOF,d0 move.l p_Channels(a2),a1 @@ -301,6 +329,17 @@ AHIsub_SetVol bsr.w scale_output_volume move.l ch_set_current(a1),a0 move.w d0,set_volume(a0) + move.l d4,d1 + sub.l #$8000,d1 + cmp.l #-32767,d1 + bge.s .pan_min_ok + move.l #-32767,d1 +.pan_min_ok + cmp.l #32767,d1 + ble.s .pan_ok + move.l #32767,d1 +.pan_ok + move.w d1,set_hpan(a0) btst #AHISB_IMM,d3 beq.s .noimm @@ -308,8 +347,9 @@ AHIsub_SetVol mulu #STREAM_OFFSET,d2 add.l d2,a0 move.w d0,STREAM_START+UAESNDSetCurrent+set_volume(a0) + move.w d1,STREAM_START+UAESNDSetCurrent+set_hpan(a0) .noimm - movem.l (sp)+,d2/a2 + movem.l (sp)+,d2/d4/a2 moveq #0,d0 IFNE DEBUG @@ -721,12 +761,15 @@ AHIsub_Disable AHIsub_Enable move.l ahiac_DriverData(a2),a1 + tst.w p_DisableCount(a1) + beq.s .already_enabled subq.w #1,p_DisableCount(a1) bne.s .exit bsr.w flush_pending_starts move.l p_Base(a1),a0 bsr uaesnd_enable .exit +.already_enabled rts flush_pending_starts @@ -783,6 +826,9 @@ debug_start AHIsub_Start + movem.l d2,-(sp) + move.l d0,d2 + IFNE DEBUG lea DEBUG_ADDR,a0 move.l d0,(a0) @@ -790,28 +836,24 @@ AHIsub_Start move.l #debug_start,4(a0) ENDC - move.l ahiac_DriverData(a2),a1 - move.l p_Base(a1),a0 - bsr reset_hw - - move.l a2,a0 - bsr.w update_playerfunc + btst #AHISB_PLAY,d2 + beq.s .noplay + bsr.w start_playback +.noplay + btst #AHISB_RECORD,d2 + beq.s .norecord move.l ahiac_DriverData(a2),a1 move.l p_Base(a1),a0 - moveq #1,d0 - moveq #0,d1 - move.w p_StreamCnt(a1),d1 - lsl.l d1,d0 - subq.l #1,d0 - move.l d0,base_stream_enable(a0) - move.l d0,base_stream_intena(a0) + bsr.w start_recording +.norecord moveq #AHIE_OK,d0 IFNE DEBUG bsr debugreturn ENDC + movem.l (sp)+,d2 rts IFNE DEBUG @@ -825,6 +867,9 @@ debug_stop AHIsub_Stop: + movem.l d2,-(sp) + move.l d0,d2 + IFNE DEBUG lea DEBUG_ADDR,a0 move.l d0,(a0) @@ -832,15 +877,123 @@ AHIsub_Stop: move.l #debug_stop,4(a0) ENDC + btst #AHISB_PLAY,d2 + beq.s .noplay + bsr.w stop_playback +.noplay + move.l ahiac_DriverData(a2),a0 - move.l p_Base(a0),a0 + btst #AHISB_RECORD,d2 + beq.s .norecord + move.l a0,a1 + move.l p_Base(a1),a0 + bsr.w stop_recording +.norecord + moveq #AHIE_OK,d0 + movem.l (sp)+,d2 + rts + + ; a2 = AHIAudioCtrlDrv +start_playback + move.l ahiac_DriverData(a2),a1 + move.l p_Base(a1),a0 bsr reset_hw + move.l a2,a0 + bsr.w update_playerfunc + move.l ahiac_DriverData(a2),a1 + move.l p_Base(a1),a0 + moveq #1,d0 + moveq #0,d1 + move.w p_StreamCnt(a1),d1 + lsl.l d1,d0 + subq.l #1,d0 + move.l d0,base_stream_enable(a0) + move.l d0,base_stream_intena(a0) + rts - move.l ahiac_DriverData(a2),a0 - clr.l p_PendingStartMask(a0) + ; a2 = AHIAudioCtrlDrv +stop_playback + move.l ahiac_DriverData(a2),a1 + move.l p_Base(a1),a0 + bsr reset_hw + clr.l p_PendingStartMask(a1) + move.l a1,a0 bsr.w reset_ch + rts - moveq #AHIE_OK,d0 + ; a1 = UAESND + ; a0 = UAESNDHW + ; a2 = AHIAudioCtrlDrv +start_recording + tst.l p_RecordBuffer(a1) + beq.s .done + move.l #AHIST_S16S,p_RecordMessage+ahirm_Type(a1) + move.l p_RecordBuffer(a1),p_RecordMessage+ahirm_Buffer(a1) + move.l #UAESND_RECORD_FRAMES,p_RecordMessage+ahirm_Length(a1) + move.l ahiac_MixFreq(a2),base_capture_frequency(a0) + move.l #UAESND_RECORD_BYTES,base_capture_threshold(a0) + move.l #UAESND_CAPTURE_CONTROL_ENABLE|UAESND_CAPTURE_CONTROL_IRQ_ENABLE,base_capture_control(a0) + btst #0,base_capture_status+3(a0) + bne.s .unavailable + move.w #1,p_RecordActive(a1) +.done + rts +.unavailable + clr.l base_capture_control(a0) + clr.l base_capture_intreq(a0) + clr.l base_capture_threshold(a0) + bra.s .done + + ; a1 = UAESND + ; a0 = UAESNDHW +stop_recording + clr.l base_capture_control(a0) + clr.l base_capture_intreq(a0) + clr.l base_capture_threshold(a0) + clr.w p_RecordActive(a1) + rts + + ; a5 = UAESND + ; a2 = AHIAudioCtrlDrv +process_recording + movem.l d0-d2/a0-a1/a6,-(sp) + tst.w p_RecordActive(a5) + beq.s .done + move.l ahiac_SamplerFunc(a2),a0 + move.l a0,d0 + beq.s .done + move.l p_Base(a5),a0 + move.l base_capture_available(a0),d0 + cmp.l #UAESND_RECORD_BYTES,d0 + blo.s .done + move.l p_Capabilities(a5),d1 + and.l #UAESND_CAP_CAPTURE_BLOCK,d1 + beq.s .bytecopy + move.l p_RecordBuffer(a5),base_capture_block_address(a0) + move.l #UAESND_RECORD_FRAMES,base_capture_block_frames(a0) + move.l #UAESND_CAPTURE_BLOCK_COMMAND_COPY,base_capture_block_command(a0) + move.l base_capture_block_done(a0),d0 + cmp.l #UAESND_RECORD_FRAMES,d0 + bne.s .done + bra.s .copied +.bytecopy + move.l p_RecordBuffer(a5),a1 + move.l #UAESND_RECORD_BYTES,d0 + subq.l #1,d0 +.copy + move.b base_capture_data(a0),(a1)+ + dbf d0,.copy + swap d0 + subq.w #1,d0 + bpl.s .copy +.copied + clr.l base_capture_intreq(a0) + lea p_RecordMessage(a5),a1 + move.l ahiac_SamplerFunc(a2),a0 + move.l h_Entry(a0),a6 + jsr (a6) +.done + movem.l (sp)+,d0-d2/a0-a1/a6 rts IFNE DEBUG @@ -881,12 +1034,13 @@ AHIsub_AllocAudio move.l ub_Base(a5),a4 move.w #$8000,p_OutputVolume(a3) move.l a4,p_Base(a3) + move.l base_capabilities(a4),p_Capabilities(a3) move.l a2,p_AudioCtrl(a3) moveq #0,d0 move.b base_max_streams(a4),d0 cmp.w ahiac_Channels(a2),d0 - bcs .error + bcs .alloc_error moveq #0,d0 move.w ahiac_Channels(a2),d0 @@ -896,7 +1050,7 @@ AHIsub_AllocAudio move.l #MEMF_PUBLIC|MEMF_CLEAR,d1 call AllocMem move.l d0,p_Channels(a3) - beq .error + beq .alloc_error move.l d0,a1 move.w ahiac_Channels(a2),d1 @@ -914,7 +1068,18 @@ AHIsub_AllocAudio move.l #MEMF_PUBLIC|MEMF_CLEAR,d1 call AllocMem move.l d0,p_Sounds(a3) - beq .error + beq .alloc_error + + move.l p_Capabilities(a3),d0 + btst #3,d0 + beq.s .norecordbuffer + move.l #UAESND_RECORD_BYTES,d0 + move.l #MEMF_PUBLIC|MEMF_CLEAR,d1 + call AllocMem + move.l d0,p_RecordBuffer(a3) + beq .alloc_error + move.l #UAESND_RECORD_BYTES,p_RecordBufferSize(a3) +.norecordbuffer move.l a3,a0 bsr.w reset_ch @@ -942,6 +1107,16 @@ AHIsub_AllocAudio move.l d0,base_stream_allocate(a0) move.l #AHISF_KNOWSTEREO|AHISF_KNOWHIFI|AHISF_KNOWMULTICHANNEL,d7 + move.l p_Capabilities(a3),d0 + btst #3,d0 + beq.s .nocanrecord + or.l #AHISF_CANRECORD,d7 +.nocanrecord + +.alloc_error + cmp.l #AHISF_ERROR,d7 + bne.s .error + bsr.w cleanup_allocaudio_error .error move.l d7,d0 @@ -953,6 +1128,37 @@ AHIsub_AllocAudio movem.l (sp)+,d2-d3/d7/a3-a6 rts +cleanup_allocaudio_error + movem.l d0/a1/a3,-(sp) + move.l ahiac_DriverData(a2),d0 + beq.s .done + move.l d0,a3 + move.l p_Sounds(a3),d0 + beq.s .nosounds + move.l d0,a1 + move.l p_SoundSize(a3),d0 + call FreeMem +.nosounds + move.l p_Channels(a3),d0 + beq.s .nochannels + move.l d0,a1 + move.l p_ChannelSize(a3),d0 + call FreeMem +.nochannels + move.l p_RecordBuffer(a3),d0 + beq.s .norecordbuffer + move.l d0,a1 + move.l p_RecordBufferSize(a3),d0 + call FreeMem +.norecordbuffer + move.l a3,a1 + move.l p_DriverDataSize(a3),d0 + call FreeMem + clr.l ahiac_DriverData(a2) +.done + movem.l (sp)+,d0/a1/a3 + rts + IFNE DEBUG debug_freeaudio dc.b "FreeAudio(%p)\n",0 @@ -980,6 +1186,9 @@ AHIsub_FreeAudio moveq #INTB_EXTER,d0 call RemIntServer + move.l p_Base(a3),a0 + move.l a3,a1 + bsr.w stop_recording move.l p_Base(a3),a0 bsr.w reset_hw clr.l base_stream_allocate(a0) @@ -998,6 +1207,12 @@ AHIsub_FreeAudio move.l p_ChannelSize(a3),d0 call FreeMem .nochannels + move.l p_RecordBuffer(a3),d0 + beq.s .norecordbuffer + move.l d0,a1 + move.l p_RecordBufferSize(a3),d0 + call FreeMem +.norecordbuffer move.l a3,a1 move.l p_DriverDataSize(a3),d0 @@ -1038,7 +1253,7 @@ AHIsub_GetAttr bne.s .notfreq lea tag_frequencies(pc),a0 move.l 0(a0,d1.w*4),d2 - bra.s .exit + bra.w .exit .notfreq cmp.l #AHIDB_Index,d0 bne.s .notindex @@ -1048,12 +1263,12 @@ AHIsub_GetAttr tst.l (a0) beq.s .freqexit cmp.l (a0)+,d1 - bls.s .exit + bls.w .exit addq.l #1,d2 bra.s .freqnext .freqexit moveq #FREQUENCIES-1,d2 - bra.s .exit + bra.w .exit .notindex cmp.l #AHIDB_Bits,d0 bne.s .notbits @@ -1061,8 +1276,60 @@ AHIsub_GetAttr tst.l d0 bmi.s .notbits move.l d0,d2 - bra.s .exit + bra.w .exit .notbits + cmp.l #AHIDB_MaxChannels,d0 + bne.s .notmaxchannels + bsr.w get_maxchannels + bra.w .exit +.notmaxchannels + cmp.l #AHIDB_Record,d0 + beq.s .capturebool + cmp.l #AHIDB_FullDuplex,d0 + beq.s .capturebool + cmp.l #AHIDB_MaxRecordSamples,d0 + beq.s .capturerecordsamples + cmp.l #AHIDB_MinInputGain,d0 + beq.s .capturegain + cmp.l #AHIDB_MaxInputGain,d0 + beq.s .capturegain + cmp.l #AHIDB_Inputs,d0 + beq.s .captureinputs + cmp.l #AHIDB_Input,d0 + beq.s .captureinput + bra.s .staticattr +.capturebool + bsr.w has_capture + move.l d0,d2 + bra.s .exit +.capturerecordsamples + bsr.w has_capture + tst.l d0 + beq.s .nocaptureattr + move.l #UAESND_RECORD_FRAMES,d2 + bra.s .exit +.capturegain + bsr.w has_capture + tst.l d0 + beq.s .nocaptureattr + moveq #1,d2 + swap d2 + bra.s .exit +.captureinputs + bsr.w has_capture + move.l d0,d2 + bra.s .exit +.captureinput + bsr.w has_capture + tst.l d0 + beq.s .nocaptureattr + lea tag_input(pc),a0 + move.l a0,d2 + bra.s .exit +.nocaptureattr + moveq #0,d2 + bra.s .exit +.staticattr lea GetAttrTags(pc),a0 .next @@ -1113,6 +1380,47 @@ get_audioid_bits moveq #-1,d0 rts +get_maxchannels + movem.l d0/a0,-(sp) + move.l a2,d0 + beq.s .library_base + move.l ahiac_DriverData(a2),d0 + beq.s .library_base + move.l d0,a0 + move.l p_Base(a0),a0 + bra.s .got_base +.library_base + move.l ub_Base(a6),a0 +.got_base + moveq #0,d2 + move.b base_max_streams(a0),d2 + bne.s .done + moveq #8,d2 +.done + movem.l (sp)+,d0/a0 + rts + +has_capture + movem.l d1/a0,-(sp) + move.l a2,d1 + beq.s .library_base + move.l ahiac_DriverData(a2),d1 + beq.s .library_base + move.l d1,a0 + move.l p_Capabilities(a0),d1 + bra.s .got_capabilities +.library_base + move.l ub_Base(a6),a0 + move.l base_capabilities(a0),d1 +.got_capabilities + moveq #0,d0 + btst #3,d1 + beq.s .done + moveq #1,d0 +.done + movem.l (sp)+,d1/a0 + rts + tag_frequencies dc.l 4410 ; CD/10 @@ -1151,7 +1459,13 @@ GetAttrTags dc.l AHIDB_Author, tag_author dc.l AHIDB_Copyright, tag_copyright dc.l AHIDB_Version, IDString - dc.l AHIDB_Record, 0 + dc.l AHIDB_Record, 1 + dc.l AHIDB_FullDuplex, 1 + dc.l AHIDB_MaxRecordSamples, UAESND_RECORD_FRAMES + dc.l AHIDB_MinInputGain, $10000 + dc.l AHIDB_MaxInputGain, $10000 + dc.l AHIDB_Inputs, 1 + dc.l AHIDB_Input, tag_input dc.l AHIDB_Outputs, 1 dc.l AHIDB_Output, tag_output dc.l AHIDB_PingPong, 1 @@ -1166,6 +1480,8 @@ tag_copyright dc.b "(C) 2020 Toni Wilen",0 tag_output dc.b "UAE",0 +tag_input + dc.b "UAE host capture",0 even IFNE DEBUG @@ -1241,12 +1557,13 @@ AHIsub_HardwareControl .dontgetinputgain cmp.l #AHIC_Input,d0 bne.s .dontsetinput - nop + move.w d1,p_Input(a1) bra.s .done .dontsetinput cmp.l #AHIC_Input_Query,d0 bne.b .dontgetinput moveq #0,d0 + move.w p_Input(a1),d0 bra.s .doneout .dontgetinput cmp.l #AHIC_Output,d0 @@ -1292,7 +1609,12 @@ callsoundfunc: interrupt_code move.l p_Base(a1),a0 move.l base_stream_intreq(a0),d0 + bne.s .gotirq + tst.w p_RecordActive(a1) + beq.w .notours + tst.l base_capture_intreq(a0) beq.w .notours +.gotirq movem.l d2-d7/a2-a4,-(sp) move.l d0,d7 @@ -1362,6 +1684,8 @@ interrupt_code cmp.w p_StreamCnt(a3),d6 bne.s .cont + move.l a3,a5 + bsr.w process_recording movem.l (sp)+,d2-d7/a2-a4 moveq #0,d0 rts diff --git a/drivers/mhi/Makefile b/drivers/mhi/Makefile new file mode 100644 index 0000000..e80aba3 --- /dev/null +++ b/drivers/mhi/Makefile @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2020-2026 Dimitris Panokostas +# SPDX-License-Identifier: GPL-3.0-or-later + +VERSION ?= 2.4 +DATE ?= 2026-06-10 +VERSION_WORDS = $(subst ., ,$(VERSION)) +LIB_VERSION = $(word 1,$(VERSION_WORDS)) +LIB_REVISION = $(word 2,$(VERSION_WORDS)) +LIB_REVISION := $(if $(LIB_REVISION),$(LIB_REVISION),0) + +ifeq ($(origin CC),default) +CC = m68k-amigaos-gcc +endif + +BUILD_DIR = build +PACKAGE_DIR = package +LIB_OUT = $(PACKAGE_DIR)/Libs/MHI/mhiuae.library +SOURCES = src/mhiuae_startup.c src/mhiuae.c +HEADERS = src/mhi_abi.h src/mhiuae.h ../../src/uae_pragmas.h +CFLAGS = -mcpu=68020 -noixemul -Os -fomit-frame-pointer -std=c99 -Wall -Wextra -Wstrict-prototypes -I../../src +VERFLAGS = -DVERSION_STR="\"$(VERSION)\"" -DDATE_STR="\"$(DATE)\"" -DLIB_VERSION=$(LIB_VERSION) -DLIB_REVISION=$(LIB_REVISION) +LDFLAGS = -nostartfiles + +.SUFFIXES: +.PHONY: all clean + +all: $(LIB_OUT) + +$(BUILD_DIR)/mhiuae.library: $(SOURCES) $(HEADERS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(VERFLAGS) $(LDFLAGS) $(SOURCES) -o $@ + +$(LIB_OUT): $(BUILD_DIR)/mhiuae.library | $(PACKAGE_DIR)/Libs/MHI + cp $< $@ + +$(BUILD_DIR): + mkdir -p $@ + +$(PACKAGE_DIR)/Libs/MHI: + mkdir -p $@ + +clean: + rm -rf $(BUILD_DIR) $(PACKAGE_DIR) diff --git a/drivers/mhi/src/mhi_abi.h b/drivers/mhi/src/mhi_abi.h new file mode 100644 index 0000000..3ed49a5 --- /dev/null +++ b/drivers/mhi/src/mhi_abi.h @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2020-2026 Dimitris Panokostas + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef MHI_ABI_H +#define MHI_ABI_H + +#define MHIF_PLAYING 0 +#define MHIF_STOPPED 1 +#define MHIF_OUT_OF_DATA 2 +#define MHIF_PAUSED 3 + +#define MHIF_UNSUPPORTED 0 +#define MHIF_SUPPORTED 1 +#define MHIF_FALSE 0 +#define MHIF_TRUE 1 + +#define MHIQ_CAPABILITIES 0 +#define MHIQ_MPEG1 1 +#define MHIQ_MPEG2 2 +#define MHIQ_MPEG25 3 + +#define MHIQ_LAYER3 12 + +#define MHIQ_VARIABLE_BITRATE 20 +#define MHIQ_JOINT_STEREO 21 + +#define MHIQ_VOLUME_CONTROL 40 +#define MHIQ_PANNING_CONTROL 41 + +#define MHIQ_DECODER_NAME 1000 +#define MHIQ_DECODER_VERSION 1001 +#define MHIQ_AUTHOR 1002 + +#define MHIQ_IS_HARDWARE 1010 +#define MHIQ_IS_68K 1011 +#define MHIQ_IS_PPC 1012 + +#define MHIP_VOLUME 0 +#define MHIP_PANNING 1 + +#endif /* MHI_ABI_H */ diff --git a/drivers/mhi/src/mhiuae.c b/drivers/mhi/src/mhiuae.c new file mode 100644 index 0000000..7128bc6 --- /dev/null +++ b/drivers/mhi/src/mhiuae.c @@ -0,0 +1,193 @@ +/* + * SPDX-FileCopyrightText: 2020-2026 Dimitris Panokostas + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define __USE_SYSBASE + +#include +#include + +#include "mhiuae.h" +#include "uae_pragmas.h" + +static const char decoder_version[] __attribute__((used)) = MHIUAE_LIBRARY_NAME " " VERSION_STR " (" DATE_STR ")"; + +BOOL mhiuae_open_uae(void) +{ + return InitUAEResource() ? TRUE : FALSE; +} + +void mhiuae_close_uae(void) +{ +} + +static struct MHIUAEPlayer *valid_player(APTR handle) +{ + return (struct MHIUAEPlayer *)handle; +} + +APTR i_MHIAllocDecoder(struct Task *task __asm("a0"), ULONG sigmask __asm("d0"), struct MHIUAEBase *base __asm("a6")) +{ + struct MHIUAEPlayer *player; + ULONG host_handle; + + ObtainSemaphore(&base->allocation_lock); + if (base->allocated_decoders != 0) { + ReleaseSemaphore(&base->allocation_lock); + return NULL; + } + + if (task == NULL) { + task = FindTask(NULL); + } + + host_handle = UaeMHIAlloc(task, sigmask); + if (host_handle == 0) { + ReleaseSemaphore(&base->allocation_lock); + return NULL; + } + + player = AllocVec(sizeof(*player), MEMF_PUBLIC | MEMF_CLEAR); + if (player == NULL) { + UaeMHIFree(host_handle); + ReleaseSemaphore(&base->allocation_lock); + return NULL; + } + + player->task = task; + player->sigmask = sigmask; + player->host_handle = host_handle; + player->status = MHIF_STOPPED; + base->allocated_decoders++; + ReleaseSemaphore(&base->allocation_lock); + return player; +} + +void i_MHIFreeDecoder(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")) +{ + struct MHIUAEPlayer *player = valid_player(handle); + + if (player == NULL) { + return; + } + + ObtainSemaphore(&base->allocation_lock); + UaeMHIFree(player->host_handle); + if (base->allocated_decoders > 0) { + base->allocated_decoders--; + } + ReleaseSemaphore(&base->allocation_lock); + FreeVec(player); +} + +BOOL i_MHIQueueBuffer(APTR handle __asm("a3"), APTR buffer __asm("a0"), ULONG size __asm("d0"), struct MHIUAEBase *base __asm("a6")) +{ + struct MHIUAEPlayer *player = valid_player(handle); + ULONG queued; + + (void)base; + if (player == NULL || buffer == NULL || size == 0) { + return FALSE; + } + + queued = UaeMHIQueue(player->host_handle, buffer, size, (ULONG)buffer); + if (queued && player->task != NULL && player->sigmask != 0) { + Signal(player->task, player->sigmask); + } + return queued ? TRUE : FALSE; +} + +APTR i_MHIGetEmpty(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")) +{ + struct MHIUAEPlayer *player = valid_player(handle); + + (void)base; + if (player == NULL) { + return NULL; + } + return (APTR)UaeMHIGetEmpty(player->host_handle); +} + +UBYTE i_MHIGetStatus(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")) +{ + struct MHIUAEPlayer *player = valid_player(handle); + + (void)base; + if (player == NULL) { + return MHIF_STOPPED; + } + player->status = (UBYTE)UaeMHIStatus(player->host_handle); + return player->status; +} + +void i_MHIPlay(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")) +{ + struct MHIUAEPlayer *player = valid_player(handle); + + (void)base; + if (player != NULL && UaeMHIPlay(player->host_handle)) { + player->status = MHIF_PLAYING; + } +} + +void i_MHIStop(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")) +{ + struct MHIUAEPlayer *player = valid_player(handle); + + (void)base; + if (player != NULL && UaeMHIStop(player->host_handle)) { + player->status = MHIF_STOPPED; + } +} + +void i_MHIPause(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")) +{ + struct MHIUAEPlayer *player = valid_player(handle); + + (void)base; + if (player != NULL && UaeMHIPause(player->host_handle)) { + player->status = MHIF_PAUSED; + } +} + +ULONG i_MHIQuery(ULONG query __asm("d1"), struct MHIUAEBase *base __asm("a6")) +{ + (void)base; + switch (query) { + case MHIQ_CAPABILITIES: + return (ULONG)MHIUAE_CAPABILITIES; + case MHIQ_DECODER_NAME: + return (ULONG)MHIUAE_DECODER_NAME; + case MHIQ_DECODER_VERSION: + return (ULONG)decoder_version; + case MHIQ_AUTHOR: + return (ULONG)MHIUAE_AUTHOR; + case MHIQ_IS_HARDWARE: + return MHIF_TRUE; + case MHIQ_IS_68K: + case MHIQ_IS_PPC: + return MHIF_FALSE; + case MHIQ_MPEG1: + case MHIQ_MPEG2: + case MHIQ_MPEG25: + case MHIQ_LAYER3: + case MHIQ_VARIABLE_BITRATE: + case MHIQ_JOINT_STEREO: + case MHIQ_VOLUME_CONTROL: + case MHIQ_PANNING_CONTROL: + return MHIF_SUPPORTED; + default: + return MHIF_UNSUPPORTED; + } +} + +void i_MHISetParam(APTR handle __asm("a3"), UWORD param __asm("d0"), ULONG value __asm("d1"), struct MHIUAEBase *base __asm("a6")) +{ + struct MHIUAEPlayer *player = valid_player(handle); + + (void)base; + if (player != NULL) { + UaeMHISetParam(player->host_handle, param, value); + } +} diff --git a/drivers/mhi/src/mhiuae.h b/drivers/mhi/src/mhiuae.h new file mode 100644 index 0000000..0ae2190 --- /dev/null +++ b/drivers/mhi/src/mhiuae.h @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2020-2026 Dimitris Panokostas + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef MHIUAE_H +#define MHIUAE_H + +#include +#include +#include +#include + +#include "mhi_abi.h" + +#define MHIUAE_LIBRARY_NAME "mhiuae.library" +#define MHIUAE_DECODER_NAME "UAE MP3 decoder" +#define MHIUAE_AUTHOR "Amiberry" +#define MHIUAE_CAPABILITIES "audio/mpeg audio/mp3" + +struct MHIUAEBase { + struct Library lib; + BPTR seg_list; + struct ExecBase *sys_base; + struct SignalSemaphore allocation_lock; + ULONG allocated_decoders; +}; + +struct MHIUAEPlayer { + struct Task *task; + ULONG sigmask; + ULONG host_handle; + UBYTE status; +}; + +BOOL mhiuae_open_uae(void); +void mhiuae_close_uae(void); + +APTR i_MHIAllocDecoder(struct Task *task __asm("a0"), ULONG sigmask __asm("d0"), struct MHIUAEBase *base __asm("a6")); +void i_MHIFreeDecoder(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")); +BOOL i_MHIQueueBuffer(APTR handle __asm("a3"), APTR buffer __asm("a0"), ULONG size __asm("d0"), struct MHIUAEBase *base __asm("a6")); +APTR i_MHIGetEmpty(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")); +UBYTE i_MHIGetStatus(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")); +void i_MHIPlay(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")); +void i_MHIStop(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")); +void i_MHIPause(APTR handle __asm("a3"), struct MHIUAEBase *base __asm("a6")); +ULONG i_MHIQuery(ULONG query __asm("d1"), struct MHIUAEBase *base __asm("a6")); +void i_MHISetParam(APTR handle __asm("a3"), UWORD param __asm("d0"), ULONG value __asm("d1"), struct MHIUAEBase *base __asm("a6")); + +#endif diff --git a/drivers/mhi/src/mhiuae_startup.c b/drivers/mhi/src/mhiuae_startup.c new file mode 100644 index 0000000..59a2730 --- /dev/null +++ b/drivers/mhi/src/mhiuae_startup.c @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2020-2026 Dimitris Panokostas + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define __USE_SYSBASE + +#include + +#include +#include +#include +#include +#include + +#include "mhiuae.h" + +struct ExecBase *SysBase = NULL; + +extern APTR FuncTab[]; +extern struct MyDataInit DataTab; +extern APTR EndResident; + +struct MHIUAEBase *InitLib(struct ExecBase *sysbase __asm("a6"), BPTR seglist __asm("a0"), struct MHIUAEBase *base __asm("d0")); +struct MHIUAEBase *OpenLib(struct MHIUAEBase *base __asm("a6")); +BPTR CloseLib(struct MHIUAEBase *base __asm("a6")); +BPTR ExpungeLib(struct MHIUAEBase *base __asm("a6")); +ULONG ExtFuncLib(void); + +static const char lib_name[] __attribute__((used)) = MHIUAE_LIBRARY_NAME; +static const char lib_id[] __attribute__((used)) = MHIUAE_LIBRARY_NAME " " VERSION_STR " (" DATE_STR ")"; +static const char ver_string[] __attribute__((used)) = "\0$VER: " MHIUAE_LIBRARY_NAME " " VERSION_STR " (" DATE_STR ")"; + +#define MHIUAE_INIT_PTR(value) ((ULONG)(uintptr_t)(value)) + +LONG LibStart(void) +{ + return -1; +} + +struct InitTable { + ULONG lib_base_size; + APTR *function_table; + struct MyDataInit *data_table; + APTR init_function; +}; + +struct InitTable InitTab = { + sizeof(struct MHIUAEBase), + &FuncTab[0], + &DataTab, + InitLib +}; + +APTR FuncTab[] = { + OpenLib, + CloseLib, + ExpungeLib, + ExtFuncLib, + i_MHIAllocDecoder, + i_MHIFreeDecoder, + i_MHIQueueBuffer, + i_MHIGetEmpty, + i_MHIGetStatus, + i_MHIPlay, + i_MHIStop, + i_MHIPause, + i_MHIQuery, + i_MHISetParam, + (APTR)((LONG)-1) +}; + +struct Resident ROMTag __attribute__((used)) = { + RTC_MATCHWORD, + &ROMTag, + &EndResident, + RTF_AUTOINIT, + LIB_VERSION, + NT_LIBRARY, + 0, + (char *)&lib_name[0], + (char *)&lib_id[0], + &InitTab +}; + +APTR EndResident; + +struct MyDataInit { + UWORD ln_Type_Init; UWORD ln_Type_Offset; UWORD ln_Type_Content; + UBYTE ln_Name_Init; UBYTE ln_Name_Offset; ULONG ln_Name_Content; + UWORD lib_Flags_Init; UWORD lib_Flags_Offset; UWORD lib_Flags_Content; + UWORD lib_Version_Init; UWORD lib_Version_Offset; UWORD lib_Version_Content; + UWORD lib_Revision_Init; UWORD lib_Revision_Offset; UWORD lib_Revision_Content; + UBYTE lib_IdString_Init; UBYTE lib_IdString_Offset; ULONG lib_IdString_Content; + ULONG end_mark; +} DataTab = { + INITBYTE(OFFSET(Node, ln_Type), NT_LIBRARY), + 0x80, (UBYTE)OFFSET(Node, ln_Name), MHIUAE_INIT_PTR(&lib_name[0]), + INITBYTE(OFFSET(Library, lib_Flags), LIBF_SUMUSED | LIBF_CHANGED), + INITWORD(OFFSET(Library, lib_Version), LIB_VERSION), + INITWORD(OFFSET(Library, lib_Revision), LIB_REVISION), + 0x80, (UBYTE)OFFSET(Library, lib_IdString), MHIUAE_INIT_PTR(&lib_id[0]), + 0 +}; + +struct MHIUAEBase *InitLib(struct ExecBase *sysbase __asm("a6"), BPTR seglist __asm("a0"), struct MHIUAEBase *base __asm("d0")) +{ + ULONG negsize; + ULONG possize; + ULONG fullsize; + UBYTE *negptr; + + SysBase = sysbase; + base->sys_base = sysbase; + base->seg_list = seglist; + InitSemaphore(&base->allocation_lock); + base->allocated_decoders = 0; + + if (mhiuae_open_uae()) { + return base; + } + + negsize = base->lib.lib_NegSize; + possize = base->lib.lib_PosSize; + fullsize = negsize + possize; + negptr = (UBYTE *)base - negsize; + FreeMem(negptr, fullsize); + return NULL; +} + +struct MHIUAEBase *OpenLib(struct MHIUAEBase *base __asm("a6")) +{ + base->lib.lib_OpenCnt++; + base->lib.lib_Flags &= ~LIBF_DELEXP; + return base; +} + +BPTR CloseLib(struct MHIUAEBase *base __asm("a6")) +{ + if (base->lib.lib_OpenCnt > 0) { + base->lib.lib_OpenCnt--; + } + if (base->lib.lib_OpenCnt == 0 && (base->lib.lib_Flags & LIBF_DELEXP)) { + return ExpungeLib(base); + } + return 0; +} + +BPTR ExpungeLib(struct MHIUAEBase *base __asm("a6")) +{ + BPTR seglist; + ULONG negsize; + ULONG possize; + ULONG fullsize; + UBYTE *negptr; + + if (base->lib.lib_OpenCnt != 0 || base->allocated_decoders != 0) { + base->lib.lib_Flags |= LIBF_DELEXP; + return 0; + } + + seglist = base->seg_list; + Remove((struct Node *)base); + mhiuae_close_uae(); + + negsize = base->lib.lib_NegSize; + possize = base->lib.lib_PosSize; + fullsize = negsize + possize; + negptr = (UBYTE *)base - negsize; + FreeMem(negptr, fullsize); + return seglist; +} + +ULONG ExtFuncLib(void) +{ + return 0; +} diff --git a/package/Help/Host-Tools.guide b/package/Help/Host-Tools.guide index e5069be..e9ac1b5 100644 --- a/package/Help/Host-Tools.guide +++ b/package/Help/Host-Tools.guide @@ -11,6 +11,7 @@ Installed components: @{"Command tools" link Commands} @{"UAE AHI audio driver" link AHI} @{"UAESND AHI audio driver" link UAESND} +@{"UAE MHI MP3 decoder" link MHI} @{"Installation notes" link InstallNotes} @endnode @@ -68,6 +69,19 @@ The installer does not change AHI preferences. Select a uaesnd mode in AHI prefe @endnode +@node MHI "UAE MHI MP3 Decoder" +The UAE MHI MP3 decoder library is installed only when selected in the installer. + +Installed file: + +Libs/MHI/mhiuae.library + +The library implements the classic MHI decoder ABI and forwards MP3 buffers to Amiberry through uae.resource. Amiberry decodes the stream with its host mpg123 decoder and feeds the result into the emulator audio mixer, so MP3 playback does not consume Amiga CPU time. + +The library does not require or configure an AHI mode. It can be used alongside Paula, UAE AHI, UAESND AHI, or another selected AHI driver. + +@endnode + @node HostRun "host-run" host-run executes a command on the host operating system. @@ -95,7 +109,7 @@ Usage: host-shell [command] -Without a command, the host user's default shell is opened. +The host user's login shell is used, so shell profile files can set paths and environment variables. This applies both to interactive sessions and to commands passed on the host-shell command line. The host terminal is sized to match the Amiga console window, so full-screen host programs render correctly. Resize the Amiga window and restart host-shell to apply a new size. diff --git a/package/Install b/package/Install index 1c3ff62..af9fdfb 100644 --- a/package/Install +++ b/package/Install @@ -5,7 +5,7 @@ (set @default-dest "") (welcome - "Host-Tools installs command line tools and the optional UAE and UAESND AHI audio drivers for classic AmigaOS 68k systems running under Amiberry." + "Host-Tools installs command line tools and the optional UAE and UAESND AHI audio drivers and UAE MHI MP3 decoder library for classic AmigaOS 68k systems running under Amiberry." ) (procedure P_SetVersionFields #_source #_destfile @@ -128,6 +128,16 @@ ) ) +(procedure P_EnsureDir #_destdir + (if + (NOT (exists #_destdir)) + (makedir #_destdir + (prompt (cat "Create " #_destdir)) + (help @makedir-help) + ) + ) +) + (procedure P_CopyVersionedFileAutomatic #_label #_source #_destdir (copylib (prompt (cat "Install " #_label)) @@ -249,6 +259,13 @@ ) ) +(procedure P_InstallMHILibrary + ( + (P_EnsureDir "LIBS:MHI") + (P_CopySelectedVersionedFile "UAE MHI MP3 decoder library" "Libs/MHI/mhiuae.library" "LIBS:MHI" "mhiuae.library") + ) +) + (set #help-language (getenv "Language") ) @@ -263,7 +280,7 @@ (if (= @user-level 0) - (set #install-components 15) + (set #install-components 31) (set #install-components (askoptions (prompt @@ -271,7 +288,8 @@ "Command tools: host commands to C:.\n" "Docs: Host-Tools.guide to HELP:.\n" "UAE AHI: uae.audio and UAE AudioMode.\n" - "UAESND AHI: uaesnd.audio and UAESND AudioMode." + "UAESND AHI: uaesnd.audio and UAESND AudioMode.\n" + "MHI MP3: mhiuae.library to LIBS:MHI." ) (help "All components are selected by default. Novice installs every component and replaces existing files without further questions. Intermediate lets you select components, then copies versioned files automatically and replaces unversioned files. Expert shows installed and package versions before replacing existing versioned files, and asks before replacing unversioned files. No startup files, AHI preferences, or system preferences are changed." @@ -281,8 +299,9 @@ "AmigaGuide documentation" "UAE AHI audio driver" "UAESND AHI audio driver" + "UAE MHI MP3 decoder" ) - (default 15) + (default 31) ) ) ) @@ -315,4 +334,8 @@ (P_InstallUAESNDDriver) ) +(if (BITAND #install-components 16) + (P_InstallMHILibrary) +) + (exit) diff --git a/src/host-shell.c b/src/host-shell.c index 28dc8d2..9044962 100644 --- a/src/host-shell.c +++ b/src/host-shell.c @@ -11,6 +11,7 @@ #include "host_capture.h" #include "host_common.h" +#include "host_shell_command.h" #define OUTBUFSIZE 4095 @@ -194,21 +195,11 @@ int main(int argc, char *argv[]) */ if (query_console_size(in, out, &term_rows, &term_cols, pending, sizeof(pending), &pending_len) && - append_size_prefix(session_command, sizeof(session_command), term_rows, term_cols)) { - if (command[0] != '\0') { - if (!host_append_literal(session_command, sizeof(session_command), command)) { - session_command[0] = '\0'; - } - } else if (!host_append_literal(session_command, sizeof(session_command), - "exec \"${SHELL:-/bin/sh}\"")) { - session_command[0] = '\0'; - } - } else { + !append_size_prefix(session_command, sizeof(session_command), term_rows, term_cols)) { session_command[0] = '\0'; } - if (session_command[0] == '\0' && - !host_append_literal(session_command, sizeof(session_command), command)) { + if (!host_append_shell_login_command(session_command, sizeof(session_command), command)) { printf("Command is too long\n"); return_code = HOST_RETURN_ERROR; goto cleanup; diff --git a/src/host_shell_command.h b/src/host_shell_command.h new file mode 100644 index 0000000..0c4ba6e --- /dev/null +++ b/src/host_shell_command.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2020-2026 Dimitris Panokostas + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef HOST_SHELL_COMMAND_H +#define HOST_SHELL_COMMAND_H + +#include + +#include "host_common.h" + +static inline int host_append_shell_login_command(char *command, size_t command_size, + const char *session_command) +{ + if (!host_append_literal(command, command_size, "h=\"${SHELL:-/bin/sh}\"; exec \"$h\" -l")) { + return 0; + } + + if (session_command == NULL || session_command[0] == '\0') { + return 1; + } + + return host_append_literal(command, command_size, " -c ") && + host_append_shell_arg(command, command_size, session_command, 0); +} + +#endif diff --git a/src/uae_pragmas.h b/src/uae_pragmas.h index 5793fa0..8e27c99 100644 --- a/src/uae_pragmas.h +++ b/src/uae_pragmas.h @@ -217,4 +217,65 @@ static int UAE_UNUSED HostShell_View(UBYTE *filename) return calltrap(89, filename); } +#define UAE_MHI_TRAP_ALLOC 110 +#define UAE_MHI_TRAP_FREE 111 +#define UAE_MHI_TRAP_QUEUE 112 +#define UAE_MHI_TRAP_GET_EMPTY 113 +#define UAE_MHI_TRAP_STATUS 114 +#define UAE_MHI_TRAP_PLAY 115 +#define UAE_MHI_TRAP_STOP 116 +#define UAE_MHI_TRAP_PAUSE 117 +#define UAE_MHI_TRAP_SET_PARAM 118 +#define UAE_MHI_TRAP_QUERY 119 + +static ULONG UAE_UNUSED UaeMHIAlloc(APTR task, ULONG sigmask) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_ALLOC, task, sigmask); +} + +static ULONG UAE_UNUSED UaeMHIFree(ULONG handle) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_FREE, handle); +} + +static ULONG UAE_UNUSED UaeMHIQueue(ULONG handle, APTR buffer, ULONG size, ULONG token) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_QUEUE, handle, buffer, size, token); +} + +static ULONG UAE_UNUSED UaeMHIGetEmpty(ULONG handle) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_GET_EMPTY, handle); +} + +static ULONG UAE_UNUSED UaeMHIStatus(ULONG handle) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_STATUS, handle); +} + +static ULONG UAE_UNUSED UaeMHIPlay(ULONG handle) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_PLAY, handle); +} + +static ULONG UAE_UNUSED UaeMHIStop(ULONG handle) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_STOP, handle); +} + +static ULONG UAE_UNUSED UaeMHIPause(ULONG handle) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_PAUSE, handle); +} + +static ULONG UAE_UNUSED UaeMHISetParam(ULONG handle, ULONG param, ULONG value) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_SET_PARAM, handle, param, value); +} + +static ULONG UAE_UNUSED UaeMHIQuery(ULONG query) +{ + return (ULONG)calltrap(UAE_MHI_TRAP_QUERY, query); +} + #endif diff --git a/tests/test_ahi_driver_source.sh b/tests/test_ahi_driver_source.sh index 165d323..5fd67ea 100644 --- a/tests/test_ahi_driver_source.sh +++ b/tests/test_ahi_driver_source.sh @@ -104,10 +104,9 @@ grep -F -q 'move.w #$8000,p_OutputVolume(a3)' "$V2_DRIVER_SOURCE" grep -F -q 'mulu.w p_OutputVolume(a2),d0' "$V2_DRIVER_SOURCE" grep -F -q 'move.w d1,p_OutputVolume(a1)' "$V2_DRIVER_SOURCE" grep -F -q 'move.w p_OutputVolume(a1),d0' "$V2_DRIVER_SOURCE" -if grep -F -q 'move.w d4,set_hpan(a0)' "$V2_DRIVER_SOURCE" || grep -F -q 'move.w d4,STREAM_START+UAESNDSetCurrent+set_hpan(a0)' "$V2_DRIVER_SOURCE"; then - echo "UAESND v2 should not apply AHI panning until fixed even/odd stereo routing is stable" >&2 - exit 1 -fi +grep -F -q 'sub.l #$8000,d1' "$V2_DRIVER_SOURCE" +grep -F -q 'move.w d1,set_hpan(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'move.w d1,STREAM_START+UAESNDSetCurrent+set_hpan(a0)' "$V2_DRIVER_SOURCE" grep -F -q 'move.b #3,set_mask(a4)' "$V2_DRIVER_SOURCE" grep -F -q 'or.b #%101,stream_master_intena(a5)' "$V2_DRIVER_SOURCE" grep -F -q 'and.b #%101,d2' "$V2_DRIVER_SOURCE" @@ -125,6 +124,10 @@ grep -F -q 'or.l d0,p_PendingStartMask(a3)' "$V2_DRIVER_SOURCE" grep -F -q 'flush_pending_starts' "$V2_DRIVER_SOURCE" grep -F -q 'or.l d2,base_stream_enable(a0)' "$V2_DRIVER_SOURCE" grep -F -q 'move.l ch_set_current(a2),stream_sample_pointer_imm(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'bsr.w cleanup_allocaudio_error' "$V2_DRIVER_SOURCE" +grep -F -q 'cleanup_allocaudio_error' "$V2_DRIVER_SOURCE" +grep -F -q 'tst.w p_DisableCount(a1)' "$V2_DRIVER_SOURCE" +grep -F -q 'beq.s .already_enabled' "$V2_DRIVER_SOURCE" grep -F -q 'dc.b "uaesnd",0' "$V2_MODE_SOURCE" grep -F -q 'AHIDB_AudioID,$003b0001' "$V2_MODE_SOURCE" grep -F -q 'AHIDB_AudioID,$003b0002' "$V2_MODE_SOURCE" @@ -140,12 +143,60 @@ grep -A10 -F 'AHIDB_AudioID,$003b0003' "$V2_MODE_SOURCE" | grep -F -q 'AHIDB_Bit test "$(grep -F -c 'AHIDB_Bits,16' "$V2_MODE_SOURCE")" -eq 1 test "$(grep -F -c 'AHIDB_Bits,32' "$V2_MODE_SOURCE")" -eq 2 grep -F -q 'move.l #UAESND_SIZEOF,p_DriverDataSize(a3)' "$V2_DRIVER_SOURCE" +grep -F -q 'UAESND_CAP_CAPTURE EQU 8' "$V2_DRIVER_SOURCE" +grep -F -q 'UAESND_CAP_CAPTURE_BLOCK EQU 16' "$V2_DRIVER_SOURCE" +grep -F -q 'UAESND_CAPTURE_CONTROL_ENABLE EQU 1' "$V2_DRIVER_SOURCE" +grep -F -q 'UAESND_CAPTURE_CONTROL_IRQ_ENABLE EQU 2' "$V2_DRIVER_SOURCE" +grep -F -q 'UAESND_CAPTURE_BLOCK_COMMAND_COPY EQU 1' "$V2_DRIVER_SOURCE" +grep -F -q 'UAESND_RECORD_FRAMES EQU 2048' "$V2_DRIVER_SOURCE" +grep -F -q 'base_capture_block_address EQU $900' "$V2_DRIVER_SOURCE" +grep -F -q 'base_capture_block_frames EQU $904' "$V2_DRIVER_SOURCE" +grep -F -q 'base_capture_block_done EQU $908' "$V2_DRIVER_SOURCE" +grep -F -q 'base_capture_block_command EQU $90c' "$V2_DRIVER_SOURCE" +grep -F -q 'ULONG base_capabilities' "$V2_DRIVER_SOURCE" +grep -F -q 'ULONG base_capture_control' "$V2_DRIVER_SOURCE" +grep -F -q 'ULONG base_capture_intreq' "$V2_DRIVER_SOURCE" +grep -F -q 'ULONG base_capture_threshold' "$V2_DRIVER_SOURCE" +grep -F -q 'ULONG base_capture_available' "$V2_DRIVER_SOURCE" +grep -F -q 'ULONG p_Capabilities' "$V2_DRIVER_SOURCE" +grep -F -q 'APTR p_RecordBuffer' "$V2_DRIVER_SOURCE" +grep -F -q 'STRUCT p_RecordMessage,AHIRecordMessage_SIZEOF' "$V2_DRIVER_SOURCE" +grep -F -q 'move.l base_capabilities(a4),p_Capabilities(a3)' "$V2_DRIVER_SOURCE" +grep -F -q 'or.l #AHISF_CANRECORD,d7' "$V2_DRIVER_SOURCE" +grep -F -q 'dc.l AHIDB_Record, 1' "$V2_DRIVER_SOURCE" +grep -F -q 'dc.l AHIDB_FullDuplex, 1' "$V2_DRIVER_SOURCE" +grep -F -q 'dc.l AHIDB_MaxRecordSamples, UAESND_RECORD_FRAMES' "$V2_DRIVER_SOURCE" +grep -F -q 'dc.l AHIDB_Inputs, 1' "$V2_DRIVER_SOURCE" +grep -F -q 'tag_input' "$V2_DRIVER_SOURCE" +grep -F -q 'btst #AHISB_RECORD,d2' "$V2_DRIVER_SOURCE" +grep -F -q 'btst #AHISB_PLAY,d2' "$V2_DRIVER_SOURCE" +grep -F -q 'bsr.w start_playback' "$V2_DRIVER_SOURCE" +grep -F -q 'bsr.w stop_playback' "$V2_DRIVER_SOURCE" +grep -F -q 'start_playback' "$V2_DRIVER_SOURCE" +grep -F -q 'stop_playback' "$V2_DRIVER_SOURCE" +grep -F -q 'move.l #UAESND_RECORD_BYTES,base_capture_threshold(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'move.l #UAESND_CAPTURE_CONTROL_ENABLE|UAESND_CAPTURE_CONTROL_IRQ_ENABLE,base_capture_control(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'btst #0,base_capture_status+3(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'bne.s .unavailable' "$V2_DRIVER_SOURCE" +grep -F -q 'tst.l base_capture_intreq(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'clr.l base_capture_intreq(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'clr.l base_capture_control(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'and.l #UAESND_CAP_CAPTURE_BLOCK,d1' "$V2_DRIVER_SOURCE" +grep -F -q 'move.l p_RecordBuffer(a5),base_capture_block_address(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'move.l #UAESND_RECORD_FRAMES,base_capture_block_frames(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'move.l #UAESND_CAPTURE_BLOCK_COMMAND_COPY,base_capture_block_command(a0)' "$V2_DRIVER_SOURCE" +grep -F -q 'move.l base_capture_block_done(a0),d0' "$V2_DRIVER_SOURCE" +grep -F -q 'move.l #AHIST_S16S,p_RecordMessage+ahirm_Type(a1)' "$V2_DRIVER_SOURCE" +grep -F -q 'move.l ahiac_SamplerFunc(a2),a0' "$V2_DRIVER_SOURCE" +grep -F -q 'bsr.w process_recording' "$V2_DRIVER_SOURCE" grep -F -q 'bsr.w get_audioid_bits' "$V2_DRIVER_SOURCE" grep -F -q 'move.l #AHIA_AudioID,d0' "$V2_DRIVER_SOURCE" grep -F -q 'move.l #AHIDB_AudioID,d0' "$V2_DRIVER_SOURCE" grep -F -q 'cmp.l #$003b0001,d0' "$V2_DRIVER_SOURCE" grep -F -q 'moveq #16,d0' "$V2_DRIVER_SOURCE" grep -F -q 'moveq #-1,d0' "$V2_DRIVER_SOURCE" +grep -F -q 'bsr.w get_maxchannels' "$V2_DRIVER_SOURCE" +grep -F -q 'move.b base_max_streams(a0),d2' "$V2_DRIVER_SOURCE" grep -F -q 'dc.l AHIDB_Bits, 32' "$V2_DRIVER_SOURCE" grep -F -q 'dc.l AHIDB_MaxChannels, 8' "$V2_DRIVER_SOURCE" if grep -F -q 'cmp.l #$003b0002,d0' "$V2_DRIVER_SOURCE" || grep -F -q 'bsr.w get_audioid_maxchannels' "$V2_DRIVER_SOURCE"; then diff --git a/tests/test_host_command_builders.c b/tests/test_host_command_builders.c index 56eaaa2..2c0da5b 100644 --- a/tests/test_host_command_builders.c +++ b/tests/test_host_command_builders.c @@ -13,6 +13,7 @@ #include "host_notify_command.h" #include "host_powershell.h" #include "host_reveal_command.h" +#include "host_shell_command.h" static void require(int condition, const char *message) { @@ -31,6 +32,14 @@ static void require_contains(const char *value, const char *needle) } } +static void require_string(const char *actual, const char *expected, const char *message) +{ + if (strcmp(actual, expected) != 0) { + fprintf(stderr, "%s\nexpected: %s\nactual: %s\n", message, expected, actual); + exit(1); + } +} + static void require_shell_syntax(const char *command) { char syntax_command[HOST_MAX_COMMAND_LEN * 2]; @@ -213,6 +222,33 @@ static void test_env_list_command(void) require_shell_syntax(command); } +static void test_shell_login_interactive_command(void) +{ + char command[HOST_MAX_COMMAND_LEN]; + + command[0] = '\0'; + require(host_append_shell_login_command(command, sizeof(command), ""), + "interactive shell login command should build"); + + require_string(command, "h=\"${SHELL:-/bin/sh}\"; exec \"$h\" -l", + "interactive shell should exec the user's login shell"); + require_shell_syntax(command); +} + +static void test_shell_login_explicit_command(void) +{ + char command[HOST_MAX_COMMAND_LEN]; + + command[0] = '\0'; + require(host_append_shell_login_command(command, sizeof(command), "printf '%s\\n' \"$PATH\""), + "explicit shell login command should build"); + + require_string(command, + "h=\"${SHELL:-/bin/sh}\"; exec \"$h\" -l -c 'printf '\\''%s\\n'\\'' \"$PATH\"'", + "explicit command should run through the user's login shell"); + require_shell_syntax(command); +} + static void require_ps_script(const char *command, const char *script) { static const char prefix[] = "powershell -NoProfile -EncodedCommand "; @@ -355,6 +391,8 @@ int main(void) test_env_set_command(); test_env_unset_command(); test_env_list_command(); + test_shell_login_interactive_command(); + test_shell_login_explicit_command(); test_ps_quoting(); test_windows_clip_commands(); test_windows_reveal_command(); diff --git a/tests/test_package_layout.sh b/tests/test_package_layout.sh index 0581bc4..490bbf1 100644 --- a/tests/test_package_layout.sh +++ b/tests/test_package_layout.sh @@ -142,6 +142,8 @@ grep -q 'UAE :16 bit HIFI Stereo++' "$PACKAGE_ROOT/Help/Host-Tools.guide" grep -q 'uaesnd: Stereo' "$PACKAGE_ROOT/Help/Host-Tools.guide" grep -q 'uaesnd: HiFi Stereo' "$PACKAGE_ROOT/Help/Host-Tools.guide" grep -q 'uaesnd: 7.1' "$PACKAGE_ROOT/Help/Host-Tools.guide" +grep -q 'UAE MHI MP3 Decoder' "$PACKAGE_ROOT/Help/Host-Tools.guide" +grep -q 'Libs/MHI/mhiuae.library' "$PACKAGE_ROOT/Help/Host-Tools.guide" for icon in "$PACKAGE_ROOT/Help/Host-Tools.guide.info"; do magic="$(od -An -tx1 -N2 "$icon" | tr -d ' \n')" @@ -163,16 +165,19 @@ grep -q "(askoptions" "$PACKAGE_ROOT/Install" grep -q "(= @user-level 0)" "$PACKAGE_ROOT/Install" grep -q "(= @user-level 1)" "$PACKAGE_ROOT/Install" grep -q "(= @user-level 2)" "$PACKAGE_ROOT/Install" -grep -q "(set #install-components 15)" "$PACKAGE_ROOT/Install" +grep -q "(set #install-components 31)" "$PACKAGE_ROOT/Install" grep -q "(procedure P_ReplaceFile" "$PACKAGE_ROOT/Install" +grep -q "(procedure P_EnsureDir" "$PACKAGE_ROOT/Install" grep -q "(procedure P_CopyVersionedFileAutomatic" "$PACKAGE_ROOT/Install" grep -q "(procedure P_CopyPlainFileAutomatic" "$PACKAGE_ROOT/Install" grep -q '"Command tools"' "$PACKAGE_ROOT/Install" grep -q '"AmigaGuide documentation"' "$PACKAGE_ROOT/Install" grep -q '"UAE AHI audio driver"' "$PACKAGE_ROOT/Install" +grep -q '"UAE MHI MP3 decoder"' "$PACKAGE_ROOT/Install" grep -q "Command tools: host commands to C:" "$PACKAGE_ROOT/Install" grep -q "Docs: Host-Tools.guide to HELP:" "$PACKAGE_ROOT/Install" grep -q "UAE AHI: uae.audio and UAE AudioMode." "$PACKAGE_ROOT/Install" +grep -q "MHI MP3: mhiuae.library to LIBS:MHI." "$PACKAGE_ROOT/Install" if grep -q "Intermediate installs selected components automatically" "$PACKAGE_ROOT/Install"; then echo "Installer component checklist prompt should not explain standard Intermediate/Expert behavior" >&2 exit 1 @@ -180,6 +185,7 @@ fi grep -q "(BITAND #install-components 1)" "$PACKAGE_ROOT/Install" grep -q "(BITAND #install-components 2)" "$PACKAGE_ROOT/Install" grep -q "(BITAND #install-components 4)" "$PACKAGE_ROOT/Install" +grep -q "(BITAND #install-components 16)" "$PACKAGE_ROOT/Install" if grep -q "(IN #install-components" "$PACKAGE_ROOT/Install"; then echo "Installer component checks should use BITAND for compatibility with common Installer scripts" >&2 exit 1 @@ -303,9 +309,18 @@ grep -q "(procedure P_InstallUAESNDDriver" "$PACKAGE_ROOT/Install" grep -q '"Devs/AHI/uaesnd.audio"' "$PACKAGE_ROOT/Install" grep -q '"Devs/AudioModes/UAESND"' "$PACKAGE_ROOT/Install" grep -q "(BITAND #install-components 8)" "$PACKAGE_ROOT/Install" -grep -q "(default 15)" "$PACKAGE_ROOT/Install" +grep -q "(default 31)" "$PACKAGE_ROOT/Install" grep -q "UAESND AHI: uaesnd.audio and UAESND AudioMode." "$PACKAGE_ROOT/Install" +test -f "$PACKAGE_ROOT/Libs/MHI/mhiuae.library" +grep -a -q '\$VER: mhiuae.library 2.4 (2026-06-10)' "$PACKAGE_ROOT/Libs/MHI/mhiuae.library" +grep -q "UAE MHI MP3 decoder library" "$PACKAGE_ROOT/Install" +grep -q "(procedure P_InstallMHILibrary" "$PACKAGE_ROOT/Install" +grep -q '"Libs/MHI/mhiuae.library"' "$PACKAGE_ROOT/Install" +grep -q '"LIBS:MHI"' "$PACKAGE_ROOT/Install" +grep -q '(P_EnsureDir "LIBS:MHI")' "$PACKAGE_ROOT/Install" +grep -q "(makedir #_destdir" "$PACKAGE_ROOT/Install" + package_dry_run="$(make -n package PACKAGE_DIR="${PACKAGE_DIR}-dryrun")" case "$package_dry_run" in *"Host-Tools-"*".lha"*) ;;