### Copyright (C) 2020 Jeremiah Orians
### This file is part of stage0.
###
### stage0 is free software: you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
### the Free Software Foundation, either version 3 of the License, or
### (at your option) any later version.
###
### stage0 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 General Public License for more details.
###
### You should have received a copy of the GNU General Public License
### along with stage0.  If not, see <http://www.gnu.org/licenses/>.

## ELF Header
# :ELF_base ; (0x8048000)
	7F 45 4C 46                     # e_ident[EI_MAG0-3] ELF's magic number

	01                              # e_ident[EI_CLASS] Indicating 32 bit
	01                              # e_ident[EI_DATA] Indicating little endianness
	01                              # e_ident[EI_VERSION] Indicating original elf

	03                              # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict
	00                              # e_ident[EI_ABIVERSION] Set at 0 because none cares

	00 00 00 00 00 00 00            # e_ident[EI_PAD]

	02 00                           # e_type Indicating Executable
	03 00                           # e_machine Indicating x86
	01 00 00 00                     # e_version Indicating original elf

	54 80 04 08                     # e_entry Address of the entry point
	34 00 00 00                     # e_phoff Address of program header table
	00 00 00 00                     # e_shoff Address of section header table

	00 00 00 00                     # e_flags

	34 00                           # e_ehsize Indicating our 52 Byte header

	20 00                           # e_phentsize size of a program header table
	01 00                           # e_phnum number of entries in program table

	00 00                           # e_shentsize size of a section header table
	00 00                           # e_shnum number of entries in section table

	00 00                           # e_shstrndx index of the section names

## Program Header
# :ELF_program_headers
# :ELF_program_header__text
	01 00 00 00                     # ph_type: PT-LOAD = 1
	00 00 00 00                     # ph_offset

	00 80 04 08                     # ph_vaddr
	00 80 04 08                     # ph_physaddr

	E1 02 00 00                     # ph_filesz
	E1 02 00 00                     # ph_memsz

	07 00 00 00                     # ph_flags: PF-X|PF-W|PF-R = 7
	01 00 00 00                     # ph_align

# :ELF_text

# :_start ; (0x8048054)
	58                              # POP_EAX                           ; Get the number of arguments
	5B                              # POP_EBX                           ; Get the program name
	5B                              # POP_EBX                           ; Get the actual input name
	83FB 00                         # CMPI8_EBX !0                      ; Check for missing output
	75 06                           # JNE8 !_start_out                  ; Have real input
	50                              # PUSH_EAX                          ; Need to adjust stack
	BB 9B820408                     # LOADI32_EBX &default_file         ; Use "kaem.run"

# :_start_out ; (0x8048062)
	B9 00000000                     # LOADI32_ECX %0                    ; prepare read_only
	B8 05000000                     # LOADI32_EAX %5                    ; the syscall number for open()
	CD80                            # INT_80                            ; Now open that damn file

	83F8 00                         # CMPI8_EAX !0                      ; IF NULL We couldn't open the file
	0F8E A9000000                   # JLE32 %Exit_Failure               ; Abort hard
	A3 C9820408                     # STORE32_Absolute32_eax &script    ; Set input pointer

	58                              # POP_EAX                           ; Get start of envp
	89E5                            # COPY_ESP_to_EBP                   ; Protect envp

	B8 2D000000                     # LOADI32_EAX %45                   ; the Syscall # for SYS_BRK
	BB 00000000                     # LOADI32_EBX %0                    ; Get current brk
	CD80                            # INT_80                            ; Let the kernel do the work
	A3 D9820408                     # STORE32_Absolute32_eax &MALLOC    ; Set our malloc pointer

	;; Where the main work gets done
	;; Using EBX for tokens and ECX for tokens[i]
