diff --git a/example/OrbisTTY.cpp b/example/OrbisTTY.cpp new file mode 100644 index 0000000..b9a61bb --- /dev/null +++ b/example/OrbisTTY.cpp @@ -0,0 +1,295 @@ +#include "OrbisTTY.h" + +OrbisTTY_2D::OrbisTTY_2D(int pixel_depth) { + this->depth = pixel_depth; +} + +void OrbisTTY_2D::update_resolution(void) { + OrbisVideoOutResolutionStatus resolution_status; + sceVideoOutGetResolutionStatus(this->video, &resolution_status); + + this->resolution.x = resolution_status.width; + this->resolution.y = resolution_status.height; + + this->frame_buffer_size = this->resolution.x * this->resolution.y * this->depth; +} + +bool OrbisTTY_2D::init(size_t mem_size, int num_frame_buffers) { + this->video = sceVideoOutOpen(ORBIS_VIDEO_USER_MAIN, ORBIS_VIDEO_OUT_BUS_MAIN, 0, 0); + this->video_mem = NULL; + + if (this->video < 0) + return false; + + if (sceSysmoduleLoadModule(ORBIS_SYSMODULE_FREETYPE_OL) < 0) + return false; + + if (FT_Init_FreeType(&this->freetype_lib) != 0) + return false; + + if (!this->init_flip_queue()) + return false; + + this->update_resolution(); + + if (!this->alloc_video_mem(mem_size, 0x200000)) + return false; + + if (!this->alloc_frame_buffers(num_frame_buffers)) + return false; + + sceVideoOutSetFlipRate(this->video, 0); + return true; +} + +bool OrbisTTY_2D::init_flip_queue(void) { + if (sceKernelCreateEqueue(&flip_queue, "orbistty flip queue") < 0) + return false; + + sceVideoOutAddFlipEvent(flip_queue, this->video, 0); + return true; +} + +bool OrbisTTY_2D::alloc_frame_buffers(int num) { + this->frame_buffers = new char* [num]; + + for (int index = 0; index < num; index++) + this->frame_buffers[index] = this->alloc_display_mem(frame_buffer_size); + + sceVideoOutSetBufferAttribute(&this->attr, 0x80000000, 1, 0, this->resolution.x, this->resolution.y, this->resolution.x); + + return (sceVideoOutRegisterBuffers(this->video, 0, reinterpret_cast(this->frame_buffers), num, &this->attr) == 0); +} + +char* OrbisTTY_2D::alloc_display_mem(size_t size) { + char* alloc_ptr = reinterpret_cast(video_memory_sp); + video_memory_sp += size; + + return alloc_ptr; +} + +bool OrbisTTY_2D::alloc_video_mem(size_t size, int alignment) { + this->direct_mem_alloc_size = (size + alignment - 1) / alignment * alignment; + + if (sceKernelAllocateDirectMemory(0, sceKernelGetDirectMemorySize(), this->direct_mem_alloc_size, alignment, 3, &this->direct_mem_off) < 0) { + this->direct_mem_alloc_size = 0; + return false; + } + + if (sceKernelMapDirectMemory(&this->video_mem, this->direct_mem_alloc_size, 0x33, 0, this->direct_mem_off, alignment) < 0) { + sceKernelReleaseDirectMemory(this->direct_mem_off, this->direct_mem_alloc_size); + + this->direct_mem_off = 0; + this->direct_mem_alloc_size = 0; + + return false; + } + + this->video_memory_sp = reinterpret_cast(this->video_mem); + return true; +} + +void OrbisTTY_2D::free_video_mem(void) { + sceKernelReleaseDirectMemory(this->direct_mem_off, this->direct_mem_alloc_size); + + this->video_mem = 0; + this->video_memory_sp = 0; + this->direct_mem_off = 0; + this->direct_mem_alloc_size = 0; + + delete this->frame_buffers; + this->frame_buffers = 0; +} + +void OrbisTTY_2D::set_active_frame_buffer(int index) { + this->active_frame_buffer_index = index; +} + +void OrbisTTY_2D::submit_flip(int frame_id) { + sceVideoOutSubmitFlip(this->video, this->active_frame_buffer_index, ORBIS_VIDEO_OUT_FLIP_VSYNC, frame_id); +} + +void OrbisTTY_2D::frame_wait(int frame_id) { + OrbisKernelEvent event; + int count; + + if (this->video == 0) + return; + + for (;;) { + OrbisVideoOutFlipStatus flip_status; + sceVideoOutGetFlipStatus(video, &flip_status); + if (flip_status.flipArg == frame_id) + break; + + if (sceKernelWaitEqueue(this->flip_queue, &event, 1, &count, 0) != 0) + break; + } +} + +void OrbisTTY_2D::frame_buffer_swap(void) { + this->active_frame_buffer_index = (this->active_frame_buffer_index + 1) % 2; +} + +void OrbisTTY_2D::frame_buffer_clear(void) { + this->frame_buffer_fill(white); +} + +bool OrbisTTY_2D::use_font(const char* font_path) { + if (FT_New_Face(this->freetype_lib, font_path, 0, &this->font_face) < 0) + return false; + + if (FT_Set_Pixel_Sizes(this->font_face, 0, 16) < 0) + return false; + + return true; +} + +void OrbisTTY_2D::frame_buffer_fill(struct OrbisTTY_Color color) { + this->draw_rectangle(0, 0, this->resolution.x, this->resolution.y, color); +} + +void OrbisTTY_2D::draw_pixel(int x, int y, struct OrbisTTY_Color color) { + int pixel = (y * this->resolution.x) + x; + + uint32_t encoded_color = 0x80000000 + (color.red << 16) + (color.green << 8) + color.blue; + + (reinterpret_cast(this->frame_buffers[this->active_frame_buffer_index]))[pixel] = encoded_color; +} + +void OrbisTTY_2D::draw_rectangle(int x, int y, int width, int height, struct OrbisTTY_Color color) { + int x_position; + int y_position; + + for (y_position = y; y_position < y + height; y_position++) { + for (x_position = x; x_position < x + width; x_position++) + this->draw_pixel(x_position, y_position, color); + } +} + +void OrbisTTY_2D::draw_text(char* text, int start_x, int start_y, struct OrbisTTY_Color bg_color, struct OrbisTTY_Color fg_color) { + int x_offset = 0; + int y_offset = 0; + + FT_GlyphSlot slot = this->font_face->glyph; + + for (int n = 0; n < strlen(text); n++) { + FT_UInt glyph_index = FT_Get_Char_Index(this->font_face, text[n]); + + if (FT_Load_Glyph(this->font_face, glyph_index, FT_LOAD_DEFAULT)) + continue; + + if (FT_Render_Glyph(slot, ft_render_mode_normal)) + continue; + + if (text[n] == '\n') { + x_offset = 0; + y_offset += slot->bitmap.width * 2; + + continue; + } + + for (int y_position = 0; y_position < slot->bitmap.rows; y_position++) { + for (int x_position = 0; x_position < slot->bitmap.width; x_position++) { + char pixel = slot->bitmap.buffer[(y_position * slot->bitmap.width) + x_position]; + + int x = start_x + x_position + x_offset + slot->bitmap_left; + int y = start_y + y_position + y_offset - slot->bitmap_top; + + uint8_t red = (pixel * fg_color.red) / 255; + uint8_t green = (pixel * fg_color.green) / 255; + uint8_t blue = (pixel * fg_color.blue) / 255; + + struct OrbisTTY_Color final_color = { red, green, blue }; + + if (x < 0 || y < 0 || x >= this->resolution.x || y >= this->resolution.y) + continue; + + if (pixel != 0x00) + this->draw_pixel(x, y, final_color); + } + } + + x_offset += slot->advance.x >> 6; + } +} + +void OrbisTTY_2D::draw_textf(int start_x, int start_y, struct OrbisTTY_Color bg_color, struct OrbisTTY_Color fg_color, char* fmt, ...) { + char buffer[512]; + + va_list args; + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + + this->draw_text(buffer, start_x, start_y, bg_color, fg_color); +} + + +// OrbisTTY Namespace + +bool OrbisTTY::init(const char* font_path) { + scene = new OrbisTTY_2D(4); + if (!scene->init(0xC000000, 2)) + return false; + + if (!set_font(font_path)) + return false; + + for (int index = 0; index <= (floor(scene->resolution.y / 17) - 1); index++) + lines.push_back(""); + + line_index = 0; + + return true; +} + +bool OrbisTTY::set_font(const char* font_path) { + if (!scene->use_font(font_path)) + return false; + + return true; +} + +void OrbisTTY::move_lines_up(void) { + int total_lines = floor(scene->resolution.y / 17) - 1; + + for (int index = 0; index <= total_lines - 1; index++) + lines[index] = lines[index + 1]; + + lines[total_lines] = ""; +} + +int OrbisTTY::get_y_offset(int line) { + return (line * 17) + 17; +} + +void OrbisTTY::orbis_printf(const char* fmt, ...) { + char buffer[512]; + + va_list args; + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + + int total_lines = floor(scene->resolution.y / 17) - 1; + + if (line_index <= total_lines - 1) + lines[line_index++] = buffer; + else { + move_lines_up(); + lines[total_lines - 1] = buffer; + } + + scene->set_active_frame_buffer((frame_id + 1) % 2); + scene->frame_buffer_fill(black); + + for (int index = 0; index <= total_lines; index++) + scene->draw_text((char*)lines[index].c_str(), 5, get_y_offset(index), black, white); + + scene->submit_flip(frame_id); + scene->frame_wait(frame_id); + scene->frame_buffer_swap(); + + frame_id++; +} \ No newline at end of file diff --git a/example/OrbisTTY.h b/example/OrbisTTY.h new file mode 100644 index 0000000..23bec79 --- /dev/null +++ b/example/OrbisTTY.h @@ -0,0 +1,99 @@ +#ifndef ORBISTTY_H_ +#define ORBISTTY_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +struct OrbisTTY_Color { + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +struct OrbisTTY_Resolution { + uint16_t x; + uint16_t y; +}; + +static struct OrbisTTY_Color white = { 255, 255, 255 }; +static struct OrbisTTY_Color black = { 0, 0, 0 }; + +class OrbisTTY_2D { +private: + FT_Library freetype_lib; + FT_Face font_face; + + int video; + int depth; + + off_t direct_mem_off; + size_t direct_mem_alloc_size; + + uintptr_t video_memory_sp; + void* video_mem; + + char** frame_buffers; + OrbisKernelEqueue flip_queue; + OrbisVideoOutBufferAttribute attr; + + int frame_buffer_size; + int frame_buffer_count; + + int active_frame_buffer_index; + + bool init_flip_queue (void); + bool alloc_frame_buffers (int num); + char* alloc_display_mem (size_t size); + bool alloc_video_mem (size_t size, int alignment); + void free_video_mem (void); + void update_resolution (void); + +public: + struct OrbisTTY_Resolution resolution = { 0, 0 }; // Screen resolution + + OrbisTTY_2D(int pixel_depth); + ~OrbisTTY_2D(void); + + bool init (size_t mem_size, int num_frame_buffers); + + void set_active_frame_buffer (int index); + void submit_flip (int frame_id); + + void frame_wait (int frame_id); + void frame_buffer_swap (void); + void frame_buffer_clear (void); + void frame_buffer_fill (struct OrbisTTY_Color color); + + void draw_pixel (int x, int y, struct OrbisTTY_Color color); + void draw_rectangle (int x, int y, int width, int height, struct OrbisTTY_Color color); + + bool use_font (const char* font_path); + void draw_text (char* text, int start_x, int start_y, struct OrbisTTY_Color bg_color, struct OrbisTTY_Color fg_color); + void draw_textf (int start_x, int start_y, struct OrbisTTY_Color bgColor, struct OrbisTTY_Color fg_color, char* fmt, ...); +}; + +namespace OrbisTTY { + static OrbisTTY_2D* scene; + static std::vector lines; + static int line_index; + static int frame_id; + + bool init (const char* font_path); + bool set_font (const char* font_path); + void move_lines_up (void); + int get_y_offset (int line); + void orbis_printf (const char* fmt, ...); +} + +#endif \ No newline at end of file diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..b632eea --- /dev/null +++ b/example/README.md @@ -0,0 +1,5 @@ +# Compiling the example + +```bat +C:\\Users\\maxim\\Downloads\\example> build.bat build orbistty-example . +``` \ No newline at end of file diff --git a/example/assets/fonts/Inconsolata-Regular.ttf b/example/assets/fonts/Inconsolata-Regular.ttf new file mode 100644 index 0000000..c3acd3e Binary files /dev/null and b/example/assets/fonts/Inconsolata-Regular.ttf differ diff --git a/example/build.bat b/example/build.bat new file mode 100644 index 0000000..960ea30 --- /dev/null +++ b/example/build.bat @@ -0,0 +1,82 @@ +@echo off + +SETLOCAL EnableDelayedExpansion + +Rem Package information +set PKG_TITLE="OrbisTTY Example" +set PKG_VERSION="1.00" +set PKG_ASSETS="assets" +set PKG_TITLE_ID="OTTY01010" +set PKG_CONTENT_ID="IV0000-OTTY01010_00-ORBISTTY0EXAMPLE" + +Rem Libraries to link in +set libraries=-lc -lkernel -lc++ -lSceVideoOut -lSceFreeType -lSceSysmodule + +Rem set extra_flags= + +Rem Read the script arguments into local vars +set intdir=%1 +set targetname=%~2 +set outputPath=%3 + +set outputElf=%intdir%\%targetname%.elf +set outputOelf=%intdir%\%targetname%.oelf + +@mkdir %intdir% + +Rem Compile object files for all the source files +for %%f in (*.c) do ( + clang --target=x86_64-pc-freebsd12-elf -fPIC -funwind-tables -I"%OO_PS4_TOOLCHAIN%\\include" -I"%OO_PS4_TOOLCHAIN%\\include\\c++\\v1" %extra_flags% -c -o %intdir%\%%~nf.o %%~nf.c +) + +for %%f in (*.cpp) do ( + clang++ --target=x86_64-pc-freebsd12-elf -fPIC -funwind-tables -I"%OO_PS4_TOOLCHAIN%\\include" -I"%OO_PS4_TOOLCHAIN%\\include\\c++\\v1" %extra_flags% -c -o %intdir%\%%~nf.o %%~nf.cpp +) + +Rem Get a list of object files for linking +set obj_files= +for %%f in (%1\\*.o) do set obj_files=!obj_files! .\%%f + +Rem Link the input ELF +ld.lld -m elf_x86_64 -pie --script "%OO_PS4_TOOLCHAIN%\link.x" --eh-frame-hdr -o "%outputElf%" "-L%OO_PS4_TOOLCHAIN%\\lib" %libraries% --verbose "%OO_PS4_TOOLCHAIN%\lib\crt1.o" %obj_files% + +Rem Create the eboot +%OO_PS4_TOOLCHAIN%\bin\windows\create-fself.exe -in "%outputElf%" --out "%outputOelf%" --eboot "eboot.bin" --paid 0x3800000000000011 + +Rem Create param.sfo +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_new sce_sys/param.sfo +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo APP_TYPE --type Integer --maxsize 4 --value 1 +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo APP_VER --type Utf8 --maxsize 8 --value %PKG_VERSION% +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo ATTRIBUTE --type Integer --maxsize 4 --value 0 +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo CATEGORY --type Utf8 --maxsize 4 --value "gd" +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo CONTENT_ID --type Utf8 --maxsize 48 --value %PKG_CONTENT_ID% +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo DOWNLOAD_DATA_SIZE --type Integer --maxsize 4 --value 0 +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo SYSTEM_VER --type Integer --maxsize 4 --value 0 +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo TITLE --type Utf8 --maxsize 128 --value %PKG_TITLE% +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo TITLE_ID --type Utf8 --maxsize 12 --value %PKG_TITLE_ID% +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe sfo_setentry sce_sys/param.sfo VERSION --type Utf8 --maxsize 8 --value %PKG_VERSION% + +Rem Get a list of assets for packaging +set module_files= +for %%f in (sce_module\\*) do set module_files=!module_files! sce_module/%%~nxf + +set asset_audio_files= +for %%f in (assets\\audio\\*) do set asset_audio_files=!asset_audio_files! assets/audio/%%~nxf + +set asset_fonts_files= +for %%f in (assets\\fonts\\*) do set asset_fonts_files=!asset_fonts_files! assets/fonts/%%~nxf + +set asset_images_files= +for %%f in (assets\\images\\*) do set asset_images_files=!asset_images_files! assets/images/%%~nxf + +set asset_misc_files= +for %%f in (assets\\misc\\*) do set asset_misc_files=!asset_misc_files! assets/misc/%%~nxf + +set asset_videos_files= +for %%f in (assets\\videos\\*) do set asset_videos_files=!asset_videos_files! assets/videos/%%~nxf + +Rem Create gp4 +%OO_PS4_TOOLCHAIN%\bin\windows\create-gp4.exe -out pkg.gp4 --content-id=%PKG_CONTENT_ID% --files "eboot.bin sce_sys/about/right.sprx sce_sys/param.sfo sce_sys/icon0.png %module_files% %asset_audio_files% %asset_fonts_files% %asset_images_files% %asset_misc_files% %asset_videos_files%" + +Rem Create pkg +%OO_PS4_TOOLCHAIN%\bin\windows\PkgTool.Core.exe pkg_build pkg.gp4 . \ No newline at end of file diff --git a/example/main.cpp b/example/main.cpp new file mode 100644 index 0000000..d77b837 --- /dev/null +++ b/example/main.cpp @@ -0,0 +1,14 @@ +#include +#include "OrbisTTY.h" + +#define printf(...) OrbisTTY::orbis_printf(__VA_ARGS__) + +int main() { + if (!OrbisTTY::init("/app0/assets/fonts/Inconsolata-Regular.ttf")) + return 1; + + for (int index = 0; ; index++) + printf("This PS4 application counts to infinitie! %d", index); + + return 0; +} \ No newline at end of file diff --git a/example/sce_module/libSceFios2.prx b/example/sce_module/libSceFios2.prx new file mode 100644 index 0000000..975e3aa Binary files /dev/null and b/example/sce_module/libSceFios2.prx differ diff --git a/example/sce_module/libc.prx b/example/sce_module/libc.prx new file mode 100644 index 0000000..d0f1faa Binary files /dev/null and b/example/sce_module/libc.prx differ diff --git a/example/sce_sys/about/right.sprx b/example/sce_sys/about/right.sprx new file mode 100644 index 0000000..66c1cb2 Binary files /dev/null and b/example/sce_sys/about/right.sprx differ diff --git a/example/sce_sys/icon0.png b/example/sce_sys/icon0.png new file mode 100644 index 0000000..a7b4c73 Binary files /dev/null and b/example/sce_sys/icon0.png differ diff --git a/example/sce_sys/param.sfo b/example/sce_sys/param.sfo new file mode 100644 index 0000000..0b5de7b Binary files /dev/null and b/example/sce_sys/param.sfo differ