# Program for playing on PC-Speaker.
# For GNU/Linux 64 bit version. Root priveleges needed.
# Version: 5.
# Written on GNU as.
# Copyright (C) 2021-2026  Artyomov Alexander
# http://self-made-free.ru/
# aralni@mail.ru

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

.intel_syntax noprefix

# Syscalls
.equ sys_write, 1
.equ sys_open, 2
.equ sys_close, 3
.equ sys_lseek, 8
.equ sys_mmap, 9
.equ sys_munmap, 11
.equ sys_nanosleep, 35
.equ sys_rt_sigaction, 13
.equ sys_exit, 60
.equ sys_getuid, 102
.equ sys_setuid, 105
.equ sys_getpid, 39
.equ sys_setpriority, 141
.equ sys_execve, 59

# Constants
.equ PROT_READ, 1
.equ MAP_PRIVATE, 2
.equ SEEK_END, 2
.equ SIGINT, 2
.equ SIGTERM, 15
.equ SA_RESTORER, 0x04000000
.equ SA_RESTART, 268435456
.equ SA_SIGINFO, 0x00000004

.global _start
_start:

.section .text

    call print_help
    call start_ports
    call set_root
    call set_nice
    call setup_signals

    # Get argc and argv
    pop r8
    cmp r8, 1
    je play_default
    
    # Check for --stop
    pop rsi
    dec r8
    pop rsi
    dec r8
    lea rdi, [rip+stop_param]
    call strcmp
    cmp rax, 0
    jne process_files
    
    call exec_stop
    jmp exit_program

process_files:
    push rsi
    inc r8
    jmp loop_args

play_default:
    call spkon
    mov bx, 1000
    call spk
    mov ax, 1000
    call sleep_ms
    call spkoff
    jmp exit_program

loop_args:
    cmp r8, 0
    je exit_program
    
    pop rsi
    dec r8

    # Print filename
    push r8
    push rsi
    
    mov rax, sys_write
    mov rdi, 1
    call print_string
    
    mov rax, sys_write
    mov rdi, 1
    lea rsi, [rip+newline]
    mov rdx, 1
    syscall
    
    pop rsi
    pop r8
    
	# Play the file
	push r8
	call playfile
	pop r8
    
    call spkoff
    mov ax, 200
    call sleep_ms
    
    jmp loop_args


# Print zero-terminated string
print_string:
    push rdx
    push rsi
    push rax
    call string_length
    mov rdx, rax
    pop rax
    pop rsi
    syscall
    pop rdx
    ret

string_length:
    xor rax, rax
strlen_loop:
    cmp byte ptr [rsi + rax], 0
    je strlen_done
    inc rax
    jmp strlen_loop
strlen_done:
    ret

print_help:
	mov rax, sys_write
	mov rdi, rax
	lea rsi, [rip+help_msg]
	lea rdx, [rip+help_msg_end]
	sub rdx, rsi
	syscall
	ret

exit_program:
    xor rdi, rdi
    mov rax, sys_exit
    syscall

strcmp:
	xor rax, rax
strcmp_loop:
	mov dl, [rdi + rax]
	mov dh, [rsi + rax]
	cmp dl, dh
	jne strcmp_diff
	test dl, dl
	jz strcmp_eq
	inc rax
	jmp strcmp_loop
strcmp_diff:
	mov rax, 1
	ret
strcmp_eq:
	mov rax, 0
	ret


set_root:
    mov rax, sys_getuid
    syscall
    cmp rax, 0
    jne check_root_done
    mov rax, sys_setuid
    xor rdi, rdi
    syscall
check_root_done:
    ret

set_nice:
    mov rax, sys_getpid
    syscall
    mov rsi, rax
    xor rdi, rdi
    mov rdx, -20
    mov rax, sys_setpriority
    syscall
    ret