# :main_loop ; (0x8048090)
	A1 D1820408                     # LOAD32_Absolute32_eax &max_args   ; Using 256 char* of space
	E8 6C010000                     # CALL32 %malloc                    ; get it
	89C3                            # COPY_EAX_to_EBX                   ; set tokens

	89D9                            # COPY_EBX_to_ECX                   ; I = 0
	B8 00000000                     # LOADI32_EAX %0                    ; Using 0
	A3 DD820408                     # STORE32_Absolute32_eax &status    ; status = 0
	A3 CD820408                     # STORE32_Absolute32_eax &command_done ; command_done = 0

	;; Using EAX for result and EBX for tokens[i]
# :collect_command ; (0x80480AD)
	E8 A9000000                     # CALL32 %collect_token             ; Get another token
	83F8 00                         # CMPI8_EAX !0                      ; if NULL == result
	74 05                           # JE8 !collect_command_comment      ; It is a comment, don't store

	8901                            # STORE32_EAX_into_ECX              ; tokens[i] = result
	83C1 04                         # ADDI8_ECX !4                      ; i = i + 1 (adjusted for char* size)

# :collect_command_comment ; (0x80480BC)
	A1 CD820408                     # LOAD32_Absolute32_eax &command_done ; Using command_done
	83F8 00                         # CMPI8_EAX !0                      ; IF 0 == command_done
	74 E7                           # JE8 !collect_command              ; keep looping

	;; Deal with line comments
	39D9                            # CMP_EBX_ECX                       ; if 0 < i
	74 C6                           # JE8 !main_loop                    ; It was a comment

	E8 5D000000                     # CALL32 %print_command             ; print the command
	8B03                            # LOAD32_EAX_from_EBX               ; program = tokens[0]
	83F8 00                         # CMPI8_EAX !0                      ; IF NULL == program
	74 4A                           # JE8 !Exit_Failure                 ; Some shit went down, abort

	53                              # PUSH_EBX                          ; Protect Tokens
	B8 02000000                     # LOADI32_EAX %2                    ; FORKing
	CD80                            # INT_80                            ; int f = FORK()
	5B                              # POP_EBX                           ; Restore Tokens

	83F8 00                         # CMPI8_EAX !0                      ; Check fork
	7C 3C                           # JL8 !Exit_Failure                 ; IF f == -1 abort hard
	75 0F                           # JNE8 !collect_command_parent      ; IF f == 0 it is child

	;; Deal with child case
	B8 0B000000                     # LOADI32_EAX %11                   ; EXECVE
	89EA                            # COPY_EBP_to_EDX                   ; third arg = envp
	89D9                            # COPY_EBX_to_ECX                   ; second arg = tokens
	8B1B                            # LOAD32_EBX_from_EBX               ; program = tokens[0]
	CD80                            # INT_80                            ; execve(program, tokens, envp);
	EB 2B                           # JMP8 !Exit_Failure                ; return error

# :collect_command_parent ; (0x80480F5)
	89C3                            # COPY_EAX_to_EBX                   ; first arg = f
	B9 DD820408                     # LOADI32_ECX &status               ; second arg = &status
	BA 00000000                     # LOADI32_EDX %0                    ; third arg = NULL
	B8 07000000                     # LOADI32_EAX %7                    ; WAITPID
	CD80                            # INT_80                            ; waitpid(f, &status, 0);

	A1 DD820408                     # LOAD32_Absolute32_eax &status     ; Using status
	83F8 00                         # CMPI8_EAX !0                      ; IF 0 == status
	0F84 7AFFFFFF                   # JE32 %main_loop                   ; Loop forever

	;; Deal with very unhappy case
	B8 A9820408                     # LOADI32_EAX &hard                 ; Using "Subprocess error\nABORTING HARD\n"
	E8 32010000                     # CALL32 %File_Print                ; Print it

;; Exit_Failure function
;; Receives nothing
;; And aborts hard
;; Does NOT return
# :Exit_Failure ; (0x8048120)
	BB 01000000                     # LOADI32_EBX %1                    ; All is wrong
	B8 01000000                     # LOADI32_EAX %1                    ; put the exit syscall number in eax
	CD80                            # INT_80                            ; Call it a bad day


