diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..79b8023985 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +# Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Users\xtvas\Repositories\jak-project codebase based on best match to current usage at 2020-08-28 +# You can modify the rules from these initially generated values to suit your own policies +# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +[*.cs] + diff --git a/.github/workflows/linux-workflow.yaml b/.github/workflows/linux-workflow.yaml index c72db11797..fbb17ac238 100644 --- a/.github/workflows/linux-workflow.yaml +++ b/.github/workflows/linux-workflow.yaml @@ -1,5 +1,5 @@ name: Linux Workflow -on: [push] +on: [push, pull_request] jobs: build: @@ -20,6 +20,7 @@ jobs: make -j - name: Test Project with gTest run: ./test.sh + timeout-minutes: 5 - name: Check Clang-Formatting run: | chmod +x ./third-party/run-clang-format/run-clang-format.py diff --git a/.gitignore b/.gitignore index 9a5df39548..e542be2281 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ # for clion cmake-build-debug/* .idea/* -build/* +build/* \ No newline at end of file diff --git a/.vs/.gitignore b/.vs/.gitignore new file mode 100644 index 0000000000..fdc7c3f305 --- /dev/null +++ b/.vs/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!launch.vs.json diff --git a/.vs/launch.vs.json b/.vs/launch.vs.json new file mode 100644 index 0000000000..7201456d92 --- /dev/null +++ b/.vs/launch.vs.json @@ -0,0 +1,45 @@ +{ + // https://docs.microsoft.com/en-us/cpp/build/launch-vs-schema-reference-cpp?view=vs-2019 + "version": "0.2.1", + "defaults": {}, + "configurations": [ + { + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", + "name": "Run Tests - Summary", + "args": ["--gtest_brief=1"], + "env": { + "NEXT_DIR": "${projectDir}" + } + }, + { + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", + "name": "Run Tests - Verbose", + "args": ["--gtest_brief=0"], + "env": { + "NEXT_DIR": "${projectDir}" + } + }, + { + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "gk.exe (bin\\gk.exe)", + "name": "Run Game" + }, + { + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc.exe (bin\\goalc.exe)", + "name": "Build Compiler" + }, + { + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "decompiler.exe (bin\\decompiler.exe)", + "name": "Build Decompiler" + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 45706cfa7f..7229990ef2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,11 +4,34 @@ project(jak) set(CMAKE_CXX_STANDARD 14) +# Set default compile flags for GCC # optimization level can be set here. Note that game/ overwrites this for building game C++ code. -set(CMAKE_CXX_FLAGS "-O0 -ggdb -Wall \ --Wextra -Wcast-align -Wcast-qual -Wdisabled-optimization -Wformat=2 \ --Winit-self -Wmissing-include-dirs -Woverloaded-virtual \ --Wredundant-decls -Wshadow -Wsign-promo ") +if(CMAKE_COMPILER_IS_GNUCXX) + message(STATUS "GCC detected, adding compile flags") + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} \ + -Wall \ + -Winit-self \ + -Wextra \ + -Wcast-align \ + -Wcast-qual \ + -Wdisabled-optimization \ + -Wformat=2 \ + -Wmissing-include-dirs \ + -Woverloaded-virtual \ + -Wredundant-decls \ + -Wshadow \ + -Wsign-promo") +else() + set(CMAKE_CXX_FLAGS "/EHsc") +endif(CMAKE_COMPILER_IS_GNUCXX) + +IF (WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +ENDIF() # includes relative to top level jak-project folder include_directories(./) @@ -38,4 +61,9 @@ add_subdirectory(test) add_subdirectory(third-party/minilzo) # build format library -add_subdirectory(third-party/fmt) \ No newline at end of file +add_subdirectory(third-party/fmt) + +# windows memory management lib +IF (WIN32) + add_subdirectory(third-party/mman) +ENDIF() diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000000..298f497edd --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "variables": [ + { + "name": "INSTALL_GTEST", + "value": "True", + "type": "BOOL" + } + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 4e4134de7a..a29e68e62b 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,96 @@ -Build Status --------------- +# Jak Project ![Linux Workflow](https://github.com/water111/jak-project/workflows/Linux%20Workflow/badge.svg?branch=master) -Project Structure ----------------------- -Requirements: +## Table of Contents + + + +- [Jak Project](#jak-project) + - [Table of Contents](#table-of-contents) + - [Requirements](#requirements) + - [Getting Started - Linux (Ubuntu)](#getting-started---linux-ubuntu) + - [Getting Started - Windows](#getting-started---windows) + - [Project Layout](#project-layout) + - [Design](#design) + - [Current State](#current-state) + - [Coding Guidelines](#coding-guidelines) + - [TODOs](#todos) + - [Project Description](#project-description) + - [GOAL Decompiler](#goal-decompiler) + - [GOAL Runtime](#goal-runtime) + - [GOAL Compiler](#goal-compiler) + - [Asset Extraction Tool](#asset-extraction-tool) + - [Asset Packing Tool](#asset-packing-tool) + + + +## Requirements + - `cmake` for build system - `clang-format` for formatting code (there is already a `.clang-format` provided) - `gtest` for testing. (Run `git submodule update --init --recursive` to check out the repository) -- `nasm` for assembling x86. There isn't much x86 assembly so if there's a better way to do this for windows, we can change. +- `nasm` for assembling x86 - Third party libraries (`nlohmann/json`, `minilzo`, and `linenoise`) are provided in the `third-party` folder -Setup (for Ubuntu): -``` +## Getting Started - Linux (Ubuntu) + +Install Packages and Init Repository + +```bash sudo apt install gcc make cmake build-essential g++ nasm clang-format +git submodule update --init --recursive ``` -Layout: +Compile + +```bash +mkdir build && cd build && cmake .. && make -j +``` + +Run Tests + +```bash +./test.sh +``` + +## Getting Started - Windows + +Install Visual Studio 2019 and get the C++ and CMake tools via the Visual Studio Installer + +On Windows, it's recommended to get Scoop to use as a package manager, making the follow steps _much_ easier. Follow the steps on the bottom of the homepage here https://scoop.sh/ + +Once Scoop is installed, run the following command: + +```ps +scoop install llvm nasm +``` + +Initialize the repository's third-party dependencies: + +```bash +git submodule update --init --recursive +``` + +Open the project as a CMake project, browse for the root level `CMakeLists.txt`: + +![](./doc/imgs/open-cmake-vs.png) + +In the toolbar, you should be able to select an individual component to compile, or combine within the root CMakeLists.txt. In the future we will pre-define configurations to make this easier. + +![](./doc/imgs/cmake-build-vs.png) + +You may also wish to view the files that pertain to each CMake target, rather than the project as it is normally: + +![](./doc/imgs/cmake-target-view.png) + +TODO + +- more steps to follow as we actually figure it out! +- running tests +- etc + +## Project Layout + - `goalc` is the GOAL compiler - `gs` contains GOOS code for parts of GOOS implemented in GOOS - `gc` contains GOAL code for parts of GOAL implemented in GOAL (must generate no machine code, just defining macros) @@ -33,8 +107,10 @@ Layout: - `tests` will contain all tests - `asset_tool` will contain the asset packer/unpacker -Design: +## Design + (if anybody has better ideas, feel free to suggest improvements! This is just a rough plan for now) + - All C++ code should build from the top-level `cmake`. - All C++ applications (GOAL compiler, asset extractor, asset packer, runtime, test) should have a script in the top level which launches them. - All file paths should be relative to the `jak` folder. @@ -52,11 +128,13 @@ Design: - `./gc.sh` : run the compiler in interactive mode - `./gs.sh` : run a goos interpreter in interactive mode - `./decomp.sh : run the decompiler - -Current state: + +## Current State + - GOAL compiler just implements the GOOS Scheme Macro Language. Running `./gc.sh` just loads the GOOS library (`goalc/gs/goos-lib.gs`) and then goes into an interactive mode. Use `(exit)` to exit. - `./test.sh` runs tests for some game C++ code, for GOOS, for the reader, for the listener connection, and for some early emitter stuff. -- The runtime boots in `fakeiso` mode which will load some dummy files. Then the C Kernel (`game/kernel`) will load the `KERNEL.CGO` and `GAME.CGO` files, which are from the "proof of concept" GOAL compiler. If you run `./gk.sh`, you should see it load stuff, then print: +- The runtime boots in `fakeiso` mode which will load some dummy files. Then the C Kernel (`game/kernel`) will load the `KERNEL.CGO` and `GAME.CGO` files, which are from the "proof of concept" GOAL compiler. If you run `./gk.sh`, you should see it load stuff, then print: + ``` calling play! ~~ HACK ~~ : fake play has been called @@ -65,13 +143,16 @@ InitCheckListener kernel: machine started ``` + where the `~~ HACK ~~` message is from code in `KERNEL.CGO`. -Code Guidelines: +## Coding Guidelines + - Avoid warnings - Use asserts over throwing exceptions in game code (throwing exceptions from C code called by GOAL code is sketchy) -TODOS: +## TODOs + - Build on Windows! - Networking - File paths @@ -86,8 +167,8 @@ TODOS: - Clean up possible duplicate code in compiler/decompiler `util` folder, consider a common utility library - Clean up header guard names (or just use `#pragma once`?) - Investigate a better config format - - The current JSON library seems to have issues with comments, which I really like -- Clean up use of namespaces + - The current JSON library seems to have issues with comments, which I really like +- Clean up use of namespaces - Clean up the print message when `gk` starts. - Finish commenting runtime stuff - Runtime document @@ -99,11 +180,10 @@ TODOS: - Clean up decompiler print spam, finish up the CFG stuff - Decompiler document - -Project Description ------------------------ +## Project Description This project is to port Jak 1 (NTSC, "black label" version) to PC. The strategy is to: + - recompile for x86 to get much better performance than emulation - create human-reabable GOAL source code that can be modified - create a GOAL compiler for x86-64 which supports live patching of code like the original @@ -111,6 +191,7 @@ This project is to port Jak 1 (NTSC, "black label" version) to PC. The strategy - unpack assets in a format that can be modified There are 6 components to this project + - GOAL decompiler. The result will be manually cleaned up for running on a PC. - GOAL compiler for x86-64. - Game source code, made from cleaning up the result of the GOAL decompiler. @@ -119,25 +200,27 @@ There are 6 components to this project - Asset packing tool. The process to build the port will be + - Build data extraction tool, GOAL compiler, and GOAL runtime library (all written in C++) - Run the GOAL compiler on the game source code to build the game engine - Run asset extraction on the game disc to get level data, textures, geometry data, music... - Run the asset packing tool to combine the unpacked assets with the compiled game engine to create the game! Some statistics: + - Estimated ~500k lines of GOAL code - 10410 functions - 5451 functions with no control flow (no branching, loops, if/else, short-circuiting boolean operators, gotos, etc) -The rough timeline is to finish sometime in 2022. If it looks like this is impossible, the project will be abandoned. But I have already spent about 4 months preparing to start this and seems doable. I also have some background in compilers, and familiarity with PS2 (worked on DobieStation PS2 emulator) / MIPS in general (wrote a PS1 emulator). I think the trick will be making good automated tools - the approach taken for SM64 and other N64 decompilations is way too labor-intensive to work. +The rough timeline is to finish sometime in 2022. If it looks like this is impossible, the project will be abandoned. But I have already spent about 4 months preparing to start this and seems doable. I also have some background in compilers, and familiarity with PS2 (worked on DobieStation PS2 emulator) / MIPS in general (wrote a PS1 emulator). I think the trick will be making good automated tools - the approach taken for SM64 and other N64 decompilations is way too labor-intensive to work. +### GOAL Decompiler -GOAL Decompiler ------------------- -The decompiler is in progress, at +The decompiler is in progress, at https://github.com/water111/jak-disassembler Here is the plan for writing the decompiler: + - [x] Decode the CGO/DGO format. - [x] Decode the linking data format. - [x] Identify all code and disassemble @@ -151,10 +234,10 @@ Here is the plan for writing the decompiler: - [ ] Variable map and scoping - [ ] S-expression construction (expression stack) +### GOAL Runtime + +The "runtime" will be a replacement for all of the C/C++ code of the original game. There is C/C++ code that runs on the main processor (EE) and the separate I/O processor (IOP). -GOAL Runtime --------------- -The "runtime" will be a replacement for all of the C/C++ code of the original game. There is C/C++ code that runs on the main processor (EE) and the separate I/O processor (IOP). - The "C Kernel", which runs on the EE and contains - [ ] File I/O (for debugging, not used by retail game) - [x] Initialization to boostrap the GOAL Kernel and start the game engine @@ -184,17 +267,18 @@ The "Sony libraries" are a simple wrapper around my `system` library, which impl Likely there will be sound/graphics code in here at some point, but this part is not fully planned yet. -GOAL Compiler ---------------- +### GOAL Compiler + The GOAL compiler will target x86-64. At first just Linux. There is a macro language called GOOS which is basically just Scheme but with a few bonus features. -The compiler will reuse a significant amount of code from my existing LISP compiler for x86-64. I have a very bad WIP combination which is capable of building a modified `gkernel.gc` for x86 as a proof of concept. It can create and run functions in threads. +The compiler will reuse a significant amount of code from my existing LISP compiler for x86-64. I have a very bad WIP combination which is capable of building a modified `gkernel.gc` for x86 as a proof of concept. It can create and run functions in threads. -An important part of the compiler is the test suite. Without tests the compiler will be full of bugs. So every feature should have a good set of tests. +An important part of the compiler is the test suite. Without tests the compiler will be full of bugs. So every feature should have a good set of tests. The major components are - [ ] GOAL-IR, a typed linear intermediate representation for GOAL code + - [ ] "Environment" - [ ] "Ref" - [ ] Constant propagation of integers/floats @@ -202,6 +286,7 @@ The major components are - [ ] Ref `in_gpr` - [ ] The type system + - [ ] Type inheritance - [ ] Integer/float/pointer types (value semantics) - [ ] Reference types @@ -220,41 +305,41 @@ The major components are - [ ] Built-in types in the GOAL runtime/C Kernel - [x] The GOOS Macro Language + - [x] S-expression parser (the "Reader") - [x] Reader text db (for error messages that point to a specific line) - [x] Scheme interpreter - + - [ ] Front-end (convert s-expressions (a tree structure) to GOAL-IR (a linear representation)) - - [ ] Parsing helpers - - [ ] Macro expansion - - [ ] Control flow/block forms - - [ ] Type definitions - - [ ] Inline assembly forms - - [ ] Function/method call - - [ ] Math forms - - [ ] Lexical scoping (immediate application of `lambda`) - - [ ] Function inlining (slightly different scoping rules of immediate `lambda`) - - [ ] Function/macro definition - - [ ] Static Objects +- [ ] Parsing helpers +- [ ] Macro expansion +- [ ] Control flow/block forms +- [ ] Type definitions +- [ ] Inline assembly forms +- [ ] Function/method call +- [ ] Math forms +- [ ] Lexical scoping (immediate application of `lambda`) +- [ ] Function inlining (slightly different scoping rules of immediate `lambda`) +- [ ] Function/macro definition +- [ ] Static Objects - [ ] Regsiter allocation - - [ ] Analysis - - [ ] Allocation - - [ ] Stack spilling - - [ ] `xmm` and `gpr` promotion/demotions for EE 128-bit register usage - +- [ ] Analysis +- [ ] Allocation +- [ ] Stack spilling +- [ ] `xmm` and `gpr` promotion/demotions for EE 128-bit register usage - [ ] Codegen / Emitter (convert GOAL-IR + register allocations to x86 object file format) - - [ ] Emitter (convert GOAL-IR to instructions) - - [ ] x86-64 instruction generation (actually generate the machine code) - - [ ] Linking data - - [ ] 64-bit GPR - - [ ] 32-bit float - - [ ] 128-bit GPR - - [ ] 32-bit float x4 vector register - - [ ] function prologue/epilogue - - [ ] stack spilling - - [ ] static object and static object links +- [ ] Emitter (convert GOAL-IR to instructions) +- [ ] x86-64 instruction generation (actually generate the machine code) +- [ ] Linking data +- [ ] 64-bit GPR +- [ ] 32-bit float +- [ ] 128-bit GPR +- [ ] 32-bit float x4 vector register +- [ ] function prologue/epilogue +- [ ] stack spilling +- [ ] static object and static object links - [ ] Listener/REPL - [ ] Network connection @@ -264,14 +349,14 @@ The major components are - [ ] Expand single macro debugging feature - [ ] Interface for running gtests +### Asset Extraction Tool -Asset Extraction Tool ------------------------ -Not started yet. The simplest version of this tool is just to use the decompiler logic to turn the level/art-group/texture/TXT files into GOAL source code, and copy all STR/sound/visibility files, as these don't need to be modified. +Not started yet. The simplest version of this tool is just to use the decompiler logic to turn the level/art-group/texture/TXT files into GOAL source code, and copy all STR/sound/visibility files, as these don't need to be modified. Eventually this should export to a more useful format. File formats: + - [ ] Art group (a GOAL object format) - There may be more formats related to art groups. - [ ] Texture page (a GOAL object format) @@ -286,12 +371,8 @@ File formats: - [ ] Loading screen image - [ ] save game icon (I do not care about this) +### Asset Packing Tool -Asset Packing Tool ------------------------ -Packs together all assets/compiled code/runtime into a format that can be played. The simplest version to go with the simplest extraction tool will just pass the level/art-group/texture/TXT files to the compiler, and copy STR/sound/visbility files into the fakeiso. Then pack in CGOs/DGOs. +Packs together all assets/compiled code/runtime into a format that can be played. The simplest version to go with the simplest extraction tool will just pass the level/art-group/texture/TXT files to the compiler, and copy STR/sound/visbility files into the fakeiso. Then pack in CGOs/DGOs. It's important that the asset extraction/packing can be automated so we can avoid distributing the assets, which are large and probably not supposed to be distributed. - - - diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index a963443399..3b61135bf5 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -3,6 +3,7 @@ #include "TypeSystem.h" #include "type_util.h" #include +#include TypeSystem::TypeSystem() { // the "none" and "_type_" types are included by default. diff --git a/doc/imgs/cmake-build-vs.png b/doc/imgs/cmake-build-vs.png new file mode 100644 index 0000000000..23f6a4ccb8 Binary files /dev/null and b/doc/imgs/cmake-build-vs.png differ diff --git a/doc/imgs/cmake-target-view.png b/doc/imgs/cmake-target-view.png new file mode 100644 index 0000000000..2a69df167a Binary files /dev/null and b/doc/imgs/cmake-target-view.png differ diff --git a/doc/imgs/open-cmake-vs.png b/doc/imgs/open-cmake-vs.png new file mode 100644 index 0000000000..914a2ff7fa Binary files /dev/null and b/doc/imgs/open-cmake-vs.png differ diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index b928900c41..59cacdf7af 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -1,11 +1,33 @@ # We define our own compilation flags here. set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_FLAGS "-O0 -ggdb -Wall \ --Wextra -Wcast-align -Wcast-qual -Wdisabled-optimization -Wformat=2 \ --Winit-self -Wmissing-include-dirs -Woverloaded-virtual \ --Wredundant-decls -Wshadow -Wsign-promo ") + +# Set default compile flags for GCC +# optimization level can be set here. Note that game/ overwrites this for building game C++ code. +if(CMAKE_COMPILER_IS_GNUCXX) + message(STATUS "GCC detected, adding compile flags") + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} \ + -ggdb \ + -Wall \ + -Winit-self \ + -Wextra \ + -Wcast-align \ + -Wcast-qual \ + -Wdisabled-optimization \ + -Wformat=2 \ + -Wmissing-include-dirs \ + -Woverloaded-virtual \ + -Wredundant-decls \ + -Wshadow \ + -Wsign-promo") +else() + set(CMAKE_CXX_FLAGS "/EHsc") +endif(CMAKE_COMPILER_IS_GNUCXX) enable_language(ASM_NASM) +set(CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS ${CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS} asm) +set(CMAKE_ASM_NASM_COMPILE_OBJECT " -f ${CMAKE_ASM_NASM_OBJECT_FORMAT} -o ") +set_source_files_properties(kernel/asm_funcs.asm PROPERTIES COMPILE_FLAGS "-g") set(RUNTIME_SOURCE runtime.cpp system/SystemThread.cpp @@ -18,7 +40,7 @@ set(RUNTIME_SOURCE sce/sif_ee.cpp sce/iop.cpp sce/stubs.cpp - kernel/asm_funcs.nasm + kernel/asm_funcs.asm kernel/fileio.cpp kernel/kboot.cpp kernel/kdgo.cpp @@ -54,4 +76,11 @@ add_executable(gk ${RUNTIME_SOURCE} main.cpp) # can be used to test other things. add_library(runtime ${RUNTIME_SOURCE}) -target_link_libraries(gk pthread) \ No newline at end of file + +IF (WIN32) + # set stuff for windows + target_link_libraries(gk mman) +ELSE() + # set stuff for other systems + target_link_libraries(gk pthread) +ENDIF() diff --git a/game/kernel/asm_funcs.nasm b/game/kernel/asm_funcs.asm similarity index 54% rename from game/kernel/asm_funcs.nasm rename to game/kernel/asm_funcs.asm index d449265338..2b41efe59d 100644 --- a/game/kernel/asm_funcs.nasm +++ b/game/kernel/asm_funcs.asm @@ -14,8 +14,46 @@ SECTION .TEXT ;; a pointer to this array of GOAL arguments as the argument. The reason for this is that GOAL and ;; the standard System V ABI used in Linux are different for 8 argument function calls. -global _format -_format: +global _format_win32 +_format_win32: + ; GOAL will call with regs RDI, RSI, RDX, RCX, R8, R9, R10, R11 + + ; to make sure the stack frame is aligned + sub rsp, 8 + + ; push all registers and create the register array on the stack + push r11 + push r10 + push r9 + push r8 + push rcx + push rdx + push rsi + push rdi + + ; set the first argument register to the stack argument array + mov rcx, rsp + sub rsp, 32 + + ; call C function to do format, result will go in RAX + call format_impl + add rsp, 32 + + ; restore + ; (note - this could probably just be add rsp 72, we don't care about the value of these register) + pop rdi + pop rsi + pop rdx + pop rcx + pop r8 + pop r9 + pop r10 + pop r11 + add rsp, 8 + ret + +global _format_linux +_format_linux: ; GOAL will call with regs RDI, RSI, RDX, RCX, R8, R9, R10, R11 ; to make sure the stack frame is aligned @@ -55,8 +93,6 @@ _format: ;; run this wrapper to call the real format_impl - - ;; The _call_goal_asm function is used to call a GOAL function from C. ;; It supports up to 3 arguments and a return value. ;; This should be called with the arguments: @@ -67,9 +103,9 @@ _format: ;; - address of the symbol table ;; - GOAL memory space offset -global _call_goal_asm +global _call_goal_asm_linux -_call_goal_asm: +_call_goal_asm_linux: ;; x86 saved registers we need to modify for GOAL should be saved push r13 push r14 @@ -95,4 +131,57 @@ _call_goal_asm: pop r15 pop r14 pop r13 - ret \ No newline at end of file + ret + + +;; The _call_goal_asm function is used to call a GOAL function from C. +;; It supports up to 3 arguments and a return value. +;; This should be called with the arguments: +;; - first goal arg +;; - second goal arg +;; - third goal arg +;; - address of function to call +;; - address of the symbol table +;; - GOAL memory space offset + +global _call_goal_asm_win32 + +_call_goal_asm_win32: + push rdx ; 8 + push rbx ; 16 + push rbp ; 24 + push rsi ; 32 + push rdi ; 40 + push r8 ; 48 + push r9 ; 56 + push r10 ; 64 + push r11 ; 72 + push r12 ; 80 + push r13 ; 88 + push r14 ; 96 + push r15 ; 104 + + mov rdi, rcx ;; rdi is GOAL first argument, rcx is windows first argument + mov rsi, rdx ;; rsi is GOAL second argument, rdx is windows second argument + mov rdx, r8 ;; rdx is GOAL third argument, r8 is windows third argument + mov r13, r9 ;; r13 is GOAL fp, r9 is windows fourth argument + mov r15, [rsp + 144] ;; symbol table + mov r14, [rsp + 152] ;; offset + + call r13 + + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rbp + pop rbx + pop rdx + + ret diff --git a/game/kernel/kboot.cpp b/game/kernel/kboot.cpp index 0d06dcf659..9f6f6de06d 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/kboot.cpp @@ -4,7 +4,6 @@ * DONE! */ -#include #include #include "common/common_types.h" #include "game/sce/libscf.h" @@ -14,6 +13,13 @@ #include "ksocket.h" #include "klisten.h" +#ifdef _WIN32 +#include "Windows.h" +#include +#elif __linux__ +#include +#endif + using namespace ee; // Level to load on boot @@ -128,14 +134,21 @@ void KernelCheckAndDispatch() { // dispatch the kernel //(**kernel_dispatcher)(); call_goal(Ptr(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem); + // TODO-WINDOWS +#ifdef __linux__ ClearPending(); +#endif // if the listener function changed, it means the kernel ran it, so we should notify compiler. if (MasterDebug && ListenerFunction->value != old_listener) { SendAck(); } - usleep(1000); // todo - remove this +#ifdef _WIN32 + Sleep(1000); // todo - remove this +#elif __linux__ + usleep(1000); +#endif } } diff --git a/game/kernel/kdsnetm.cpp b/game/kernel/kdsnetm.cpp index 8dcec18a9b..2d06a93160 100644 --- a/game/kernel/kdsnetm.cpp +++ b/game/kernel/kdsnetm.cpp @@ -27,6 +27,8 @@ void kdsnetm_init_globals() { protoBlock.reset(); } +// TODO-WINDOWS +#ifdef __linux__ /*! * Register GOAL DECI2 Protocol Driver with DECI2 service * DONE, EXACT @@ -219,4 +221,5 @@ s32 SendFromBufferD(s32 msg_kind, u64 p2, char* data, s32 size) { void GoalProtoStatus() { Msg(6, "gproto: got %d %d\n", protoBlock.most_recent_event, protoBlock.most_recent_param); Msg(6, "gproto: %d %d\n", protoBlock.last_receive_size, protoBlock.send_remaining); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/game/kernel/kdsnetm.h b/game/kernel/kdsnetm.h index bdbef66096..51125e0c1f 100644 --- a/game/kernel/kdsnetm.h +++ b/game/kernel/kdsnetm.h @@ -51,6 +51,8 @@ extern GoalProtoBlock protoBlock; */ void kdsnetm_init_globals(); +// TODO-WINDOWS +#ifdef __linux__ /*! * Register GOAL DECI2 Protocol Driver with DECI2 service * DONE, EXACT @@ -63,6 +65,8 @@ void InitGoalProto(); */ void ShutdownGoalProto(); +#endif + /*! * Handle a DECI2 Protocol Event for the GOAL Proto. * Called by the DECI2 Protocol driver @@ -70,6 +74,8 @@ void ShutdownGoalProto(); */ void GoalProtoHandler(int event, int param, void* data); +// TODO-WINDOWS +#ifdef __linux__ /*! * Low level DECI2 send * Will block until send is complete. @@ -77,6 +83,7 @@ void GoalProtoHandler(int event, int param, void* data); * removed */ s32 SendFromBufferD(s32 p1, u64 p2, char* data, s32 size); +#endif /*! * Print GOAL Protocol status diff --git a/game/kernel/klisten.cpp b/game/kernel/klisten.cpp index dc29e08043..7f990bf27e 100644 --- a/game/kernel/klisten.cpp +++ b/game/kernel/klisten.cpp @@ -71,7 +71,10 @@ void ClearPending() { Ptr msg = OutputBufArea.cast() + sizeof(GoalMessageHeader); auto size = strlen(msg.c()); // note - if size is ever greater than 2^16 this will cause an issue. + // TODO-WINDOWS +#ifdef __linux__ SendFromBuffer(msg.c(), size); +#endif clear_output(); } @@ -84,7 +87,10 @@ void ClearPending() { if (send_size > 64000) { send_size = 64000; } +// TODO-WINDOWS +#ifdef __linux__ SendFromBufferD(2, 0, msg, send_size); +#endif size -= send_size; msg += send_size; } @@ -103,9 +109,11 @@ void ClearPending() { */ void SendAck() { if (MasterDebug) { +#ifdef __linux__ SendFromBufferD(u16(ListenerMessageKind::MSG_ACK), protoBlock.msg_id, AckBufArea + sizeof(GoalMessageHeader), strlen(AckBufArea + sizeof(GoalMessageHeader))); +#endif } } diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index 168619844a..4752b90b93 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -329,7 +329,10 @@ int InitMachine() { // } if (MasterDebug) { // connect to GOAL compiler +// TODO-WINDOWS +#ifdef __linux__ InitGoalProto(); +#endif } printf("InitSound\n"); @@ -359,7 +362,10 @@ int ShutdownMachine() { StopIOP(); CloseListener(); ShutdownSound(); +// TODO-WINDOWS +#ifdef __linux__ ShutdownGoalProto(); +#endif Msg(6, "kernel: machine shutdown"); return 0; } diff --git a/game/kernel/kprint.h b/game/kernel/kprint.h index d26fea7bc3..0c4d178b80 100644 --- a/game/kernel/kprint.h +++ b/game/kernel/kprint.h @@ -62,28 +62,59 @@ void output_unload(const char* name); */ void output_segment_load(const char* name, Ptr link_block, u32 flags); +#ifdef __linux__ /*! * Print to the GOAL print buffer from C */ void cprintf(const char* format, ...) __attribute__((format(printf, 1, 2))); +#elif _WIN32 +/*! + * Print to the GOAL print buffer from C + */ +void cprintf(const char* format, ...); +#endif +#ifdef __linux__ /*! * Print directly to the C stdout * The "k" parameter is ignored, so this is just like printf */ void Msg(s32 k, const char* format, ...) __attribute__((format(printf, 2, 3))); +#elif _WIN32 +/*! + * Print directly to the C stdout + * The "k" parameter is ignored, so this is just like printf + */ +void Msg(s32 k, const char* format, ...); +#endif +#ifdef __linux__ /*! * Print directly to the C stdout * This is identical to Msg. */ void MsgWarn(const char* format, ...) __attribute__((format(printf, 1, 2))); +#elif _WIN32 +/*! + * Print directly to the C stdout + * This is identical to Msg. + */ +void MsgWarn(const char* format, ...); +#endif +#ifdef __linux__ /*! * Print directly to the C stdout * This is identical to Msg. */ void MsgErr(const char* format, ...) __attribute__((format(printf, 1, 2))); +#elif _WIN32 +/*! + * Print directly to the C stdout + * This is identical to Msg. + */ +void MsgErr(const char* format, ...); +#endif /*! * Reverse string in place. diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index 1fb6a379b6..d2c41a96cb 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -313,13 +313,7 @@ u64 make_string_from_c(const char* c_str) { return mem; } -/*! - * Create a GOAL function from a C function. This doesn't export it as a global function, it just - * creates a function object on the global heap. - * - * The implementation is to create a simple trampoline function which jumps to the C function. - */ -Ptr make_function_from_c(void* func) { +Ptr make_function_from_c_linux(void* func) { // allocate a function object on the global heap auto mem = Ptr( alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, *(s7 + FIX_SYM_FUNCTION_TYPE), 0x40)); @@ -344,6 +338,68 @@ Ptr make_function_from_c(void* func) { return mem.cast(); } +/*! + * Create a GOAL function from a C function. This doesn't export it as a global function, it just + * creates a function object on the global heap. + * + * The implementation is to create a simple trampoline function which jumps to the C function. + */ +Ptr make_function_from_c_win32(void* func) { + // allocate a function object on the global heap + auto mem = Ptr( + alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, *(s7 + FIX_SYM_FUNCTION_TYPE), 0x80)); + auto f = (uint64_t)func; + auto fp = (u8*)&f; + + int i = 0; + // we will put the function address in RAX with a movabs rax, imm8 + mem.c()[i++] = 0x48; + mem.c()[i++] = 0xb8; + for (int j = 0; j < 8; j++) { + mem.c()[i++] = fp[j]; + } + + /* + * push rdi + * push rsi + * push rdx + * push rcx + * pop r9 + * pop r8 + * pop rdx + * pop rcx + * + * sub rsp, 40 + * call rax + * add rsp, 40 + * ret + */ + for (auto x : {0x57, 0x56, 0x52, 0x51, 0x41, 0x59, 0x41, 0x58, 0x5A, 0x59, 0x48, + 0x83, 0xEC, 0x28, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, 0xC3}) { + mem.c()[i++] = x; + } + + // the C function's ret will return to the caller of this trampoline. + + // CacheFlush(mem, 0x34); + + return mem.cast(); +} + +/*! + * Create a GOAL function from a C function. This doesn't export it as a global function, it just + * creates a function object on the global heap. + * + * The implementation is to create a simple trampoline function which jumps to the C function. + */ +Ptr make_function_from_c(void* func) { +#ifdef __linux__ + return make_function_from_c_linux(func); +#elif _WIN32 + return make_function_from_c_win32(func); +#endif +} + /*! * Create a GOAL function which does nothing and immediately returns. */ @@ -890,8 +946,12 @@ u64 method_set(u32 type_, u32 method_id, u32 method) { } extern "C" { -// defined in asm_funcs.nasm -uint64_t _call_goal_asm(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, void* offset); +// defined in asm_funcs.asm +#ifdef __linux__ +uint64_t _call_goal_asm_linux(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, void* offset); +#elif _WIN32 +uint64_t _call_goal_asm_win32(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, void* offset); +#endif } /*! @@ -900,7 +960,11 @@ uint64_t _call_goal_asm(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, void* u64 call_goal(Ptr f, u64 a, u64 b, u64 c, u64 st, void* offset) { auto st_ptr = (void*)((uint8_t*)(offset) + st); void* fptr = f.c(); - return _call_goal_asm(a, b, c, fptr, st_ptr, offset); +#ifdef __linux__ + return _call_goal_asm_linux(a, b, c, fptr, st_ptr, offset); +#elif _WIN32 + return _call_goal_asm_win32(a, b, c, fptr, st_ptr, offset); +#endif } /*! @@ -1453,7 +1517,11 @@ s32 test_function(s32 arg0, s32 arg1, s32 arg2, s32 arg3) { extern "C" { // defined in asm_funcs. It calls format_impl and sets up arguments correctly. -void _format(); +#ifdef __linux__ +void _format_linux(); +#elif _WIN32 +void _format_win32(); +#endif } /*! @@ -1705,8 +1773,11 @@ s32 InitHeapAndSymbol() { make_function_symbol_from_c("load", (void*)load); make_function_symbol_from_c("loado", (void*)loado); make_function_symbol_from_c("unload", (void*)unload); - - make_function_symbol_from_c("_format", (void*)_format); +#ifdef __linux__ + make_function_symbol_from_c("_format", (void*)_format_linux); +#elif _WIN32 + make_function_symbol_from_c("_format", (void*)_format_win32); +#endif // allocations make_function_symbol_from_c("malloc", (void*)alloc_heap_memory); @@ -1885,4 +1956,4 @@ s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 return (s32)rv.offset; } -// todo, read lcock code \ No newline at end of file +// todo, read lcock code diff --git a/game/kernel/ksocket.cpp b/game/kernel/ksocket.cpp index 4eb0194017..b66fc1dd7e 100644 --- a/game/kernel/ksocket.cpp +++ b/game/kernel/ksocket.cpp @@ -52,6 +52,8 @@ u32 ReceiveToBuffer(char* buff) { return msg_size; } +// TODO-WINDOWS +#ifdef __linux__ /*! * Do a DECI2 send and block until it is complete. * The message type is OUTPUT @@ -60,6 +62,7 @@ u32 ReceiveToBuffer(char* buff) { s32 SendFromBuffer(char* buff, s32 size) { return SendFromBufferD(u16(ListenerMessageKind::MSG_OUTPUT), 0, buff, size); } +#endif /*! * Just prepare the Ack buffer, doesn't actually connect. diff --git a/game/overlord/fake_iso.cpp b/game/overlord/fake_iso.cpp index b869684cf7..f06c22ba83 100644 --- a/game/overlord/fake_iso.cpp +++ b/game/overlord/fake_iso.cpp @@ -73,6 +73,7 @@ void fake_iso_init_globals() { //! will hold prefix for the source folder. static const char* next_dir = nullptr; +static const char* fake_iso_path = nullptr; /*! * Initialize the file system. @@ -80,13 +81,15 @@ static const char* next_dir = nullptr; int FS_Init(u8* buffer) { (void)buffer; // get path to next/. Will be set in the gk.sh launch script. - next_dir = std::getenv("NEXT_DIR"); // todo windows? + next_dir = std::getenv("NEXT_DIR"); assert(next_dir); // get path to next/data/fake_iso.txt, the map file. char fakeiso_path[512]; strcpy(fakeiso_path, next_dir); - strcat(fakeiso_path, "/game/fake_iso.txt"); // todo windows paths? + fake_iso_path = std::getenv("FAKE_ISO_PATH"); + assert(fake_iso_path); + strcat(fakeiso_path, fake_iso_path); // open the map. FILE* fp = fopen(fakeiso_path, "r"); @@ -191,7 +194,9 @@ static const char* get_file_path(FileRecord* fr) { assert(fr->location < fake_iso_entry_count); static char path_buffer[1024]; strcpy(path_buffer, next_dir); +#ifdef __linux__ strcat(path_buffer, "/"); +#endif strcat(path_buffer, fake_iso_entries[fr->location].file_path); return path_buffer; } @@ -350,4 +355,4 @@ uint32_t FS_LoadSoundBank(char* name, void* buffer) { (void)name; (void)buffer; assert(false); -} \ No newline at end of file +} diff --git a/game/runtime.cpp b/game/runtime.cpp index 14af11cb9b..379898a9a1 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -3,8 +3,15 @@ * Setup and launcher for the runtime. */ +#ifdef __linux__ #include #include +#elif _WIN32 +#include +#include +#include +#endif + #include #include "runtime.h" @@ -38,21 +45,29 @@ u8* g_ee_main_mem = nullptr; +/*! + * TODO-WINDOWS + * runtime.cpp - Deci2Listener has been disabled for now, pending rewriting for Windows. + */ + namespace { /*! * SystemThread function for running the DECI2 communication with the GOAL compiler. */ -void deci2_runner(SystemThreadInterface& interface) { + +void deci2_runner(SystemThreadInterface& iface) { +// TODO-WINDOWS +#ifdef __linux__ // callback function so the server knows when to give up and shutdown - std::function shutdown_callback = [&]() { return interface.get_want_exit(); }; + std::function shutdown_callback = [&]() { return iface.get_want_exit(); }; // create and register server Deci2Server server(shutdown_callback); ee::LIBRARY_sceDeci2_register(&server); // now its ok to continue with initialization - interface.initialization_complete(); + iface.initialization_complete(); // in our own thread, wait for the EE to register the first protocol driver printf("[DECI2] waiting for EE to register protos\n"); @@ -64,7 +79,7 @@ void deci2_runner(SystemThreadInterface& interface) { printf("[DECI2] waiting for listener...\n"); bool saw_listener = false; - while (!interface.get_want_exit()) { + while (!iface.get_want_exit()) { if (server.check_for_listener()) { if (!saw_listener) { printf("[DECI2] Connected!\n"); @@ -77,6 +92,7 @@ void deci2_runner(SystemThreadInterface& interface) { usleep(50000); } } +#endif } // EE System @@ -95,7 +111,7 @@ constexpr int GOAL_ARGC = 4; /*! * SystemThread Function for the EE (PS2 Main CPU) */ -void ee_runner(SystemThreadInterface& interface) { +void ee_runner(SystemThreadInterface& iface) { // Allocate Main RAM. Must have execute enabled. if (EE_MEM_LOW_MAP) { g_ee_main_mem = @@ -109,7 +125,7 @@ void ee_runner(SystemThreadInterface& interface) { if (g_ee_main_mem == (u8*)(-1)) { printf(" Failed to initialize main memory! %s\n", strerror(errno)); - interface.initialization_complete(); + iface.initialization_complete(); return; } @@ -118,7 +134,7 @@ void ee_runner(SystemThreadInterface& interface) { (double)EE_MAIN_MEM_SIZE / (1 << 20)); printf("[EE] Initialization complete!\n"); - interface.initialization_complete(); + iface.initialization_complete(); printf("[EE] Run!\n"); memset((void*)g_ee_main_mem, 0, EE_MAIN_MEM_SIZE); @@ -145,13 +161,13 @@ void ee_runner(SystemThreadInterface& interface) { munmap(g_ee_main_mem, EE_MAIN_MEM_SIZE); // after main returns, trigger a shutdown. - interface.trigger_shutdown(); + iface.trigger_shutdown(); } /*! * SystemThread function for running the IOP (separate I/O Processor) */ -void iop_runner(SystemThreadInterface& interface) { +void iop_runner(SystemThreadInterface& iface) { IOP iop; printf("\n\n\n[IOP] Restart!\n"); iop.reset_allocator(); @@ -174,7 +190,7 @@ void iop_runner(SystemThreadInterface& interface) { // ssound // stream - interface.initialization_complete(); + iface.initialization_complete(); printf("[IOP] Wait for OVERLORD to be started...\n"); iop.wait_for_overlord_start_cmd(); @@ -195,7 +211,7 @@ void iop_runner(SystemThreadInterface& interface) { iop.signal_overlord_init_finish(); // IOP Kernel loop - while (!interface.get_want_exit() && !iop.want_exit) { + while (!iface.get_want_exit() && !iop.want_exit) { // the IOP kernel just runs at full blast, so we only run the IOP when the EE is waiting on the // IOP. Each time the EE is waiting on the IOP, it will run an iteration of the IOP kernel. iop.wait_run_iop(); @@ -220,7 +236,10 @@ void exec_runtime(int argc, char** argv) { // step 1: sce library prep iop::LIBRARY_INIT(); ee::LIBRARY_INIT_sceCd(); +// TODO-WINDOWS +#ifdef __linux__ ee::LIBRARY_INIT_sceDeci2(); +#endif ee::LIBRARY_INIT_sceSif(); // step 2: system prep @@ -244,4 +263,4 @@ void exec_runtime(int argc, char** argv) { // join and exit tm.join(); printf("GOAL Runtime Shutdown\n"); -} \ No newline at end of file +} diff --git a/game/sce/deci2.cpp b/game/sce/deci2.cpp index 03c2f7c39a..6cb5a84058 100644 --- a/game/sce/deci2.cpp +++ b/game/sce/deci2.cpp @@ -12,17 +12,23 @@ namespace ee { namespace { +// TODO-WINDOWS +#ifdef __linux__ constexpr int MAX_DECI2_PROTOCOLS = 4; Deci2Driver protocols[MAX_DECI2_PROTOCOLS]; // info for each deci2 protocol registered int protocol_count; // number of registered protocols Deci2Driver* sending_driver; // currently sending protocol driver ::Deci2Server* server; // the server to send data to +#endif + } // namespace -/*! +/* * Initialize the library. */ void LIBRARY_INIT_sceDeci2() { +// TODO-WINDOWS +#ifdef __linux__ // reset protocols for (auto& p : protocols) { p = Deci2Driver(); @@ -30,12 +36,15 @@ void LIBRARY_INIT_sceDeci2() { protocol_count = 0; server = nullptr; sending_driver = nullptr; +#endif } /*! * Run any pending requested sends. */ void LIBRARY_sceDeci2_run_sends() { +// TODO-WINDOWS +#ifdef __linux__ for (auto& prot : protocols) { if (prot.active && prot.pending_send == 'H') { sending_driver = &prot; @@ -45,13 +54,17 @@ void LIBRARY_sceDeci2_run_sends() { (prot.handler)(DECI2_WRITEDONE, 0, prot.opt); } } +#endif } /*! * Register a Deci2Server with this library. */ void LIBRARY_sceDeci2_register(::Deci2Server* s) { +// TODO-WINDOWS +#ifdef __linux__ server = s; +#endif } /*! @@ -60,6 +73,8 @@ void LIBRARY_sceDeci2_register(::Deci2Server* s) { * I don't know why it's like this. */ s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param, void* opt)) { +// TODO-WINDOWS +#ifdef __linux__ server->lock(); Deci2Driver drv; drv.protocol = protocol; @@ -78,14 +93,20 @@ s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param, } return drv.id; +#elif _WIN32 + return 0; +#endif } /*! * Deactivate a DECI2 protocol by socket descriptor. */ s32 sceDeci2Close(s32 s) { +// TODO-WINDOWS +#ifdef __linux__ assert(s - 1 < protocol_count); protocols[s - 1].active = false; +#endif return 1; } @@ -93,9 +114,12 @@ s32 sceDeci2Close(s32 s) { * Start a send. */ s32 sceDeci2ReqSend(s32 s, char dest) { +// TODO-WINDOWS +#ifdef __linux__ assert(s - 1 < protocol_count); auto& proto = protocols[s - 1]; proto.pending_send = dest; +#endif return 0; } @@ -104,6 +128,8 @@ s32 sceDeci2ReqSend(s32 s, char dest) { * Returns after data is copied. */ s32 sceDeci2ExRecv(s32 s, void* buf, u16 len) { +// TODO-WINDOWS +#ifdef __linux__ assert(s - 1 < protocol_count); protocols[s - 1].recv_size = len; auto avail = protocols[s - 1].available_to_receive; @@ -114,12 +140,17 @@ s32 sceDeci2ExRecv(s32 s, void* buf, u16 len) { printf("[DECI2] Error: ExRecv %d, only %d available!\n", len, avail); return -1; } +#elif _WIN32 + return 0; +#endif } /*! * Do a send. */ s32 sceDeci2ExSend(s32 s, void* buf, u16 len) { +// TODO-WINDOWS +#ifdef __linux__ assert(s - 1 < protocol_count); if (!sending_driver) { printf("sceDeci2ExSend called at illegal time!\n"); @@ -131,6 +162,8 @@ s32 sceDeci2ExSend(s32 s, void* buf, u16 len) { server->send_data(buf, len); return len; +#elif _WIN32 + return 0; +#endif } - -} // namespace ee \ No newline at end of file +} // namespace ee diff --git a/game/system/Deci2Server.cpp b/game/system/Deci2Server.cpp index 9b2a29f942..600f34462e 100644 --- a/game/system/Deci2Server.cpp +++ b/game/system/Deci2Server.cpp @@ -4,6 +4,9 @@ * Works with deci2.cpp (sceDeci2) to implement the networking on target */ +// TODO-WINDOWS +#ifdef __linux__ + #include #include #include @@ -262,3 +265,4 @@ void Deci2Server::accept_thread_func() { } } } +#endif \ No newline at end of file diff --git a/game/system/Deci2Server.h b/game/system/Deci2Server.h index d3db7d96ca..8695ff020d 100644 --- a/game/system/Deci2Server.h +++ b/game/system/Deci2Server.h @@ -4,6 +4,7 @@ * Works with deci2.cpp (sceDeci2) to implement the networking on target */ +#ifdef __linux__ #ifndef JAK1_DECI2SERVER_H #define JAK1_DECI2SERVER_H @@ -51,3 +52,4 @@ class Deci2Server { }; #endif // JAK1_DECI2SERVER_H +#endif diff --git a/game/system/SystemThread.cpp b/game/system/SystemThread.cpp index aa49932491..64121c6e11 100644 --- a/game/system/SystemThread.cpp +++ b/game/system/SystemThread.cpp @@ -83,7 +83,7 @@ void* bootstrap_thread_func(void* x) { void SystemThread::start(std::function f) { printf("# Initialize %s...\n", name.c_str()); function = f; - pthread_create(&thread, nullptr, bootstrap_thread_func, this); + thread = std::thread(bootstrap_thread_func, this); running = true; // and wait for initialization @@ -99,8 +99,7 @@ void SystemThread::start(std::function f) { * Join a system thread */ void SystemThread::join() { - void* x; - pthread_join(thread, &x); + thread.join(); running = false; } @@ -136,6 +135,8 @@ void SystemThreadInterface::trigger_shutdown() { thread.manager->shutdown(); } +// TODO-Windows +#ifdef __linux__ #include #include @@ -164,4 +165,5 @@ void SystemThreadInterface::report_perf_stats() { thread.last_cpu_user = current_user; thread.last_collection_nanoseconds = current_ns; } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/game/system/SystemThread.h b/game/system/SystemThread.h index 9edcea4678..0a01af1837 100644 --- a/game/system/SystemThread.h +++ b/game/system/SystemThread.h @@ -8,10 +8,11 @@ #include #include -#include #include #include +#include #include + #include "Timer.h" constexpr int MAX_SYSTEM_THREADS = 16; @@ -39,7 +40,7 @@ class SystemThread { friend void* bootstrap_thread_func(void* thd); std::string name = "invalid"; - pthread_t thread; + std::thread thread; SystemThreadManager* manager; std::function function; bool initialization_complete = false; diff --git a/game/system/deci_common.h b/game/system/deci_common.h index 3aba37edf1..94760abca6 100644 --- a/game/system/deci_common.h +++ b/game/system/deci_common.h @@ -1,6 +1,7 @@ #ifndef JAK_DECIM_COMMON_H #define JAK_DECIM_COMMON_H #include "common/common_types.h" + struct Deci2Driver { u16 protocol = 0; void* opt = nullptr; diff --git a/game/system/iop_thread.cpp b/game/system/iop_thread.cpp index be00ffed7d..67998ab879 100644 --- a/game/system/iop_thread.cpp +++ b/game/system/iop_thread.cpp @@ -1,6 +1,10 @@ #include "iop_thread.h" +#ifdef __linux__ #include +#elif _WIN32 +#include +#endif #include "SystemThread.h" //#include "shared_config.h" //#include "ps2/SCE_IOP.h" diff --git a/gc.sh b/gc.sh index 19ab0e192f..ac079580a1 100755 --- a/gc.sh +++ b/gc.sh @@ -4,4 +4,5 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export NEXT_DIR=$DIR +export FAKE_ISO_PATH=/game/fake_iso.txt $DIR/build/goalc/goalc "$@" \ No newline at end of file diff --git a/gk.sh b/gk.sh index f33bbcd06f..d49ccff100 100755 --- a/gk.sh +++ b/gk.sh @@ -4,4 +4,5 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export NEXT_DIR=$DIR +export FAKE_ISO_PATH=/game/fake_iso.txt $DIR/build/game/gk "$@" diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 1ec43eb75f..86a915cd0c 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -1,9 +1,18 @@ add_subdirectory(util) add_subdirectory(goos) -add_subdirectory(listener) +IF (WIN32) + # TODO - implement windows listener + message("Windows Listener Not Implemented!") +ELSE() + add_subdirectory(listener) +ENDIF() add_subdirectory(emitter) add_executable(goalc main.cpp compiler/Compiler.cpp) +IF (WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +ENDIF() + target_link_libraries(goalc util goos type_system) \ No newline at end of file diff --git a/goalc/emitter/CodeTester.cpp b/goalc/emitter/CodeTester.cpp index da5bfab9ae..920c86605e 100644 --- a/goalc/emitter/CodeTester.cpp +++ b/goalc/emitter/CodeTester.cpp @@ -6,7 +6,13 @@ * The CodeTester can't be used for tests requiring the full GOAL language/linking. */ +#ifdef __linux__ #include +#elif _WIN32 +#include +#endif + +#include #include "CodeTester.h" #include "IGen.h" diff --git a/goalc/goos/Interpreter.cpp b/goalc/goos/Interpreter.cpp index 3f514a19ff..285f607fbe 100644 --- a/goalc/goos/Interpreter.cpp +++ b/goalc/goos/Interpreter.cpp @@ -925,4 +925,4 @@ Object Interpreter::eval_while(const Object& form, return rv; } -} // namespace goos \ No newline at end of file +} // namespace goos diff --git a/goalc/goos/Reader.cpp b/goalc/goos/Reader.cpp index 7e195a73cc..fa77d54996 100644 --- a/goalc/goos/Reader.cpp +++ b/goalc/goos/Reader.cpp @@ -652,13 +652,13 @@ bool Reader::try_token_as_integer(const Token& tok, Object& obj) { return false; } } - uint64_t v = 0; try { std::size_t end = 0; v = std::stoll(tok.text, &end); - if (end != tok.text.size()) + if (end != tok.text.size()) { return false; + } obj = Object::make_integer(v); return true; } catch (std::exception& e) { @@ -707,4 +707,4 @@ void Reader::throw_reader_error(TextStream& here, const std::string& err, int se std::string Reader::get_source_dir() { return source_dir; } -} // namespace goos \ No newline at end of file +} // namespace goos diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index 72265faaba..2a53f26612 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -3,6 +3,9 @@ * The Listener can connect to a Deci2Server for debugging. */ +// TODO-Windows +#ifdef __linux__ + #include #include #include @@ -239,3 +242,4 @@ void Listener::receive_func() { } } // namespace listener +#endif diff --git a/test.sh b/test.sh index 3cff7a5976..6370a8a2db 100755 --- a/test.sh +++ b/test.sh @@ -4,4 +4,5 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export NEXT_DIR=$DIR +export FAKE_ISO_PATH=/game/fake_iso.txt $DIR/build/test/goalc-test --gtest_color=yes "$@" \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 05be29603e..d1d0a912e9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,3 +1,5 @@ +enable_testing() + add_executable(goalc-test test_main.cpp test_test.cpp @@ -14,4 +16,11 @@ add_executable(goalc-test test_emitter_integer_math.cpp ) -target_link_libraries(goalc-test goos util listener runtime emitter type_system gtest) \ No newline at end of file +IF (WIN32) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + # TODO - implement windows listener + message("Windows Listener Not Implemented!") + target_link_libraries(goalc-test mman goos util runtime emitter type_system gtest) +ELSE() + target_link_libraries(goalc-test goos util listener runtime emitter type_system gtest) +ENDIF() diff --git a/test/test_goos.cpp b/test/test_goos.cpp index 6060fcbeef..6b67bf3354 100644 --- a/test/test_goos.cpp +++ b/test/test_goos.cpp @@ -946,6 +946,8 @@ TEST(GoosObject, char_to_string) { * Test the EmptyListObject */ TEST(GoosObject, EmptyList) { +// TODO-Windows +#ifdef __linux__ // create two empty lists Object nil = EmptyListObject::make_new(); Object nil2 = EmptyListObject::make_new(); @@ -965,6 +967,7 @@ TEST(GoosObject, EmptyList) { // check print and inspect EXPECT_EQ(nil.print(), "()"); EXPECT_EQ(nil.inspect(), "[empty list] ()\n"); +#endif } /*! @@ -1286,4 +1289,4 @@ TEST(GoosSpecialForms, And) { for (auto x : {"(and)"}) { EXPECT_ANY_THROW(e(i, x)); } -} \ No newline at end of file +} diff --git a/test/test_kernel.cpp b/test/test_kernel.cpp index 658f357e8a..fb4b17a4c9 100644 --- a/test/test_kernel.cpp +++ b/test/test_kernel.cpp @@ -157,7 +157,8 @@ TEST(Kernel, ftoa) { ftoa(buffer, 1., 1, ' ', 1, 0); EXPECT_EQ("1.0", std::string(buffer)); - ftoa(buffer, 0.f / 0.f, 1, ' ', 4, 0); + float zero = 0.0f; + ftoa(buffer, 0.f / zero, 1, ' ', 4, 0); EXPECT_EQ("NaN", std::string(buffer)); ftoa(buffer, 1., 8, ' ', 1, 0); @@ -166,7 +167,7 @@ TEST(Kernel, ftoa) { ftoa(buffer, -1., 8, '0', 1, 0); EXPECT_EQ("0000-1.0", std::string(buffer)); - ftoa(buffer, 0.f / 0.f, 8, ' ', 4, 0); + ftoa(buffer, 0.f / zero, 8, ' ', 4, 0); EXPECT_EQ(" NaN", std::string(buffer)); ftoa(buffer, 0.1, 1, ' ', 4, 0); diff --git a/test/test_listener_deci2.cpp b/test/test_listener_deci2.cpp index 05e8c843db..d7b65cba2a 100644 --- a/test/test_listener_deci2.cpp +++ b/test/test_listener_deci2.cpp @@ -1,3 +1,5 @@ +#ifdef __linux__ + #include "gtest/gtest.h" #include "goalc/listener/Listener.h" #include "game/system/Deci2Server.h" @@ -123,4 +125,6 @@ TEST(Listener, ListenerMultipleDecis) { } l.disconnect(); } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/third-party/mman/CMakeLists.txt b/third-party/mman/CMakeLists.txt new file mode 100644 index 0000000000..bc8675e384 --- /dev/null +++ b/third-party/mman/CMakeLists.txt @@ -0,0 +1 @@ +add_library(mman SHARED mman.c) \ No newline at end of file diff --git a/third-party/mman/mman.c b/third-party/mman/mman.c new file mode 100644 index 0000000000..486ed94d88 --- /dev/null +++ b/third-party/mman/mman.c @@ -0,0 +1,180 @@ + +#include +#include +#include + +#include "mman.h" + +#ifndef FILE_MAP_EXECUTE +#define FILE_MAP_EXECUTE 0x0020 +#endif /* FILE_MAP_EXECUTE */ + +static int __map_mman_error(const DWORD err, const int deferr) +{ + if (err == 0) + return 0; + //TODO: implement + return err; +} + +static DWORD __map_mmap_prot_page(const int prot) +{ + DWORD protect = 0; + + if (prot == PROT_NONE) + return protect; + + if ((prot & PROT_EXEC) != 0) + { + protect = ((prot & PROT_WRITE) != 0) ? + PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; + } + else + { + protect = ((prot & PROT_WRITE) != 0) ? + PAGE_READWRITE : PAGE_READONLY; + } + + return protect; +} + +static DWORD __map_mmap_prot_file(const int prot) +{ + DWORD desiredAccess = 0; + + if (prot == PROT_NONE) + return desiredAccess; + + if ((prot & PROT_READ) != 0) + desiredAccess |= FILE_MAP_READ; + if ((prot & PROT_WRITE) != 0) + desiredAccess |= FILE_MAP_WRITE; + if ((prot & PROT_EXEC) != 0) + desiredAccess |= FILE_MAP_EXECUTE; + + return desiredAccess; +} + +void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) +{ + HANDLE fm, h; + + void * map = MAP_FAILED; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4293) +#endif + + const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)off : (DWORD)(off & 0xFFFFFFFFL); + const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)0 : (DWORD)((off >> 32) & 0xFFFFFFFFL); + const DWORD protect = __map_mmap_prot_page(prot); + const DWORD desiredAccess = __map_mmap_prot_file(prot); + + const off_t maxSize = off + (off_t)len; + + const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)maxSize : (DWORD)(maxSize & 0xFFFFFFFFL); + const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD)) ? + (DWORD)0 : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL); + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + errno = 0; + + if (len == 0 + /* Unsupported flag combinations */ + || (flags & MAP_FIXED) != 0 + /* Usupported protection combinations */ + || prot == PROT_EXEC) + { + errno = EINVAL; + return MAP_FAILED; + } + + h = ((flags & MAP_ANONYMOUS) == 0) ? + (HANDLE)_get_osfhandle(fildes) : INVALID_HANDLE_VALUE; + + if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE) + { + errno = EBADF; + return MAP_FAILED; + } + + fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL); + + if (fm == NULL) + { + errno = __map_mman_error(GetLastError(), EPERM); + return MAP_FAILED; + } + + map = MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len); + + CloseHandle(fm); + + if (map == NULL) + { + errno = __map_mman_error(GetLastError(), EPERM); + return MAP_FAILED; + } + + return map; +} + +int munmap(void *addr, size_t len) +{ + if (UnmapViewOfFile(addr)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int mprotect(void *addr, size_t len, int prot) +{ + DWORD newProtect = __map_mmap_prot_page(prot); + DWORD oldProtect = 0; + + if (VirtualProtect(addr, len, newProtect, &oldProtect)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int msync(void *addr, size_t len, int flags) +{ + if (FlushViewOfFile(addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int mlock(const void *addr, size_t len) +{ + if (VirtualLock((LPVOID)addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int munlock(const void *addr, size_t len) +{ + if (VirtualUnlock((LPVOID)addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} diff --git a/third-party/mman/mman.h b/third-party/mman/mman.h new file mode 100644 index 0000000000..febfbcbca1 --- /dev/null +++ b/third-party/mman/mman.h @@ -0,0 +1,57 @@ +/* + * sys/mman.h + * mman-win32 + */ + +#ifndef _SYS_MMAN_H_ +#define _SYS_MMAN_H_ + +#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. +#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. +#endif + +/* All the headers include this file. */ +#ifndef _MSC_VER +#include <_mingw.h> +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PROT_NONE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_FILE 0 +#define MAP_SHARED 1 +#define MAP_PRIVATE 2 +#define MAP_POPULATE 0x08000 +#define MAP_TYPE 0xf +#define MAP_FIXED 0x10 +#define MAP_ANONYMOUS 0x20 +#define MAP_32BIT 0x40 /* Only give out 32-bit addresses. */ +#define MAP_ANON MAP_ANONYMOUS + +#define MAP_FAILED ((void *)-1) + +/* Flags for msync. */ +#define MS_ASYNC 1 +#define MS_SYNC 2 +#define MS_INVALIDATE 4 + +void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); +int munmap(void *addr, size_t len); +int mprotect(void *addr, size_t len, int prot); +int msync(void *addr, size_t len, int flags); +int mlock(const void *addr, size_t len); +int munlock(const void *addr, size_t len); + +#ifdef __cplusplus +}; +#endif + +#endif /* _SYS_MMAN_H_ */