# Setup signal handlers
setup_signals:
    lea r10, [rip+signal_handler]
    mov [rip+sa_handler], r10
    mov r10, 8 # sizeof(sigset_t)
    xor rdx, rdx
    lea rsi, [rip+sigaction]
    mov rdi, SIGINT
    mov rax, sys_rt_sigaction
    syscall
    mov rdi, SIGTERM
    mov rax, sys_rt_sigaction
    syscall
ret

signal_handler:
    call spkoff
    mov rax, sys_write
    mov rdi, 1
    lea rsi, [rip+signal_msg]
    lea rdx, [rip+signal_msg_end]
    sub rdx, rsi
    syscall
    jmp exit_program

start_ports:
	mov rax, 173
	mov rdi, 0x42
	mov rsi, 2
	mov rdx, 1
	syscall
	cmp rax, 0
	jne ports_error
	mov rax, 173
	mov rdi, 0x61
	mov rsi, 1
	mov rdx, 1
	syscall
	cmp rax, 0
	jne ports_error
	ret

ports_error:
	mov rax, sys_write
	mov rdi, 1
	lea rsi, [rip+ports_msg]
	lea rdx, [rip+ports_msg_end]
	sub rdx, rsi
	syscall
	jmp exit_program

exec_stop:
    lea rax, [rip+kill_cmd]
    mov [rip+argv], rax
    lea rax, [rip+kill_param]
    mov [rip+argv+8], rax
    mov qword ptr [rip+argv+16], 0
    mov rax, sys_execve
    lea rdi, [rip+kill_cmd]
    lea rsi, [rip+argv]
    xor rdx, rdx
    syscall
    ret

spkon:
	in al, 0x61
	or al, 0x03
	out 0x61, al
	ret

spkoff:
	in al, 0x61
	and al, 0xFC
	out 0x61, al
	ret

spk:
	push rax
	push rbx
	mov al, 0xB6
	out 0x43, al
	mov al, bl
	out 0x42, al
	mov al, bh
	out 0x42, al
	pop rbx
	pop rax
	ret

# Упрощённый sleep_ms - сохраняет только необходимые регистры
sleep_ms:
    cmp ax, 0
    je sleep_ms_done
    
    push rax
    push rbx
    push rcx
    push rdx
    push rsi
    push rdi
    
    # Convert milliseconds to seconds and nanoseconds
    movzx rax, ax
    mov rbx, 1000
    xor rdx, rdx
    div rbx
    mov rcx, rax        # seconds
    mov rax, rdx
    mov rbx, 1000000
    mul rbx             # nanoseconds in rax
    
    # Setup timespec on stack
    sub rsp, 16
    mov qword ptr [rsp], rcx    # seconds
    mov qword ptr [rsp+8], rax  # nanoseconds
    
    mov rax, sys_nanosleep
    mov rdi, rsp
    xor rsi, rsi
    syscall
    
    add rsp, 16
    
    pop rdi
    pop rsi
    pop rdx
    pop rcx
    pop rbx
    pop rax
    
sleep_ms_done:
    ret

playfile:
    push rbx
    push r12
    push r13
    push r14
    push r15
    
    # Open file
    mov rdi, rsi
    xor rsi, rsi
    mov rax, sys_open
    syscall
    cmp rax, 0
    jl playfile_open_error
    mov rbx, rax
    
    # Get file size
    mov rdi, rbx
    xor rsi, rsi
    mov rdx, SEEK_END
    mov rax, sys_lseek
    syscall
    cmp rax, 0
    jl playfile_len_error
    mov r15, rax
    
    # Check alignment
    test r15, 3
    jnz playfile_align_error
    
    # mmap file
    xor rdi, rdi
    mov rsi, r15
    mov rdx, PROT_READ
    mov r10, MAP_PRIVATE
    mov r8, rbx
    xor r9, r9
    mov rax, sys_mmap
    syscall
    cmp rax, -1
    je playfile_map_error
    mov r13, rax
    
    # Close file
    mov rdi, rbx
    mov rax, sys_close
    syscall
    
    # Play
    mov rbx, r13
    mov rdx, r15
    call spkon
    