;; print_command function
;; Recieves tokens[j] in EBX and tokens[i] in ECX
;; Modifies EAX
# :print_command ; (0x804812C)
	53                              # PUSH_EBX                          ; Protect EBX
	B8 A4820408                     # LOADI32_EAX &prefix               ; using " +> "
	E8 1B010000                     # CALL32 %File_Print                ; print it
# :print_command_loop ; (0x8048137)
	8B03                            # LOAD32_EAX_from_EBX               ; using tokens[j]
	E8 14010000                     # CALL32 %File_Print                ; print it
	83C3 04                         # ADDI8_EBX !4                      ; j = j + 1
	B8 20000000                     # LOADI32_EAX %32                   ; using ' '
	E8 27010000                     # CALL32 %fputc                     ; print it
	39CB                            # CMP_ECX_EBX                       ; IF j < i
	75 E8                           # JNE8 !print_command_loop          ; otherwise keep looping

	B8 0A000000                     # LOADI32_EAX %10                   ; using '\n'
	E8 19010000                     # CALL32 %fputc                     ; print it
	5B                              # POP_EBX                           ; Restore EBX
	C3                              # RETURN


;; collect_token function
;; Recieves nothing
;; Overwrites EAX
;; Uses EAX as C, EBX as token and ECX as token[i]
# :collect_token ; (0x804815B)
	53                              # PUSH_EBX                          ; Protect EBX
	51                              # PUSH_ECX                          ; Protect ECX
	A1 D5820408                     # LOAD32_Absolute32_eax &max_string ; Using max_string
	E8 9F000000                     # CALL32 %malloc                    ; allocate space
	89C3                            # COPY_EAX_to_EBX                   ; token = malloc(max_string);
	89C1                            # COPY_EAX_to_ECX                   ; i = 0; set token[i]

# :collect_token_loop ; (0x804816B)
	E8 B7000000                     # CALL32 %fgetc                     ; c = fgetc(input);
	83F8 FC                         # CMPI8_EAX !-4                     ; if C == EOF
	0F84 16010000                   # JE32 %Done                        ; We are done

	83F8 20                         # CMPI8_EAX !32                     ; IF C == ' '
	74 4B                           # JE8 !collect_token_done           ; Space terminates token

	83F8 09                         # CMPI8_EAX !9                      ; IF C == '\t'
	74 46                           # JE8 !collect_token_done           ; tab terminates token

	83F8 0A                         # CMPI8_EAX !10                     ; IF C == '\n'
	75 0C                           # JNE8 !collect_token_string        ; otherwise check next

	;; It is a newline
	B8 01000000                     # LOADI32_EAX %1                    ; Using 1
	A3 CD820408                     # STORE32_Absolute32_eax &command_done ; Set command_done = TRUE
	EB 35                           # JMP8 !collect_token_done          ; Be done

# :collect_token_string ; (0x8048194)
	83F8 22                         # CMPI8_EAX !34                     ; IF C == '\"'
	75 07                           # JNE8 !collect_token_comment       ; otherwise check next

	;; It is a RAW STRING
	E8 39000000                     # CALL32 %collect_string            ; Get the rest of the string
	EB 29                           # JMP8 !collect_token_done          ; Be done

# :collect_token_comment ; (0x80481A0)
	83F8 23                         # CMPI8_EAX !35                     ; IF C == '#'
	75 11                           # JNE8 !collect_token_escape        ; otherwise check next

	;; It is a line comment
	E8 48000000                     # CALL32 %collect_comment           ; Read it all
	B8 01000000                     # LOADI32_EAX %1                    ; Using 1
	A3 CD820408                     # STORE32_Absolute32_eax &command_done ; Set command_done = TRUE
	EB 13                           # JMP8 !collect_token_done          ; Be done

# :collect_token_escape ; (0x80481B6)
	83F8 5C                         # CMPI8_EAX !92                     ; IF C == '\\'
	75 07                           # JNE8 !collect_token_other         ; otherwise just store it

	;; It is an escape char
	E8 67000000                     # CALL32 %fgetc                     ; Read the char to drop
	EB 07                           # JMP8 !collect_token_done          ; Be done

