From 58c7d9e0b909ad569ab29fbe95151d17ff65731f Mon Sep 17 00:00:00 2001 From: OmniaX Date: Mon, 2 Sep 2024 18:04:12 +0200 Subject: [PATCH] Started work on Text16-Mode part of the int 0x30 Video Drived --- build.nr | 2 +- compile | 6 ++--- extra/config/testMachine.dvm | 2 +- extra/dragon/bios.bin | Bin 4096 -> 4096 bytes extra/dragon/disk1.dr | Bin 4194304 -> 4194304 bytes extra/dss/bios/data.dss | 14 ++++++---- extra/dss/bios/drivers.dss | 11 +++++++- extra/dss/bios_api.dss | 46 +++++++++++++++++++++++++------- extra/dss/drake/bootsector.dss | 38 ++++++++++++++++++++++++++ extra/info/info | 8 ++++-- extra/make | 4 +-- extra/make_and_debug | 4 +-- extra/make_and_run | 4 +-- src/hardware/VirtualDisplay.cpp | 42 ++++++++++++++++++++--------- src/hardware/VirtualDisplay.hpp | 13 ++++++--- src/runtime/ConfigLoader.cpp | 9 ++++++- src/runtime/ConfigLoader.hpp | 1 + src/tools/GlobalData.hpp | 5 +++- 18 files changed, 164 insertions(+), 45 deletions(-) diff --git a/build.nr b/build.nr index 3c8cfd3..74d36ca 100644 --- a/build.nr +++ b/build.nr @@ -1 +1 @@ -1602 +1603 diff --git a/compile b/compile index fbfb8ce..781f96c 100755 --- a/compile +++ b/compile @@ -5,7 +5,7 @@ rm -r bin if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then cmake -B bin -S ./ cd bin - make + make -j$(grep -c ^processor /proc/cpuinfo) mkdir disassembly if [ $? -eq 0 ]; then cd .. @@ -19,7 +19,7 @@ if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then cmake -B bin -S ./ -G "MinGW Makefiles" cd bin - mingw32-make.exe + mingw32-make.exe -j16 mkdir disassembly if [ $? -eq 0 ]; then cd .. @@ -30,4 +30,4 @@ elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then else cd .. fi -fi \ No newline at end of file +fi diff --git a/extra/config/testMachine.dvm b/extra/config/testMachine.dvm index 6f7116d..42e22d6 100644 --- a/extra/config/testMachine.dvm +++ b/extra/config/testMachine.dvm @@ -9,4 +9,4 @@ memory_extension_pages = 16 SingleColor_foreground = #009900FF SingleColor_background = #111111FF -16Color_Palette = config/test_palette.dpal \ No newline at end of file +16Color_Palette = 0 \ No newline at end of file diff --git a/extra/dragon/bios.bin b/extra/dragon/bios.bin index 3d8814d66881f77e43073cff7571333ccfb3b240..644e773dbf389dd0e4db1b2f7440d2f7c2085348 100644 GIT binary patch delta 504 zcmYjOF>e!54F2xk*)MfKDH1*t>gnPsHRh(EGTK|6em3n1;@?2;ePQcRH0uiT6@^q z53T(eDY;SFMi(!##HG3A1^q~_mS_BM=I;XsQ`r@`TzE%%^W3o|#?l@#%*9Pv{WD-o zt{eY+OHj!@;e(z4Q?7j-KViF)#xK+_fYn`wta69#v5VRIY1o%|{LuEzS?Wl#0lh9T U;M-D(`V*k)HvahA+>bi{03nlFjsO4v delta 460 zcmYjNu}T9$5PiA5nT-=Axf4#yBZx*sh=QeX{y}2M6_%35E{^SCf}Nd^pRg3t*x75N zom!bM(AGlZZX#&0%$ql}@4ek&I!tel!YaM=rr#71w}1{mj84MiffV6uJ9b11LC4Bh z#LG$>C@JkCP}+x4>LOI?ft1d`O6Raj7bq)TVnOK&qI8Xl(j69+?(wGdgh@fqn@Jd_ zi3uD`lTaKq4mKoq1K$N%JZNy-cP?;)nnmEFlsa~M-kN{yub5YGSJ|C#4#|~2O_Rkl z$*Y;|30EOW@qnT*bak7~F*L4!txQ-f)`+|*#g<#y$i%r1BQZQJY7f9y=SPOX( z3m?LVkZ8bCEDrp|$EnxrZoR!Xw=;fod|RvChfRmTeL6Gd+|lX-|=O4UrURH@zcTL0p%*9uq7ihGbX@Qj`%Hl`$zvSt?SMahZ@wsY(A6 z%alyZjLb@1=AvLK7HB+IfQO<9#SS(gpjlr7no9cjs~?8&|y$e|p`v7E@MoXNRd L$faC;aQ*WKdEG?G diff --git a/extra/dss/bios/data.dss b/extra/dss/bios/data.dss index 3327622..3d9c9c3 100644 --- a/extra/dss/bios/data.dss +++ b/extra/dss/bios/data.dss @@ -39,6 +39,8 @@ KEY_PRESSED 0xA0 KEY_RELEASED 0xA1 TEXT_ENTERED 0xA2 + + TEXT16_SCREEN_REFRESHED 0xE0 @end @raw_export_end @export_comment BIOS_API " --\n" @@ -57,10 +59,12 @@ TEXT_SIGNLE_STR { MemoryAddresses.VGA + 0x0006 } MEMORY_CONTROLLER_X { MemoryAddresses.VGA + 0x0080 } - MEMORY_CONTROLLER_Y { MemoryAddresses.VGA + 0x0084 } - MEMORY_CONTROLLER_CHAR { MemoryAddresses.VGA + 0x0088 } - MEMORY_CONTROLLER_BG_COL { MemoryAddresses.VGA + 0x0089 } - MEMORY_CONTROLLER_FG_COL { MemoryAddresses.VGA + 0x008A } + MEMORY_CONTROLLER_X_BYTE { MemoryAddresses.VGA + 0x0080 + 1 } + MEMORY_CONTROLLER_Y { MemoryAddresses.VGA + 0x0082 } + MEMORY_CONTROLLER_Y_BYTE { MemoryAddresses.VGA + 0x0082 + 1 } + MEMORY_CONTROLLER_CHAR { MemoryAddresses.VGA + 0x0084 } + MEMORY_CONTROLLER_BG_COL { MemoryAddresses.VGA + 0x0085 } + MEMORY_CONTROLLER_FG_COL { MemoryAddresses.VGA + 0x0086 } BUFF_START { MemoryAddresses.VGA + 0x00E0 } @end @@ -159,7 +163,7 @@ @export_comment BIOS_API " Structure to store a character cell for the Text16-Video-Mode." @raw_export_start BIOS_API -@struct Text16VModeTextCell +@struct Text16VModeCell Character:1 Foreground:1 Background:1 diff --git a/extra/dss/bios/drivers.dss b/extra/dss/bios/drivers.dss index 8bdc665..9a5061f 100644 --- a/extra/dss/bios/drivers.dss +++ b/extra/dss/bios/drivers.dss @@ -128,7 +128,16 @@ _int_30_print_string_buffered: jmp $_int_30_end _int_30_write_vram_text16: - + movb [VGA_Registers.MEMORY_CONTROLLER_CHAR], *R9 + inc R9 + movb [VGA_Registers.MEMORY_CONTROLLER_FG_COL], *R9 + inc R9 + movb [VGA_Registers.MEMORY_CONTROLLER_BG_COL], *R9 + inc R9 + movb [VGA_Registers.MEMORY_CONTROLLER_X_BYTE], *R9 + inc R9 + movb [VGA_Registers.MEMORY_CONTROLLER_Y_BYTE], *R9 + movb [VGA_Registers.SIGNAL], Sig_VGA_Text_16_Color.WRITE_VRAM jmp $_int_30_end _int_30_read_vram_text16: diff --git a/extra/dss/bios_api.dss b/extra/dss/bios_api.dss index d7ec2c7..745b6b1 100644 --- a/extra/dss/bios_api.dss +++ b/extra/dss/bios_api.dss @@ -1,5 +1,5 @@ ## -- -## -- This file is automatically generated by the DragonAssembler (version 0.4.1601) +## -- This file is automatically generated by the DragonAssembler (version 0.4.1602) ## -- Please do not modify this file in any way. ## -- @@ -20,19 +20,27 @@ KEY_PRESSED 0xA0 KEY_RELEASED 0xA1 TEXT_ENTERED 0xA2 + TEXT16_SCREEN_REFRESHED 0xE0 @end ## -- ## These are the memory-mapped registers used to interact with the video card's interface. @group VGA_Registers - VIDEO_MODE { MemoryAddresses.VGA + 0x0000 } - CLEAR_COLOR { MemoryAddresses.VGA + 0x0001 } - PALETTE { MemoryAddresses.VGA + 0x0002 } - SIGNAL { MemoryAddresses.VGA + 0x0003 } - TEXT_SINGLE_CHAR { MemoryAddresses.VGA + 0x0004 } - TEXT_SIGNLE_INVERT { MemoryAddresses.VGA + 0x0005 } - TEXT_SIGNLE_STR { MemoryAddresses.VGA + 0x0006 } - BUFF_START { MemoryAddresses.VGA + 0x00E0 } + VIDEO_MODE { MemoryAddresses.VGA + 0x0000 } + CLEAR_COLOR { MemoryAddresses.VGA + 0x0001 } + PALETTE { MemoryAddresses.VGA + 0x0002 } + SIGNAL { MemoryAddresses.VGA + 0x0003 } + TEXT_SINGLE_CHAR { MemoryAddresses.VGA + 0x0004 } + TEXT_SIGNLE_INVERT { MemoryAddresses.VGA + 0x0005 } + TEXT_SIGNLE_STR { MemoryAddresses.VGA + 0x0006 } + MEMORY_CONTROLLER_X { MemoryAddresses.VGA + 0x0080 } + MEMORY_CONTROLLER_X_BYTE { MemoryAddresses.VGA + 0x0080 + 1 } + MEMORY_CONTROLLER_Y { MemoryAddresses.VGA + 0x0082 } + MEMORY_CONTROLLER_Y_BYTE { MemoryAddresses.VGA + 0x0082 + 1 } + MEMORY_CONTROLLER_CHAR { MemoryAddresses.VGA + 0x0084 } + MEMORY_CONTROLLER_BG_COL { MemoryAddresses.VGA + 0x0085 } + MEMORY_CONTROLLER_FG_COL { MemoryAddresses.VGA + 0x0086 } + BUFF_START { MemoryAddresses.VGA + 0x00E0 } @end ## -- @@ -57,6 +65,16 @@ @end ## -- +## These are signals used to comunicate with the video card's interface, when in TEXT16_COLOR mode. +@group Sig_VGA_Text_16_Color + CONTINUE 0x00 + READ_VRAM 0x10 + WRITE_VRAM 0x11 + FORCE_REFRESH_SCREEN 0xE0 + FORCE_CLEAR_SCREEN 0xE1 +@end +## -- + ## Structure of a DPT (Dragon Partition Table). @group DPTStructure DISK_ADDR 0x0200 @@ -81,3 +99,13 @@ @end ## -- +## Structure to store a character cell for the Text16-Video-Mode. +@struct Text16VModeCell + Character:1 + Foreground:1 + Background:1 + CoordX:1 + CoordY:1 +@end +## -- + diff --git a/extra/dss/drake/bootsector.dss b/extra/dss/drake/bootsector.dss index 0c9f131..7927e9a 100644 --- a/extra/dss/drake/bootsector.dss +++ b/extra/dss/drake/bootsector.dss @@ -6,11 +6,49 @@ $string "Hello " $ddd $dpt_block + $textCell .code push 0 call $_load_dpt_block + movb [VGA_Registers.VIDEO_MODE], VGA_VideoModes.TEXT_16_COLORS + + mov R5, 0 +draw_loop: + + movb [$textCell.Character], 65 + movb [$textCell.Foreground], 6 + movb [$textCell.Background], 0 + movb [$textCell.CoordX], R5 + movb [$textCell.CoordY], 1 + + mov R10, 0x21 + mov R9, $textCell + int 0x30 + + inc R5 + mov ACC, R5 + jgr $draw_loop, 90 + + mov R5, 0 +draw_loop2: + + movb [$textCell.Character], 65 + movb [$textCell.Foreground], 6 + movb [$textCell.Background], 0 + movb [$textCell.CoordX], R5 + movb [$textCell.CoordY], 10 + + mov R10, 0x21 + mov R9, $textCell + int 0x30 + + inc R5 + mov ACC, R5 + jgr $draw_loop2, 90 + + mov R10, 0x0C mov R9, $string int 0x30 diff --git a/extra/info/info b/extra/info/info index 042d336..59e826a 100644 --- a/extra/info/info +++ b/extra/info/info @@ -102,8 +102,8 @@ 0x06: (2 bytes) Text Single Color Buffered String Address 0x80: Memory Controller - 0x80: XCoordinate (4 Bytes) - 0x84: YCoordinate (4 Bytes) + 0x80: XCoordinate (2 Bytes) + 0x84: YCoordinate (2 Bytes) 0x88: Character (1 Byte) 0x09: Foreground (1 Byte) 0x8A: Background (1 Byte) @@ -142,6 +142,9 @@ 0x0A: Store Null-Terminated String in buffer in Text Single Mode (string address stored in R9 register) 0x0B: Toggles inverted colors in Text Single Mode + 0x20: Read VRAM in Text16 Colors Mode (Cell address stored in R9) + 0x20: Write VRAM in Text16 Colors Mode (Cell address stored in R9) + 0xE0: Refresh Screen 0xE1: Clear Screen @@ -153,6 +156,7 @@ 0xA0: Keyboard Interface - Key Pressed 0xA1: Keyboard Interface - Key Released 0xA2: Keyboard Interface - Text Entered + 0xE0: Virtual Display - Text16_Mode Screen Refreshed diff --git a/extra/make b/extra/make index 8f22b0a..b2ea5ab 100755 --- a/extra/make +++ b/extra/make @@ -6,8 +6,8 @@ clear='\033[0m' printf "${green}\n============================================[ Building Application ]============================================\n\n${clear}" \cp -r ../extra/* ./ if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then - make + make -j$(grep -c ^processor /proc/cpuinfo) elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then - mingw32-make.exe + mingw32-make.exe -j16 fi printf "${green}\n=================================================================================================================\n\n" \ No newline at end of file diff --git a/extra/make_and_debug b/extra/make_and_debug index b8e5a76..8222b28 100755 --- a/extra/make_and_debug +++ b/extra/make_and_debug @@ -6,9 +6,9 @@ clear='\033[0m' printf "${green}\n============================================[ Building Application ]============================================\n\n${clear}" \cp -r ../extra/* ./ if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then - make + make -j$(grep -c ^processor /proc/cpuinfo) elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then - mingw32-make.exe + mingw32-make.exe -j16 fi printf "${green}\n=================================================================================================================\n\n" if [ $? -eq 0 ]; then diff --git a/extra/make_and_run b/extra/make_and_run index 4c67165..3a0844a 100755 --- a/extra/make_and_run +++ b/extra/make_and_run @@ -6,9 +6,9 @@ clear='\033[0m' printf "${green}\n============================================[ Building Application ]============================================\n\n${clear}" \cp -r ../extra/* ./ if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then - make + make -j$(grep -c ^processor /proc/cpuinfo) elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then - mingw32-make.exe + mingw32-make.exe -j16 fi printf "${green}\n=================================================================================================================\n\n" if [ $? -eq 0 ]; then diff --git a/src/hardware/VirtualDisplay.cpp b/src/hardware/VirtualDisplay.cpp index d368f6f..3edf1dd 100644 --- a/src/hardware/VirtualDisplay.cpp +++ b/src/hardware/VirtualDisplay.cpp @@ -12,7 +12,13 @@ namespace dragon m_renderer.initialize(*this); RawTextRenderer::initialize(); - m_text16_palette = new data::BiosVideoDefaultPalette; //TODO: Delete, Memory Leak + text16_load_palettes(); + m_currentPaletteID = DragonRuntime::machine_config.text16_palette; + if (m_currentPaletteID >= m_text16_palettes.size()) + m_text16_Currentpalette = m_text16_palettes[0]; + else + m_text16_Currentpalette = m_text16_palettes[m_currentPaletteID]; + text16_init_buffer(); } @@ -49,17 +55,19 @@ namespace dragon if (m_refreshScreen) { uint8_t clear_color = mem.read8(vga_addr + tRegisters::ClearColor); - ostd::Color clearColor = m_text16_palette->getColor(clear_color); + ostd::Color clearColor = m_text16_Currentpalette->getColor(clear_color); m_renderer.clear(clearColor); for (int32_t i = 0; i < RawTextRenderer::CONSOLE_CHARS_V * RawTextRenderer::CONSOLE_CHARS_H; i++) { auto& cell = m_text16_buffer[i]; - ostd::Color background = m_text16_palette->getColor(cell.backgroundColor); - ostd::Color foreground = m_text16_palette->getColor(cell.foregroundColor); + ostd::Color background = m_text16_Currentpalette->getColor(cell.backgroundColor); + ostd::Color foreground = m_text16_Currentpalette->getColor(cell.foregroundColor); char character = static_cast(cell.character); auto xy = CONVERT_1D_2D(i, RawTextRenderer::CONSOLE_CHARS_H); RawTextRenderer::drawString(ostd::String().addChar(character), xy.x, xy.y, m_renderer.getScreenPixels(), getWindowWidth(), getWindowHeight(), m_fontPixels, foreground, background); } + DragonRuntime::cpu.handleInterrupt(data::InterruptCodes::Text16ModeScreenRefreshed, true); + m_refreshScreen = false; } } m_renderer.updateBuffer(); @@ -121,6 +129,22 @@ namespace dragon } else if (video_mode == tVideoModeValues::Text16Colors) { + if (signal == tSignalValues::Text16Color_ReadMemory) + { + + } + else if (signal == tSignalValues::Text16Color_WriteMemory) + { + hw::interface::Graphics::tText16_Cell textCell; + + textCell.foregroundColor = mem.read8(vga_addr + tRegisters::MemControllerFGCol); + textCell.backgroundColor = mem.read8(vga_addr + tRegisters::MemControllerBGCol); + textCell.character = mem.read8(vga_addr + tRegisters::MemControllerChar); + int16_t x = mem.read16(vga_addr + tRegisters::MemControllerX); + int16_t y = mem.read16(vga_addr + tRegisters::MemControllerY); + + DragonRuntime::vGraphicsInterface.writeVRAM_16Colors(static_cast(x), static_cast(y), textCell.character, textCell.backgroundColor, textCell.foregroundColor); + } } else return; mem.write8(vga_addr + tRegisters::Signal, tSignalValues::Continue); @@ -133,7 +157,6 @@ namespace dragon uint16_t vga_addr = data::MemoryMapAddresses::VideoCardInterface_Start; uint8_t video_mode = mem.read8(vga_addr + tRegisters::VideoMode); uint8_t signal = mem.read8(vga_addr + tRegisters::Signal); - if (signal == tSignalValues::Continue) return; if (video_mode == tVideoModeValues::Text16Colors) { m_refreshScreen = true; @@ -147,8 +170,6 @@ namespace dragon } } - void VirtualDisplay::onFixedUpdate(void) { } - void VirtualDisplay::onSlowUpdate(void) { } void VirtualDisplay::single_text_add_char_to_line(char c) @@ -244,12 +265,9 @@ namespace dragon m_text16_buffer.push_back({ 0, 0, ' ' }); } - void VirtualDisplay::text16_buffer_diff(void) + void VirtualDisplay::text16_load_palettes(void) { - for (int32_t i = 0; i < RawTextRenderer::CONSOLE_CHARS_V * RawTextRenderer::CONSOLE_CHARS_H; i++) - { - - } + m_text16_palettes.push_back(new data::BiosVideoDefaultPalette); //TODO: Delete, Memory Leak } } diff --git a/src/hardware/VirtualDisplay.hpp b/src/hardware/VirtualDisplay.hpp index a39bf10..7c38b04 100644 --- a/src/hardware/VirtualDisplay.hpp +++ b/src/hardware/VirtualDisplay.hpp @@ -20,6 +20,12 @@ namespace dragon inline static constexpr uint8_t TextSingleCharacter = 0x04; inline static constexpr uint8_t TextSingleInvertColors = 0x05; inline static constexpr uint8_t TextSingleString = 0x06; + + inline static constexpr uint8_t MemControllerX = 0x80; + inline static constexpr uint8_t MemControllerY = 0x82; + inline static constexpr uint8_t MemControllerChar = 0x84; + inline static constexpr uint8_t MemControllerBGCol = 0x85; + inline static constexpr uint8_t MemControllerFGCol = 0x86; }; public: struct tVideoModeValues { @@ -49,7 +55,6 @@ namespace dragon void onRender(void) override; void onUpdate(void) override; void onFixedUpdate(void) override; - void onFixedUpdate(void) override; void onSlowUpdate(void) override; private: @@ -63,7 +68,7 @@ namespace dragon void single_text_refresh_screen(void); void text16_init_buffer(void); - void text16_buffer_diff(void); + void text16_load_palettes(void); private: Renderer m_renderer; @@ -72,7 +77,9 @@ namespace dragon ostd::String m_singleTextBuffer { "" }; std::vector m_text16_buffer; - data::IBiosVideoPalette* m_text16_palette; + std::vector m_text16_palettes; + data::IBiosVideoPalette* m_text16_Currentpalette { nullptr }; + uint8_t m_currentPaletteID { 0 }; }; } } \ No newline at end of file diff --git a/src/runtime/ConfigLoader.cpp b/src/runtime/ConfigLoader.cpp index 46c5a55..bb71ea4 100644 --- a/src/runtime/ConfigLoader.cpp +++ b/src/runtime/ConfigLoader.cpp @@ -9,7 +9,7 @@ namespace dragon { tMachineConfig config; ostd::TextFileBuffer file(configFilePath.cpp_str()); - if (!file.exists()) return config; + if (!file.exists()) return config; //TODO: Error auto lines = file.getLines(); for (auto& line : lines) { @@ -102,6 +102,13 @@ namespace dragon if (config.memory_extension_pages > data::DefaultValues::MaxMemoryExtensionPages) config.memory_extension_pages = data::DefaultValues::MaxMemoryExtensionPages; } + else if (lineEdit == "16color_palette") + { + lineEdit = tokens.next(); + lineEdit.trim().toLower(); + if (!lineEdit.isNumeric()) continue; //TODO: Error + config.text16_palette = lineEdit.toInt(); + } else continue; //TODO: Warning } return validate_machine_config(config); diff --git a/src/runtime/ConfigLoader.hpp b/src/runtime/ConfigLoader.hpp index 3c5108d..306ca0e 100644 --- a/src/runtime/ConfigLoader.hpp +++ b/src/runtime/ConfigLoader.hpp @@ -17,6 +17,7 @@ namespace dragon ostd::String cmos_path; ostd::Color singleColor_background; ostd::Color singleColor_foreground; + uint8_t text16_palette { 0 }; inline bool isValid(void) const { return m_valid; } inline void destroy(void) { for (auto& ptr : cpuext_list) delete ptr.second; } diff --git a/src/tools/GlobalData.hpp b/src/tools/GlobalData.hpp index bccc62b..2139591 100644 --- a/src/tools/GlobalData.hpp +++ b/src/tools/GlobalData.hpp @@ -150,7 +150,7 @@ namespace dragon return m_colors[col]; } - private: + protected: std::vector m_colors; }; @@ -196,6 +196,8 @@ namespace dragon inline static constexpr uint8_t KeyReleased = 0xA1; inline static constexpr uint8_t TextEntered = 0xA2; + inline static constexpr uint8_t Text16ModeScreenRefreshed = 0xE0; + inline static ostd::String getInterruptName(uint8_t code) { switch (code) @@ -204,6 +206,7 @@ namespace dragon case KeyPressed: return "Key_Pressed"; case KeyReleased: return "Key_Released"; case TextEntered: return "Text_Entered"; + case Text16ModeScreenRefreshed: return "Text16_Mode_Screen_Refreshed"; default: return "UNKNOWN"; } return "UNKNOWN";