├── readme.md ├── license.txt ├── src ├── libps │ ├── peripherals │ │ ├── scph1020.h │ │ ├── scph1020.c │ │ ├── scph1010.h │ │ └── scph1010.c │ ├── utility │ │ ├── math.h │ │ ├── memory.h │ │ ├── memory.c │ │ ├── fifo.h │ │ └── fifo.c │ ├── renderer │ │ ├── sw.h │ │ └── sw.c │ ├── include │ │ ├── disasm.h │ │ ├── rcnt.h │ │ ├── cpu.h │ │ ├── ps.h │ │ ├── gpu.h │ │ ├── bus.h │ │ ├── cpu_defs.h │ │ └── cd.h │ ├── CMakeLists.txt │ ├── rcnt.c │ ├── ps.c │ ├── cd.c │ ├── gpu.c │ └── cpu.c ├── CMakeLists.txt └── test │ ├── debug │ ├── gpu.cpp │ ├── cpu.cpp │ ├── cpu.h │ ├── gpu.h │ ├── log.h │ └── log.cpp │ ├── main.cpp │ ├── CMakeLists.txt │ ├── main_window.h │ ├── pstest.h │ ├── main_window.cpp │ ├── emulator.h │ ├── emulator.cpp │ └── pstest.cpp ├── CMakeSettings.json └── CMakeLists.txt /readme.md: -------------------------------------------------------------------------------- 1 | # ps -- Sony PlayStationⓇ emulator 2 | 3 | This emulator is open source but copyrighted under the ISC license; refer to 4 | the "license.txt" file for more information. 5 | 6 | This emulator is highly work in progress, and development is ongoing. 7 | 8 | ![alt text](https://i.imgur.com/jluWdsw.png "Stage I of BIOS bootup") 9 | ![alt text](https://i.imgur.com/kFwLJgD.png "Stage II of BIOS bootup") -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2019 Michael Rodriguez 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /src/libps/peripherals/scph1020.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Michael Rodriguez 2 | # 3 | # Permission to use, copy, modify, and/or distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | # AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | # PERFORMANCE OF THIS SOFTWARE. 14 | 15 | add_subdirectory(libps) 16 | add_subdirectory(test) -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Clang-Release", 5 | "generator": "Ninja", 6 | "configurationType": "RelWithDebInfo", 7 | "buildRoot": "${projectDir}\\out\\build\\${name}", 8 | "installRoot": "${projectDir}\\out\\install\\${name}", 9 | "cmakeCommandArgs": "", 10 | "buildCommandArgs": "-v", 11 | "ctestCommandArgs": "", 12 | "inheritEnvironments": [ "clang_cl_x64" ], 13 | "variables": [] 14 | }, 15 | { 16 | "name": "x64-Clang-Debug", 17 | "generator": "Ninja", 18 | "configurationType": "Debug", 19 | "buildRoot": "${projectDir}\\out\\build\\${name}", 20 | "installRoot": "${projectDir}\\out\\install\\${name}", 21 | "cmakeCommandArgs": "", 22 | "buildCommandArgs": "-v", 23 | "ctestCommandArgs": "", 24 | "inheritEnvironments": [ "clang_cl_x64" ], 25 | "variables": [] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /src/libps/peripherals/scph1020.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // Implements the first memory card ever released for the PlayStation 16 | // (SCPH-1020). 17 | 18 | #include "scph1020.h" -------------------------------------------------------------------------------- /src/test/debug/gpu.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include "gpu.h" 16 | 17 | GPUDebugger::GPUDebugger() 18 | { } 19 | 20 | GPUDebugger::~GPUDebugger() 21 | { } 22 | 23 | void GPUDebugger::refresh() 24 | { } -------------------------------------------------------------------------------- /src/test/debug/cpu.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include "cpu.h" 16 | 17 | CPUDebugger::CPUDebugger() : disassembly_group(new QGroupBox(tr("Disassembly"), this)) 18 | { } 19 | 20 | CPUDebugger::~CPUDebugger() 21 | { } 22 | 23 | -------------------------------------------------------------------------------- /src/libps/utility/math.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif // __cplusplus 21 | #define LIBPS_BCD_TO_DEC(x) (x - (6 * (x >> 4))) 22 | #ifdef __cplusplus 23 | } 24 | #endif // __cplusplus -------------------------------------------------------------------------------- /src/test/debug/cpu.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #include 18 | 19 | class CPUDebugger : public QMainWindow 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | CPUDebugger(); 25 | ~CPUDebugger(); 26 | 27 | private: 28 | QGroupBox* disassembly_group; 29 | }; -------------------------------------------------------------------------------- /src/test/debug/gpu.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #include 18 | 19 | class GPUDebugger : public QWidget 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | GPUDebugger() noexcept; 25 | ~GPUDebugger() noexcept; 26 | 27 | void refresh() noexcept; 28 | }; -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Michael Rodriguez 2 | # 3 | # Permission to use, copy, modify, and/or distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | # AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | # PERFORMANCE OF THIS SOFTWARE. 14 | 15 | cmake_minimum_required(VERSION 3.15.4 FATAL_ERROR) 16 | 17 | project(ps VERSION 1.0.0.0 18 | DESCRIPTION "Sony PlayStationⓇ emulator" 19 | HOMEPAGE_URL "https://github.com/xiphffff/ps" 20 | LANGUAGES C CXX) 21 | 22 | add_subdirectory(src) -------------------------------------------------------------------------------- /src/test/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include "pstest.h" 17 | 18 | int main(int argc, char* argv[]) 19 | { 20 | QApplication qt(argc, argv); 21 | 22 | qt.setApplicationName("libps debugging station"); 23 | qt.setApplicationVersion("1.0"); 24 | 25 | PSTest pstest; 26 | return qt.exec(); 27 | } -------------------------------------------------------------------------------- /src/libps/peripherals/scph1010.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | struct libps_system; 18 | 19 | struct libps_scph1010 20 | { }; 21 | 22 | // Creates a SCPH-1010. 23 | struct libps_scph1010* libps_scph1010_create(struct libps_system* ps); 24 | 25 | // Destroys the SCPH-1010. 26 | void libps_scph1010_destroy(struct libps_scph1010* controller); -------------------------------------------------------------------------------- /src/libps/utility/memory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif // __cplusplus 21 | 22 | // Attempts to allocate memory, and if memory allocation is successful returns 23 | // a pointer to the memory, or calls `abort()` if memory allocation was 24 | // unsuccessful. 25 | void* libps_safe_malloc(const size_t size); 26 | 27 | // Calls `free()` and sets `ptr` to `NULL`. 28 | void libps_safe_free(void* ptr); 29 | 30 | #ifdef __cplusplus 31 | } 32 | #endif // __cplusplus -------------------------------------------------------------------------------- /src/libps/peripherals/scph1010.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // Implements the first controller ever released for the PlayStation 16 | // (SCPH-1010). 17 | 18 | #include "scph1010.h" 19 | #include "../utility/memory.h" 20 | 21 | // Creates a SCPH-1010. 22 | struct libps_scph1010* libps_scph1010_create(struct libps_system* ps) 23 | { 24 | struct libps_scph1010* controller = 25 | libps_safe_malloc(sizeof(struct libps_scph1010)); 26 | 27 | return controller; 28 | } 29 | 30 | // Destroys the SCPH-1010. 31 | void libps_scph1010_destroy(struct libps_scph1010* controller) 32 | { 33 | libps_safe_free(controller); 34 | } -------------------------------------------------------------------------------- /src/libps/renderer/sw.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif // __cplusplus 21 | 22 | struct libps_gpu; 23 | struct libps_gpu_vertex; 24 | 25 | void libps_renderer_sw_draw_polygon(struct libps_gpu* gpu, 26 | const struct libps_gpu_vertex* const v0, 27 | struct libps_gpu_vertex* const v1, 28 | struct libps_gpu_vertex* const v2); 29 | 30 | void libps_renderer_sw_draw_rect(struct libps_gpu* gpu, 31 | const struct libps_gpu_vertex* const vertex); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif // __cplusplus -------------------------------------------------------------------------------- /src/libps/utility/memory.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include 17 | #include "memory.h" 18 | 19 | // Attempts to allocate memory, and if memory allocation is successful returns 20 | // a pointer to the memory, or calls `abort()` if memory allocation was 21 | // unsuccessful. 22 | void* libps_safe_malloc(const size_t size) 23 | { 24 | void* ptr = malloc(size); 25 | 26 | if (ptr == NULL) 27 | { 28 | abort(); 29 | return NULL; 30 | } 31 | return ptr; 32 | } 33 | 34 | // Calls `free()` and sets `ptr` to `NULL`. 35 | void libps_safe_free(void* ptr) 36 | { 37 | // Anything passed to this function should never be `NULL` when getting 38 | // here in the first place. 39 | assert(ptr != NULL); 40 | 41 | free(ptr); 42 | ptr = NULL; 43 | } -------------------------------------------------------------------------------- /src/libps/include/disasm.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef LIBPS_DEBUG 18 | 19 | #ifdef __cplusplus 20 | extern "C" 21 | { 22 | #endif // __cplusplus 23 | 24 | #include 25 | 26 | // Maximum possible length of a result from `libps_disassemble_instruction()`. 27 | #define LIBPS_DISASM_MAX_LENGTH 30 28 | 29 | // Converts `instruction` to MIPS-I assembly language, and stores this result 30 | // in `result`. `pc` is required so as to compute a proper branch target in the 31 | // event `instruction` is a branch instruction. 32 | void libps_disassemble_instruction(const uint32_t instruction, 33 | const uint32_t pc, 34 | char* result); 35 | #ifdef __cplusplus 36 | } 37 | #endif // __cplusplus 38 | 39 | #endif // LIBPS_DEBUG -------------------------------------------------------------------------------- /src/test/debug/log.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #include 18 | 19 | class MessageLogger : public QMainWindow 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | MessageLogger(QWidget* parent); 25 | ~MessageLogger(); 26 | 27 | void append(const QString& data); 28 | void reset(); 29 | 30 | QAction* tty_strings; 31 | QAction* bios_calls; 32 | #ifdef LIBPS_DEBUG 33 | QAction* unknown_memory_load; 34 | QAction* unknown_memory_store; 35 | QAction* irqs; 36 | #endif 37 | 38 | private: 39 | void on_select_font(); 40 | void on_save_log(); 41 | 42 | // Name of the logger 43 | QString name; 44 | 45 | QMenu* file_menu; 46 | QAction* save_log; 47 | 48 | QMenu* view_menu; 49 | QAction* select_font; 50 | QAction* clear_log; 51 | 52 | QMenu* events_menu; 53 | QPlainTextEdit* text_edit; 54 | }; -------------------------------------------------------------------------------- /src/libps/utility/fifo.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif // __cplusplus 21 | 22 | #include 23 | 24 | struct libps_fifo 25 | { 26 | int* entries; 27 | 28 | unsigned int current_size; 29 | unsigned int max_size; 30 | 31 | unsigned int head; 32 | unsigned int tail; 33 | }; 34 | 35 | // Creates a fixed-size FIFO. 36 | void libps_fifo_setup(struct libps_fifo* fifo, const unsigned int size); 37 | 38 | // Destroys a FIFO. 39 | void libps_fifo_cleanup(struct libps_fifo* fifo); 40 | 41 | // Clears a FIFO. 42 | void libps_fifo_reset(struct libps_fifo* fifo); 43 | 44 | // Returns `true` if the FIFO is empty, or `false` otherwise. 45 | bool libps_fifo_is_empty(struct libps_fifo* fifo); 46 | 47 | // Returns `true` if the FIFO is full, or `false` otherwise. 48 | bool libps_fifo_is_full(struct libps_fifo* fifo); 49 | 50 | // Adds an item to the FIFO. 51 | void libps_fifo_enqueue(struct libps_fifo* fifo, const int data); 52 | 53 | // Removes an item from the FIFO and returns this value. 54 | int libps_fifo_dequeue(struct libps_fifo* fifo); 55 | 56 | #ifdef __cplusplus 57 | } 58 | #endif // __cplusplus 59 | -------------------------------------------------------------------------------- /src/libps/include/rcnt.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | 20 | struct libps_rcnt 21 | { 22 | struct rcnt_spec 23 | { 24 | // 1F801100h+N*10h - Timer 0..2 Current Counter Value (R/W) 25 | uint32_t value; 26 | 27 | // 1F801104h + N * 10h - Timer 0..2 Counter Mode(R / W) 28 | uint32_t mode; 29 | 30 | // 1F801108h + N * 10h - Timer 0..2 Counter Target Value(R / W) 31 | uint32_t target; 32 | 33 | // Current cycle count 34 | unsigned int counter; 35 | 36 | // The number of cycles we need to wait before incrementing the timer 37 | unsigned int threshold; 38 | } rcnts[3]; 39 | }; 40 | 41 | // Resets the timers to their initial state. 42 | void libps_rcnt_reset(struct libps_rcnt* rcnt); 43 | 44 | // Adjusts the clock source of the timer specified by `timer_id` and resets 45 | // the value for said timer. 46 | void libps_rcnt_set_mode(struct libps_rcnt* rcnt, 47 | const unsigned int rcnt_id, 48 | const uint32_t mode); 49 | 50 | // Steps the root counters. 51 | void libps_rcnt_step(struct libps_rcnt* rcnt); -------------------------------------------------------------------------------- /src/libps/include/cpu.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif // __cplusplus 21 | 22 | #include 23 | #include 24 | 25 | struct libps_bus; 26 | 27 | struct libps_cpu 28 | { 29 | // Current program counter (PC) 30 | uint32_t pc; 31 | 32 | // Next program counter 33 | uint32_t next_pc; 34 | 35 | // Current instruction 36 | uint32_t instruction; 37 | 38 | // Quotient part of a division operation 39 | uint32_t reg_lo; 40 | 41 | // Remainder part of a division operation 42 | uint32_t reg_hi; 43 | 44 | // General purpose registers 45 | uint32_t gpr[32]; 46 | 47 | // System control co-processor (COP0) registers 48 | uint32_t cop0_cpr[32]; 49 | }; 50 | 51 | // Sets the pointer to the system bus to `b`. This cannot be `NULL`. 52 | void libps_cpu_set_bus(struct libps_bus* b); 53 | 54 | // Triggers a reset exception, thereby initializing the CPU to the predefined 55 | // startup state. 56 | void libps_cpu_reset(struct libps_cpu* cpu); 57 | 58 | // Executes one instruction. 59 | void libps_cpu_step(struct libps_cpu* cpu); 60 | 61 | #ifdef __cplusplus 62 | } 63 | #endif // __cplusplus 64 | -------------------------------------------------------------------------------- /src/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Michael Rodriguez 2 | # 3 | # Permission to use, copy, modify, and/or distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | # AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | # PERFORMANCE OF THIS SOFTWARE. 14 | 15 | find_package(Qt5 COMPONENTS Widgets REQUIRED) 16 | 17 | set(CMAKE_AUTOMOC ON) 18 | set(CMAKE_AUTORCC ON) 19 | set(CMAKE_AUTOUIC ON) 20 | 21 | if (CMAKE_VERSION VERSION_LESS "3.7.0") 22 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 23 | endif() 24 | 25 | set(SRCS emulator.cpp main.cpp main_window.cpp pstest.cpp) 26 | set(HDRS emulator.h main_window.h pstest.h) 27 | 28 | set(DEBUG_SRCS debug/cpu.cpp 29 | debug/gpu.cpp 30 | debug/log.cpp) 31 | 32 | set(DEBUG_HDRS debug/cpu.h debug/gpu.h debug/log.h) 33 | 34 | source_group("Source Files\\debug" FILES ${DEBUG_SRCS}) 35 | source_group("Header Files\\debug" FILES ${DEBUG_HDRS}) 36 | 37 | add_executable(pstest ${SRCS} ${HDRS} ${DEBUG_SRCS} ${DEBUG_HDRS}) 38 | 39 | set_target_properties(pstest PROPERTIES 40 | CXX_STANDARD 17 41 | CXX_STANDARD_REQUIRED YES 42 | CXX_EXTENSIONS ON) 43 | 44 | target_link_libraries(pstest ps Qt5::Widgets) 45 | 46 | target_compile_options(pstest PRIVATE 47 | $<$,$,$>: 48 | -Wall -Wextra -Wno-gnu-case-range -Wno-old-style-cast -Wno-c++98-compat>) 49 | 50 | target_compile_definitions(pstest PRIVATE LIBPS_DEBUG) 51 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT pstest) -------------------------------------------------------------------------------- /src/test/main_window.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #include 18 | 19 | class MainWindow : public QMainWindow 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | MainWindow(); 25 | ~MainWindow(); 26 | 27 | // Renders the VRAM data. 28 | void render_frame(const uint16_t* vram); 29 | 30 | // "File -> Insert CD-ROM image..." 31 | QAction* insert_cdrom_image; 32 | 33 | // "File -> Run PS-X EXE..." 34 | QAction* run_ps_exe; 35 | 36 | // "Debug -> Display libps log" 37 | QAction* display_libps_log; 38 | 39 | // "Emulation -> Start" or "Emulation -> Resume" depending on the run state 40 | // of the emulator 41 | QAction* start_emu; 42 | 43 | // "Emulation -> Stop" 44 | QAction* stop_emu; 45 | 46 | // "Emulation -> Pause" 47 | QAction* pause_emu; 48 | 49 | // "Emulation -> Reset" 50 | QAction* reset_emu; 51 | 52 | private: 53 | // Image 54 | QImage* vram_image; 55 | 56 | // Image view 57 | QLabel* vram_image_view; 58 | 59 | QMenu* file_menu; 60 | QMenu* emulation_menu; 61 | QMenu* debug_menu; 62 | 63 | // Called when the user triggers "File -> Insert CD-ROM image..." 64 | void on_insert_cdrom_image(); 65 | 66 | // Called when the user triggers "File -> Run PS-X EXE..." 67 | void on_run_ps_x_exe(); 68 | 69 | signals: 70 | // Called when the user triggers "File -> Insert CD-ROM image..." 71 | void selected_cdrom_image(const QString& file_name); 72 | 73 | // Emitted when the user selects a PS-X EXE. 74 | void selected_ps_x_exe(const QString& exe_file); 75 | }; -------------------------------------------------------------------------------- /src/libps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Michael Rodriguez 2 | # 3 | # Permission to use, copy, modify, and/or distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | # AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | # PERFORMANCE OF THIS SOFTWARE. 14 | 15 | set(SRCS bus.c 16 | cd.c 17 | cpu.c 18 | disasm.c 19 | gpu.c 20 | ps.c 21 | rcnt.c) 22 | 23 | set(HDRS include/bus.h 24 | include/cd.h 25 | include/cpu.h 26 | include/cpu_defs.h 27 | include/disasm.h 28 | include/gpu.h 29 | include/ps.h 30 | include/rcnt.h) 31 | 32 | set(PERIPHERALS_SRCS peripherals/scph1010.c peripherals/scph1020.c) 33 | set(PERIPHERALS_HDRS peripherals/scph1010.h peripherals/scph1020.h) 34 | 35 | set(RENDERER_SRCS renderer/sw.c) 36 | set(RENDERER_HDRS renderer/sw.h) 37 | 38 | set(UTILITY_SRCS utility/fifo.c utility/memory.c) 39 | set(UTILITY_HDRS utility/fifo.h utility/math.h utility/memory.h) 40 | 41 | add_library(ps STATIC ${SRCS} 42 | ${HDRS} 43 | ${PERIPHERALS_SRCS} 44 | ${PERIPHERALS_HDRS} 45 | ${RENDERER_SRCS} 46 | ${RENDERER_HDRS} 47 | ${UTILITY_SRCS} 48 | ${UTILITY_HDRS}) 49 | 50 | set_target_properties(ps PROPERTIES 51 | C_STANDARD 17 52 | C_STANDARD_REQUIRED YES 53 | C_EXTENSIONS ON) 54 | 55 | target_include_directories(ps PRIVATE include) 56 | 57 | target_compile_definitions(ps PRIVATE LIBPS_DEBUG) 58 | 59 | target_compile_options(ps PRIVATE 60 | $<$,$,$>: 61 | -Wall -Wextra -Wno-gnu-case-range -Wno-old-style-cast -fsanitize=address>) -------------------------------------------------------------------------------- /src/libps/include/ps.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif // __cplusplus 21 | #define LIBPS_API_VERSION_MAJOR 1 22 | #define LIBPS_API_VERSION_MINOR 0 23 | #define LIBPS_API_VERSION_PATCH 0 24 | 25 | #include "bus.h" 26 | #include "cd.h" 27 | #include "cpu.h" 28 | #include "cpu_defs.h" 29 | #include "gpu.h" 30 | 31 | // Defines the structure of a PlayStation emulator. 32 | struct libps_system 33 | { 34 | struct libps_bus bus; 35 | struct libps_cpu cpu; 36 | }; 37 | 38 | // Creates a PlayStation emulator. `bios_data` is a pointer to the BIOS data 39 | // supplied by the caller and cannot be `NULL`. If `bios_data` is `NULL`, this 40 | // function will return `NULL` and the emulator will not be created. 41 | struct libps_system* libps_system_create(uint8_t* const bios_data); 42 | 43 | // Destroys a PlayStation emulator. 44 | void libps_system_destroy(struct libps_system* ps); 45 | 46 | // Resets a PlayStation to the startup state. This is called automatically by 47 | // `libps_system_create()`. 48 | void libps_system_reset(struct libps_system* ps); 49 | 50 | // Executes one full system step. 51 | void libps_system_step(struct libps_system* ps); 52 | 53 | // "Inserts" a CD-ROM `cdrom_info` into a PlayStation emulator `ps`. If 54 | // `cdrom_info` is `NULL`, the CD-ROM, if any will be removed. 55 | // 56 | // Returns `true` if `cdrom_info` is valid and a CD-ROM has been inserted, or 57 | // `false` otherwise. 58 | bool libps_system_set_cdrom(struct libps_system* ps, 59 | struct libps_cdrom_info* cdrom_info); 60 | #ifdef __cplusplus 61 | } 62 | #endif // __cplusplus 63 | -------------------------------------------------------------------------------- /src/libps/rcnt.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include 17 | #include 18 | #include "cpu_defs.h" 19 | #include "rcnt.h" 20 | #include "utility/memory.h" 21 | 22 | // Resets the root counters to their initial state. 23 | void libps_rcnt_reset(struct libps_rcnt* rcnt) 24 | { 25 | assert(rcnt != NULL); 26 | memset(rcnt->rcnts, 0, sizeof(rcnt->rcnts)); 27 | } 28 | 29 | // Adjusts the clock source of the timer specified by `rcnt_id` and resets 30 | // the value for said timer. 31 | void libps_rcnt_set_mode(struct libps_rcnt* rcnt, 32 | const unsigned int rcnt_id, 33 | const uint32_t mode) 34 | { 35 | assert(rcnt != NULL); 36 | assert(rcnt_id < 3); 37 | 38 | rcnt->rcnts[rcnt_id].mode = mode; 39 | 40 | switch (rcnt_id) 41 | { 42 | case 2: 43 | switch (rcnt->rcnts[rcnt_id].mode & 0x300) 44 | { 45 | // 2 or 3 = System Clock/8 46 | case 0x200: 47 | rcnt->rcnts[2].threshold = 8; 48 | break; 49 | 50 | default: 51 | rcnt->rcnts[2].threshold = 1; 52 | break; 53 | } 54 | break; 55 | } 56 | 57 | rcnt->rcnts[rcnt_id].counter = 0; 58 | rcnt->rcnts[rcnt_id].value = 0x0000; 59 | } 60 | 61 | // Steps the root counters. 62 | void libps_rcnt_step(struct libps_rcnt* rcnt) 63 | { 64 | assert(rcnt != NULL); 65 | 66 | if (rcnt->rcnts[2].counter == rcnt->rcnts[2].threshold) 67 | { 68 | rcnt->rcnts[2].value++; 69 | rcnt->rcnts[2].counter = 0; 70 | } 71 | else 72 | { 73 | rcnt->rcnts[2].counter++; 74 | } 75 | } -------------------------------------------------------------------------------- /src/test/pstest.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #include "emulator.h" 18 | #include "main_window.h" 19 | #include "debug/log.h" 20 | 21 | class PSTest : public QObject 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | PSTest(); 27 | ~PSTest(); 28 | 29 | private: 30 | // Called when the emulator core reports that BIOS call 31 | // `A(0x40) - SystemErrorUnresolvedException()` was reached. 32 | void emu_report_system_error(); 33 | 34 | // Called when the emulator core reports that a BIOS call other than 35 | // A(0x40), A(0x3C), or B(0x3D) was reached. 36 | void emu_bios_call(struct bios_trace_info* bios_trace); 37 | 38 | // Called when the user triggers `Debug -> Display libps log`. 39 | void display_libps_log(); 40 | 41 | // Called when the user triggers `Emulation -> Start`. This function is 42 | // also called upon startup, and is used also to resume emulation from a 43 | // paused state. 44 | void start_emu(); 45 | 46 | // Called when the user triggers `Emulation -> Stop`. 47 | void stop_emu(); 48 | 49 | // Called when the user triggers `Emulation -> Pause`. 50 | void pause_emu(); 51 | 52 | // Called when the user triggers `Emulation -> Reset`. 53 | void reset_emu(); 54 | 55 | #ifdef LIBPS_DEBUG 56 | // Called when an unknown word load has been attempted 57 | void on_debug_unknown_memory_load(const uint32_t paddr, 58 | const unsigned int type); 59 | 60 | // Called when an unknown word store has been attempted 61 | void on_debug_unknown_memory_store(const uint32_t paddr, 62 | const unsigned int data, 63 | const unsigned int type); 64 | 65 | void on_debug_interrupt_requested(const unsigned int interrupt); 66 | void on_debug_interrupt_acknowledged(const unsigned int interrupt); 67 | #endif // LIBPS_DEBUG 68 | 69 | // Called when a TTY string has been generated 70 | void on_tty_string(const QString& tty_string); 71 | 72 | MainWindow* main_window; 73 | MessageLogger* libps_log; 74 | Emulator* emulator; 75 | }; -------------------------------------------------------------------------------- /src/libps/utility/fifo.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include 17 | #include "fifo.h" 18 | #include "memory.h" 19 | 20 | // Sets up a fixed-sized FIFO. 21 | void libps_fifo_setup(struct libps_fifo* fifo, const unsigned int size) 22 | { 23 | fifo->entries = libps_safe_malloc(sizeof(int) * size); 24 | fifo->max_size = size; 25 | 26 | libps_fifo_reset(fifo); 27 | } 28 | 29 | // Destroys a FIFO. 30 | void libps_fifo_cleanup(struct libps_fifo* fifo) 31 | { 32 | fifo->current_size = 0; 33 | libps_safe_free(fifo->entries); 34 | } 35 | 36 | // Clears a FIFO. 37 | void libps_fifo_reset(struct libps_fifo* fifo) 38 | { 39 | assert(fifo != NULL); 40 | 41 | fifo->current_size = 0; 42 | fifo->head = 0; 43 | fifo->tail = fifo->max_size - 1; 44 | 45 | for (unsigned int i = 0; i < fifo->max_size; ++i) 46 | { 47 | fifo->entries[i] = 0; 48 | } 49 | } 50 | 51 | // Returns `true` if the FIFO is empty, or `false` otherwise. 52 | bool libps_fifo_is_empty(struct libps_fifo* fifo) 53 | { 54 | assert(fifo != NULL); 55 | 56 | return fifo->current_size == 0; 57 | } 58 | 59 | // Returns `true` if the FIFO is full, or `false` otherwise. 60 | bool libps_fifo_is_full(struct libps_fifo* fifo) 61 | { 62 | assert(fifo != NULL); 63 | 64 | return fifo->current_size == fifo->max_size; 65 | } 66 | 67 | // Adds an item to the FIFO. 68 | void libps_fifo_enqueue(struct libps_fifo* fifo, const int data) 69 | { 70 | assert(fifo != NULL); 71 | 72 | if (libps_fifo_is_full(fifo)) 73 | { 74 | return; 75 | } 76 | 77 | fifo->tail = (fifo->tail + 1) % fifo->max_size; 78 | fifo->current_size++; 79 | 80 | fifo->entries[fifo->tail] = data; 81 | } 82 | 83 | // Removes an item from the FIFO and returns this value. 84 | int libps_fifo_dequeue(struct libps_fifo* fifo) 85 | { 86 | assert(fifo != NULL); 87 | 88 | if (libps_fifo_is_empty(fifo)) 89 | { 90 | return 0; 91 | } 92 | 93 | const int entry = fifo->entries[fifo->head]; 94 | 95 | fifo->head = (fifo->head + 1) % fifo->max_size; 96 | fifo->current_size--; 97 | 98 | return entry; 99 | } -------------------------------------------------------------------------------- /src/libps/ps.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include 17 | #include "ps.h" 18 | #include "utility/memory.h" 19 | 20 | // Creates a PlayStation emulator. `bios_data` is a pointer to the BIOS data 21 | // supplied by the caller and cannot be `NULL`. If `bios_data` is `NULL`, this 22 | // function will return `NULL` and the emulator will not be created. 23 | struct libps_system* libps_system_create(uint8_t* const bios_data) 24 | { 25 | if (bios_data == NULL) 26 | { 27 | return NULL; 28 | } 29 | 30 | struct libps_system* ps = libps_safe_malloc(sizeof(struct libps_system)); 31 | 32 | libps_bus_setup(&ps->bus, bios_data); 33 | libps_cpu_set_bus(&ps->bus); 34 | 35 | libps_system_reset(ps); 36 | return ps; 37 | } 38 | 39 | // Destroys a PlayStation emulator. 40 | void libps_system_destroy(struct libps_system* ps) 41 | { 42 | libps_bus_cleanup(&ps->bus); 43 | libps_safe_free(ps); 44 | } 45 | 46 | // Resets the PlayStation to the startup state. This is called automatically by 47 | // `libps_system_create()`. 48 | void libps_system_reset(struct libps_system* ps) 49 | { 50 | assert(ps != NULL); 51 | 52 | libps_bus_reset(&ps->bus); 53 | libps_cpu_reset(&ps->cpu); 54 | } 55 | 56 | // Executes one full system step. 57 | void libps_system_step(struct libps_system* ps) 58 | { 59 | assert(ps != NULL); 60 | 61 | // Step 1: Check for DMAs and tick the hardware. 62 | libps_bus_step(&ps->bus); 63 | libps_bus_step(&ps->bus); 64 | 65 | // Step 2: Check to see if the interrupt line needs to be enabled. 66 | if ((ps->bus.i_mask & ps->bus.i_stat) != 0) 67 | { 68 | ps->cpu.cop0_cpr[LIBPS_CPU_COP0_REG_CAUSE] |= (1 << 10); 69 | } 70 | else 71 | { 72 | ps->cpu.cop0_cpr[LIBPS_CPU_COP0_REG_CAUSE] &= ~(1 << 10); 73 | } 74 | 75 | // Step 3: Execute one instruction. 76 | libps_cpu_step(&ps->cpu); 77 | } 78 | 79 | // "Inserts" a CD-ROM `cdrom_info` into a PlayStation emulator `ps`. If 80 | // `cdrom_info` is `NULL`, the CD-ROM, if any will be removed. 81 | // 82 | // Returns `true` if `cdrom_info` is valid and a CD-ROM has been inserted, or 83 | // `false` otherwise. 84 | bool libps_system_set_cdrom(struct libps_system* ps, 85 | struct libps_cdrom_info* cdrom_info) 86 | { 87 | assert(ps != NULL); 88 | 89 | if (cdrom_info) 90 | { 91 | if (cdrom_info->read_cb) 92 | { 93 | ps->bus.cdrom.cdrom_info.read_cb = cdrom_info->read_cb; 94 | return true; 95 | } 96 | return false; 97 | } 98 | 99 | ps->bus.cdrom.cdrom_info.read_cb = NULL; 100 | return true; 101 | } -------------------------------------------------------------------------------- /src/libps/include/gpu.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif // __cplusplus 21 | 22 | #include 23 | 24 | // Drawing flags 25 | #define DRAW_FLAG_MONOCHROME (1 << 0) 26 | #define DRAW_FLAG_SHADED (1 << 1) 27 | #define DRAW_FLAG_TEXTURED (1 << 2) 28 | #define DRAW_FLAG_QUAD (1 << 3) 29 | #define DRAW_FLAG_OPAQUE (1 << 4) 30 | #define DRAW_FLAG_TEXTURE_BLENDING (1 << 5) 31 | #define DRAW_FLAG_RAW_TEXTURE (1 << 6) 32 | #define DRAW_FLAG_VARIABLE_SIZE (1 << 7) 33 | 34 | #define LIBPS_GPU_VRAM_WIDTH 1024 35 | #define LIBPS_GPU_VRAM_HEIGHT 512 36 | 37 | // Interrupts 38 | #define LIBPS_IRQ_VBLANK (1 << 0) 39 | 40 | enum libps_gpu_state 41 | { 42 | LIBPS_GPU_AWAITING_COMMAND, 43 | LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS, 44 | LIBPS_GPU_RECEIVING_COMMAND_DATA, 45 | LIBPS_GPU_TRANSFERRING_DATA, 46 | }; 47 | 48 | struct libps_gpu_vertex 49 | { 50 | // (-1024..+1023) 51 | int16_t x; 52 | 53 | // (-1024..+1023) 54 | int16_t y; 55 | 56 | uint16_t palette; 57 | uint16_t texcoord; 58 | uint16_t texpage; 59 | 60 | uint32_t color; 61 | }; 62 | 63 | struct libps_gpu 64 | { 65 | // 0x1F801810 - Read responses to GP0(C0h) and GP1(10h) commands 66 | uint32_t gpuread; 67 | 68 | // 0x1F801814 - GPU Status Register (R) 69 | uint32_t gpustat; 70 | 71 | // The 1MByte VRAM is organized as 512 lines of 2048 bytes. 72 | uint16_t* vram; 73 | 74 | // State of the GP0 port. 75 | enum libps_gpu_state state; 76 | 77 | void (*draw_polygon)(struct libps_gpu* gpu, 78 | const struct libps_gpu_vertex* const v0, 79 | struct libps_gpu_vertex* const v1, 80 | struct libps_gpu_vertex* const v2); 81 | 82 | void (*draw_rect)(struct libps_gpu* gpu, 83 | const struct libps_gpu_vertex* const vertex); 84 | 85 | struct 86 | { 87 | uint32_t params[32]; 88 | unsigned int remaining_words; 89 | unsigned int flags; 90 | uint32_t raw; 91 | } cmd_packet; 92 | 93 | struct 94 | { 95 | // (0..1023) 96 | uint16_t x1; 97 | uint16_t x2; 98 | 99 | // (0..511) 100 | uint16_t y1; 101 | uint16_t y2; 102 | } drawing_area; 103 | 104 | // (-1024..+1023) 105 | int16_t drawing_offset_x; 106 | 107 | // (-1024..+1023) 108 | int16_t drawing_offset_y; 109 | 110 | uint32_t received_data; 111 | }; 112 | 113 | // Initializes a GPU. 114 | void libps_gpu_setup(struct libps_gpu* gpu); 115 | 116 | // Destroys the PlayStation GPU. 117 | void libps_gpu_cleanup(struct libps_gpu* gpu); 118 | 119 | // Resets the GPU to the initial state. 120 | void libps_gpu_reset(struct libps_gpu* gpu); 121 | 122 | // Processes a GP0 packet. 123 | void libps_gpu_process_gp0(struct libps_gpu* gpu, const uint32_t packet); 124 | 125 | // Processes a GP1 packet. 126 | void libps_gpu_process_gp1(struct libps_gpu* gpu, const uint32_t packet); 127 | 128 | #ifdef __cplusplus 129 | } 130 | #endif // __cplusplus 131 | -------------------------------------------------------------------------------- /src/test/main_window.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include "main_window.h" 16 | 17 | MainWindow::MainWindow() 18 | { 19 | vram_image = new QImage(1024, 512, QImage::Format_RGB555); 20 | vram_image->fill(Qt::black); 21 | 22 | vram_image_view = new QLabel(this); 23 | vram_image_view->setPixmap(QPixmap::fromImage(*vram_image)); 24 | 25 | file_menu = menuBar()->addMenu(tr("&File")); 26 | 27 | insert_cdrom_image = new QAction(tr("Insert CD-ROM image..."), this); 28 | run_ps_exe = new QAction(tr("Run PS-X EXE..."), this); 29 | 30 | connect(insert_cdrom_image, 31 | &QAction::triggered, 32 | this, 33 | &MainWindow::on_insert_cdrom_image); 34 | 35 | connect(run_ps_exe, 36 | &QAction::triggered, 37 | this, 38 | &MainWindow::on_run_ps_x_exe); 39 | 40 | file_menu->addAction(insert_cdrom_image); 41 | file_menu->addAction(run_ps_exe); 42 | 43 | emulation_menu = menuBar()->addMenu(tr("&Emulation")); 44 | 45 | start_emu = new QAction(tr("Start"), this); 46 | stop_emu = new QAction(tr("Stop"), this); 47 | pause_emu = new QAction(tr("Pause"), this); 48 | reset_emu = new QAction(tr("Reset"), this); 49 | 50 | emulation_menu->addAction(start_emu); 51 | emulation_menu->addAction(stop_emu); 52 | emulation_menu->addAction(pause_emu); 53 | emulation_menu->addAction(reset_emu); 54 | 55 | debug_menu = menuBar()->addMenu(tr("&Debug")); 56 | 57 | display_libps_log = new QAction(tr("Display libps log"), this); 58 | debug_menu->addAction(display_libps_log); 59 | 60 | setWindowFlags(Qt::MSWindowsFixedSizeDialogHint); 61 | setCentralWidget(vram_image_view); 62 | } 63 | 64 | MainWindow::~MainWindow() 65 | { } 66 | 67 | // Called when the user triggers "File -> Insert CD-ROM image..." 68 | void MainWindow::on_insert_cdrom_image() 69 | { 70 | QString file_name = QFileDialog::getOpenFileName(this, 71 | tr("Select CD-ROM image"), 72 | "", 73 | tr("CD-ROM images (*.bin)")); 74 | 75 | if (!file_name.isEmpty()) 76 | { 77 | emit selected_cdrom_image(file_name); 78 | } 79 | } 80 | 81 | // Called when the user triggers "File -> Run PS-X EXE..." 82 | void MainWindow::on_run_ps_x_exe() 83 | { 84 | QString file_name = QFileDialog::getOpenFileName(this, 85 | tr("Select PS-X EXE"), 86 | "", 87 | tr("PS-X EXEs (*.exe)")); 88 | 89 | if (!file_name.isEmpty()) 90 | { 91 | emit selected_ps_x_exe(file_name); 92 | } 93 | } 94 | 95 | // Updates the VRAM image displayed. 96 | void MainWindow::render_frame(const uint16_t* vram) 97 | { 98 | memcpy(vram_image->bits(), vram, vram_image->sizeInBytes()); 99 | 100 | // This is seriously awful. 101 | QImage img = vram_image->rgbSwapped(); 102 | 103 | vram_image_view->setPixmap(QPixmap::fromImage(img)); 104 | } -------------------------------------------------------------------------------- /src/test/debug/log.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include "log.h" 16 | 17 | MessageLogger::MessageLogger(QWidget* parent) : QMainWindow(parent) 18 | { 19 | file_menu = menuBar()->addMenu(tr("&File")); 20 | 21 | save_log = new QAction(tr("&Save..."), this); 22 | 23 | connect(save_log, 24 | &QAction::triggered, 25 | this, 26 | &MessageLogger::on_save_log); 27 | 28 | file_menu->addAction(save_log); 29 | 30 | view_menu = menuBar()->addMenu(tr("&View")); 31 | 32 | select_font = new QAction(tr("&Font..."), this); 33 | clear_log = new QAction(tr("&Clear"), this); 34 | 35 | connect(select_font, 36 | &QAction::triggered, 37 | this, 38 | &MessageLogger::on_select_font); 39 | 40 | connect(clear_log, &QAction::triggered, this, &MessageLogger::reset); 41 | 42 | view_menu->addAction(select_font); 43 | view_menu->addAction(clear_log); 44 | 45 | events_menu = menuBar()->addMenu(tr("&Events")); 46 | 47 | tty_strings = new QAction(tr("TTY output"), this); 48 | bios_calls = new QAction(tr("BIOS calls"), this); 49 | 50 | #ifdef LIBPS_DEBUG 51 | unknown_memory_load = new QAction(tr("Unknown memory load"), this); 52 | unknown_memory_store = new QAction(tr("Unknown memory store"), this); 53 | irqs = new QAction(tr("IRQs"), this); 54 | #endif // LIBPS_DEBUG 55 | 56 | tty_strings->setCheckable(true); 57 | bios_calls->setCheckable(true); 58 | 59 | #ifdef LIBPS_DEBUG 60 | unknown_memory_load->setCheckable(true); 61 | unknown_memory_store->setCheckable(true); 62 | irqs->setCheckable(true); 63 | #endif // LIBPS_DEBUG 64 | 65 | events_menu->addAction(tty_strings); 66 | events_menu->addAction(bios_calls); 67 | 68 | #ifdef LIBPS_DEBUG 69 | events_menu->addAction(unknown_memory_load); 70 | events_menu->addAction(unknown_memory_store); 71 | events_menu->addAction(irqs); 72 | #endif // LIBPS_DEBUG 73 | 74 | text_edit = new QPlainTextEdit(this); 75 | text_edit->setReadOnly(true); 76 | 77 | QSettings config_file("pstest.ini", QSettings::IniFormat, this); 78 | 79 | const QString font_name = config_file.value("message_logger/font_name", 80 | "Lucida Console").toString(); 81 | 82 | const int font_size = config_file.value("message_logger/font_size", 83 | 10).toInt(); 84 | QFont font(font_name, font_size); 85 | 86 | QTextDocument* doc = text_edit->document(); 87 | doc->setDefaultFont(font); 88 | 89 | setCentralWidget(text_edit); 90 | } 91 | 92 | MessageLogger::~MessageLogger() 93 | { } 94 | 95 | void MessageLogger::append(const QString& data) 96 | { 97 | text_edit->insertPlainText(data); 98 | } 99 | 100 | void MessageLogger::reset() 101 | { 102 | text_edit->clear(); 103 | } 104 | 105 | void MessageLogger::on_select_font() 106 | { 107 | bool ok; 108 | 109 | QFont font = QFontDialog::getFont(&ok, 110 | QFont("Lucida Console", 10), 111 | this); 112 | if (ok) 113 | { 114 | QTextDocument* doc = text_edit->document(); 115 | doc->setDefaultFont(font); 116 | 117 | QSettings config_file("pstest.ini", QSettings::IniFormat, this); 118 | 119 | config_file.setValue("message_logger/font_name", font.toString()); 120 | config_file.setValue("message_logger/font_size", font.pointSize()); 121 | } 122 | } 123 | 124 | // Called when the user triggers "File -> Save" to save the log contents. 125 | void MessageLogger::on_save_log() 126 | { 127 | QString file_name = QFileDialog::getSaveFileName(this, 128 | tr("Save log"), 129 | "", 130 | tr("Log files (*.txt)")); 131 | 132 | if (!file_name.isEmpty()) 133 | { 134 | QFile log_file(file_name); 135 | 136 | log_file.open(QIODevice::WriteOnly | QIODevice::Text); 137 | 138 | QTextStream out(&log_file); 139 | out << text_edit->toPlainText(); 140 | 141 | log_file.close(); 142 | } 143 | } -------------------------------------------------------------------------------- /src/test/emulator.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | 20 | // Forward declaration 21 | struct libps_system; 22 | 23 | struct bios_trace_info 24 | { 25 | // Function origin 26 | quint32 origin; 27 | 28 | // Function 29 | quint32 func; 30 | 31 | // Arguments to the BIOS call 32 | QVector args; 33 | 34 | // BIOS call return value 35 | quint32 return_value; 36 | }; 37 | 38 | class Emulator : public QThread 39 | { 40 | Q_OBJECT 41 | 42 | public: 43 | Emulator(QObject* parent, const QString& bios_file); 44 | ~Emulator(); 45 | 46 | // Starts the emulation if it is not running. 47 | void start_run_loop(); 48 | 49 | // Stops the emulation if it is running, resetting the emulator to the 50 | // startup state. 51 | void stop_run_loop(); 52 | 53 | // Pauses the emulation if it is running, but *does not* reset the emulator 54 | // to the startup state. 55 | void pause_run_loop(); 56 | 57 | // Called when the user selects a game image to load after triggering 58 | // "File -> Insert CD-ROM image..." on the main window. 59 | void insert_cdrom_image(const QString& file_name); 60 | 61 | // Called when the user selects a PS-X EXE to inject after triggering 62 | // "File -> Run PS-X EXE..." on the main window. 63 | void run_ps_x_exe(const QString& file_name); 64 | 65 | // Returns the number of total cycles taken by the emulator. 66 | unsigned int total_cycles_taken() noexcept; 67 | 68 | 69 | bool tracing; 70 | 71 | private: 72 | // Called when it is time to inject the PS-X EXE specified by 73 | // `run_ps_x_exe()`. 74 | void inject_ps_x_exe(); 75 | 76 | // Called when the BIOS reaches the `std_out_putchar()` call. 77 | void handle_tty_string(); 78 | 79 | // Initiates a trace of a BIOS call. 80 | void trace_bios_call(const uint32_t pc, const uint32_t fn); 81 | 82 | // Called when it is time to seek to a specified position on the CD-ROM 83 | // image. 84 | void handle_cdrom_image_seek(); 85 | 86 | // Thread entry point 87 | void run() override; 88 | 89 | // Called when it is time to read data off of the CD-ROM image. 90 | void handle_cdrom_image_read(const unsigned int offset); 91 | 92 | // The file handle of the CD-ROM image, if any. 93 | FILE* cdrom_image_file; 94 | 95 | // Is the emulator running? 96 | bool running; 97 | 98 | // Are we injecting a PS-X EXE? 99 | bool injecting_ps_x_exe; 100 | 101 | FILE* trace_file; 102 | 103 | // Are we currently tracing a BIOS call? 104 | bool tracing_bios_call; 105 | 106 | // File name of the PS-X EXE we are injecting 107 | QString ps_x_exe; 108 | 109 | // Pointer to the BIOS data 110 | uint8_t* bios; 111 | 112 | // The PC to stop tracing a BIOS call on. This is the PC set by 113 | // `jr $t2` 114 | quint32 bios_call_trace_pc; 115 | 116 | // Current sector data 117 | uint8_t sector_data[2352]; 118 | 119 | // The total number of cycles taken by the emulator. 120 | unsigned int total_cycles; 121 | 122 | // Current position in the game image 123 | unsigned int cdrom_image_pos; 124 | 125 | // BIOS trace information 126 | struct bios_trace_info bios_trace; 127 | 128 | // Emulator instance 129 | struct libps_system* sys; 130 | 131 | signals: 132 | #ifdef LIBPS_DEBUG 133 | // Exception other than an interrupt or system call was raised by the CPU. 134 | void exception_raised(const unsigned int exccode, const uint32_t vaddr); 135 | 136 | // Illegal instruction occurred (Reserved Instruction exception). 137 | void illegal_instruction(const uint32_t instruction, const uint32_t pc); 138 | 139 | // Called when an unknown memory load has been attempted 140 | void on_debug_unknown_memory_load(const uint32_t paddr, 141 | const unsigned int type); 142 | 143 | // Called when an unknown memory store has been attempted 144 | void on_debug_unknown_memory_store(const uint32_t paddr, 145 | const unsigned int data, 146 | const unsigned int type); 147 | 148 | // Called when an interrupt has been requested 149 | void on_debug_interrupt_requested(const unsigned int interrupt); 150 | 151 | // Called when an interrupt has been acknowledged 152 | void on_debug_interrupt_acknowledged(const unsigned int interrupt); 153 | #endif // LIBPS_DEBUG 154 | // SystemErrorUnresolvedException() was called by the BIOS. 155 | void system_error(); 156 | 157 | // A BIOS call other than A(0x40), A(0x3C), or B(0x3D) was reached. 158 | void bios_call(struct bios_trace_info* trace_info); 159 | 160 | // TTY string has been printed 161 | void tty_string(const QString& string); 162 | 163 | // Time to render a frame 164 | void render_frame(const uint16_t* vram); 165 | }; -------------------------------------------------------------------------------- /src/libps/include/bus.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif // __cplusplus 21 | 22 | #include 23 | #include 24 | #include "cd.h" 25 | #include "gpu.h" 26 | #include "rcnt.h" 27 | 28 | #ifdef LIBPS_DEBUG 29 | #define LIBPS_DEBUG_WORD 0xFFFFFFFF 30 | #define LIBPS_DEBUG_HALFWORD 0xFFFF 31 | #define LIBPS_DEBUG_BYTE 0xFF 32 | #endif // LIBPS_DEBUG 33 | 34 | struct libps_dma_channel 35 | { 36 | // Base address 37 | uint32_t madr; 38 | 39 | // Block Control 40 | uint32_t bcr; 41 | 42 | // Channel Control 43 | uint32_t chcr; 44 | }; 45 | 46 | struct libps_bus 47 | { 48 | // Main RAM (first 64K reserved for BIOS) 49 | uint8_t* ram; 50 | 51 | uint8_t scratch_pad[4096]; 52 | 53 | // 0x1F801070 - I_STAT - Interrupt status register 54 | // (R=Status, W=Acknowledge) 55 | uint32_t i_stat; 56 | 57 | // 0x1F801074 - I_MASK - Interrupt mask register (R/W) 58 | uint32_t i_mask; 59 | 60 | // 0x1F8010F0 - DMA Control Register (R/W) 61 | uint32_t dpcr; 62 | 63 | // 0x1F8010F4 - DMA Interrupt Register (R/W) 64 | uint32_t dicr; 65 | 66 | uint16_t joy_ctrl; 67 | 68 | // GPU instance 69 | struct libps_gpu gpu; 70 | 71 | // CD-ROM instance 72 | struct libps_cdrom cdrom; 73 | 74 | // Root counter instance 75 | struct libps_rcnt rcnt; 76 | 77 | // DMA channel 2 - GPU (lists + image data) 78 | struct libps_dma_channel dma_gpu_channel; 79 | 80 | // DMA channel 3 - CDROM 81 | struct libps_dma_channel dma_cdrom_channel; 82 | 83 | // DMA channel 6 - OTC (reverse clear OT) 84 | struct libps_dma_channel dma_otc_channel; 85 | #ifdef LIBPS_DEBUG 86 | // If using C++, it would probably be wise to set this to `this`. 87 | void* debug_user_data; 88 | 89 | // Called when an unknown memory load has been attempted 90 | void (*debug_unknown_memory_load)(void* user_data, 91 | const uint32_t paddr, 92 | const unsigned int type); 93 | 94 | // Called when an unknown word store has been attempted 95 | void (*debug_unknown_memory_store)(void* user_data, 96 | const uint32_t paddr, 97 | const unsigned int data, 98 | const unsigned int type); 99 | 100 | // Interrupt has been requested 101 | void (*debug_interrupt_requested)(void* user_data, 102 | const unsigned int interrupt); 103 | 104 | // Interrupt has been acknowledged 105 | void (*debug_interrupt_acknowledged)(void* user_data, 106 | const unsigned int interrupt); 107 | #endif // LIBPS_DEBUG 108 | }; 109 | 110 | // Initializes the system bus. The system bus is the interconnect between the 111 | // CPU and devices, and accordingly has primary ownership of devices. The 112 | // system bus does not directly know about the CPU, however. 113 | // 114 | // `bios_data_ptr` is a pointer to the BIOS data loaded by the caller, passed 115 | // by `libps_system_create()`. 116 | // 117 | // Do not call this function directly. 118 | void libps_bus_setup(struct libps_bus* bus, uint8_t* const bios_data_ptr); 119 | 120 | // Destroys the system bus, destroying all memory and devices. Please note that 121 | // connected peripherals WILL NOT BE DESTROYED with this function; refer to the 122 | // peripherals' own destroy functions and use them accordingly. 123 | void libps_bus_cleanup(struct libps_bus* bus); 124 | 125 | // Resets the system bus, which resets the peripherals to their startup state. 126 | void libps_bus_reset(struct libps_bus* bus); 127 | 128 | // Handles DMA requests. 129 | void libps_bus_step(struct libps_bus* bus); 130 | 131 | // Stores word `data` into memory referenced by virtual address `vaddr`. 132 | void libps_bus_store_word(struct libps_bus* bus, 133 | const uint32_t vaddr, 134 | const uint32_t data); 135 | 136 | // Stores halfword `data` into memory referenced by virtual address `vaddr`. 137 | void libps_bus_store_halfword(struct libps_bus* bus, 138 | const uint32_t vaddr, 139 | const uint16_t data); 140 | 141 | // Stores byte `data` into memory referenced by virtual address `vaddr`. 142 | void libps_bus_store_byte(struct libps_bus* bus, 143 | const uint32_t vaddr, 144 | const uint8_t data); 145 | 146 | // Returns a word from memory referenced by virtual address `vaddr`. 147 | uint32_t libps_bus_load_word(struct libps_bus* bus, const uint32_t vaddr); 148 | 149 | // Returns a halfword from memory referenced by virtual address `vaddr`. 150 | uint16_t libps_bus_load_halfword(struct libps_bus* bus, const uint32_t vaddr); 151 | 152 | // Returns a byte from memory referenced by virtual address `vaddr`. 153 | uint8_t libps_bus_load_byte(struct libps_bus* bus, const uint32_t vaddr); 154 | 155 | #ifdef __cplusplus 156 | } 157 | #endif // __cplusplus 158 | -------------------------------------------------------------------------------- /src/libps/include/cpu_defs.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // This file serves to provide the disassembler, CPU implementations and the 16 | // frontend necessary information to carry out their respective functions. 17 | 18 | #pragma once 19 | 20 | #ifdef __cplusplus 21 | extern "C" 22 | { 23 | #endif // __cplusplus 24 | 25 | // Instruction groups 26 | #define LIBPS_CPU_OP_GROUP_SPECIAL 0x00 27 | #define LIBPS_CPU_OP_GROUP_BCOND 0x01 28 | #define LIBPS_CPU_OP_GROUP_COP0 0x10 29 | #define LIBPS_CPU_OP_GROUP_COP2 0x12 30 | 31 | // Primary instructions 32 | #define LIBPS_CPU_OP_J 0x02 33 | #define LIBPS_CPU_OP_JAL 0x03 34 | #define LIBPS_CPU_OP_BEQ 0x04 35 | #define LIBPS_CPU_OP_BNE 0x05 36 | #define LIBPS_CPU_OP_BLEZ 0x06 37 | #define LIBPS_CPU_OP_BGTZ 0x07 38 | #define LIBPS_CPU_OP_ADDI 0x08 39 | #define LIBPS_CPU_OP_ADDIU 0x09 40 | #define LIBPS_CPU_OP_SLTI 0x0A 41 | #define LIBPS_CPU_OP_SLTIU 0x0B 42 | #define LIBPS_CPU_OP_ANDI 0x0C 43 | #define LIBPS_CPU_OP_ORI 0x0D 44 | #define LIBPS_CPU_OP_XORI 0x0E 45 | #define LIBPS_CPU_OP_LUI 0x0F 46 | #define LIBPS_CPU_OP_LB 0x20 47 | #define LIBPS_CPU_OP_LH 0x21 48 | #define LIBPS_CPU_OP_LWL 0x22 49 | #define LIBPS_CPU_OP_LW 0x23 50 | #define LIBPS_CPU_OP_LBU 0x24 51 | #define LIBPS_CPU_OP_LHU 0x25 52 | #define LIBPS_CPU_OP_LWR 0x26 53 | #define LIBPS_CPU_OP_SB 0x28 54 | #define LIBPS_CPU_OP_SH 0x29 55 | #define LIBPS_CPU_OP_SWL 0x2A 56 | #define LIBPS_CPU_OP_SW 0x2B 57 | #define LIBPS_CPU_OP_SWR 0x2E 58 | #define LIBPS_CPU_OP_LWC2 0x32 59 | #define LIBPS_CPU_OP_SWC2 0x3A 60 | 61 | // SPECIAL instruction group 62 | #define LIBPS_CPU_OP_SLL 0x00 63 | #define LIBPS_CPU_OP_SRL 0x02 64 | #define LIBPS_CPU_OP_SRA 0x03 65 | #define LIBPS_CPU_OP_SLLV 0x04 66 | #define LIBPS_CPU_OP_SRLV 0x06 67 | #define LIBPS_CPU_OP_SRAV 0x07 68 | #define LIBPS_CPU_OP_JR 0x08 69 | #define LIBPS_CPU_OP_JALR 0x09 70 | #define LIBPS_CPU_OP_SYSCALL 0x0C 71 | #ifdef LIBPS_DEBUG 72 | #define LIBPS_CPU_OP_BREAK 0x0D 73 | #endif // LIBPS_DEBUG 74 | #define LIBPS_CPU_OP_MFHI 0x10 75 | #define LIBPS_CPU_OP_MTHI 0x11 76 | #define LIBPS_CPU_OP_MFLO 0x12 77 | #define LIBPS_CPU_OP_MTLO 0x13 78 | #define LIBPS_CPU_OP_MULT 0x18 79 | #define LIBPS_CPU_OP_MULTU 0x19 80 | #define LIBPS_CPU_OP_DIV 0x1A 81 | #define LIBPS_CPU_OP_DIVU 0x1B 82 | #define LIBPS_CPU_OP_ADD 0x20 83 | #define LIBPS_CPU_OP_ADDU 0x21 84 | #define LIBPS_CPU_OP_SUB 0x22 85 | #define LIBPS_CPU_OP_SUBU 0x23 86 | #define LIBPS_CPU_OP_AND 0x24 87 | #define LIBPS_CPU_OP_OR 0x25 88 | #define LIBPS_CPU_OP_XOR 0x26 89 | #define LIBPS_CPU_OP_NOR 0x27 90 | #define LIBPS_CPU_OP_SLT 0x2A 91 | #define LIBPS_CPU_OP_SLTU 0x2B 92 | 93 | // BCOND instruction group 94 | #define LIBPS_CPU_OP_BLTZ 0x00 95 | #define LIBPS_CPU_OP_BGEZ 0x01 96 | #define LIBPS_CPU_OP_BLTZAL 0x10 97 | #define LIBPS_CPU_OP_BGEZAL 0x11 98 | 99 | // Inherent co-processor instructions 100 | #define LIBPS_CPU_OP_MF 0x00 101 | #define LIBPS_CPU_OP_CF 0x02 102 | #define LIBPS_CPU_OP_MT 0x04 103 | #define LIBPS_CPU_OP_CT 0x06 104 | 105 | // System control co-processor (COP0) instructions 106 | #define LIBPS_CPU_OP_RFE 0x10 107 | 108 | // Geometry Transformation Engine (COP2, GTE) instructions 109 | #define LIBPS_CPU_OP_RTPS 0x01 110 | #define LIBPS_CPU_OP_NCLIP 0x06 111 | #define LIBPS_CPU_OP_OP 0x0C 112 | #define LIBPS_CPU_OP_DPCS 0x10 113 | #define LIBPS_CPU_OP_INTPL 0x11 114 | #define LIBPS_CPU_OP_MVMVA 0x12 115 | #define LIBPS_CPU_OP_NCDS 0x13 116 | #define LIBPS_CPU_OP_CDP 0x14 117 | #define LIBPS_CPU_OP_NCDT 0x16 118 | #define LIBPS_CPU_OP_NCCS 0x1B 119 | #define LIBPS_CPU_OP_NCS 0x1E 120 | #define LIBPS_CPU_OP_NCT 0x20 121 | #define LIBPS_CPU_OP_SQR 0x28 122 | #define LIBPS_CPU_OP_DCPL 0x29 123 | #define LIBPS_CPU_OP_DPCT 0x2A 124 | #define LIBPS_CPU_OP_AVSZ3 0x2D 125 | #define LIBPS_CPU_OP_AVSZ4 0x2E 126 | #define LIBPS_CPU_OP_RTPT 0x30 127 | #define LIBPS_CPU_OP_GPF 0x3D 128 | #define LIBPS_CPU_OP_GPL 0x3E 129 | #define LIBPS_CPU_OP_NCCT 0x3F 130 | 131 | // Instruction decoders 132 | #define LIBPS_CPU_DECODE_OP(instruction) (instruction >> 26) 133 | #define LIBPS_CPU_DECODE_RS(instruction) ((instruction >> 21) & 0x1F) 134 | #define LIBPS_CPU_DECODE_RT(instruction) ((instruction >> 16) & 0x1F) 135 | #define LIBPS_CPU_DECODE_RD(instruction) ((instruction >> 11) & 0x1F) 136 | #define LIBPS_CPU_DECODE_SHAMT(instruction) ((instruction >> 6) & 0x1F) 137 | #define LIBPS_CPU_DECODE_FUNCT(instruction) (instruction & 0x3F) 138 | #define LIBPS_CPU_DECODE_IMMEDIATE(instruction) (instruction & 0x0000FFFF) 139 | #define LIBPS_CPU_DECODE_TARGET(instruction) (instruction & 0x03FFFFFF) 140 | 141 | // Instruction decoder aliases as per MIPS conventions 142 | #define LIBPS_CPU_DECODE_BASE(instruction) LIBPS_CPU_DECODE_RS(instruction) 143 | 144 | #define LIBPS_CPU_DECODE_OFFSET(instruction) \ 145 | LIBPS_CPU_DECODE_IMMEDIATE(instruction) 146 | 147 | // System control co-processor (COP0) registers 148 | #ifdef LIBPS_DEBUG 149 | #define LIBPS_CPU_COP0_REG_BADVADDR 8 150 | #endif // LIBPS_DEBUG 151 | #define LIBPS_CPU_COP0_REG_SR 12 152 | #define LIBPS_CPU_COP0_REG_CAUSE 13 153 | #define LIBPS_CPU_COP0_REG_EPC 14 154 | 155 | // Status register (SR) flags 156 | #define LIBPS_CPU_SR_IsC (1 << 16) 157 | 158 | // Exception codes (ExcCode) 159 | #define LIBPS_CPU_EXCCODE_Int 0 160 | #define LIBPS_CPU_EXCCODE_Sys 8 161 | 162 | #ifdef LIBPS_DEBUG 163 | #define LIBPS_CPU_EXCCODE_AdEL 4 164 | #define LIBPS_CPU_EXCCODE_AdES 5 165 | #define LIBPS_CPU_EXCCODE_Bp 9 166 | #define LIBPS_CPU_EXCCODE_RI 10 167 | #define LIBPS_CPU_EXCCODE_Ov 12 168 | #endif // LIBPS_DEBUG 169 | 170 | #ifdef __cplusplus 171 | } 172 | #endif // __cplusplus 173 | -------------------------------------------------------------------------------- /src/libps/renderer/sw.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include "gpu.h" 17 | #include "sw.h" 18 | 19 | // XXX: This should probably be a helper function. 20 | static uint16_t process_pixel_through_clut(struct libps_gpu* gpu, 21 | const unsigned int x, 22 | const uint16_t texel, 23 | const unsigned int texpage_color_depth, 24 | const uint16_t clut) 25 | { 26 | assert(gpu != NULL); 27 | 28 | const unsigned int clut_x = (clut & 0x3F) * 16; 29 | const unsigned int clut_y = (clut >> 6) & 0x1FF; 30 | 31 | switch (texpage_color_depth) 32 | { 33 | case 4: 34 | { 35 | const unsigned int offset = (texel >> (x & 3) * 4) & 0xF; 36 | return gpu->vram[(clut_x + offset) + (LIBPS_GPU_VRAM_WIDTH * clut_y)]; 37 | } 38 | 39 | case 16: 40 | return texel; 41 | 42 | default: 43 | return 0xFFFF; 44 | } 45 | } 46 | 47 | static float edge_function(const struct libps_gpu_vertex* const v0, 48 | const struct libps_gpu_vertex* const v1, 49 | const struct libps_gpu_vertex* const v2) 50 | { 51 | return ((v1->x - v0->x) * (v2->y - v0->y)) - 52 | ((v1->y - v0->y) * (v2->x - v0->x)); 53 | } 54 | 55 | void libps_renderer_sw_draw_polygon(struct libps_gpu* gpu, 56 | const struct libps_gpu_vertex* const v0, 57 | struct libps_gpu_vertex* const v1, 58 | struct libps_gpu_vertex* const v2) 59 | { 60 | assert(gpu != NULL); 61 | assert(v0 != NULL); 62 | assert(v1 != NULL); 63 | assert(v2 != NULL); 64 | 65 | // https://fgiesen.wordpress.com/2013/02/08/triangle-rasterization-in-practice/ 66 | struct libps_gpu_vertex p; 67 | const struct libps_gpu_vertex* v1_real; 68 | const struct libps_gpu_vertex* v2_real; 69 | 70 | if (edge_function(v0, v1, v2) < 0) 71 | { 72 | v1_real = v2; 73 | v2_real = v1; 74 | } 75 | else 76 | { 77 | v1_real = v1; 78 | v2_real = v2; 79 | } 80 | 81 | const float area = edge_function(v0, v1_real, v2_real); 82 | 83 | for (p.y = gpu->drawing_area.y1; p.y <= gpu->drawing_area.y2; p.y++) 84 | { 85 | for (p.x = gpu->drawing_area.x1; p.x <= gpu->drawing_area.x2; p.x++) 86 | { 87 | float w0 = edge_function(v1_real, v2_real, &p); 88 | float w1 = edge_function(v2_real, v0, &p); 89 | float w2 = edge_function(v0, v1_real, &p); 90 | 91 | if (w0 >= 0 && w1 >= 0 && w2 >= 0) 92 | { 93 | unsigned int pixel_r; 94 | unsigned int pixel_g; 95 | unsigned int pixel_b; 96 | 97 | if (gpu->cmd_packet.flags & DRAW_FLAG_TEXTURED) 98 | { 99 | const uint16_t texcoord_x = ((w0 * (v0->texcoord & 0x00FF)) + 100 | (w1 * (v1_real->texcoord & 0x00FF)) + 101 | (w2 * (v2_real->texcoord & 0x00FF))) / area; 102 | 103 | const uint16_t texcoord_y = ((w0 * (v0->texcoord >> 8)) + 104 | (w1 * (v1_real->texcoord >> 8)) + 105 | (w2 * (v2_real->texcoord >> 8))) / area; 106 | 107 | const unsigned int texpage_x_base = v1->texpage & 0x0F; 108 | const unsigned int texpage_y_base = (v1->texpage & (1 << 4)) ? 256 : 0; 109 | 110 | unsigned int texpage_color_depth; 111 | 112 | uint16_t final_texcoord_x; 113 | 114 | switch ((v1->texpage >> 7) & 0x03) 115 | { 116 | case 0: 117 | texpage_color_depth = 4; 118 | final_texcoord_x = (texpage_x_base * 64) + (texcoord_x / 4); 119 | 120 | break; 121 | 122 | case 1: 123 | texpage_color_depth = 8; 124 | final_texcoord_x = (texpage_x_base * 64) + (texcoord_x / 8); 125 | 126 | break; 127 | 128 | case 2: 129 | texpage_color_depth = 16; 130 | 131 | final_texcoord_x = (texpage_x_base * 64) + texcoord_x; 132 | break; 133 | } 134 | 135 | const uint16_t final_texcoord_y = texpage_y_base + texcoord_y; 136 | 137 | const uint16_t texel = gpu->vram[final_texcoord_x + 138 | (LIBPS_GPU_VRAM_WIDTH * final_texcoord_y)]; 139 | 140 | const uint16_t color = 141 | process_pixel_through_clut(gpu, texcoord_x, texel, texpage_color_depth, v0->palette); 142 | 143 | if (color == 0x0000) 144 | { 145 | continue; 146 | } 147 | 148 | // G5B5R5A1 149 | gpu->vram[p.x + (LIBPS_GPU_VRAM_WIDTH * p.y)] = color; 150 | } 151 | else 152 | { 153 | pixel_r = ((w0 * (v0->color & 0x000000FF)) + 154 | (w1 * (v1_real->color & 0x000000FF)) + 155 | (w2 * (v2_real->color & 0x000000FF))) / area / 8; 156 | 157 | pixel_g = ((w0 * ((v0->color >> 8) & 0xFF)) + 158 | (w1 * ((v1_real->color >> 8) & 0xFF)) + 159 | (w2 * ((v2_real->color >> 8) & 0xFF))) / area / 8; 160 | 161 | pixel_b = ((w0 * ((v0->color >> 16) & 0xFF)) + 162 | (w1 * ((v1_real->color >> 16) & 0xFF)) + 163 | (w2 * ((v2_real->color >> 16) & 0xFF))) / area / 8; 164 | 165 | // G5B5R5A1 166 | gpu->vram[p.x + (LIBPS_GPU_VRAM_WIDTH * p.y)] = 167 | (pixel_g << 5) | (pixel_b << 10) | pixel_r; 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | void libps_renderer_sw_draw_rect(struct libps_gpu* gpu, 175 | const struct libps_gpu_vertex* const vertex) 176 | { 177 | assert(gpu != NULL); 178 | assert(vertex != NULL); 179 | 180 | const unsigned int pixel_r = (vertex->color & 0x000000FF) / 8; 181 | const unsigned int pixel_g = ((vertex->color >> 8) & 0xFF) / 8; 182 | const unsigned int pixel_b = ((vertex->color >> 16) & 0xFF) / 8; 183 | 184 | gpu->vram[vertex->x + (LIBPS_GPU_VRAM_WIDTH * vertex->y)] = 185 | (pixel_g << 5) | (pixel_b << 10) | pixel_r; 186 | } -------------------------------------------------------------------------------- /src/libps/include/cd.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" 19 | { 20 | #endif // __cplusplus 21 | 22 | #include "../utility/fifo.h" 23 | 24 | #include 25 | #include 26 | 27 | // Received SECOND (or further) response to ReadS/ReadN (and Play+Report) 28 | #define LIBPS_CDROM_INT1 1 29 | 30 | // Received SECOND response (to various commands) 31 | #define LIBPS_CDROM_INT2 2 32 | 33 | // Received FIRST response (to any command) 34 | #define LIBPS_CDROM_INT3 3 35 | 36 | // DataEnd (when Play/Forward reaches end of disk) (maybe also for Read?) 37 | #define LIBPS_CDROM_INT4 4 38 | 39 | // Received error - code(in FIRST or SECOND response) 40 | // 41 | // INT5 also occurs on SECOND GetID response, on unlicensed disks. 42 | // INT5 also occurs when opening the drive door(even if no command 43 | // was sent, ie.even if no read - command or other command is active) 44 | #define LIBPS_CDROM_INT5 5 45 | 46 | // Interrupts 47 | #define LIBPS_IRQ_CDROM (1 << 2) 48 | 49 | // Defines the absolute size of a sector in bytes. 50 | #define LIBPS_CDROM_SECTOR_SIZE 2352 51 | 52 | // Defines the structure of an interrupt. 53 | struct libps_cdrom_interrupt 54 | { 55 | // Does this interrupt need to be fired? 56 | bool pending; 57 | 58 | // How many cycles to wait before firing this interrupt? 59 | unsigned int cycles; 60 | 61 | // The type of interrupt 62 | unsigned int type; 63 | 64 | // Response parameters 65 | struct libps_fifo response; 66 | 67 | // The next interrupt to fire, if any 68 | struct libps_cdrom_interrupt* next_interrupt; 69 | }; 70 | 71 | // Pass this structure to `libps_system_set_cdrom()`. 72 | struct libps_cdrom_info 73 | { 74 | // Function to call when it is time to read a sector. `address` is an 75 | // absolute address. 76 | void (*read_cb)(void* user_data, const unsigned int address); 77 | }; 78 | 79 | struct libps_cdrom 80 | { 81 | // 0x1F801800 - Index/Status Register (Bit 0-1 R/W) (Bit 2-7 Read Only) 82 | // 83 | // 0 - 1: Index - Port 1F801801h - 1F801803h index(0..3 = Index0..Index3) (R / W) 84 | // 2: ADPBUSY XA - ADPCM fifo empty(0 = Empty); set when playing XA - ADPCM sound 85 | // 3: PRMEMPT Parameter fifo empty(1 = Empty); triggered before writing 1st byte 86 | // 4: PRMWRDY Parameter fifo full(0 = Full); triggered after writing 16 bytes 87 | // 5: RSLRRDY Response fifo empty(0 = Empty); triggered after reading LAST byte 88 | // 6: DRQSTS Data fifo empty(0 = Empty); triggered after reading LAST byte 89 | // 7: BUSYSTS Command / parameter transmission busy(1 = Busy) 90 | union 91 | { 92 | struct 93 | { 94 | unsigned int index : 1; 95 | unsigned int : 1; 96 | unsigned int parameter_fifo_empty : 1; 97 | //unsigned int parameter_fifo_empty : 1; 98 | unsigned int response_fifo_full : 1; 99 | unsigned int data_fifo_full : 1; 100 | unsigned int busy : 1; 101 | }; 102 | uint8_t raw; 103 | } status; 104 | 105 | // 1F801802h.Index1 - Interrupt Enable Register (W) 106 | // 1F801803h.Index0 - Interrupt Enable Register (R) 107 | uint8_t interrupt_enable; 108 | 109 | // 0x1F801803.Index1 - Interrupt Flag Register (R/W) 110 | uint8_t interrupt_flag; 111 | 112 | // The 8-bit status code is returned by Getstat command (and many other 113 | // commands), the meaning of the separate stat bits is: 114 | // 115 | // 7 - Play: Playing CD-DA 116 | // 6 - Seek: Seeking 117 | // 5 - Read: Reading data sectors 118 | // 4 - ShellOpen: Once shell open (0=Closed, 1=Is/was open) 119 | // 3 - IdError: (0=Okay, 1=GetID denied) (also set when Setmode.Bit4=1) 120 | // 2 - SeekError: (0=Okay, 1=Seek error) (followed by Error Byte) 121 | // 1 - Spindle Motor: (0=Motor off, or in spin-up phase, 1=Motor on) 122 | // 0 - Error Invalid Command/parameters (followed by Error Byte) 123 | uint8_t response_status; 124 | 125 | // Set by the Setmode command 126 | // 127 | // 7 - Speed (0 = Normal speed, 1 = Double speed) 128 | // 6 XA - ADPCM(0 = Off, 1 = Send XA - ADPCM sectors to SPU Audio Input) 129 | // 5 Sector Size(0 = 800h = DataOnly, 1 = 924h = WholeSectorExceptSyncBytes) 130 | // 4 Ignore Bit(0 = Normal, 1 = Ignore Sector Size and Setloc position) 131 | // 3 XA - Filter(0 = Off, 1 = Process only XA - ADPCM sectors that match Setfilter) 132 | // 2 Report(0 = Off, 1 = Enable Report - Interrupts for Audio Play) 133 | // 1 AutoPause(0 = Off, 1 = Auto Pause upon End of Track); for Audio Play 134 | // 0 CDDA(0 = Off, 1 = Allow to Read CD - DA Sectors; ignore missing EDC) 135 | uint8_t mode; 136 | 137 | struct libps_fifo parameter_fifo; 138 | struct libps_fifo data_fifo; 139 | struct libps_fifo* response_fifo; 140 | 141 | // Interrupt lines 142 | struct libps_cdrom_interrupt int1; 143 | struct libps_cdrom_interrupt int2; 144 | struct libps_cdrom_interrupt int3; 145 | struct libps_cdrom_interrupt int5; 146 | 147 | // The current interrupt we're processing. 148 | struct libps_cdrom_interrupt* current_interrupt; 149 | 150 | // The current CD-ROM position. 151 | struct 152 | { 153 | uint8_t minute; 154 | uint8_t second; 155 | uint8_t sector; 156 | } position; 157 | 158 | bool fire_interrupt; 159 | 160 | // This should not be set directly; use `libps_system_set_cdrom()`. 161 | struct libps_cdrom_info cdrom_info; 162 | 163 | // Current sector read cycle count 164 | unsigned int sector_read_cycle_count; 165 | 166 | // The number of cycles to wait before reading another sector 167 | unsigned int sector_read_cycle_count_max; 168 | 169 | // Current sector we're reading 170 | unsigned int sector_count; 171 | 172 | // The number of sectors we can read. This will only ever be 74 or 149. 173 | unsigned int sector_count_max; 174 | 175 | // The sector of a sector as defined by the `Setmode` command. This can 176 | // only ever be 0x800 (2048) or 0x924 (2340). 177 | unsigned int sector_size; 178 | 179 | // Pointer to the current sector data 180 | uint8_t* sector_data; 181 | 182 | void* user_data; 183 | }; 184 | 185 | void libps_cdrom_setup(struct libps_cdrom* cdrom); 186 | void libps_cdrom_cleanup(struct libps_cdrom* cdrom); 187 | void libps_cdrom_reset(struct libps_cdrom* cdrom); 188 | void libps_cdrom_step(struct libps_cdrom* cdrom); 189 | 190 | uint8_t libps_cdrom_register_load(struct libps_cdrom* cdrom, 191 | const unsigned int reg); 192 | 193 | void libps_cdrom_register_store(struct libps_cdrom* cdrom, 194 | const unsigned int reg, 195 | const uint8_t data); 196 | #ifdef __cplusplus 197 | } 198 | #endif // __cplusplus -------------------------------------------------------------------------------- /src/test/emulator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include "emulator.h" 17 | #include "../libps/include/disasm.h" 18 | #include "../libps/include/ps.h" 19 | 20 | Emulator::Emulator(QObject* parent, const QString& bios_file) : QThread(parent) 21 | { 22 | FILE* bios_file_handle = fopen(qPrintable(bios_file), "rb"); 23 | bios = new uint8_t[0x80000]; 24 | fread(bios, 1, 0x80000, bios_file_handle); 25 | fclose(bios_file_handle); 26 | 27 | sys = libps_system_create(bios); 28 | 29 | sys->bus.cdrom.user_data = this; 30 | 31 | sys->bus.cdrom.sector_data = sector_data; 32 | 33 | #ifdef LIBPS_DEBUG 34 | sys->bus.debug_user_data = this; 35 | 36 | sys->bus.debug_unknown_memory_load = [](void* user_data, 37 | const uint32_t paddr, 38 | const unsigned int type) 39 | { 40 | Emulator* ps = reinterpret_cast(user_data); 41 | emit ps->on_debug_unknown_memory_load(paddr, type); 42 | }; 43 | 44 | sys->bus.debug_unknown_memory_store = [](void* user_data, 45 | const uint32_t paddr, 46 | const unsigned int data, 47 | const unsigned int type) 48 | { 49 | Emulator* ps = reinterpret_cast(user_data); 50 | emit ps->on_debug_unknown_memory_store(paddr, data, type); 51 | }; 52 | 53 | sys->bus.debug_interrupt_requested = [](void* user_data, 54 | const unsigned int interrupt) 55 | { 56 | Emulator* ps = reinterpret_cast(user_data); 57 | emit ps->on_debug_interrupt_requested(interrupt); 58 | }; 59 | 60 | sys->bus.debug_interrupt_acknowledged = [](void* user_data, 61 | const unsigned int interrupt) 62 | { 63 | Emulator* ps = reinterpret_cast(user_data); 64 | emit ps->on_debug_interrupt_acknowledged(interrupt); 65 | }; 66 | #endif // LIBPS_DEBUG 67 | running = false; 68 | injecting_ps_x_exe = false; 69 | tracing = false; 70 | 71 | trace_file = fopen("trace.txt", "w"); 72 | 73 | total_cycles = 0; 74 | } 75 | 76 | Emulator::~Emulator() 77 | { 78 | libps_system_destroy(sys); 79 | } 80 | 81 | // Starts the emulation if it is not running. 82 | void Emulator::start_run_loop() 83 | { 84 | if (!running) 85 | { 86 | running = true; 87 | start(); 88 | } 89 | } 90 | 91 | // Stops the emulation if it is running, resetting the emulator to the 92 | // startup state. 93 | void Emulator::stop_run_loop() 94 | { 95 | if (running) 96 | { 97 | running = false; 98 | libps_system_reset(sys); 99 | 100 | total_cycles = 0; 101 | exit(); 102 | } 103 | } 104 | 105 | // Pauses the emulation if it is running, but *does not* reset the emulator to 106 | // the startup state. 107 | void Emulator::pause_run_loop() 108 | { 109 | if (running) 110 | { 111 | running = false; 112 | exit(); 113 | } 114 | } 115 | 116 | // Called when the user selects a game image to load after triggering 117 | // "File -> Insert CD-ROM image..." on the main window. 118 | void Emulator::insert_cdrom_image(const QString& file_name) 119 | { 120 | struct libps_cdrom_info cdrom_info; 121 | 122 | cdrom_image_file = fopen(qPrintable(file_name), "rb"); 123 | 124 | cdrom_info.read_cb = [](void* user_data, const unsigned int address) 125 | { 126 | Emulator* emu = reinterpret_cast(user_data); 127 | emu->handle_cdrom_image_read(address); 128 | }; 129 | libps_system_set_cdrom(sys, &cdrom_info); 130 | } 131 | 132 | // Called when the user selects a PS-X EXE to inject after triggering 133 | // "File -> Run PS-X EXE..." on the main window. 134 | void Emulator::run_ps_x_exe(const QString& file_name) 135 | { 136 | ps_x_exe = file_name; 137 | 138 | injecting_ps_x_exe = true; 139 | 140 | // Restart emulation. 141 | stop_run_loop(); 142 | start_run_loop(); 143 | } 144 | 145 | // Returns the number of total cycles taken by the emulator. 146 | unsigned int Emulator::total_cycles_taken() noexcept 147 | { 148 | return total_cycles; 149 | } 150 | 151 | // Called when it is time to inject the PS-X EXE specified by `run_ps_x_exe()`. 152 | void Emulator::inject_ps_x_exe() 153 | { 154 | const auto file_name{ qPrintable(ps_x_exe) }; 155 | 156 | FILE* ps_x_exe_handle = fopen(file_name, "rb"); 157 | const auto file_size = std::filesystem::file_size(file_name); 158 | uint8_t* ps_x_exe_data = static_cast(malloc(file_size)); 159 | fread(ps_x_exe_data, 1, file_size, ps_x_exe_handle); 160 | fclose(ps_x_exe_handle); 161 | 162 | uint32_t dest = *(uint32_t *)(ps_x_exe_data + 0x10); 163 | 164 | for (unsigned int ptr = 0x800; 165 | ptr != (file_size - 0x800); 166 | ++ptr) 167 | { 168 | *(uint32_t *)(sys->bus.ram + (dest++ & 0x1FFFFFFF)) = 169 | ps_x_exe_data[ptr]; 170 | } 171 | 172 | sys->cpu.pc = *(uint32_t *)(ps_x_exe_data + 0x18); 173 | sys->cpu.next_pc = sys->cpu.pc; 174 | 175 | sys->cpu.instruction = libps_bus_load_word(&sys->bus, sys->cpu.pc); 176 | 177 | injecting_ps_x_exe = false; 178 | ps_x_exe = nullptr; 179 | } 180 | 181 | // Called when `std_out_putchar` has been called by the BIOS. 182 | void Emulator::handle_tty_string() 183 | { 184 | static QString tty_str; 185 | 186 | tty_str += sys->cpu.gpr[4]; 187 | 188 | if (sys->cpu.gpr[4] == '\n') 189 | { 190 | emit tty_string(tty_str); 191 | tty_str.clear(); 192 | } 193 | } 194 | 195 | // Initiates a trace of a BIOS call. 196 | void Emulator::trace_bios_call(const uint32_t pc, const uint32_t fn) 197 | { } 198 | 199 | // Thread entry point 200 | void Emulator::run() 201 | { 202 | while (running) 203 | { 204 | QElapsedTimer timer; 205 | timer.start(); 206 | 207 | for (unsigned int cycle = 0; 208 | cycle < 33868800 / 60; 209 | cycle += 2, 210 | total_cycles += 2) 211 | { 212 | if (!running) 213 | { 214 | break; 215 | } 216 | 217 | if (tracing) 218 | { 219 | char disasm[256]; 220 | libps_disassemble_instruction(sys->cpu.instruction, sys->cpu.pc, disasm); 221 | 222 | fprintf(trace_file, "0x%08X: %s\n", sys->cpu.pc, disasm); 223 | fflush(trace_file); 224 | } 225 | 226 | if (tracing_bios_call && bios_call_trace_pc == sys->cpu.pc) 227 | { 228 | emit bios_call(&bios_trace); 229 | tracing_bios_call = false; 230 | } 231 | 232 | // We can only inject a PS-X EXE at this point. This is the 233 | // earliest point during boot-up where the kernel is initialized 234 | // far enough to allow execution of PS-X EXEs. 235 | if (injecting_ps_x_exe && sys->cpu.pc == 0x80030000) 236 | { 237 | inject_ps_x_exe(); 238 | } 239 | 240 | if (sys->cpu.pc == 0x000000A0) 241 | { 242 | switch (sys->cpu.gpr[9]) 243 | { 244 | case 0x3C: 245 | handle_tty_string(); 246 | break; 247 | 248 | case 0x40: 249 | emit system_error(); 250 | pause_run_loop(); 251 | 252 | break; 253 | 254 | default: 255 | bios_trace.func = sys->cpu.gpr[9]; 256 | bios_trace.origin = sys->cpu.pc; 257 | 258 | emit bios_call(&bios_trace); 259 | break; 260 | } 261 | } 262 | 263 | if (sys->cpu.pc == 0x000000B0) 264 | { 265 | switch (sys->cpu.gpr[9]) 266 | { 267 | case 0x3D: 268 | handle_tty_string(); 269 | break; 270 | 271 | default: 272 | bios_trace.func = sys->cpu.gpr[9]; 273 | bios_trace.origin = sys->cpu.pc; 274 | 275 | emit bios_call(&bios_trace); 276 | break; 277 | } 278 | } 279 | 280 | if (sys->cpu.pc == 0x000000C0) 281 | { 282 | bios_trace.func = sys->cpu.gpr[9]; 283 | bios_trace.origin = sys->cpu.pc; 284 | 285 | emit bios_call(&bios_trace); 286 | } 287 | libps_system_step(sys); 288 | } 289 | 290 | sys->bus.i_stat |= LIBPS_IRQ_VBLANK; 291 | 292 | emit render_frame(sys->bus.gpu.vram); 293 | 294 | const qint64 elapsed = timer.elapsed(); 295 | 296 | if (elapsed < (1000 / 60)) 297 | { 298 | QThread::msleep((1000 / 60) - elapsed); 299 | } 300 | } 301 | } 302 | 303 | // Called when it is time to read data off of the CD-ROM image. 304 | void Emulator::handle_cdrom_image_read(const unsigned int address) 305 | { 306 | fseek(cdrom_image_file, address, SEEK_SET); 307 | fread(sector_data, 2352, 1, cdrom_image_file); 308 | } -------------------------------------------------------------------------------- /src/test/pstest.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include "pstest.h" 17 | #include "../libps/include/ps.h" 18 | 19 | PSTest::PSTest() 20 | { 21 | goto staging; 22 | 23 | staging: 24 | QSettings config_file("pstest.ini", QSettings::IniFormat, this); 25 | 26 | QString bios_file = config_file.value("files/bios").toString(); 27 | 28 | if (bios_file.isEmpty()) 29 | { 30 | bios_file = QFileDialog::getOpenFileName(nullptr, 31 | tr("Select PlayStation BIOS"), 32 | "", 33 | tr("PlayStation BIOS files (*.bin)")); 34 | 35 | if (bios_file.isEmpty()) 36 | { 37 | const int result = 38 | QMessageBox::warning(nullptr, 39 | tr("BIOS file not selected"), 40 | tr("libps debugging station requires that you" 41 | " provide a BIOS file, preferably SCPH-100" 42 | "1."), 43 | QMessageBox::Retry, 44 | QMessageBox::Cancel); 45 | 46 | switch (result) 47 | { 48 | case QMessageBox::Retry: 49 | goto staging; 50 | 51 | case QMessageBox::Cancel: 52 | exit(EXIT_FAILURE); 53 | } 54 | } 55 | else 56 | { 57 | config_file.setValue("files/bios", bios_file); 58 | } 59 | } 60 | 61 | main_window = new MainWindow(); 62 | main_window->setAttribute(Qt::WA_QuitOnClose); 63 | 64 | emulator = new Emulator(this, bios_file); 65 | 66 | connect(emulator, &Emulator::finished, emulator, &QObject::deleteLater); 67 | connect(emulator, &Emulator::render_frame, main_window, &MainWindow::render_frame); 68 | connect(emulator, &Emulator::system_error, this, &PSTest::emu_report_system_error); 69 | connect(emulator, &Emulator::bios_call, this, &PSTest::emu_bios_call); 70 | 71 | #ifdef LIBPS_DEBUG 72 | connect(emulator, &Emulator::on_debug_unknown_memory_load, this, &PSTest::on_debug_unknown_memory_load); 73 | connect(emulator, &Emulator::on_debug_unknown_memory_store, this, &PSTest::on_debug_unknown_memory_store); 74 | connect(emulator, &Emulator::on_debug_interrupt_requested, this, &PSTest::on_debug_interrupt_requested); 75 | connect(emulator, &Emulator::on_debug_interrupt_acknowledged, this, &PSTest::on_debug_interrupt_acknowledged); 76 | #endif // LIBPS_DEBUG 77 | 78 | // "File" menu 79 | connect(main_window, &MainWindow::selected_cdrom_image, emulator, &Emulator::insert_cdrom_image); 80 | connect(main_window, &MainWindow::selected_ps_x_exe, emulator, &Emulator::run_ps_x_exe); 81 | 82 | // "Emulation" menu 83 | connect(main_window->start_emu, &QAction::triggered, this, &PSTest::start_emu); 84 | connect(main_window->stop_emu, &QAction::triggered, this, &PSTest::stop_emu); 85 | connect(main_window->reset_emu, &QAction::triggered, this, &PSTest::reset_emu); 86 | connect(main_window->pause_emu, &QAction::triggered, this, &PSTest::pause_emu); 87 | 88 | // "Debug" menu 89 | connect(main_window->display_libps_log, &QAction::triggered, this, &PSTest::display_libps_log); 90 | 91 | main_window->setWindowTitle("libps debugging station"); 92 | main_window->resize(1024, 512); 93 | 94 | main_window->show(); 95 | } 96 | 97 | PSTest::~PSTest() 98 | { } 99 | 100 | // Called when the emulator core reports that BIOS call 101 | // `A(0x40) - SystemErrorUnresolvedException()` was reached. 102 | void PSTest::emu_report_system_error() 103 | { 104 | QMessageBox::critical(main_window, 105 | tr("Emulation failure"), 106 | tr("A($40): SystemErrorUnresolvedException() " 107 | "reached. Emulation halted.")); 108 | } 109 | 110 | // Called when the emulator core reports that a BIOS call other than 111 | // A(0x40), A(0x3C), or B(0x3D) was reached. 112 | void PSTest::emu_bios_call(struct bios_trace_info* bios_trace) 113 | { 114 | if (libps_log && libps_log->bios_calls->isChecked()) 115 | { 116 | libps_log->append(QString("[BIOS CALL]: %1(%2)\n") 117 | .arg(QString::number(bios_trace->origin, 16).toUpper()) 118 | .arg(QString::number(bios_trace->func, 16).toUpper())); 119 | } 120 | } 121 | 122 | void PSTest::display_libps_log() 123 | { 124 | libps_log = new MessageLogger(main_window); 125 | libps_log->setAttribute(Qt::WA_DeleteOnClose); 126 | 127 | connect(emulator, 128 | &Emulator::tty_string, 129 | this, 130 | &PSTest::on_tty_string); 131 | 132 | libps_log->setWindowTitle(tr("libps log")); 133 | libps_log->resize(500, 500); 134 | 135 | libps_log->show(); 136 | } 137 | 138 | // Called when the user triggers `Emulation -> Start`. This function is also 139 | // called upon startup, and is used also to resume emulation from a paused 140 | // state. 141 | void PSTest::start_emu() 142 | { 143 | if (main_window->start_emu->text() == tr("Resume")) 144 | { 145 | main_window->start_emu->setText(tr("Start")); 146 | } 147 | 148 | main_window->start_emu->setDisabled(true); 149 | main_window->stop_emu->setEnabled(true); 150 | main_window->pause_emu->setEnabled(true); 151 | 152 | emulator->start_run_loop(); 153 | } 154 | 155 | // Called when the user triggers `Emulation -> Stop`. 156 | void PSTest::stop_emu() 157 | { 158 | emulator->stop_run_loop(); 159 | 160 | main_window->start_emu->setEnabled(true); 161 | main_window->stop_emu->setDisabled(true); 162 | main_window->pause_emu->setDisabled(true); 163 | main_window->reset_emu->setDisabled(true); 164 | } 165 | 166 | // Called when the user triggers `Emulation -> Pause`. 167 | void PSTest::pause_emu() 168 | { 169 | emulator->pause_run_loop(); 170 | 171 | main_window->start_emu->setText(tr("Resume")); 172 | 173 | main_window->pause_emu->setDisabled(true); 174 | main_window->start_emu->setEnabled(true); 175 | main_window->reset_emu->setEnabled(true); 176 | } 177 | 178 | // Called when the user triggers `Emulation -> Reset`. 179 | void PSTest::reset_emu() 180 | { 181 | if (libps_log) 182 | { 183 | libps_log->reset(); 184 | } 185 | 186 | emulator->stop_run_loop(); 187 | emulator->start_run_loop(); 188 | } 189 | 190 | // Called when a TTY string has been generated 191 | void PSTest::on_tty_string(const QString& tty_string) 192 | { 193 | if (libps_log && libps_log->tty_strings->isChecked()) 194 | { 195 | libps_log->append("[TTY]: " + tty_string); 196 | } 197 | } 198 | 199 | #ifdef LIBPS_DEBUG 200 | // Called when an unknown memory load has been attempted 201 | void PSTest::on_debug_unknown_memory_load(const uint32_t paddr, 202 | const unsigned int type) 203 | { 204 | if (libps_log && libps_log->unknown_memory_load->isChecked()) 205 | { 206 | QString m_type; 207 | 208 | switch (type) 209 | { 210 | case LIBPS_DEBUG_WORD: 211 | m_type = "word"; 212 | break; 213 | 214 | case LIBPS_DEBUG_HALFWORD: 215 | m_type = "halfword"; 216 | break; 217 | 218 | case LIBPS_DEBUG_BYTE: 219 | m_type = "byte"; 220 | break; 221 | } 222 | 223 | libps_log->append(QString("Unknown %1 load: 0x%2\n") 224 | .arg(m_type) 225 | .arg(QString::number(paddr, 16).toUpper())); 226 | } 227 | } 228 | 229 | // Called when an unknown word store has been attempted 230 | void PSTest::on_debug_unknown_memory_store(const uint32_t paddr, 231 | const unsigned int data, 232 | const unsigned int type) 233 | { 234 | if (libps_log && libps_log->unknown_memory_store->isChecked()) 235 | { 236 | QString m_type; 237 | QString result; 238 | 239 | switch (type) 240 | { 241 | case LIBPS_DEBUG_WORD: 242 | m_type = "word"; 243 | result = QString("%1").arg(data & type, 8, 16, QChar('0')).toUpper(); 244 | 245 | break; 246 | 247 | case LIBPS_DEBUG_HALFWORD: 248 | m_type = "halfword"; 249 | result = QString("%1").arg(data & type, 4, 16, QChar('0')).toUpper(); 250 | 251 | break; 252 | 253 | case LIBPS_DEBUG_BYTE: 254 | m_type = "byte"; 255 | result = QString("%1").arg(data & type, 2, 16, QChar('0')).toUpper(); 256 | 257 | break; 258 | } 259 | 260 | QString str = QString("Unknown %1 store: 0x%2 <- 0x%3\n") 261 | .arg(m_type) 262 | .arg(QString::number(paddr, 16).toUpper()) 263 | .arg(result); 264 | 265 | libps_log->append(str); 266 | } 267 | } 268 | 269 | void PSTest::on_debug_interrupt_requested(const unsigned int interrupt) 270 | { 271 | static const std::array irq_as_string = 272 | { 273 | "IRQ0 VBLANK", 274 | "IRQ1 GPU", 275 | "IRQ2 CDROM", 276 | "IRQ3 DMA", 277 | "IRQ4 TMR0", 278 | "IRQ5 TMR1", 279 | "IRQ6 TMR2", 280 | "IRQ7 Controller and Memory Card", 281 | "IRQ8 SIO", 282 | "IRQ9 SPU", 283 | "IRQ10 Controller" 284 | }; 285 | 286 | if (libps_log && libps_log->irqs->isChecked()) 287 | { 288 | libps_log->append(QString("[total_cycles=%1], %2 requested\n") 289 | .arg(QString::number(emulator->total_cycles_taken(), 10)) 290 | .arg(irq_as_string[interrupt])); 291 | } 292 | } 293 | 294 | void PSTest::on_debug_interrupt_acknowledged(const unsigned int interrupt) 295 | { 296 | static const std::array irq_as_string = 297 | { 298 | "IRQ0 VBLANK", 299 | "IRQ1 GPU", 300 | "IRQ2 CDROM", 301 | "IRQ3 DMA", 302 | "IRQ4 TMR0", 303 | "IRQ5 TMR1", 304 | "IRQ6 TMR2", 305 | "IRQ7 Controller and Memory Card", 306 | "IRQ8 SIO", 307 | "IRQ9 SPU", 308 | "IRQ10 Controller" 309 | }; 310 | 311 | if (libps_log && libps_log->irqs->isChecked()) 312 | { 313 | libps_log->append(QString("[total_cycles=%1], %2 acknowledged\n") 314 | .arg(QString::number(emulator->total_cycles_taken(), 10)) 315 | .arg(irq_as_string[interrupt])); 316 | } 317 | } 318 | 319 | #endif // LIBPS_DEBUG -------------------------------------------------------------------------------- /src/libps/cd.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "cd.h" 20 | #include "utility/fifo.h" 21 | #include "utility/math.h" 22 | #include "utility/memory.h" 23 | 24 | #include 25 | 26 | // Resets an interrupt line `interrupt`. 27 | static void reset_interrupt(struct libps_cdrom_interrupt* interrupt) 28 | { 29 | assert(interrupt != NULL); 30 | 31 | interrupt->next_interrupt = NULL; 32 | 33 | libps_fifo_reset(&interrupt->response); 34 | 35 | interrupt->pending = false; 36 | interrupt->cycles = 0; 37 | } 38 | 39 | // Pushes a response to interrupt line `interrupt`, delaying its firing by 40 | // `delay_cycles` cycles. 41 | static void push_response(struct libps_cdrom_interrupt* interrupt, 42 | const unsigned int delay_cycles, 43 | const unsigned int num_args, 44 | ...) 45 | { 46 | va_list args; 47 | va_start(args, num_args); 48 | 49 | int arg; 50 | 51 | for (unsigned int i = 0; i < num_args; ++i) 52 | { 53 | arg = va_arg(args, int); 54 | libps_fifo_enqueue(&interrupt->response, arg); 55 | } 56 | va_end(args); 57 | 58 | interrupt->pending = true; 59 | interrupt->cycles = delay_cycles; 60 | } 61 | 62 | // Initializes a CD-ROM drive `cdrom`. 63 | void libps_cdrom_setup(struct libps_cdrom* cdrom) 64 | { 65 | assert(cdrom != NULL); 66 | 67 | libps_fifo_setup(&cdrom->parameter_fifo, 16); 68 | libps_fifo_setup(&cdrom->data_fifo, 4096); 69 | 70 | cdrom->int1.type = LIBPS_CDROM_INT1; 71 | cdrom->int2.type = LIBPS_CDROM_INT2; 72 | cdrom->int3.type = LIBPS_CDROM_INT3; 73 | cdrom->int5.type = LIBPS_CDROM_INT5; 74 | 75 | libps_fifo_setup(&cdrom->int1.response, 16); 76 | libps_fifo_setup(&cdrom->int2.response, 16); 77 | libps_fifo_setup(&cdrom->int3.response, 16); 78 | libps_fifo_setup(&cdrom->int5.response, 16); 79 | 80 | cdrom->user_data = NULL; 81 | } 82 | 83 | void libps_cdrom_cleanup(struct libps_cdrom* cdrom) 84 | { 85 | assert(cdrom != NULL); 86 | 87 | libps_fifo_cleanup(&cdrom->parameter_fifo); 88 | libps_fifo_cleanup(&cdrom->data_fifo); 89 | 90 | libps_fifo_cleanup(&cdrom->int1.response); 91 | libps_fifo_cleanup(&cdrom->int2.response); 92 | libps_fifo_cleanup(&cdrom->int3.response); 93 | libps_fifo_cleanup(&cdrom->int5.response); 94 | } 95 | 96 | // Resets the CD-ROM drive to its initial state. 97 | void libps_cdrom_reset(struct libps_cdrom* cdrom) 98 | { 99 | assert(cdrom != NULL); 100 | 101 | libps_fifo_reset(&cdrom->parameter_fifo); 102 | libps_fifo_reset(&cdrom->data_fifo); 103 | 104 | reset_interrupt(&cdrom->int1); 105 | reset_interrupt(&cdrom->int2); 106 | reset_interrupt(&cdrom->int3); 107 | reset_interrupt(&cdrom->int5); 108 | 109 | cdrom->current_interrupt = NULL; 110 | 111 | cdrom->interrupt_flag = 0x00; 112 | 113 | cdrom->status.raw = 0x18; 114 | cdrom->response_status = 0x00; 115 | 116 | cdrom->sector_count = 0; 117 | cdrom->sector_count_max = 0; 118 | cdrom->sector_read_cycle_count = 0; 119 | cdrom->sector_read_cycle_count_max = 0; 120 | 121 | cdrom->fire_interrupt = false; 122 | } 123 | 124 | // Checks to see if interrupts needs to be fired. 125 | void libps_cdrom_step(struct libps_cdrom* cdrom) 126 | { 127 | assert(cdrom != NULL); 128 | 129 | // This takes priority over everything else. 130 | if (cdrom->response_status & (1 << 5)) 131 | { 132 | if (cdrom->sector_read_cycle_count >= 133 | cdrom->sector_read_cycle_count_max) 134 | { 135 | // Read a new sector 136 | const unsigned int address = 137 | ((cdrom->position.sector + cdrom->sector_count++) + 138 | (cdrom->position.second * 75) + 139 | (cdrom->position.minute * 60 * 75) - 150) * LIBPS_CDROM_SECTOR_SIZE; 140 | 141 | cdrom->cdrom_info.read_cb(cdrom->user_data, address + 24); 142 | 143 | push_response(&cdrom->int1, 144 | 100000, 145 | 1, 146 | cdrom->response_status); 147 | 148 | cdrom->int1.next_interrupt = &cdrom->int1; 149 | cdrom->current_interrupt = &cdrom->int1; 150 | 151 | cdrom->sector_read_cycle_count = 0; 152 | } 153 | else 154 | { 155 | cdrom->sector_read_cycle_count++; 156 | } 157 | } 158 | 159 | // Is there an interrupt pending? 160 | if ((cdrom->current_interrupt != NULL) && 161 | cdrom->current_interrupt->pending) 162 | { 163 | if (cdrom->current_interrupt->cycles != 0) 164 | { 165 | cdrom->current_interrupt->cycles--; 166 | } 167 | else 168 | { 169 | cdrom->response_fifo = &cdrom->current_interrupt->response; 170 | 171 | cdrom->current_interrupt->pending = false; 172 | cdrom->fire_interrupt = true; 173 | 174 | cdrom->interrupt_flag = (cdrom->interrupt_flag & ~0x07) | 175 | (cdrom->current_interrupt->type & 0x07); 176 | } 177 | } 178 | } 179 | 180 | // Loads indexed CD-ROM register `reg`. 181 | uint8_t libps_cdrom_register_load(struct libps_cdrom* cdrom, 182 | const unsigned int reg) 183 | { 184 | assert(cdrom != NULL); 185 | 186 | switch (reg) 187 | { 188 | // 0x1F801801 189 | case 1: 190 | switch (cdrom->status.index) 191 | { 192 | // 1F801801h.Index1 - Response Fifo (R) 193 | case 1: 194 | return libps_fifo_dequeue(cdrom->response_fifo); 195 | 196 | default: 197 | __debugbreak(); 198 | break; 199 | } 200 | break; 201 | 202 | // 0x1F801803 203 | case 3: 204 | switch (cdrom->status.index) 205 | { 206 | // 1F801803h.Index0 - Interrupt Enable Register (R) 207 | case 0: 208 | return cdrom->interrupt_enable; 209 | 210 | // 1F801803h.Index1 - Interrupt Flag Register (R/W) 211 | case 1: 212 | return cdrom->interrupt_flag; 213 | 214 | default: 215 | __debugbreak(); 216 | break; 217 | } 218 | break; 219 | 220 | default: 221 | __debugbreak(); 222 | break; 223 | } 224 | } 225 | 226 | // Stores `data` into indexed CD-ROM register `reg`. 227 | void libps_cdrom_register_store(struct libps_cdrom* cdrom, 228 | const unsigned int reg, 229 | const uint8_t data) 230 | { 231 | assert(cdrom != NULL); 232 | 233 | switch (reg) 234 | { 235 | // 0x1F801801 236 | case 1: 237 | switch (cdrom->status.index) 238 | { 239 | // 1F801801h.Index0 - Command Register (W) 240 | case 0: 241 | switch (data) 242 | { 243 | // Getstat 244 | case 0x01: 245 | push_response(&cdrom->int3, 246 | 20000, 247 | 1, 248 | cdrom->response_status); 249 | 250 | cdrom->int3.next_interrupt = NULL; 251 | 252 | cdrom->current_interrupt = &cdrom->int3; 253 | break; 254 | 255 | // Setloc 256 | case 0x02: 257 | cdrom->position.minute = 258 | libps_fifo_dequeue(&cdrom->parameter_fifo); 259 | 260 | cdrom->position.second = 261 | libps_fifo_dequeue(&cdrom->parameter_fifo); 262 | 263 | cdrom->position.sector = 264 | libps_fifo_dequeue(&cdrom->parameter_fifo); 265 | 266 | cdrom->position.minute = 267 | LIBPS_BCD_TO_DEC(cdrom->position.minute); 268 | 269 | cdrom->position.second = 270 | LIBPS_BCD_TO_DEC(cdrom->position.second); 271 | 272 | cdrom->position.sector = 273 | LIBPS_BCD_TO_DEC(cdrom->position.sector); 274 | 275 | push_response(&cdrom->int3, 276 | 20000, 277 | 1, 278 | cdrom->response_status); 279 | 280 | cdrom->int3.next_interrupt = NULL; 281 | 282 | cdrom->current_interrupt = &cdrom->int3; 283 | break; 284 | 285 | // ReadN 286 | case 0x06: 287 | { 288 | const unsigned int threshold = 289 | cdrom->mode ? 150 : 75; 290 | 291 | push_response(&cdrom->int3, 292 | 20000, 293 | 1, 294 | cdrom->response_status); 295 | 296 | cdrom->response_status |= (1 << 5); 297 | cdrom->response_status |= (1 << 1); 298 | 299 | cdrom->sector_count = 0; 300 | cdrom->sector_count_max = threshold - 1; 301 | 302 | cdrom->sector_read_cycle_count_max = 303 | 33868800 / cdrom->sector_count_max; 304 | 305 | // Second response comes in `libps_cdrom_step()`. 306 | cdrom->current_interrupt = &cdrom->int3; 307 | break; 308 | } 309 | 310 | // Pause 311 | case 0x09: 312 | push_response(&cdrom->int3, 313 | 20000, 314 | 1, 315 | cdrom->response_status); 316 | 317 | cdrom->response_status &= ~(1 << 5); 318 | cdrom->response_status &= ~(1 << 1); 319 | 320 | push_response(&cdrom->int2, 321 | 25000, 322 | 1, 323 | cdrom->response_status); 324 | 325 | cdrom->int3.next_interrupt = &cdrom->int2; 326 | cdrom->int2.next_interrupt = NULL; 327 | 328 | cdrom->current_interrupt = &cdrom->int3; 329 | break; 330 | 331 | // Init 332 | case 0x0A: 333 | push_response(&cdrom->int3, 334 | 20000, 335 | 1, 336 | cdrom->response_status); 337 | 338 | cdrom->mode = 0x02; 339 | 340 | push_response(&cdrom->int2, 341 | 25000, 342 | 1, 343 | cdrom->response_status); 344 | 345 | cdrom->int3.next_interrupt = &cdrom->int2; 346 | cdrom->int2.next_interrupt = NULL; 347 | 348 | cdrom->current_interrupt = &cdrom->int3; 349 | break; 350 | 351 | // Setmode 352 | case 0x0E: 353 | cdrom->mode = 354 | libps_fifo_dequeue(&cdrom->parameter_fifo); 355 | 356 | cdrom->sector_size = 357 | ((cdrom->mode & (1 << 5)) ? 0x924 : 0x800); 358 | 359 | push_response(&cdrom->int3, 360 | 20000, 361 | 1, 362 | cdrom->response_status); 363 | 364 | cdrom->int3.next_interrupt = NULL; 365 | 366 | cdrom->current_interrupt = &cdrom->int3; 367 | break; 368 | 369 | // SeekL 370 | case 0x15: 371 | cdrom->response_status |= (1 << 6); 372 | cdrom->response_status |= (1 << 1); 373 | 374 | push_response(&cdrom->int3, 375 | 20000, 376 | 1, 377 | cdrom->response_status); 378 | 379 | cdrom->response_status &= ~(1 << 6); 380 | cdrom->response_status &= ~(1 << 1); 381 | 382 | push_response(&cdrom->int2, 383 | 25000, 384 | 1, 385 | cdrom->response_status); 386 | 387 | cdrom->int3.next_interrupt = &cdrom->int2; 388 | cdrom->int2.next_interrupt = NULL; 389 | 390 | cdrom->current_interrupt = &cdrom->int3; 391 | break; 392 | 393 | case 0x19: 394 | switch (libps_fifo_dequeue(&cdrom->parameter_fifo)) 395 | { 396 | // Get cdrom BIOS date/version (yy,mm,dd,ver) 397 | case 0x20: 398 | push_response(&cdrom->int3, 399 | 20000, 400 | 4, 401 | 0x94, 0x09, 0x19, 0xC0); 402 | 403 | cdrom->int3.next_interrupt = NULL; 404 | cdrom->current_interrupt = &cdrom->int3; 405 | 406 | break; 407 | 408 | default: 409 | __debugbreak(); 410 | break; 411 | } 412 | break; 413 | 414 | // GetID 415 | case 0x1A: 416 | // Is there a disc? 417 | if (cdrom->cdrom_info.read_cb) 418 | { 419 | // Yes. 420 | push_response(&cdrom->int3, 421 | 20000, 422 | 1, 423 | cdrom->response_status); 424 | 425 | push_response(&cdrom->int2, 426 | 25000, 427 | 8, 428 | 0x02, 0x00, 0x20, 0x00, 429 | 'S', 'C', 'E', 'A'); 430 | 431 | cdrom->int3.next_interrupt = &cdrom->int2; 432 | cdrom->int2.next_interrupt = NULL; 433 | } 434 | else 435 | { 436 | // No. 437 | push_response(&cdrom->int3, 438 | 20000, 439 | 1, 440 | cdrom->response_status); 441 | 442 | push_response(&cdrom->int5, 443 | 20000, 444 | 8, 445 | 0x08, 0x40, 0x00, 0x00, 446 | 0x00, 0x00, 0x00, 0x00); 447 | 448 | cdrom->int3.next_interrupt = &cdrom->int5; 449 | cdrom->int5.next_interrupt = NULL; 450 | } 451 | cdrom->current_interrupt = &cdrom->int3; 452 | break; 453 | 454 | default: 455 | __debugbreak(); 456 | break; 457 | } 458 | libps_fifo_reset(&cdrom->parameter_fifo); 459 | break; 460 | 461 | default: 462 | __debugbreak(); 463 | break; 464 | } 465 | break; 466 | 467 | // 0x1F801802 468 | case 2: 469 | switch (cdrom->status.index) 470 | { 471 | // 1F801802h.Index0 - Parameter Fifo (W) 472 | case 0: 473 | libps_fifo_enqueue(&cdrom->parameter_fifo, data); 474 | break; 475 | 476 | // 1F801802h.Index1 - Interrupt Enable Register (W) 477 | case 1: 478 | cdrom->interrupt_enable = data; 479 | break; 480 | 481 | default: 482 | __debugbreak(); 483 | break; 484 | } 485 | break; 486 | 487 | // 0x1F801803 488 | case 3: 489 | switch (cdrom->status.index) 490 | { 491 | // 1F801803h.Index0 - Request Register (W) 492 | case 0: 493 | if (data & (1 << 7)) 494 | { 495 | libps_fifo_reset(&cdrom->data_fifo); 496 | 497 | for (unsigned int index = 0; 498 | index < cdrom->sector_size; 499 | ++index) 500 | { 501 | libps_fifo_enqueue(&cdrom->data_fifo, 502 | cdrom->sector_data[index]); 503 | } 504 | } 505 | else 506 | { 507 | libps_fifo_reset(&cdrom->data_fifo); 508 | } 509 | break; 510 | 511 | // 1F801803h.Index1 - Interrupt Flag Register (R/W) 512 | case 1: 513 | // Has an interrupt that we care about been acknowledged? 514 | if ((cdrom->current_interrupt != NULL) && 515 | ((data & 0x07) & cdrom->current_interrupt->type)) 516 | { 517 | // It has, send the next one, if any. 518 | if (cdrom->current_interrupt->next_interrupt == NULL) 519 | { 520 | reset_interrupt(cdrom->current_interrupt); 521 | cdrom->current_interrupt = NULL; 522 | } 523 | else 524 | { 525 | cdrom->current_interrupt = 526 | cdrom->current_interrupt->next_interrupt; 527 | } 528 | } 529 | 530 | cdrom->interrupt_flag &= data; 531 | break; 532 | 533 | default: 534 | __debugbreak(); 535 | break; 536 | } 537 | break; 538 | 539 | default: 540 | __debugbreak(); 541 | break; 542 | } 543 | } -------------------------------------------------------------------------------- /src/libps/gpu.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "cpu_defs.h" 20 | #include "gpu.h" 21 | #include "utility/memory.h" 22 | #include "renderer/sw.h" 23 | 24 | static void (*cmd_func)(struct libps_gpu*); 25 | static unsigned int params_pos; 26 | 27 | // Handles the GP0(A0h) command - Copy Rectangle (CPU to VRAM) 28 | static void copy_rect_from_cpu(struct libps_gpu* gpu) 29 | { 30 | assert(gpu != NULL); 31 | 32 | // Current X position 33 | static unsigned int vram_x_pos; 34 | 35 | // Current Y position 36 | static unsigned int vram_y_pos; 37 | 38 | // Maximum length of a line (should be Xxxx+Xsiz) 39 | static unsigned int vram_x_pos_max; 40 | 41 | if (gpu->state == LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS) 42 | { 43 | const uint16_t width = 44 | (((gpu->cmd_packet.params[1] & 0x0000FFFF) - 1) & 0x000003FF) + 1; 45 | 46 | const uint16_t height = 47 | (((gpu->cmd_packet.params[1] >> 16) - 1) & 0x000001FF) + 1; 48 | 49 | vram_x_pos = 50 | ((gpu->cmd_packet.params[0] & 0x0000FFFF) & 0x000003FF); 51 | 52 | vram_y_pos = 53 | ((gpu->cmd_packet.params[0] >> 16) & 0x000001FF); 54 | 55 | vram_x_pos_max = vram_x_pos + width; 56 | 57 | gpu->cmd_packet.remaining_words = (width * height) / 2; 58 | 59 | // Lock the GP0 state to this function. 60 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_DATA; 61 | 62 | // Again, we don't want to do anything until we receive at least one 63 | // data word. 64 | return; 65 | } 66 | 67 | if (gpu->state == LIBPS_GPU_RECEIVING_COMMAND_DATA) 68 | { 69 | if (gpu->cmd_packet.remaining_words != 0) 70 | { 71 | gpu->vram[vram_x_pos++ + (LIBPS_GPU_VRAM_WIDTH * vram_y_pos)] = 72 | gpu->received_data & 0x0000FFFF; 73 | 74 | if (vram_x_pos >= vram_x_pos_max) 75 | { 76 | vram_y_pos++; 77 | vram_x_pos = ((gpu->cmd_packet.params[0] & 0x0000FFFF) & 0x000003FF); 78 | } 79 | 80 | gpu->vram[vram_x_pos++ + (LIBPS_GPU_VRAM_WIDTH * vram_y_pos)] = 81 | gpu->received_data >> 16; 82 | 83 | if (vram_x_pos >= vram_x_pos_max) 84 | { 85 | vram_y_pos++; 86 | vram_x_pos = ((gpu->cmd_packet.params[0] & 0x0000FFFF) & 0x000003FF); 87 | } 88 | gpu->cmd_packet.remaining_words--; 89 | } 90 | else 91 | { 92 | // All of the expected data has been sent. Return to normal 93 | // operation. 94 | memset(&gpu->cmd_packet, 0, sizeof(gpu->cmd_packet)); 95 | params_pos = 0; 96 | 97 | gpu->state = LIBPS_GPU_AWAITING_COMMAND; 98 | } 99 | } 100 | } 101 | 102 | // Handles the GP0(C0h) command - Copy Rectangle (VRAM to CPU) 103 | static void copy_rect_to_cpu(struct libps_gpu* gpu) 104 | { 105 | assert(gpu != NULL); 106 | 107 | // Current X position 108 | static unsigned int vram_x_pos; 109 | 110 | // Current Y position 111 | static unsigned int vram_y_pos; 112 | 113 | // Maximum length of a line (should be Xxxx+Xsiz) 114 | static unsigned int vram_x_pos_max; 115 | 116 | if (gpu->state == LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS) 117 | { 118 | const uint16_t width = 119 | (((gpu->cmd_packet.params[1] & 0x0000FFFF) - 1) & 0x000003FF) + 1; 120 | 121 | const uint16_t height = 122 | (((gpu->cmd_packet.params[1] >> 16) - 1) & 0x000001FF) + 1; 123 | 124 | vram_x_pos = 125 | ((gpu->cmd_packet.params[0] & 0x0000FFFF) & 0x000003FF); 126 | 127 | vram_y_pos = 128 | ((gpu->cmd_packet.params[0] >> 16) & 0x000001FF); 129 | 130 | vram_x_pos_max = vram_x_pos + width; 131 | 132 | gpu->cmd_packet.remaining_words = (width * height) / 2; 133 | 134 | // Lock the GP0 state to this function. 135 | gpu->state = LIBPS_GPU_TRANSFERRING_DATA; 136 | 137 | return; 138 | } 139 | 140 | if (gpu->state == LIBPS_GPU_TRANSFERRING_DATA) 141 | { 142 | if (gpu->cmd_packet.remaining_words != 0) 143 | { 144 | const uint16_t pixel0 = 145 | gpu->vram[vram_x_pos++ + (LIBPS_GPU_VRAM_WIDTH * vram_y_pos)]; 146 | 147 | if (vram_x_pos >= vram_x_pos_max) 148 | { 149 | vram_y_pos++; 150 | vram_x_pos = ((gpu->cmd_packet.params[0] & 0x0000FFFF) & 0x000003FF); 151 | } 152 | 153 | const uint16_t pixel1 = 154 | gpu->vram[vram_x_pos++ + (LIBPS_GPU_VRAM_WIDTH * vram_y_pos)]; 155 | 156 | if (vram_x_pos >= vram_x_pos_max) 157 | { 158 | vram_y_pos++; 159 | vram_x_pos = ((gpu->cmd_packet.params[0] & 0x0000FFFF) & 0x000003FF); 160 | } 161 | 162 | gpu->gpuread = ((pixel1 << 16) | pixel0); 163 | gpu->cmd_packet.remaining_words--; 164 | } 165 | else 166 | { 167 | // All of the expected data has been sent. Return to normal 168 | // operation. 169 | memset(&gpu->cmd_packet, 0, sizeof(gpu->cmd_packet)); 170 | params_pos = 0; 171 | 172 | gpu->state = LIBPS_GPU_AWAITING_COMMAND; 173 | } 174 | } 175 | } 176 | 177 | static void fill_rect_in_vram(struct libps_gpu* gpu) 178 | { 179 | const unsigned int color = gpu->cmd_packet.params[0]; 180 | 181 | const unsigned int x_pos = gpu->cmd_packet.params[1] & 0x0000FFFF; 182 | const unsigned int y_pos = gpu->cmd_packet.params[1] >> 16; 183 | 184 | const unsigned int width = gpu->cmd_packet.params[2] & 0x0000FFFF; 185 | const unsigned int height = gpu->cmd_packet.params[2] >> 16; 186 | 187 | for (unsigned int x = x_pos; x != (x_pos + width); ++x) 188 | { 189 | for (unsigned int y = y_pos; y != (y_pos + height); ++y) 190 | { 191 | const unsigned int pixel_r = (color & 0x000000FF) / 8; 192 | const unsigned int pixel_g = ((color >> 8) & 0xFF) / 8; 193 | const unsigned int pixel_b = ((color >> 16) & 0xFF) / 8; 194 | 195 | gpu->vram[(x & 0x3FF) + (LIBPS_GPU_VRAM_WIDTH * (y & 0x1FF))] = 196 | (pixel_g << 5) | (pixel_b << 10) | pixel_r; 197 | } 198 | } 199 | 200 | memset(&gpu->cmd_packet, 0, sizeof(gpu->cmd_packet)); 201 | params_pos = 0; 202 | 203 | gpu->state = LIBPS_GPU_AWAITING_COMMAND; 204 | return; 205 | } 206 | 207 | // This function is called whenever we must render a polygon. It assembles 208 | // vertices primarily based on the following flags: 209 | // 210 | // * DRAW_FLAG_MONOCHROME 211 | // * DRAW_FLAG_TEXTURED 212 | // * DRAW_FLAG_SHADED 213 | // 214 | // It will call the renderer's `draw_polygon()` function once all of the 215 | // vertices have been assembled, and resets the parameter FIFO. 216 | static void draw_polygon_helper(struct libps_gpu* gpu) 217 | { 218 | assert(gpu != NULL); 219 | 220 | if (gpu->cmd_packet.flags & DRAW_FLAG_MONOCHROME) 221 | { 222 | const struct libps_gpu_vertex v0 = 223 | { 224 | .x = (int16_t)(gpu->cmd_packet.params[1] & 0x0000FFFF), 225 | .y = (int16_t)(gpu->cmd_packet.params[1] >> 16), 226 | .color = gpu->cmd_packet.params[0] 227 | }; 228 | 229 | const struct libps_gpu_vertex v1 = 230 | { 231 | .x = (int16_t)(gpu->cmd_packet.params[2] & 0x0000FFFF), 232 | .y = (int16_t)(gpu->cmd_packet.params[2] >> 16), 233 | .color = gpu->cmd_packet.params[0] 234 | }; 235 | 236 | const struct libps_gpu_vertex v2 = 237 | { 238 | .x = (int16_t)(gpu->cmd_packet.params[3] & 0x0000FFFF), 239 | .y = (int16_t)(gpu->cmd_packet.params[3] >> 16), 240 | .color = gpu->cmd_packet.params[0] 241 | }; 242 | 243 | gpu->draw_polygon(gpu, &v0, &v1, &v2); 244 | 245 | if (gpu->cmd_packet.flags & DRAW_FLAG_QUAD) 246 | { 247 | const struct libps_gpu_vertex v3 = 248 | { 249 | .x = (int16_t)(gpu->cmd_packet.params[4] & 0x0000FFFF), 250 | .y = (int16_t)(gpu->cmd_packet.params[4] >> 16), 251 | .color = gpu->cmd_packet.params[0] 252 | }; 253 | gpu->draw_polygon(gpu, &v1, &v2, &v3); 254 | } 255 | 256 | memset(&gpu->cmd_packet, 0, sizeof(gpu->cmd_packet)); 257 | params_pos = 0; 258 | 259 | gpu->state = LIBPS_GPU_AWAITING_COMMAND; 260 | return; 261 | } 262 | 263 | if (gpu->cmd_packet.flags & DRAW_FLAG_TEXTURED) 264 | { 265 | const struct libps_gpu_vertex v0 = 266 | { 267 | .x = (int16_t)(gpu->cmd_packet.params[1] & 0x0000FFFF), 268 | .y = (int16_t)(gpu->cmd_packet.params[1] >> 16), 269 | .palette = gpu->cmd_packet.params[2] >> 16, 270 | .texcoord = gpu->cmd_packet.params[2] & 0x0000FFFF, 271 | .color = gpu->cmd_packet.params[0] 272 | }; 273 | 274 | const struct libps_gpu_vertex v1 = 275 | { 276 | .x = (int16_t)(gpu->cmd_packet.params[3] & 0x0000FFFF), 277 | .y = (int16_t)(gpu->cmd_packet.params[3] >> 16), 278 | .palette = gpu->cmd_packet.params[2] >> 16, // Hack 279 | .texpage = gpu->cmd_packet.params[4] >> 16, 280 | .texcoord = gpu->cmd_packet.params[4] & 0x0000FFFF, 281 | .color = gpu->cmd_packet.params[0] 282 | }; 283 | 284 | const struct libps_gpu_vertex v2 = 285 | { 286 | .x = (int16_t)(gpu->cmd_packet.params[5] & 0x0000FFFF), 287 | .y = (int16_t)(gpu->cmd_packet.params[5] >> 16), 288 | .texpage = gpu->cmd_packet.params[4] >> 16, // Hack 289 | .texcoord = gpu->cmd_packet.params[6] & 0x0000FFFF, 290 | .color = gpu->cmd_packet.params[0] 291 | }; 292 | 293 | gpu->draw_polygon(gpu, &v0, &v1, &v2); 294 | 295 | if (gpu->cmd_packet.flags & DRAW_FLAG_QUAD) 296 | { 297 | const struct libps_gpu_vertex v3 = 298 | { 299 | .x = (int16_t)(gpu->cmd_packet.params[7] & 0x0000FFFF), 300 | .y = (int16_t)(gpu->cmd_packet.params[7] >> 16), 301 | .texcoord = gpu->cmd_packet.params[8] & 0x0000FFFF, 302 | .color = gpu->cmd_packet.params[0] 303 | }; 304 | gpu->draw_polygon(gpu, &v1, &v2, &v3); 305 | } 306 | 307 | memset(&gpu->cmd_packet, 0, sizeof(gpu->cmd_packet)); 308 | params_pos = 0; 309 | 310 | gpu->state = LIBPS_GPU_AWAITING_COMMAND; 311 | return; 312 | } 313 | 314 | if (gpu->cmd_packet.flags & DRAW_FLAG_SHADED) 315 | { 316 | const struct libps_gpu_vertex v0 = 317 | { 318 | .x = (int16_t)(gpu->cmd_packet.params[1] & 0x0000FFFF), 319 | .y = (int16_t)(gpu->cmd_packet.params[1] >> 16), 320 | .color = gpu->cmd_packet.params[0] & 0x00FFFFFF 321 | }; 322 | 323 | const struct libps_gpu_vertex v1 = 324 | { 325 | .x = (int16_t)(gpu->cmd_packet.params[3] & 0x0000FFFF), 326 | .y = (int16_t)(gpu->cmd_packet.params[3] >> 16), 327 | .color = gpu->cmd_packet.params[2] & 0x00FFFFFF 328 | }; 329 | 330 | const struct libps_gpu_vertex v2 = 331 | { 332 | .x = (int16_t)(gpu->cmd_packet.params[5] & 0x0000FFFF), 333 | .y = (int16_t)(gpu->cmd_packet.params[5] >> 16), 334 | .color = gpu->cmd_packet.params[4] & 0x00FFFFFF 335 | }; 336 | 337 | gpu->draw_polygon(gpu, &v0, &v1, &v2); 338 | 339 | if (gpu->cmd_packet.flags & DRAW_FLAG_QUAD) 340 | { 341 | const struct libps_gpu_vertex v3 = 342 | { 343 | .x = (int16_t)(gpu->cmd_packet.params[7] & 0x0000FFFF), 344 | .y = (int16_t)(gpu->cmd_packet.params[7] >> 16), 345 | .color = gpu->cmd_packet.params[6] & 0x00FFFFFF 346 | }; 347 | gpu->draw_polygon(gpu, &v1, &v2, &v3); 348 | } 349 | } 350 | 351 | memset(&gpu->cmd_packet, 0, sizeof(gpu->cmd_packet)); 352 | params_pos = 0; 353 | 354 | gpu->state = LIBPS_GPU_AWAITING_COMMAND; 355 | } 356 | 357 | static void draw_rect_helper(struct libps_gpu* gpu) 358 | { 359 | assert(gpu != NULL); 360 | 361 | const struct libps_gpu_vertex vertex = 362 | { 363 | .color = gpu->cmd_packet.params[0], 364 | .y = gpu->cmd_packet.params[1] >> 16, 365 | .x = gpu->cmd_packet.params[1] & 0x0000FFFF, 366 | .texcoord = gpu->cmd_packet.params[2] >> 16, 367 | .palette = gpu->cmd_packet.params[2] & 0x0000FFFF 368 | }; 369 | 370 | gpu->draw_rect(gpu, &vertex); 371 | 372 | memset(&gpu->cmd_packet, 0, sizeof(gpu->cmd_packet)); 373 | params_pos = 0; 374 | 375 | gpu->state = LIBPS_GPU_AWAITING_COMMAND; 376 | } 377 | 378 | // Initializes a GPU. 379 | void libps_gpu_setup(struct libps_gpu* gpu) 380 | { 381 | assert(gpu != NULL); 382 | 383 | gpu->draw_polygon = &libps_renderer_sw_draw_polygon; 384 | gpu->draw_rect = &libps_renderer_sw_draw_rect; 385 | 386 | gpu->vram = 387 | libps_safe_malloc(LIBPS_GPU_VRAM_WIDTH * 388 | LIBPS_GPU_VRAM_HEIGHT * 389 | sizeof(uint16_t)); 390 | } 391 | 392 | // Destroys the PlayStation GPU. 393 | void libps_gpu_cleanup(struct libps_gpu* gpu) 394 | { 395 | assert(gpu != NULL); 396 | libps_safe_free(gpu->vram); 397 | } 398 | 399 | // Resets the GPU to the initial state. 400 | void libps_gpu_reset(struct libps_gpu* gpu) 401 | { 402 | assert(gpu != NULL); 403 | 404 | gpu->gpustat = 0x14802000; 405 | gpu->gpuread = 0x00000000; 406 | 407 | gpu->drawing_offset_x = 0x00000000; 408 | gpu->drawing_offset_y = 0x00000000; 409 | 410 | gpu->received_data = 0x00000000; 411 | 412 | memset(&gpu->cmd_packet, 0, sizeof(gpu->cmd_packet)); 413 | memset(&gpu->drawing_area, 0, sizeof(gpu->drawing_area)); 414 | memset(gpu->vram, 0, (LIBPS_GPU_VRAM_WIDTH * LIBPS_GPU_VRAM_HEIGHT) * sizeof(uint16_t)); 415 | 416 | params_pos = 0; 417 | gpu->state = LIBPS_GPU_AWAITING_COMMAND; 418 | } 419 | 420 | // Processes a GP0 packet. 421 | void libps_gpu_process_gp0(struct libps_gpu* gpu, const uint32_t packet) 422 | { 423 | assert(gpu != NULL); 424 | 425 | switch (gpu->state) 426 | { 427 | case LIBPS_GPU_AWAITING_COMMAND: 428 | switch (packet >> 24) 429 | { 430 | // GP0(00h) - NOP(?) 431 | case 0x00: 432 | break; 433 | 434 | // GP0(01h) - Clear Cache 435 | case 0x01: 436 | break; 437 | 438 | // GP0(02h) - Fill Rectangle in VRAM 439 | case 0x02: 440 | gpu->cmd_packet.params[params_pos++] = 441 | packet & 0x00FFFFFF; 442 | 443 | gpu->cmd_packet.remaining_words = 2; 444 | 445 | gpu->cmd_packet.raw = packet; 446 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 447 | 448 | cmd_func = &fill_rect_in_vram; 449 | break; 450 | 451 | // GP0(28h) - Monochrome four-point polygon, opaque 452 | // 453 | // XXX: monochrome means "uses constant color" 454 | case 0x28: 455 | gpu->cmd_packet.params[params_pos++] = 456 | packet & 0x00FFFFFF; 457 | 458 | gpu->cmd_packet.remaining_words = 4; 459 | 460 | gpu->cmd_packet.flags |= DRAW_FLAG_MONOCHROME; 461 | gpu->cmd_packet.flags |= DRAW_FLAG_QUAD; 462 | gpu->cmd_packet.flags |= DRAW_FLAG_OPAQUE; 463 | 464 | gpu->cmd_packet.raw = packet; 465 | 466 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 467 | 468 | cmd_func = &draw_polygon_helper; 469 | break; 470 | 471 | // GP0(2Dh) - Textured four-point polygon, opaque, raw-texture 472 | case 0x2D: 473 | gpu->cmd_packet.params[params_pos++] = 474 | packet & 0x00FFFFFF; 475 | 476 | gpu->cmd_packet.remaining_words = 8; 477 | 478 | gpu->cmd_packet.flags |= DRAW_FLAG_TEXTURED; 479 | gpu->cmd_packet.flags |= DRAW_FLAG_QUAD; 480 | gpu->cmd_packet.flags |= DRAW_FLAG_OPAQUE; 481 | gpu->cmd_packet.flags |= DRAW_FLAG_RAW_TEXTURE; 482 | 483 | gpu->cmd_packet.raw = packet; 484 | 485 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 486 | 487 | cmd_func = &draw_polygon_helper; 488 | break; 489 | 490 | // GP0(2Ch) - Textured four-point polygon, opaque, texture-blending 491 | case 0x2C: 492 | gpu->cmd_packet.params[params_pos++] = 493 | packet & 0x00FFFFFF; 494 | 495 | gpu->cmd_packet.remaining_words = 8; 496 | 497 | gpu->cmd_packet.flags |= DRAW_FLAG_TEXTURED; 498 | gpu->cmd_packet.flags |= DRAW_FLAG_QUAD; 499 | gpu->cmd_packet.flags |= DRAW_FLAG_OPAQUE; 500 | gpu->cmd_packet.flags |= DRAW_FLAG_TEXTURE_BLENDING; 501 | 502 | gpu->cmd_packet.raw = packet; 503 | 504 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 505 | 506 | cmd_func = &draw_polygon_helper; 507 | break; 508 | 509 | // GP0(30h) - Shaded three-point polygon, opaque 510 | case 0x30: 511 | gpu->cmd_packet.params[params_pos++] = 512 | packet & 0x00FFFFFF; 513 | 514 | gpu->cmd_packet.remaining_words = 5; 515 | 516 | gpu->cmd_packet.flags |= DRAW_FLAG_SHADED; 517 | gpu->cmd_packet.flags |= DRAW_FLAG_OPAQUE; 518 | 519 | gpu->cmd_packet.raw = packet; 520 | 521 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 522 | 523 | cmd_func = &draw_polygon_helper; 524 | break; 525 | 526 | // GP0(38h) - Shaded four-point polygon, opaque 527 | case 0x38: 528 | gpu->cmd_packet.params[params_pos++] = 529 | packet & 0x00FFFFFF; 530 | 531 | gpu->cmd_packet.remaining_words = 7; 532 | 533 | gpu->cmd_packet.flags |= DRAW_FLAG_SHADED; 534 | gpu->cmd_packet.flags |= DRAW_FLAG_QUAD; 535 | gpu->cmd_packet.flags |= DRAW_FLAG_OPAQUE; 536 | 537 | gpu->cmd_packet.raw = packet; 538 | 539 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 540 | 541 | cmd_func = &draw_polygon_helper; 542 | break; 543 | 544 | // GP0(65h) - Textured Rectangle, variable size, opaque, 545 | // raw-texture 546 | case 0x65: 547 | gpu->cmd_packet.params[params_pos++] = 548 | packet & 0x00FFFFFF; 549 | 550 | gpu->cmd_packet.remaining_words = 3; 551 | 552 | gpu->cmd_packet.flags |= DRAW_FLAG_TEXTURED; 553 | gpu->cmd_packet.flags |= DRAW_FLAG_OPAQUE; 554 | gpu->cmd_packet.flags |= DRAW_FLAG_RAW_TEXTURE; 555 | gpu->cmd_packet.flags |= DRAW_FLAG_VARIABLE_SIZE; 556 | 557 | gpu->cmd_packet.raw = packet; 558 | 559 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 560 | 561 | cmd_func = &draw_rect_helper; 562 | break; 563 | 564 | // GP0(68h) - Monochrome Rectangle (1x1) (Dot) (opaque) 565 | case 0x68: 566 | gpu->cmd_packet.params[params_pos++] = 567 | packet & 0x00FFFFFF; 568 | 569 | gpu->cmd_packet.remaining_words = 1; 570 | 571 | gpu->cmd_packet.flags |= DRAW_FLAG_MONOCHROME; 572 | gpu->cmd_packet.flags |= DRAW_FLAG_OPAQUE; 573 | 574 | gpu->cmd_packet.raw = packet; 575 | 576 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 577 | 578 | cmd_func = &draw_rect_helper; 579 | break; 580 | 581 | // GP0(A0h) - Copy Rectangle (CPU to VRAM) 582 | case 0xA0: 583 | gpu->cmd_packet.remaining_words = 2; 584 | 585 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 586 | 587 | cmd_func = ©_rect_from_cpu; 588 | break; 589 | 590 | // GP0(C0h) - Copy Rectangle (VRAM to CPU) 591 | case 0xC0: 592 | gpu->cmd_packet.remaining_words = 2; 593 | 594 | gpu->state = LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS; 595 | 596 | gpu->cmd_packet.raw = packet; 597 | 598 | cmd_func = ©_rect_to_cpu; 599 | break; 600 | 601 | // GP0(E1h) - Draw Mode setting(aka "Texpage") 602 | case 0xE1: 603 | break; 604 | 605 | // GP0(E2h) - Texture Window setting 606 | case 0xE2: 607 | break; 608 | 609 | // GP0(E3h) - Set Drawing Area top left (X1, Y1) 610 | case 0xE3: 611 | gpu->drawing_area.x1 = packet & 0x000003FF; 612 | gpu->drawing_area.y1 = (packet >> 10) & 0x000001FF; 613 | 614 | break; 615 | 616 | // GP0(E4h) - Set Drawing Area bottom right (X2,Y2) 617 | case 0xE4: 618 | gpu->drawing_area.x2 = packet & 0x000003FF; 619 | gpu->drawing_area.y2 = (packet >> 10) & 0x000001FF; 620 | 621 | break; 622 | 623 | // GP0(E5h) - Set Drawing Offset (X,Y) 624 | case 0xE5: 625 | gpu->drawing_offset_x = packet & 0x000003FF; 626 | gpu->drawing_offset_y = (packet >> 10) & 0x000001FF; 627 | 628 | break; 629 | 630 | // GP0(E6h) - Mask Bit Setting 631 | case 0xE6: 632 | break; 633 | 634 | default: 635 | __debugbreak(); 636 | break; 637 | } 638 | break; 639 | 640 | case LIBPS_GPU_RECEIVING_COMMAND_PARAMETERS: 641 | gpu->cmd_packet.params[params_pos++] = packet; 642 | gpu->cmd_packet.remaining_words--; 643 | 644 | if (gpu->cmd_packet.remaining_words == 0) 645 | { 646 | cmd_func(gpu); 647 | } 648 | break; 649 | 650 | case LIBPS_GPU_RECEIVING_COMMAND_DATA: 651 | gpu->received_data = packet; 652 | cmd_func(gpu); 653 | 654 | break; 655 | 656 | // Used only by GP0(C0h) 657 | case LIBPS_GPU_TRANSFERRING_DATA: 658 | cmd_func(gpu); 659 | break; 660 | } 661 | } 662 | 663 | // Processes a GP1 packet. 664 | void libps_gpu_process_gp1(struct libps_gpu* gpu, const uint32_t packet) 665 | { 666 | assert(gpu != NULL); 667 | 668 | switch (packet >> 24) 669 | { 670 | // GP1(00h) - Reset GPU 671 | case 0x00: 672 | gpu->gpustat = 0x14802000; 673 | break; 674 | 675 | // GP1(01h) - Reset Command Buffer 676 | case 0x01: 677 | break; 678 | 679 | // GP1(02h) - Acknowledge GPU Interrupt (IRQ1) 680 | case 0x02: 681 | break; 682 | 683 | // GP1(03h) - Display Enable 684 | case 0x03: 685 | break; 686 | 687 | // GP1(04h) - DMA Direction / Data Request 688 | case 0x04: 689 | break; 690 | 691 | // GP1(05h) - Start of Display area (in VRAM) 692 | case 0x05: 693 | break; 694 | 695 | // GP1(06h) - Horizontal Display range (on Screen) 696 | case 0x06: 697 | break; 698 | 699 | // GP1(07h) - Vertical Display range (on Screen) 700 | case 0x07: 701 | break; 702 | 703 | // GP1(08h) - Display mode 704 | case 0x08: 705 | break; 706 | 707 | // GP1(10h) - Get GPU Info 708 | case 0x10: 709 | switch (packet & 0x00FFFFFF) 710 | { 711 | // Returns Nothing (old value in GPUREAD remains unchanged) 712 | case 0x07: 713 | gpu->gpuread = 2; 714 | break; 715 | 716 | default: 717 | __debugbreak(); 718 | break; 719 | } 720 | break; 721 | 722 | default: 723 | __debugbreak(); 724 | break; 725 | } 726 | } 727 | -------------------------------------------------------------------------------- /src/libps/cpu.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Michael Rodriguez 2 | // 3 | // Permission to use, copy, modify, and/or distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // A few key things to note here: 16 | // 17 | // * The PlayStation CPU is *not* a vanilla MIPS R3000, it is an LSI LR33300. 18 | // There are currently efforts being made to retrieve the manual for a 19 | // radiation hardened version of the LR33300 which should hopefully yield 20 | // some further insights. Though, I don't really think we're likely to find 21 | // too much out of the ordinary. 22 | // 23 | // * All PlayStation software runs entirely in kernel mode. Accordingly, we do 24 | // not support any user mode separation whatsoever. 25 | // 26 | // * There is no support for caches, or breakpoint registers (e.g. BDA). 27 | // 28 | // * The PlayStation is fixed to little endian, therefore there is no support 29 | // for changing to big endian or even supporting it whatsoever. 30 | // 31 | // * There is no MMU, therefore there is no support for TLB instructions and 32 | // all address translations are fixed. 33 | // 34 | // * No support for load delays. Undoubtedly will be required for games, but 35 | // apparently they don't seem to be required for the BIOS. 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include "bus.h" 42 | #include "cpu.h" 43 | #include "cpu_defs.h" 44 | #include "utility/memory.h" 45 | 46 | // `libps_cpu` doesn't need to know about this. 47 | static struct libps_bus* bus; 48 | 49 | // Used for calling `raise_exception()` when throwing an exception that is not 50 | // an address exception. 51 | #define UNUSED 0x00000000 52 | 53 | static bool in_delay_slot = false; 54 | 55 | // Throws exception `exccode`. 56 | static void raise_exception(struct libps_cpu* cpu, 57 | const unsigned int exccode, 58 | const uint32_t bad_vaddr) 59 | { 60 | assert(cpu != NULL); 61 | 62 | // So on an exception, the CPU: 63 | 64 | // 1) sets up EPC to point to the restart location. 65 | cpu->cop0_cpr[LIBPS_CPU_COP0_REG_EPC] = 66 | !in_delay_slot ? cpu->pc : cpu->pc - 4; 67 | 68 | // 2) The pre-existing user-mode and interrupt-enable flags in SR are saved 69 | // by pushing the 3 - entry stack inside SR, and changing to kernel mode 70 | // with interrupts disabled. 71 | cpu->cop0_cpr[LIBPS_CPU_COP0_REG_SR] = 72 | (cpu->cop0_cpr[LIBPS_CPU_COP0_REG_SR] & 0xFFFFFFC0) | 73 | ((cpu->cop0_cpr[LIBPS_CPU_COP0_REG_SR] & 0xF) << 2); 74 | 75 | // 3a) Cause is setup so that software can see the reason for the 76 | // exception. 77 | cpu->cop0_cpr[LIBPS_CPU_COP0_REG_CAUSE] = 78 | (cpu->cop0_cpr[LIBPS_CPU_COP0_REG_CAUSE] & ~0xFFFF00FF) | 79 | (exccode << 2); 80 | 81 | #ifdef LIBPS_DEBUG 82 | // 3b) On address exceptions BadVaddr is also set. 83 | if (exccode == LIBPS_CPU_EXCCODE_AdEL) 84 | { 85 | cpu->cop0_cpr[LIBPS_CPU_COP0_REG_BADVADDR] = bad_vaddr; 86 | } 87 | #endif // LIBPS_DEBUG 88 | 89 | // 4) Transfers control to the exception entry point. 90 | cpu->next_pc = 0x80000080; 91 | cpu->pc = 0x80000080 - 4; 92 | } 93 | 94 | // Sets the pointer to the system bus to `b`. 95 | void libps_cpu_set_bus(struct libps_bus* b) 96 | { 97 | bus = b; 98 | } 99 | 100 | // Triggers a reset exception, thereby initializing the CPU to the predefined 101 | // startup state. 102 | void libps_cpu_reset(struct libps_cpu* cpu) 103 | { 104 | assert(cpu != NULL); 105 | 106 | // The PlayStation BIOS does clear the general purpose registers early on, 107 | // but not early enough before it stores a bad word to 0x1F801060 and 108 | // 0x1F80100C if the registers are not set to zero upon reset. It wouldn't 109 | // affect anything as we don't handle what those memory addresses represent 110 | // ("RAM Size" and "Expansion 3 Delay/Size" respectively) but of course, we 111 | // should still clear these anyway. 112 | memset(cpu->gpr, 0, sizeof(cpu->gpr)); 113 | memset(cpu->cop0_cpr, 0, sizeof(cpu->cop0_cpr)); 114 | 115 | cpu->pc = 0xBFC00000; 116 | cpu->next_pc = 0xBFC00000; 117 | 118 | cpu->instruction = libps_bus_load_word(bus, cpu->pc); 119 | } 120 | 121 | // Executes one instruction. 122 | void libps_cpu_step(struct libps_cpu* cpu) 123 | { 124 | assert(cpu != NULL); 125 | 126 | if (cpu->cop0_cpr[LIBPS_CPU_COP0_REG_CAUSE] & (1 << 10) && 127 | (cpu->cop0_cpr[LIBPS_CPU_COP0_REG_SR] & (1 << 10)) && 128 | (cpu->cop0_cpr[LIBPS_CPU_COP0_REG_SR] & 1)) 129 | { 130 | raise_exception(cpu, LIBPS_CPU_EXCCODE_Int, UNUSED); 131 | 132 | cpu->instruction = libps_bus_load_word(bus, cpu->pc += 4); 133 | return; 134 | } 135 | 136 | cpu->pc = cpu->next_pc; 137 | cpu->next_pc += 4; 138 | 139 | in_delay_slot = false; 140 | 141 | switch (LIBPS_CPU_DECODE_OP(cpu->instruction)) 142 | { 143 | case LIBPS_CPU_OP_GROUP_SPECIAL: 144 | switch (LIBPS_CPU_DECODE_FUNCT(cpu->instruction)) 145 | { 146 | case LIBPS_CPU_OP_SLL: 147 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 148 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] << 149 | LIBPS_CPU_DECODE_SHAMT(cpu->instruction); 150 | 151 | break; 152 | 153 | case LIBPS_CPU_OP_SRL: 154 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 155 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] >> 156 | LIBPS_CPU_DECODE_SHAMT(cpu->instruction); 157 | 158 | break; 159 | 160 | case LIBPS_CPU_OP_SRA: 161 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 162 | (int32_t)cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] >> 163 | LIBPS_CPU_DECODE_SHAMT(cpu->instruction); 164 | 165 | break; 166 | 167 | case LIBPS_CPU_OP_SLLV: 168 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 169 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] << 170 | (cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] & 171 | 0x0000001F); 172 | 173 | break; 174 | 175 | case LIBPS_CPU_OP_SRLV: 176 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 177 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] >> 178 | (cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] & 179 | 0x0000001F); 180 | 181 | break; 182 | 183 | case LIBPS_CPU_OP_SRAV: 184 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 185 | (int32_t)cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] >> 186 | (cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] & 187 | 0x0000001F); 188 | 189 | break; 190 | 191 | case LIBPS_CPU_OP_JR: 192 | { 193 | const uint32_t target = 194 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] - 4; 195 | #ifdef LIBPS_DEBUG 196 | if ((target & 0x00000003) != 0) 197 | { 198 | raise_exception(cpu, LIBPS_CPU_EXCCODE_AdEL, target); 199 | break; 200 | } 201 | #endif // LIBPS_DEBUG 202 | cpu->next_pc = target; 203 | in_delay_slot = true; 204 | 205 | break; 206 | } 207 | 208 | case LIBPS_CPU_OP_JALR: 209 | { 210 | const uint32_t target = 211 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] - 4; 212 | 213 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 214 | cpu->pc + 8; 215 | #ifdef LIBPS_DEBUG 216 | if ((target & 0x00000003) != 0) 217 | { 218 | raise_exception(cpu, LIBPS_CPU_EXCCODE_AdEL, target); 219 | break; 220 | } 221 | #endif // LIBPS_DEBUG 222 | cpu->next_pc = target; 223 | in_delay_slot = true; 224 | 225 | break; 226 | } 227 | 228 | case LIBPS_CPU_OP_SYSCALL: 229 | raise_exception(cpu, LIBPS_CPU_EXCCODE_Sys, UNUSED); 230 | break; 231 | 232 | #ifdef LIBPS_DEBUG 233 | case LIBPS_CPU_OP_BREAK: 234 | raise_exception(cpu, LIBPS_CPU_EXCCODE_Bp, UNUSED); 235 | break; 236 | #endif // LIBPS_DEBUG 237 | 238 | case LIBPS_CPU_OP_MFHI: 239 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 240 | cpu->reg_hi; 241 | 242 | break; 243 | 244 | case LIBPS_CPU_OP_MTHI: 245 | cpu->reg_hi = 246 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)]; 247 | 248 | break; 249 | 250 | case LIBPS_CPU_OP_MFLO: 251 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 252 | cpu->reg_lo; 253 | 254 | break; 255 | 256 | case LIBPS_CPU_OP_MTLO: 257 | cpu->reg_lo = 258 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)]; 259 | 260 | break; 261 | 262 | case LIBPS_CPU_OP_MULT: 263 | { 264 | const uint64_t result = 265 | (int64_t)(int32_t)cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] * 266 | (int64_t)(int32_t)cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 267 | 268 | cpu->reg_lo = result & 0x00000000FFFFFFFF; 269 | cpu->reg_hi = result >> 32; 270 | 271 | break; 272 | } 273 | 274 | case LIBPS_CPU_OP_MULTU: 275 | { 276 | const uint64_t result = 277 | (uint64_t)cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] * 278 | (uint64_t)cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 279 | 280 | cpu->reg_lo = result & 0x00000000FFFFFFFF; 281 | cpu->reg_hi = result >> 32; 282 | 283 | break; 284 | } 285 | 286 | case LIBPS_CPU_OP_DIV: 287 | { 288 | // The result of a division by zero is consistent with the 289 | // result of a simple radix-2 ("one bit at a time") 290 | // implementation. 291 | const int32_t rt = 292 | (int32_t)cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 293 | 294 | const int32_t rs = 295 | (int32_t)cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)]; 296 | 297 | #ifdef LIBPS_DEBUG 298 | // Divisor is zero 299 | if (rt == 0) 300 | { 301 | // If the dividend is negative, the quotient is 1 302 | // (0x00000001), and if the dividend is positive or 303 | // zero, the quotient is -1 (0xFFFFFFFF). 304 | cpu->reg_lo = (rs < 0) ? 0x00000001 : 0xFFFFFFFF; 305 | 306 | // In both cases the remainder equals the dividend. 307 | cpu->reg_hi = (uint32_t)rs; 308 | } 309 | // Will trigger an arithmetic exception when dividing 310 | // 0x80000000 by 0xFFFFFFFF. The result of the division is 311 | // a quotient of 0x80000000 and a remainder of 0x00000000. 312 | else if ((uint32_t)rs == 0x80000000 && 313 | (uint32_t)rt == 0xFFFFFFFF) 314 | { 315 | cpu->reg_lo = (uint32_t)rs; 316 | cpu->reg_hi = 0x00000000; 317 | } 318 | else 319 | { 320 | cpu->reg_lo = rs / rt; 321 | cpu->reg_hi = rs % rt; 322 | } 323 | #else 324 | cpu->reg_lo = rs / rt; 325 | cpu->reg_hi = rs % rt; 326 | #endif // LIBPS_DEBUG 327 | break; 328 | } 329 | 330 | case LIBPS_CPU_OP_DIVU: 331 | { 332 | const uint32_t rt = 333 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 334 | 335 | const uint32_t rs = 336 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)]; 337 | #ifdef LIBPS_DEBUG 338 | // In the case of unsigned division, the dividend can't be 339 | // negative and thus the quotient is always -1 (0xFFFFFFFF) 340 | // and the remainder equals the dividend. 341 | if (rt == 0) 342 | { 343 | cpu->reg_lo = 0xFFFFFFFF; 344 | cpu->reg_hi = rs; 345 | } 346 | else 347 | { 348 | cpu->reg_lo = rs / rt; 349 | cpu->reg_hi = rs % rt; 350 | } 351 | #else 352 | cpu->reg_lo = rs / rt; 353 | cpu->reg_hi = rs % rt; 354 | #endif // LIBPS_DEBUG 355 | break; 356 | } 357 | 358 | case LIBPS_CPU_OP_ADD: 359 | #ifdef LIBPS_DEBUG 360 | { 361 | const uint32_t rs = 362 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)]; 363 | 364 | const uint32_t rt = 365 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 366 | 367 | const uint32_t result = rs + rt; 368 | 369 | if (!((rs ^ rt) & 0x80000000) && 370 | ((result ^ rs) & 0x80000000)) 371 | { 372 | raise_exception(cpu, LIBPS_CPU_EXCCODE_Ov, UNUSED); 373 | break; 374 | } 375 | 376 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = result; 377 | break; 378 | } 379 | #endif // LIBPS_DEBUG 380 | 381 | case LIBPS_CPU_OP_ADDU: 382 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 383 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] + 384 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 385 | 386 | break; 387 | 388 | case LIBPS_CPU_OP_SUB: 389 | #ifdef LIBPS_DEBUG 390 | { 391 | const uint32_t rs = cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)]; 392 | const uint32_t rt = cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 393 | 394 | const uint32_t result = rs - rt; 395 | 396 | if (((rs ^ rt) & 0x80000000) && ((result ^ rs) & 0x80000000)) 397 | { 398 | raise_exception(cpu, LIBPS_CPU_EXCCODE_Ov, UNUSED); 399 | break; 400 | } 401 | 402 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = result; 403 | break; 404 | } 405 | #endif // LIBPS_DEBUG 406 | 407 | case LIBPS_CPU_OP_SUBU: 408 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 409 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] - 410 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 411 | 412 | break; 413 | 414 | case LIBPS_CPU_OP_AND: 415 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 416 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] & 417 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 418 | 419 | break; 420 | 421 | case LIBPS_CPU_OP_OR: 422 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 423 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] | 424 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 425 | 426 | break; 427 | 428 | case LIBPS_CPU_OP_XOR: 429 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 430 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] ^ 431 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 432 | 433 | break; 434 | 435 | case LIBPS_CPU_OP_NOR: 436 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 437 | ~(cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] | 438 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]); 439 | 440 | break; 441 | 442 | case LIBPS_CPU_OP_SLT: 443 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 444 | (int32_t)cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] < 445 | (int32_t)cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 446 | 447 | break; 448 | 449 | case LIBPS_CPU_OP_SLTU: 450 | cpu->gpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 451 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] < 452 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 453 | 454 | break; 455 | 456 | #ifdef LIBPS_DEBUG 457 | default: 458 | raise_exception(cpu, LIBPS_CPU_EXCCODE_RI, UNUSED); 459 | break; 460 | #endif // LIBPS_DEBUG 461 | } 462 | break; 463 | 464 | case LIBPS_CPU_OP_GROUP_BCOND: 465 | { 466 | const unsigned int op = LIBPS_CPU_DECODE_RT(cpu->instruction); 467 | 468 | const bool should_link = (op & 0x1E) == 0x10; 469 | 470 | const bool should_branch = 471 | (int32_t)(cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] ^ (op << 31)) < 0; 472 | 473 | if (should_link) cpu->gpr[31] = cpu->pc + 8; 474 | 475 | if (should_branch) 476 | { 477 | cpu->next_pc = 478 | (uint32_t)(int16_t)(LIBPS_CPU_DECODE_OFFSET(cpu->instruction) << 2) + 479 | cpu->pc; 480 | in_delay_slot = true; 481 | } 482 | break; 483 | } 484 | 485 | case LIBPS_CPU_OP_J: 486 | cpu->next_pc = ((LIBPS_CPU_DECODE_TARGET(cpu->instruction) << 2) | 487 | (cpu->pc & 0xF0000000)) - 4; 488 | in_delay_slot = true; 489 | break; 490 | 491 | case LIBPS_CPU_OP_JAL: 492 | cpu->gpr[31] = cpu->pc + 8; 493 | 494 | cpu->next_pc = ((LIBPS_CPU_DECODE_TARGET(cpu->instruction) << 2) | 495 | (cpu->pc & 0xF0000000)) - 4; 496 | in_delay_slot = true; 497 | break; 498 | 499 | case LIBPS_CPU_OP_BEQ: 500 | if (cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] == 501 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]) 502 | { 503 | cpu->next_pc = 504 | (int16_t)(LIBPS_CPU_DECODE_OFFSET(cpu->instruction) << 2) + 505 | cpu->pc; 506 | in_delay_slot = true; 507 | } 508 | break; 509 | 510 | case LIBPS_CPU_OP_BNE: 511 | if (cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] != 512 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]) 513 | { 514 | cpu->next_pc = 515 | (int16_t)(LIBPS_CPU_DECODE_OFFSET(cpu->instruction) << 2) + 516 | cpu->pc; 517 | in_delay_slot = true; 518 | } 519 | break; 520 | 521 | case LIBPS_CPU_OP_BLEZ: 522 | if ((int32_t)cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] <= 0) 523 | { 524 | cpu->next_pc = 525 | (int16_t)(LIBPS_CPU_DECODE_OFFSET(cpu->instruction) << 2) + 526 | cpu->pc; 527 | in_delay_slot = true; 528 | } 529 | break; 530 | 531 | case LIBPS_CPU_OP_BGTZ: 532 | if ((int32_t)cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] > 0) 533 | { 534 | cpu->next_pc = 535 | (int16_t)(LIBPS_CPU_DECODE_OFFSET(cpu->instruction) << 2) + 536 | cpu->pc; 537 | in_delay_slot = true; 538 | } 539 | break; 540 | 541 | case LIBPS_CPU_OP_ADDI: 542 | #ifdef LIBPS_DEBUG 543 | { 544 | const uint32_t imm = (uint32_t)(int16_t)LIBPS_CPU_DECODE_IMMEDIATE(cpu->instruction); 545 | const uint32_t rs = cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)]; 546 | 547 | const uint32_t result = imm + rs; 548 | 549 | if (!((rs ^ imm) & 0x80000000) && ((result ^ rs) & 0x80000000)) 550 | { 551 | raise_exception(cpu, LIBPS_CPU_EXCCODE_Ov, UNUSED); 552 | break; 553 | } 554 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = result; 555 | break; 556 | } 557 | #endif // LIBPS_DEBUG 558 | 559 | case LIBPS_CPU_OP_ADDIU: 560 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = 561 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] + 562 | (int16_t)LIBPS_CPU_DECODE_IMMEDIATE(cpu->instruction); 563 | 564 | break; 565 | 566 | case LIBPS_CPU_OP_SLTI: 567 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = 568 | (int32_t)cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] < 569 | (int16_t)LIBPS_CPU_DECODE_IMMEDIATE(cpu->instruction); 570 | 571 | break; 572 | 573 | case LIBPS_CPU_OP_SLTIU: 574 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = 575 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] < 576 | (uint32_t)(int16_t)LIBPS_CPU_DECODE_IMMEDIATE(cpu->instruction); 577 | 578 | break; 579 | 580 | case LIBPS_CPU_OP_ANDI: 581 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = 582 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] & 583 | LIBPS_CPU_DECODE_IMMEDIATE(cpu->instruction); 584 | 585 | break; 586 | 587 | case LIBPS_CPU_OP_ORI: 588 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = 589 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] | 590 | LIBPS_CPU_DECODE_IMMEDIATE(cpu->instruction); 591 | 592 | break; 593 | 594 | case LIBPS_CPU_OP_XORI: 595 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = 596 | cpu->gpr[LIBPS_CPU_DECODE_RS(cpu->instruction)] ^ 597 | LIBPS_CPU_DECODE_IMMEDIATE(cpu->instruction); 598 | 599 | break; 600 | 601 | case LIBPS_CPU_OP_LUI: 602 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = 603 | LIBPS_CPU_DECODE_IMMEDIATE(cpu->instruction) << 16; 604 | 605 | break; 606 | 607 | case LIBPS_CPU_OP_GROUP_COP0: 608 | switch (LIBPS_CPU_DECODE_RS(cpu->instruction)) 609 | { 610 | case LIBPS_CPU_OP_MF: 611 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = 612 | cpu->cop0_cpr[LIBPS_CPU_DECODE_RD(cpu->instruction)]; 613 | 614 | break; 615 | 616 | case LIBPS_CPU_OP_MT: 617 | cpu->cop0_cpr[LIBPS_CPU_DECODE_RD(cpu->instruction)] = 618 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]; 619 | 620 | break; 621 | 622 | default: 623 | switch (LIBPS_CPU_DECODE_FUNCT(cpu->instruction)) 624 | { 625 | case LIBPS_CPU_OP_RFE: 626 | cpu->cop0_cpr[LIBPS_CPU_COP0_REG_SR] = 627 | (cpu->cop0_cpr[LIBPS_CPU_COP0_REG_SR] & 0xFFFFFFF0) | 628 | ((cpu->cop0_cpr[LIBPS_CPU_COP0_REG_SR] & 0x3C) >> 2); 629 | 630 | break; 631 | 632 | #ifdef LIBPS_DEBUG 633 | default: 634 | raise_exception(cpu, LIBPS_CPU_EXCCODE_RI, UNUSED); 635 | break; 636 | #endif // LIBPS_DEBUG 637 | } 638 | break; 639 | } 640 | break; 641 | 642 | case LIBPS_CPU_OP_GROUP_COP2: 643 | break; 644 | 645 | case LIBPS_CPU_OP_LB: 646 | { 647 | const uint32_t vaddr = 648 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 649 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 650 | 651 | const int8_t data = (int8_t)libps_bus_load_byte(bus, vaddr); 652 | 653 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = data; 654 | break; 655 | } 656 | 657 | case LIBPS_CPU_OP_LH: 658 | { 659 | const uint32_t vaddr = 660 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 661 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 662 | #ifdef LIBPS_DEBUG 663 | if ((vaddr & 1) != 0) 664 | { 665 | raise_exception(cpu, LIBPS_CPU_EXCCODE_AdEL, vaddr); 666 | break; 667 | } 668 | #endif // LIBPS_DEBUG 669 | const int16_t data = (int16_t)libps_bus_load_halfword(bus, vaddr); 670 | 671 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = data; 672 | break; 673 | } 674 | 675 | case LIBPS_CPU_OP_LWL: 676 | { 677 | const uint32_t vaddr = 678 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 679 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 680 | 681 | const uint32_t data = libps_bus_load_word(bus, vaddr & 0xFFFFFFFC); 682 | 683 | const unsigned int rt = LIBPS_CPU_DECODE_RT(cpu->instruction); 684 | 685 | switch (vaddr & 3) 686 | { 687 | case 0: 688 | cpu->gpr[rt] = (cpu->gpr[rt] & 0x00FFFFFF) | (data << 24); 689 | break; 690 | 691 | case 1: 692 | cpu->gpr[rt] = (cpu->gpr[rt] & 0x0000FFFF) | (data << 16); 693 | break; 694 | 695 | case 2: 696 | cpu->gpr[rt] = (cpu->gpr[rt] & 0x000000FF) | (data << 8); 697 | break; 698 | 699 | case 3: 700 | cpu->gpr[rt] = (cpu->gpr[rt] & 0x00000000) | (data << 0); 701 | break; 702 | } 703 | break; 704 | } 705 | 706 | // WARNING: At BIOS address `0x80059CA0`, there is an instruction that 707 | // loads GPUSTAT to $zero for no clear reason, presumably a write to 708 | // $zero is just a weird way to perform a `nop`. 709 | case LIBPS_CPU_OP_LW: 710 | { 711 | const uint32_t vaddr = 712 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 713 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 714 | 715 | #ifdef LIBPS_DEBUG 716 | if ((vaddr & 0x00000003) != 0) 717 | { 718 | raise_exception(cpu, LIBPS_CPU_EXCCODE_AdEL, vaddr); 719 | break; 720 | } 721 | #endif // LIBPS_DEBUG 722 | 723 | const uint32_t data = libps_bus_load_word(bus, vaddr); 724 | 725 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = data; 726 | break; 727 | } 728 | 729 | case LIBPS_CPU_OP_LBU: 730 | { 731 | const uint32_t vaddr = 732 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 733 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 734 | 735 | const uint8_t data = libps_bus_load_byte(bus, vaddr); 736 | 737 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = data; 738 | break; 739 | } 740 | 741 | case LIBPS_CPU_OP_LHU: 742 | { 743 | const uint32_t vaddr = 744 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 745 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 746 | #ifdef LIBPS_DEBUG 747 | if ((vaddr & 1) != 0) 748 | { 749 | raise_exception(cpu, LIBPS_CPU_EXCCODE_AdEL, vaddr); 750 | break; 751 | } 752 | #endif // LIBPS_DEBUG 753 | 754 | const uint16_t data = libps_bus_load_halfword(bus, vaddr); 755 | 756 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] = data; 757 | break; 758 | } 759 | 760 | case LIBPS_CPU_OP_LWR: 761 | { 762 | const uint32_t vaddr = 763 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 764 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 765 | 766 | const uint32_t data = libps_bus_load_word(bus, vaddr & 0xFFFFFFFC); 767 | 768 | const unsigned int rt = LIBPS_CPU_DECODE_RT(cpu->instruction); 769 | 770 | switch (vaddr & 3) 771 | { 772 | case 0: 773 | cpu->gpr[rt] = data; 774 | break; 775 | 776 | case 1: 777 | cpu->gpr[rt] = (cpu->gpr[rt] & 0xFF000000) | (data >> 8); 778 | break; 779 | 780 | case 2: 781 | cpu->gpr[rt] = (cpu->gpr[rt] & 0xFFFF0000) | (data >> 16); 782 | break; 783 | 784 | case 3: 785 | cpu->gpr[rt] = (cpu->gpr[rt] & 0xFFFFFF00) | (data >> 24); 786 | break; 787 | } 788 | break; 789 | } 790 | 791 | case LIBPS_CPU_OP_SB: 792 | { 793 | const uint32_t vaddr = 794 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 795 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 796 | 797 | libps_bus_store_byte(bus, 798 | vaddr, 799 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] & 0x000000FF); 800 | break; 801 | } 802 | 803 | case LIBPS_CPU_OP_SH: 804 | { 805 | const uint32_t vaddr = 806 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 807 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 808 | 809 | #ifdef LIBPS_DEBUG 810 | if ((vaddr & 1) != 0) 811 | { 812 | raise_exception(cpu, LIBPS_CPU_EXCCODE_AdES, vaddr); 813 | break; 814 | } 815 | #endif // LIBPS_DEBUG 816 | 817 | libps_bus_store_halfword(bus, 818 | vaddr, 819 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)] & 0x0000FFFF); 820 | break; 821 | } 822 | 823 | case LIBPS_CPU_OP_SWL: 824 | { 825 | const uint32_t vaddr = 826 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 827 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 828 | 829 | const unsigned int rt = LIBPS_CPU_DECODE_RT(cpu->instruction); 830 | 831 | uint32_t data = libps_bus_load_word(bus, vaddr & 0xFFFFFFFC); 832 | 833 | switch (vaddr & 3) 834 | { 835 | case 0: 836 | data = (data & 0xFFFFFF00) | (cpu->gpr[rt] >> 24); 837 | break; 838 | 839 | case 1: 840 | data = (data & 0xFFFF0000) | (cpu->gpr[rt] >> 16); 841 | break; 842 | 843 | case 2: 844 | data = (data & 0xFF000000) | (cpu->gpr[rt] >> 8); 845 | 846 | break; 847 | 848 | case 3: 849 | data = (data & 0x00000000) | (cpu->gpr[rt] >> 0); 850 | break; 851 | } 852 | 853 | libps_bus_store_word(bus, vaddr & 0xFFFFFFFC, data); 854 | break; 855 | } 856 | 857 | case LIBPS_CPU_OP_SW: 858 | { 859 | if (!(cpu->cop0_cpr[LIBPS_CPU_COP0_REG_SR] & LIBPS_CPU_SR_IsC)) 860 | { 861 | const uint32_t vaddr = 862 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 863 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 864 | 865 | #ifdef LIBPS_DEBUG 866 | if ((vaddr & 0x00000003) != 0) 867 | { 868 | raise_exception(cpu, LIBPS_CPU_EXCCODE_AdES, vaddr); 869 | break; 870 | } 871 | #endif // LIBPS_DEBUG 872 | 873 | libps_bus_store_word(bus, 874 | vaddr, 875 | cpu->gpr[LIBPS_CPU_DECODE_RT(cpu->instruction)]); 876 | } 877 | break; 878 | } 879 | 880 | case LIBPS_CPU_OP_SWR: 881 | { 882 | const uint32_t vaddr = 883 | (int16_t)LIBPS_CPU_DECODE_OFFSET(cpu->instruction) + 884 | cpu->gpr[LIBPS_CPU_DECODE_BASE(cpu->instruction)]; 885 | 886 | const unsigned int rt = LIBPS_CPU_DECODE_RT(cpu->instruction); 887 | 888 | uint32_t data = libps_bus_load_word(bus, vaddr & 0xFFFFFFFC); 889 | 890 | switch (vaddr & 3) 891 | { 892 | case 0: 893 | data = (data & 0x00000000) | (cpu->gpr[rt] << 0); 894 | break; 895 | 896 | case 1: 897 | data = (data & 0x000000FF) | (cpu->gpr[rt] << 8); 898 | break; 899 | 900 | case 2: 901 | data = (data & 0x0000FFFF) | (cpu->gpr[rt] << 16); 902 | break; 903 | 904 | case 3: 905 | data = (data & 0x00FFFFFF) | (cpu->gpr[rt] << 24); 906 | break; 907 | } 908 | 909 | libps_bus_store_word(bus, vaddr & 0xFFFFFFFC, data); 910 | break; 911 | } 912 | 913 | case LIBPS_CPU_OP_LWC2: 914 | break; 915 | 916 | case LIBPS_CPU_OP_SWC2: 917 | break; 918 | 919 | #ifdef LIBPS_DEBUG 920 | default: 921 | raise_exception(cpu, LIBPS_CPU_EXCCODE_RI, UNUSED); 922 | break; 923 | #endif // LIBPS_DEBUG 924 | } 925 | 926 | cpu->instruction = libps_bus_load_word(bus, cpu->pc += 4); 927 | cpu->gpr[0] = 0x00000000; 928 | } 929 | --------------------------------------------------------------------------------