# :collect_token_other ; (0x80481C2)
	8801                            # STORE8_al_into_Address_ECX        ; token[i] = C
	83C1 01                         # ADDI8_ECX !1                      ; i = i + 1
	EB A2                           # JMP8 !collect_token_loop          ; Keep going

# :collect_token_done ; (0x80481C9)
	39CB                            # CMP_ECX_EBX                       ; IF i == 0
	75 05                           # JNE8 !collect_token_good          ; otherwise return the token
	BB 00000000                     # LOADI32_EBX %0                    ; token = NULL

# :collect_token_good ; (0x80481D2)
	89D8                            # COPY_EBX_to_EAX                   ; Return token
	59                              # POP_ECX                           ; Restore ECX
	5B                              # POP_EBX                           ; Restore EBX
	C3                              # RETURN


;; collect_string function
;; Recieves target[index] in ECX
;; Modifies EAX
;; Uses EAX as C
# :collect_string ; (0x80481D7)
	E8 4B000000                     # CALL32 %fgetc                     ; C = fgetc(input)
	83F8 FC                         # CMPI8_EAX !-4                     ; if C == EOF
	0F84 3BFFFFFF                   # JE32 %Exit_Failure                ; Something went horribly wrong

	83F8 22                         # CMPI8_EAX !34                     ; IF C == '\"'
	74 07                           # JE8 !collect_string_done          ; be done

	;; deal with inside of string
	8801                            # STORE8_al_into_Address_ECX        ; target[index] = C
	83C1 01                         # ADDI8_ECX !1                      ; index = index + 1
	EB E6                           # JMP8 !collect_string              ; Keep going

# :collect_string_done ; (0x80481F1)
	C3                              # RETURN


;; collect_comment function
;; Recieves nothing
;; Modifies EAX
;; uses EAX as Int C
;; Just throws away everything it reads
# :collect_comment ; (0x80481F2)
	E8 30000000                     # CALL32 %fgetc                     ; C = fgetc(input)
	83F8 FC                         # CMPI8_EAX !-4                     ; IF C == EOF
	0F84 20FFFFFF                   # JE32 %Exit_Failure                ; abort hard

	83F8 0A                         # CMPI8_EAX !10                     ; IF C == '\n'
	75 ED                           # JNE8 !collect_comment             ; otherwise keep looping
	C3                              # RETURN


;; Malloc isn't actually required if the program being built fits in the initial memory
;; However, it doesn't take much to add it.
;; Requires [MALLOC] to be initialized and EAX to have the number of desired bytes
# :malloc ; (0x8048206)
	53                              # PUSH_EBX                          ; Protect EBX
	51                              # PUSH_ECX                          ; Protect ECX
	52                              # PUSH_EDX                          ; Protect EDX
	8B1D D9820408                   # LOAD32_Absolute32_ebx &MALLOC     ; Using the current pointer
	01C3                            # ADD_eax_into_ebx                  ; Request the number of desired bytes
	B8 2D000000                     # LOADI32_EAX %45                   ; the Syscall # for SYS_BRK
	CD80                            # INT_80                            ; call the Kernel
	A1 D9820408                     # LOAD32_Absolute32_eax &MALLOC     ; Return pointer
	891D D9820408                   # STORE32_Absolute32_ebx &MALLOC    ; Update pointer
	5A                              # POP_EDX                           ; Restore EDX
	59                              # POP_ECX                           ; Restore ECX
	5B                              # POP_EBX                           ; Restore EBX
	C3                              # RETURN


;; fgetc function
;; Loads FILE* from [script]
;; Returns -4 (EOF) or char in EAX
# :fgetc ; (0x8048227)
	53                              # PUSH_EBX                          ; Protect EBX
	51                              # PUSH_ECX                          ; Protect ECX
	52                              # PUSH_EDX                          ; Protect EDX
	B8 FCFFFFFF                     # LOADI32_EAX %-4                   ; Put EOF in eax
	50                              # PUSH_EAX                          ; Assume bad (If nothing read, value will remain EOF)
	8D0C24                          # LEA32_ECX_from_esp                ; Get stack address
	8B1D C9820408                   # LOAD32_Absolute32_ebx &script     ; Where are we reading from
	B8 03000000                     # LOADI32_EAX %3                    ; the syscall number for read
	BA 01000000                     # LOADI32_EDX %1                    ; set the size of chars we want
	CD80                            # INT_80                            ; call the Kernel
	58                              # POP_EAX                           ; Get either char or EOF
	83F8 FC                         # CMPI8_EAX !-4                     ; Check for EOF
	74 03                           # JE8 !fgetc_done                   ; Return as is
	0FB6C0                          # MOVZX_al                          ; Make it useful