playfile_loop:
    cmp rdx, 4
    jl playfile_done
    
    mov eax, [rbx]
    movzx r12, ax           # frequency
    shr eax, 16
    movzx r14, ax           # duration
    
    # Skip zero duration
    cmp r14, 0
    je playfile_skip
    
    cmp r12, 0
    je playfile_silence
    
    # Play tone
    push rbx
    push rdx
    mov bx, r12w
    call spk
    mov ax, r14w
    call sleep_ms
    pop rdx
    pop rbx
    jmp playfile_next
    
playfile_silence:
    push rbx
    push rdx
    call spkoff
    mov ax, r14w
    call sleep_ms
    call spkon
    pop rdx
    pop rbx
    jmp playfile_next
    
playfile_skip:
#    nop
    
playfile_next:
    add rbx, 4
    sub rdx, 4
    jmp playfile_loop
    
playfile_done:
    # Unmap
    mov rdi, r13
    mov rsi, r15
    mov rax, sys_munmap
    syscall
    
    pop r15
    pop r14
    pop r13
    pop r12
    pop rbx
    ret

playfile_open_error:
    mov rax, sys_write
    mov rdi, 1
    lea rsi, [rip+open_err_msg]
    lea rdx, [rip+open_err_msg_end]
	sub rdx, rsi
    syscall
    pop r15
    pop r14
    pop r13
    pop r12
    pop rbx
    ret

playfile_len_error:
    mov rax, sys_write
    mov rdi, 1
    lea rsi, [rip+len_err_msg]
    lea rdx, [rip+len_err_msg_end]
	sub rdx, rsi
    syscall
    pop r15
    pop r14
    pop r13
    pop r12
    pop rbx
    ret

playfile_map_error:
    mov rax, sys_write
    mov rdi, 1
    lea rsi, [rip+map_err_msg]
    lea rdx, [rip+map_err_msg_end]
	sub rdx, rsi
    syscall
    pop r15
    pop r14
    pop r13
    pop r12
    pop rbx
    ret

playfile_align_error:
    mov rax, sys_write
    mov rdi, 1
    lea rsi, [rip+align_err_msg]
    lea rdx, [rip+align_err_msg_end]
	sub rdx, rsi
    syscall
    pop r15
    pop r14
    pop r13
    pop r12
    pop rbx
    ret

.section .rodata

ports_msg: .ascii "\nError: Cannot get I/O ports access. Need root privileges.\n"
ports_msg_end:
stop_param: .ascii "--stop\0"
open_err_msg: .ascii "\n>>> [*] FILE OPEN Error\n"
open_err_msg_end:
len_err_msg: .ascii "\n>>> [><] GET FILE LENGTH Error\n"
len_err_msg_end:
map_err_msg: .ascii "\n>>> [*] FILE MAP Error\n"
map_err_msg_end:
align_err_msg: .ascii "\n>>> [*] FILE Alignment Error (not multiple of 4)\n"
align_err_msg_end:
signal_msg: .ascii "\n--==[[ Breaked by SIGNAL ]]==--\n"
signal_msg_end:
kill_cmd: .ascii "/usr/bin/killall\0"
kill_param: .ascii "gorg64_spkplay\0"
newline: .ascii "\n"
help_msg: .ascii "GALAXY ORGANIZER SPEAKER PLAYER Version 5\nArtyomov Alexander 2022-2026 License: GNU AGPLv3 and above\nUse: gorg64_spkplay or gorg64_spkplay somemusic.speaker somemusic2.speaker ... or gorg64_spkplay --stop\n\0"
help_msg_end:

.section .data

.align 16
sigaction:
    sa_handler:  .quad 0
    sa_flags:    .quad SA_RESTART + SA_RESTORER
    sa_restorer: .quad 0;
    sa_mask:     .quad 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

.align 8
argv: .quad 0, 0, 0