# :fgetc_done ; (0x804824E)
	5A                              # POP_EDX                           ; Restore EDX
	59                              # POP_ECX                           ; Restore ECX
	5B                              # POP_EBX                           ; Restore EBX
	C3                              # RETURN


;; File_Print function
;; Receives CHAR* in EAX
;; calls fputc for every non-null char
# :File_Print ; (0x8048252)
	53                              # PUSH_EBX                          ; Protect EBX
	51                              # PUSH_ECX                          ; Protect ECX
	89C3                            # COPY_EAX_to_EBX                   ; Protect S
	83F8 00                         # CMPI8_EAX !0                      ; Protect against nulls
	74 14                           # JE8 !File_Print_Done              ; Simply don't try to print them
# :File_Print_Loop ; (0x804825B)
	8A03                            # LOAD8_al_from_EBX                 ; Read byte
	0FB6C0                          # MOVZX_al                          ; zero extend
	83F8 00                         # CMPI8_EAX !0                      ; Check for NULL
	74 0A                           # JE8 !File_Print_Done              ; Stop at NULL

	E8 08000000                     # CALL32 %fputc                     ; write it
	83C3 01                         # ADDI8_EBX !1                      ; S = S + 1
	EB EC                           # JMP8 !File_Print_Loop             ; Keep going

# :File_Print_Done ; (0x804826F)
	59                              # POP_ECX                           ; Restore ECX
	5B                              # POP_EBX                           ; Restore EBX
	C3                              # RETURN


;; fputc function
;; receives CHAR in EAX and load FILE* from stdout
;; writes char and returns
# :fputc ; (0x8048272)
	53                              # PUSH_EBX                          ; Protect EBX
	51                              # PUSH_ECX                          ; Protect ECX
	52                              # PUSH_EDX                          ; Protect EDX
	50                              # PUSH_EAX                          ; We are writing eax
	8D0C24                          # LEA32_ECX_from_esp                ; Get stack address
	BB 01000000                     # LOADI32_EBX %1                    ; Write to target file
	B8 04000000                     # LOADI32_EAX %4                    ; the syscall number for write
	BA 01000000                     # LOADI32_EDX %1                    ; set the size of chars we want
	CD80                            # INT_80                            ; call the Kernel
	58                              # POP_EAX                           ; Restore stack
	5A                              # POP_EDX                           ; Restore EDX
	59                              # POP_ECX                           ; Restore ECX
	5B                              # POP_EBX                           ; Restore EBX
	C3                              # RETURN


# :Done ; (0x804828F)
	; program completed Successfully
	BB 00000000                     # LOADI32_EBX %0                    ; All is well
	B8 01000000                     # LOADI32_EAX %1                    ; put the exit syscall number in eax
	CD80                            # INT_80                            ; Call it a good day

# :default_file ; (0x804829B)
	6B61656D2E72756E00              # "kaem.run"
# :prefix ; (0x80482A4)
	202B3E2000                      # " +> "
# :hard ; (0x80482A9)
	53756270726F63657373206572726F720A41424F5254494E4720484152440A00 # "Subprocess error\nABORTING HARD\n"
# :script ; (0x80482C9)
	00000000
# :command_done ; (0x80482CD)
	00000000
# :max_args ; (0x80482D1)
	00040000
# :max_string ; (0x80482D5)
	00100000
# :MALLOC ; (0x80482D9)
	00000000
# :status ; (0x80482DD)
	00000000
# :ELF_end ; (0x80482E1)
