diff options
| author | John "Lameguy" Wilbert Villamor <lameguy64@gmail.com> | 2021-10-25 08:18:23 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-10-25 08:18:23 +0800 |
| commit | 8d17a5abe3c600b603f1910239d5df0fff9f4eed (patch) | |
| tree | 20d3110363ab468e99502337c0a46852662bb365 | |
| parent | 0a7f59de1eb079930e7c25ad35adc39c9946958c (diff) | |
| parent | 4e0d5bceb24042a6d31c76958ce2c6157369ac68 (diff) | |
| download | psn00bsdk-8d17a5abe3c600b603f1910239d5df0fff9f4eed.tar.gz | |
Merge pull request #41 from spicyjpeg/cmake
CMake fixes, Windows build instructions
| -rw-r--r-- | CMakeLists.txt | 106 | ||||
| -rw-r--r-- | INSTALL.md | 124 | ||||
| -rw-r--r-- | TOOLCHAIN.md | 223 | ||||
| -rw-r--r-- | changelog.txt | 16 | ||||
| -rw-r--r-- | cpack/setup.cmake | 184 | ||||
| -rw-r--r-- | doc/dev notes.txt | 20 | ||||
| -rw-r--r-- | examples/sound/vagsample/CMakeLists.txt | 22 | ||||
| -rw-r--r-- | examples/sound/vagsample/makefile | 65 | ||||
| -rw-r--r-- | examples/system/dynlink/main.c | 10 | ||||
| -rw-r--r-- | libpsn00b/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | libpsn00b/cmake/internal_setup.cmake | 17 | ||||
| -rw-r--r-- | libpsn00b/include/dlfcn.h | 25 | ||||
| -rw-r--r-- | libpsn00b/psxetc/dl.c | 206 | ||||
| -rw-r--r-- | libpsn00b/readme.txt | 32 | ||||
| -rw-r--r-- | toolchain.txt | 171 |
15 files changed, 771 insertions, 459 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5097a50..431e95b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,8 @@ project( # there's no way to mute it. include(GNUInstallDirs) +## Settings + # These are passed through to libpsn00b and the examples (they are defined in # the toolchain file). set( @@ -34,8 +36,12 @@ set( ) set( - SKIP_DOWNLOAD OFF - CACHE BOOL "Skip downloading and building tinyxml2 and mkpsxiso" + SKIP_TINYXML2 OFF + CACHE BOOL "Skip downloading and building tinyxml2 (if already installed)" +) +set( + SKIP_MKPSXISO OFF + CACHE BOOL "Skip downloading and building mkpsxiso (if already installed)" ) set( SKIP_EXAMPLES OFF @@ -49,50 +55,55 @@ set( # invoked (ExternalProject_Add() runs the subprojects' install step at build # time). set( - SUBPROJECT_ARGS + COMMON_ARGS -DPSN00BSDK_TC:PATH=${PSN00BSDK_TC} -DPSN00BSDK_TARGET:STRING=${PSN00BSDK_TARGET} + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} +) +set( + EXT_LIBRARY_ARGS + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_INSTALL_PREFIX:PATH=${PROJECT_BINARY_DIR}/install_temp + -DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=MultiThreaded$<$<CONFIG:Debug>:Debug> +) +set( + SUBPROJECT_ARGS -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_INSTALL_PREFIX:PATH=${PROJECT_BINARY_DIR}/install_tree - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} ) set( EXAMPLES_ARGS - -DPSN00BSDK_TC:PATH=${PSN00BSDK_TC} - -DPSN00BSDK_TARGET:STRING=${PSN00BSDK_TARGET} -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${PROJECT_BINARY_DIR}/install_tree/${CMAKE_INSTALL_LIBDIR}/libpsn00b/cmake/sdk.cmake -DCMAKE_INSTALL_PREFIX:PATH=${PROJECT_BINARY_DIR}/examples - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} ) ## External dependencies -if(NOT SKIP_DOWNLOAD) +if(NOT SKIP_TINYXML2) list(APPEND SUBPROJECT_ARGS -Dtinyxml2_ROOT:PATH=${PROJECT_BINARY_DIR}/install_temp) ExternalProject_Add( tinyxml2 GIT_REPOSITORY "https://github.com/leethomason/tinyxml2" - CMAKE_CACHE_ARGS - -DCMAKE_INSTALL_PREFIX:PATH=${PROJECT_BINARY_DIR}/install_temp - -DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=MultiThreaded$<$<CONFIG:Debug>:Debug> + CMAKE_CACHE_ARGS ${COMMON_ARGS} ${EXT_LIBRARY_ARGS} INSTALL_DIR install_temp ) +else() + list(APPEND SUBPROJECT_ARGS -Dtinyxml2_ROOT:PATH=${tinyxml2_ROOT}) + + # Create a dummy target so CMake doesn't throw missing dependency errors. + add_library(tinyxml2 INTERFACE) +endif() + +if(NOT SKIP_MKPSXISO) ExternalProject_Add( mkpsxiso - # IMPORTANT TODO: migrate to Lameguy64/mkpsxiso once PR #18 is merged GIT_REPOSITORY "https://github.com/Lameguy64/mkpsxiso" - GIT_TAG cmake - #GIT_REPOSITORY "https://github.com/Lameguy64/mkpsxiso" - CMAKE_CACHE_ARGS ${SUBPROJECT_ARGS} + CMAKE_CACHE_ARGS ${COMMON_ARGS} ${SUBPROJECT_ARGS} INSTALL_DIR install_tree DEPENDS tinyxml2 ) else() - list(APPEND SUBPROJECT_ARGS -Dtinyxml2_ROOT:PATH=${tinyxml2_ROOT}) - - # Create dummy targets so CMake doesn't throw missing dependency errors. - add_library(tinyxml2 INTERFACE) add_library(mkpsxiso INTERFACE) endif() @@ -101,21 +112,21 @@ endif() ExternalProject_Add( libpsn00b SOURCE_DIR ${PROJECT_SOURCE_DIR}/libpsn00b - CMAKE_CACHE_ARGS ${SUBPROJECT_ARGS} + CMAKE_CACHE_ARGS ${COMMON_ARGS} ${SUBPROJECT_ARGS} INSTALL_DIR install_tree ) ExternalProject_Add( tools SOURCE_DIR ${PROJECT_SOURCE_DIR}/tools - CMAKE_CACHE_ARGS ${SUBPROJECT_ARGS} + CMAKE_CACHE_ARGS ${COMMON_ARGS} ${SUBPROJECT_ARGS} INSTALL_DIR install_tree DEPENDS tinyxml2 ) ExternalProject_Add( examples SOURCE_DIR ${PROJECT_SOURCE_DIR}/examples - CMAKE_CACHE_ARGS ${EXAMPLES_ARGS} - INSTALL_DIR install_tree + CMAKE_CACHE_ARGS ${COMMON_ARGS} ${EXAMPLES_ARGS} + INSTALL_DIR examples DEPENDS libpsn00b tools mkpsxiso EXCLUDE_FROM_ALL ${SKIP_EXAMPLES} ) @@ -125,58 +136,15 @@ ExternalProject_Add( install( DIRECTORY ${PROJECT_BINARY_DIR}/install_tree/ # THE TRAILING SLASH IS IMPORTANT DESTINATION . + COMPONENT sdk USE_SOURCE_PERMISSIONS ) install( DIRECTORY doc template DESTINATION ${CMAKE_INSTALL_DATADIR}/psn00bsdk + COMPONENT docs ) ## CPack configuration -if(WIN32) - set(CPACK_GENERATOR ZIP NSIS) -elseif(APPLE) - # TODO: add a macOS installer and related options - set(CPACK_GENERATOR ZIP) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(CPACK_GENERATOR ZIP DEB RPM) -else() - set(CPACK_GENERATOR ZIP) -endif() - -set(CPACK_PACKAGE_DIRECTORY ${PROJECT_BINARY_DIR}/cpack) -set(CPACK_PACKAGE_NAME PSn00bSDK) -set(CPACK_PACKAGE_VENDOR Lameguy64) -set(CPACK_PACKAGE_CONTACT Lameguy64) -set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/cpack/icon.ico) -set(CPACK_PACKAGE_DESCRIPTION_FILE ${PROJECT_SOURCE_DIR}/cpack/description.txt) -set(CPACK_RESOURCE_FILE_WELCOME ${PROJECT_SOURCE_DIR}/cpack/welcome.txt) -set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md) -set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE.md) -set(CPACK_PRE_BUILD_SCRIPTS ${PROJECT_SOURCE_DIR}/cpack/fakeroot_fix.cmake) - -set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.28), cmake (>= 3.21), gcc-mipsel-unknown-elf") -set(CPACK_DEBIAN_PACKAGE_SUGGESTS "git") -set(CPACK_DEBIAN_PACKAGE_SECTION devel) -set(CPACK_RPM_PACKAGE_REQUIRES "cmake >= 3.21, gcc-mipsel-unknown-elf") -set(CPACK_RPM_PACKAGE_SUGGESTS "git") -#set(CPACK_RPM_PACKAGE_RELOCATABLE ON) - -set(CPACK_NSIS_MUI_ICON ${PROJECT_SOURCE_DIR}/cpack/icon.ico) -set(CPACK_NSIS_MUI_UNIICON ${PROJECT_SOURCE_DIR}/cpack/uninstall.ico) -set(CPACK_NSIS_MUI_HEADERIMAGE ${PROJECT_SOURCE_DIR}/cpack/nsis_header.bmp) -set(CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP ${PROJECT_SOURCE_DIR}/cpack/nsis_banner.bmp) -set(CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP ${PROJECT_SOURCE_DIR}/cpack/nsis_banner.bmp) -set(CPACK_NSIS_BRANDING_TEXT "PSn00bSDK - Meido-Tek Productions") -set(CPACK_NSIS_URL_INFO_ABOUT "${PROJECT_HOMEPAGE_URL}") -set(CPACK_NSIS_MODIFY_PATH ON) -set( - CPACK_NSIS_MENU_LINKS - "${PROJECT_HOMEPAGE_URL}" "About PSn00bSDK" - "https://github.com/Lameguy64/PSn00bSDK" "GitHub repo" -) - -# This will generate a CPack configuration file and add a "package" target to -# launch CPack. -include(CPack) +include(cpack/setup.cmake) @@ -6,27 +6,33 @@ The instructions below are for Windows and Linux. Building on macOS hasn't been tested but should work. -1. Install a host C compiler toolchain if you don't already have one (this is - required to build the tools). You can use MSVC or MinGW on Windows, or - install the `build-essential` package provided by most Linux distros. - -2. Install Git and CMake. Note that some Linux distros ship relatively old - versions of CMake, so make sure you have at least CMake 3.21. You will also - need [Ninja](https://ninja-build.org) (it is a single executable, you have to - copy it to any directory listed in the `PATH` environment variable) on - Windows as there is no preinstalled build system; on Linux you can use `make` - instead, but Ninja is still recommended. - -3. Build and install a GCC toolchain for `mipsel-unknown-elf`. As GCC is - notoriously hard to compile under Windows, you may download a precompiled +1. Set up a host compiler. Most Linux distros provide a `build-essential`, + `base-devel` or similar all-in-one package. You'll also need to install the + [Ninja](https://ninja-build.org) build engine (it's usually in a package + called `ninja-build`). On Windows install [MSys2](https://www.msys2.org), + then run the following command in the MSys2 shell to install MinGW and Ninja: + + ```bash + pacman -Syu mingw-w64-x86_64-gcc mingw-w64-x86_64-ninja + ``` + + Add `C:\msys64\mingw64\bin` (replace `C:\msys64` if you installed MSys2 to a + different location) to the `PATH` environment variable using System + Properties. + +2. Install Git and CMake. Note that MSys2 and some Linux distros ship relatively + old versions (PSn00bSDK requires 3.21+), so grab the latest CMake release + from [here](https://cmake.org) instead of through your package manager. + +3. Build and install a GCC toolchain for `mipsel-unknown-elf`, as detailed in + [TOOLCHAIN.md](TOOLCHAIN.md). On Windows, you may download a precompiled version from [Lameguy64's website](http://lameguy64.net?page=psn00bsdk) and - and extract it into Program Files instead. See [toolchain.txt](toolchain.txt) - for details on compiling GCC. + extract it into one of the directories listed below instead. -4. If you chose a non-standard install location for the toolchain, set the - `PSN00BSDK_TC` environment variable to point to the toolchain's root - directory. This step is unnecessary if you installed/extracted the toolchain - into any of these directories: +4. If you chose a non-standard install location for the toolchain, add the `bin` + subfolder (inside the top-level toolchain directory) to the `PATH` + environment variable. This step is unnecessary if you installed/extracted the + toolchain into any of these directories: - `C:\Program Files\mipsel-unknown-elf` - `C:\Program Files (x86)\mipsel-unknown-elf` @@ -35,49 +41,84 @@ tested but should work. - `/usr/mipsel-unknown-elf` - `/opt/mipsel-unknown-elf` -5. Clone/download the PSn00bSDK repo and run the following commands: +5. Clone/download the PSn00bSDK repo and run the following commands from its + directory: ```bash - cmake -S . -B ./build -G Ninja --install-prefix INSTALL_PATH + cmake -S . -B ./build -G Ninja cmake --build ./build + ``` + + If you want to install the SDK to a custom location rather than the default + one (`C:\Program Files\PSn00bSDK` or `/usr/local` depending on your OS), add + `--install-prefix <INSTALL_PATH>` to the first command. Remove `-G Ninja` to + use `make` instead of Ninja (slower, not recommended). + + If you run into errors, try passing `-DSKIP_TINYXML2=ON` to the first command + after installing `tinyxml2` manually. [See below](#advanced-build-options) + for more details. + +6. Install the SDK to the path you chose by running this command (add `sudo` if + necessary): + + ```bash cmake --install ./build ``` - Replace `INSTALL_PATH` with the directory you want PSn00bSDK to be installed - to (default is `C:\Program Files\PSn00bSDK` or `/usr/local`), and remove - `-G Ninja` if you want to use `make` instead (not recommended). The following - subdirectories will be created: + This will create and populate the following directories: - - `INSTALL_PATH/bin` - - `INSTALL_PATH/lib/libpsn00b` - - `INSTALL_PATH/share/psn00bsdk` + - `<INSTALL_PATH>/bin` + - `<INSTALL_PATH>/lib/libpsn00b` + - `<INSTALL_PATH>/share/psn00bsdk` -6. Set the `PSN00BSDK_LIBS` environment variable to - `INSTALL_PATH/lib/libpsn00b` and add `INSTALL_PATH/bin` to `PATH`. +7. Set the `PSN00BSDK_LIBS` environment variable to point to the `lib/libpsn00b` + subfolder inside the install directory. You might also want to add the `bin` + folder to `PATH` if it's not listed already. Although not strictly required, you'll probably want to install a PS1 emulator with debugging capabilities such as [no$psx](https://problemkaputt.de/psx.htm) -(Windows only) or [pcsx-redux](https://github.com/grumpycoders/pcsx-redux). +(Windows only), [DuckStation](https://github.com/stenzek/duckstation) or +[pcsx-redux](https://github.com/grumpycoders/pcsx-redux). **Avoid ePSXe and anything based on MAME** as they are inaccurate. -## Building installer packages +## Advanced build options + +### Skipping external dependency downloads + +By default [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) (required for +building CD images) and [tinyxml2](https://github.com/leethomason/tinyxml2) +(required to build mkpsxiso and other SDK tools) are automatically cloned from +their respective repos and built as part of the PSn00bSDK build process, +*even if they are already installed*. + +If you wish to disable this behavior (e.g. because it leads to errors, or to +perform an offline build), invoke CMake with the `-DSKIP_MKPSXISO=ON` and/or +`-DSKIP_TINYXML2=ON` options when configuring the SDK. Note that you must have +`mkpsxiso` and/or `tinyxml2` already installed (either manually or via vcpkg or +your distro's package manager) to be able to skip them. + +### Building installer packages CPack can be used to build NSIS-based installers, DEB/RPM packages and zipped -releases. Note that currently none of the built packages include the toolchain, -thus their usefulness is limited. Distributing prebuilt releases is discouraged -anyway since PSn00bSDK is still far from being feature-complete. +releases that include built SDK libraries, headers as well as the GCC toolchain. +Distributing prebuilt releases is however discouraged since PSn00bSDK is still +far from being feature-complete. -1. Follow steps 1-4 above to set up the toolchain, then install NSIS on Windows - or `dpkg` and `rpm` on Linux. +1. Follow steps 1-4 above to set up the toolchain, then install + [NSIS](https://nsis.sourceforge.io/Download) on Windows or `dpkg` and `rpm` + on Linux. 2. Run the following commands from the PSn00bSDK directory: ```bash - cmake -S . -B ./build -G Ninja + cmake -S . -B ./build -G Ninja -DBUNDLE_TOOLCHAIN=ON cmake --build ./build -t package ``` - All built packages will be copied to the `build/cpack` folder. + All built packages will be copied to the `build/packages` folder. + + **NOTE**: do not use `-DSKIP_MKPSXISO=ON`, otherwise the mkpsxiso binary will + not be included in the packages. ## Creating a project @@ -89,7 +130,6 @@ anyway since PSn00bSDK is still far from being feature-complete. ```bash cmake -S . -B ./build -G Ninja cmake --build ./build - cmake --install ./build ``` If you did everything correctly there should be a `template.bin` CD image in @@ -99,10 +139,10 @@ Note that, even though the template relies on the `PSN00BSDK_LIBS` environment variable to locate the SDK by default, you can also specify the path directly on the CMake command line by adding `-DCMAKE_TOOLCHAIN_FILE=INSTALL_PATH/lib/libpsn00b/cmake/sdk.cmake` (replace -`INSTALL_PATH` as usual) to the first command. +`INSTALL_PATH`) to the first command. The toolchain script defines a few CMake macros to create PS1 executables, DLLs and CD images. See the [reference](doc/cmake_reference.md) for details. ----------------------------------------- -_Last updated on 2021-09-26 by spicyjpeg_ +_Last updated on 2021-10-18 by spicyjpeg_ diff --git a/TOOLCHAIN.md b/TOOLCHAIN.md new file mode 100644 index 0000000..8ecee38 --- /dev/null +++ b/TOOLCHAIN.md @@ -0,0 +1,223 @@ + +# Building the GCC toolchain + +If you wish to build the toolchain yourself, beware that this process can get +pretty tedious if your machine is not fairly recent. Ensure you have at least a +quad-core processor and 4 GB of free space before continuing. + +You'll need a Linux environment, even if you want to build a Windows toolchain +(as GCC is basically impossible to build under Windows but can be cross-compiled +via MinGW). Due to how the GCC build process works, you'll have to build a Linux +version of the toolchain first to be able to compile it for Windows. This +basically means you will have to build the whole toolchain twice if you want to +target Windows. + +These instructions are for Debian/Ubuntu, however it should be relatively easy +to follow them if you are using another distro. If you do not have access to a +Linux system already, consider spinning up a VM (a headless Debian or Ubuntu +Server install is recommended) or using WSL, whose setup is out of the scope of +this guide. + +## Choosing a GCC version + +PSn00bSDK *should* work with any GCC version. In most cases you'll want to get +the latest stable release of GCC and binutils. If for some reason you are having +problems you may try building one of the following versions, which have been +tested extensively: + +- GCC **7.4.0** with binutils **2.31** +- GCC **11.2.0** with binutils **2.37** + +If you wish to pick an older GCC release but don't know which binutils version +it requires, see [here](https://wiki.osdev.org/Cross-Compiler_Successful_Builds) +for a compatibility table. + +## Downloading GCC + +1. Run the following commands to install a host toolchain and prerequisites + (adapt them for non-Debian distros if necessary): + + ```bash + sudo apt update + sudo apt install -y build-essential make wget + ``` + +2. Create an empty directory to store build artifacts in. You'll be able to + delete it once the toolchain is installed. + +3. Download the GCC and binutils source packages from + [here](https://ftpmirror.gnu.org/gnu) and unzip them into the folder you + created, or run the following commands to do the same (replace `<VERSION>` + with the versions you chose): + + ```bash + wget https://ftpmirror.gnu.org/gnu/binutils/binutils-<VERSION>.tar.xz + wget https://ftpmirror.gnu.org/gnu/gcc/gcc-<VERSION>/gcc-<VERSION>.tar.xz + tar xvf binutils-<VERSION>.tar.xz + tar xvf gcc-<VERSION>.tar.xz + rm -f *.tar.xz + ``` + +4. From the extracted GCC directory run the `download_prerequisites` script to + download additional dependencies: + + ```bash + cd gcc-<VERSION> + ./contrib/download_prerequisites + ``` + +## Building binutils + +1. Go back to the folder you made earlier and create a new subdirectory to build + binutils in (don't create it inside the extracted binutils source directory). + Call it `binutils-build` or whatever. + +2. Run the binutils configuration script from that folder: + + ```bash + ../binutils-<VERSION>/configure \ + --prefix=/usr/local/mipsel-unknown-elf --target=mipsel-unknown-elf \ + --disable-docs --disable-nls --with-float=soft + ``` + + Replace `<VERSION>` as usual. If you don't want to install the toolchain into + `/usr/local/mipsel-unknown-elf` you can change the `--prefix` option. + +3. Compile and install binutils (this will take a few minutes to finish): + + ```bash + make -j 4 + sudo make install-strip + ``` + + Increase `-j 4` to speed up the build if your machine or VM has more than 4 + CPU cores. + + **NOTE**: if the build fails with a "`uint` undeclared" or similar error, try + editing the source file that caused the error and pasting this line at the + beginning: + + ```c + typedef unsigned int uint; + ``` + + Rerun `make` to resume the build after saving the file. + +## Building GCC + +The process is mostly the same as binutils, just with different configuration +options. + +1. Go back to the main directory and create an empty `gcc-build` (or whatever) + subfolder. + +2. Run the GCC configuration script from there: + + ```bash + ../gcc-<VERSION>/configure \ + --prefix=/usr/local/mipsel-unknown-elf --target=mipsel-unknown-elf \ + --disable-docs --disable-nls --disable-libada --disable-libssp \ + --disable-libquadmath --disable-libstdc++-v3 --with-float=soft \ + --enable-languages=c,c++ --with-gnu-as --with-gnu-ld + ``` + + If you previously set a custom installation path, remember to set it here as + well (it must be the same). + +3. Compile and install GCC (will take a long time, usually around half an hour): + + ```bash + make -j 4 + sudo make install-strip + ``` + + Increase `-j 4` to speed up the build if your machine or VM has more than 4 + threads. + +4. Add the toolchain to the `PATH` environment variable. This is required to + rebuild the toolchain for Windows (see below), but it will also allow + PSn00bSDK to find the toolchain if you installed it in a custom location. + + Edit the `.bashrc` or `.bash_profile` file in your home directory and add + this line at the end (replace the path with the install location you chose + earlier, but keep the `/bin` at the end): + + ```bash + export PATH=$PATH:/usr/local/mipsel-unknown-elf/bin + ``` + + Restart the shell by closing and reopening the terminal window or SSH + connection afterwards. + +## Rebuilding for Windows + +At this point you should be able to build and install PSn00bSDK on your Linux +system. The instructions below are for building a second copy of the toolchain +that runs on Windows. + +1. Install the MinGW host toolchain: + + ```bash + sudo apt install -y g++-mingw-w64-x86-64 + ``` + +2. Create two new `binutils-win` and `gcc-win` folders in the directory you + extracted/built everything in. + +3. From the `binutils-win` directory, rerun the binutils configuration script + with the following options (do not change the installation path): + + ```bash + ../binutils-<VERSION>/configure \ + --build=x86_64-linux-gnu --host=x86_64-w64-mingw32 \ + --prefix=/tmp/mipsel-unknown-elf --target=mipsel-unknown-elf \ + --disable-docs --disable-nls --with-float=soft + ``` + + Then build binutils again: + + ```bash + make -j 4 + make install-strip + ``` + +4. Do the same for GCC from the `gcc-win` directory: + + ```bash + ../gcc-<VERSION>/configure \ + --build=x86_64-linux-gnu --host=x86_64-w64-mingw32 \ + --prefix=/tmp/mipsel-unknown-elf --target=mipsel-unknown-elf \ + --disable-docs --disable-nls --disable-libada --disable-libssp \ + --disable-libquadmath --disable-libstdc++-v3 --with-float=soft \ + --enable-languages=c,c++ --with-gnu-as --with-gnu-ld + ``` + + And build it as usual: + + ```bash + make -j 4 + make install-strip + ``` + +5. Copy the entire `/tmp/mipsel-unknown-elf` directory over to your Windows + machine using VM shared folders, a network share, `scp` or whichever method + you prefer. It's recommended to put the toolchain in + `C:\Program Files\mipsel-unknown-elf` or `C:\mipsel-unknown-elf`. + +6. If you want to keep the toolchain in another location and/or use it from the + command line, add the `bin` subdirectory inside `mipsel-unknown-elf` to the + `PATH` environment variable (as you did on Linux) using System Properties. + +## Note regarding C++ support + +C++ support in PSn00bSDK, besides compile-time features like `constexpr`, only +goes as far as basic classes, namespaces and the ability to dynamically create +and delete class objects at any point of the program. The required dependencies +(which are just wrappers around `malloc()` and `free()`) are supplied by `libc`. + +Standard C++ libraries are not implemented and likely never going to be +implemented due to bloat concerns that it may introduce. Besides, the official +SDK lacks full C++ support as well. + +----------------------------------------- +_Last updated on 2021-10-18 by spicyjpeg_ diff --git a/changelog.txt b/changelog.txt index 7f141f6..3d7ee97 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,22 @@ PSn00bSDK changelog Items that are lower in the log are more recently implemented. +10-17-2021 by spicyjpeg: + +* Added SKIP_TINYXML2 and SKIP_MKPSXISO build options in place of the old + SKIP_DOWNLOAD option, and a new BUNDLE_TOOLCHAIN option for building packages. + Updated INSTALL.md accordingly and fixed git branch error when downloading + mkpsxiso during build. + +* Rewritten toolchain.txt (which is now TOOLCHAIN.md) and added proper Windows + toolchain build instructions. + +* Added safety checks in CMake scripts to ensure elf2x/mkpsxiso are installed + before attempting to build an executable or CD image. + +* examples: Added CMake build script to (and removed makefile from) vagsample. + + 10-06-2021 by Lameguy64: * psxspu: Fixed register typo in SpyKeyOn causing unpredictable instability. diff --git a/cpack/setup.cmake b/cpack/setup.cmake new file mode 100644 index 0000000..a483462 --- /dev/null +++ b/cpack/setup.cmake @@ -0,0 +1,184 @@ +# This script sets up all CPack-related variables and generates installation +# rules to bundle the GCC toolchain and CMake in packages. It is included by +# the main CMakeLists.txt script. + +cmake_minimum_required(VERSION 3.21) + +## Settings + +# These can be set from the command line to completely disable bundling CMake +# and GCC in packages built by CPack. They have no effect on installing the SDK +# normally. +# TODO: BUNDLE_CMAKE should not be used, needs more testing +set( + BUNDLE_TOOLCHAIN OFF + CACHE BOOL "Include the GCC toolchain in installer packages" +) +set( + BUNDLE_CMAKE OFF + CACHE BOOL "Include CMake in installer packages (Windows only)" +) + +## Bundled components + +# "Install" the toolchain and CMake (by pulling files from its their install +# locations). This is only useful when building installers, as CPack will pick +# up these installation rules and bundle the toolchain in the installers. +# NOTE: unfortunately there is no easy way to reuse the toolchain finding logic +# in sdk.cmake, so I had to copypaste it here. +if(BUNDLE_TOOLCHAIN) + find_program( + _gcc ${PSN00BSDK_TARGET}-gcc + HINTS + ${PSN00BSDK_TC}/bin + ${PSN00BSDK_TC}/../bin + PATHS + "C:/Program Files/${PSN00BSDK_TARGET}/bin" + "C:/Program Files (x86)/${PSN00BSDK_TARGET}/bin" + "C:/${PSN00BSDK_TARGET}/bin" + /opt/${PSN00BSDK_TARGET}/bin + /usr/local/${PSN00BSDK_TARGET}/bin + /usr/${PSN00BSDK_TARGET}/bin + NO_CACHE REQUIRED + ) + cmake_path(GET _gcc PARENT_PATH _bin) + cmake_path(GET _bin PARENT_PATH _toolchain) + + # Check if the toolchain is actually part of an existing PSn00bSDK install. + # If so, disable bundling as we can't determine which files are part of the + # toolchain and which ones are part of the SDK. + if(EXISTS ${_bin}/elf2x${CMAKE_EXECUTABLE_SUFFIX}) + message(FATAL_ERROR "${_toolchain} contains a full PSn00bSDK installation. To bundle the toolchain, set PSN00BSDK_TC to point to a directory containing only the toolchain files.") + else() + install( + DIRECTORY ${_toolchain}/ + DESTINATION . + COMPONENT toolchain + USE_SOURCE_PERMISSIONS + #EXCLUDE_FROM_ALL + ) + endif() +endif() + +if(BUNDLE_CMAKE) + cmake_path(GET CMAKE_COMMAND PARENT_PATH _bin) + cmake_path(GET _bin PARENT_PATH _cmakedir) + + # Bundling CMake is only allowed on Windows, both because finding CMake + # installation files is difficult on Linux and because it's better to + # specify CMake as a DEB/RPM dependency anyway. + if(NOT WIN32) + message(FATAL_ERROR "Bundling CMake into installers is only supported (and should only be done) on Windows.") + else() + install( + DIRECTORY ${_cmakedir}/ + DESTINATION . + COMPONENT cmake + USE_SOURCE_PERMISSIONS + #EXCLUDE_FROM_ALL + ) + endif() +endif() + +## Variables common to all package types + +if(NOT DEFINED CPACK_GENERATOR) + if(WIN32) + set(CPACK_GENERATOR ZIP NSIS) + elseif(APPLE) + # TODO: add a macOS installer and related options + set(CPACK_GENERATOR ZIP) + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(CPACK_GENERATOR ZIP DEB RPM) + else() + set(CPACK_GENERATOR ZIP) + endif() +endif() + +set(CPACK_VERBATIM_VARIABLES ON) +set(CPACK_ARCHIVE_THREADS 0) +set(CPACK_PACKAGE_DIRECTORY ${PROJECT_BINARY_DIR}/packages) +set(CPACK_PACKAGE_NAME PSn00bSDK) +set(CPACK_PACKAGE_VENDOR Lameguy64) +set(CPACK_PACKAGE_CONTACT Lameguy64) +set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md) +set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE.md) +set(CPACK_PACKAGE_ICON ${CMAKE_CURRENT_LIST_DIR}/icon.ico) +set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_CURRENT_LIST_DIR}/description.txt) +set(CPACK_RESOURCE_FILE_WELCOME ${CMAKE_CURRENT_LIST_DIR}/welcome.txt) +set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_CURRENT_LIST_DIR}/fakeroot_fix.cmake) +set(CPACK_PACKAGE_INSTALL_DIRECTORY PSn00bSDK) + +## DEB/RPM variables + +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.28), cmake (>= 3.21)") +set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "ninja-build (>= 1.10)") +set(CPACK_DEBIAN_PACKAGE_SUGGESTS "git (>= 2.25)") +set(CPACK_DEBIAN_PACKAGE_SECTION devel) +set(CPACK_RPM_PACKAGE_REQUIRES "cmake >= 3.21") +set(CPACK_RPM_PACKAGE_SUGGESTS "ninja-build >= 1.10, git >= 2.25") +#set(CPACK_RPM_PACKAGE_RELOCATABLE ON) + +## NSIS variables + +set(CPACK_NSIS_MUI_ICON ${CMAKE_CURRENT_LIST_DIR}/icon.ico) +set(CPACK_NSIS_MUI_UNIICON ${CMAKE_CURRENT_LIST_DIR}/uninstall.ico) +set(CPACK_NSIS_MUI_HEADERIMAGE ${CMAKE_CURRENT_LIST_DIR}/nsis_header.bmp) +set(CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP ${CMAKE_CURRENT_LIST_DIR}/nsis_banner.bmp) +set(CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP ${CMAKE_CURRENT_LIST_DIR}/nsis_banner.bmp) +set(CPACK_NSIS_BRANDING_TEXT "PSn00bSDK - Meido-Tek Productions") +set(CPACK_NSIS_URL_INFO_ABOUT "${PROJECT_HOMEPAGE_URL}") +set(CPACK_NSIS_MODIFY_PATH ON) +set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) +set( + CPACK_NSIS_MENU_LINKS + "${PROJECT_HOMEPAGE_URL}" "About PSn00bSDK" + "https://github.com/Lameguy64/PSn00bSDK" "GitHub repo" +) + +# Paths in CPACK_NSIS_* variables are not converted to native paths by CMake +# for some reason (and NSIS doesn't like paths with forward slashes), so we +# have to do it manually. +foreach( + _var IN ITEMS + CPACK_NSIS_MUI_ICON + CPACK_NSIS_MUI_UNIICON + CPACK_NSIS_MUI_HEADERIMAGE + CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP + CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP +) + cmake_path(NATIVE_PATH ${_var} ${_var}) +endforeach() + +## Component setup + +# This must be done after setting CPack-related variables. +include(CPack) + +cpack_add_component( + sdk + DISPLAY_NAME "SDK libraries and tools" + DESCRIPTION "These files are always required and their installation cannot be skipped." + REQUIRED +) +cpack_add_component( + docs + DISPLAY_NAME "SDK documentation" + DESCRIPTION "Select to install additional documentation files and a project template (recommended)." +) + +if(BUNDLE_TOOLCHAIN) + cpack_add_component( + toolchain + DISPLAY_NAME "GCC MIPS toolchain" + DESCRIPTION "Do not skip unless you already have a toolchain that targets ${PSN00BSDK_TARGET} installed." + ) +endif() +if(BUNDLE_CMAKE) + cpack_add_component( + cmake + DISPLAY_NAME "CMake ${CMAKE_VERSION}" + DESCRIPTION "Select this if you do not have CMake installed already. Note that CMake will be installed in the same directory as PSn00bSDK." + DISABLED + ) +endif() diff --git a/doc/dev notes.txt b/doc/dev notes.txt index 8fd8d7f..1bc9ccd 100644 --- a/doc/dev notes.txt +++ b/doc/dev notes.txt @@ -153,6 +153,26 @@ ignore -isystem completely). I eventually gave up and just set the include directories manually for each target, and for some reason CMake actually started passing -I instead of -isystem to GCC. +* When using CPack with NSIS, all CPACK_NSIS_* variables are passed to NSIS +verbatim, i.e. without the usual slash-to-backslash path conversion that CMake +does on Windows. Most Windows programs accept paths with slashes without issue, +unfortunately the NSIS builder is not one of those. To add insult to injury, +CMake doesn't even escape backslashes by default when quoting strings in the +generated CPack config file! So you have to convert the paths manually *and* +tell CMake to enable escaping by setting CPACK_VERBATIM_VARIABLES, like this: + + set(CPACK_VERBATIM_VARIABLES ON) + foreach( + _var IN ITEMS + CPACK_NSIS_MUI_ICON + CPACK_NSIS_MUI_UNIICON + CPACK_NSIS_MUI_HEADERIMAGE + CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP + CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP + ) + cmake_path(NATIVE_PATH ${_var} ${_var}) + endforeach() + * Not a CMake/CPack bug per se, but NSIS is picky about the banner and header images shown in generated installers. They must be Windows BMP version 3 files with no alpha channel, no compression and no metadata. They can either be diff --git a/examples/sound/vagsample/CMakeLists.txt b/examples/sound/vagsample/CMakeLists.txt new file mode 100644 index 0000000..1f42608 --- /dev/null +++ b/examples/sound/vagsample/CMakeLists.txt @@ -0,0 +1,22 @@ +# PSn00bSDK example CMake script +# (C) 2021 spicyjpeg - MPL licensed + +cmake_minimum_required(VERSION 3.21) + +if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS}) + set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake) +endif() + +project( + vagsample + LANGUAGES C + VERSION 1.0.0 + DESCRIPTION "PSn00bSDK SPU sound playback example" + HOMEPAGE_URL "http://lameguy64.net/?page=psn00bsdk" +) + +file(GLOB _sources *.c) +psn00bsdk_add_executable(vagsample STATIC ${_sources}) +#psn00bsdk_add_cd_image(vagsample_iso vagsample iso.xml DEPENDS vagsample) + +install(FILES ${PROJECT_BINARY_DIR}/vagsample.exe DESTINATION .) diff --git a/examples/sound/vagsample/makefile b/examples/sound/vagsample/makefile deleted file mode 100644 index acbaaad..0000000 --- a/examples/sound/vagsample/makefile +++ /dev/null @@ -1,65 +0,0 @@ -# PSn00bSDK makefile template -# Part of the PSn00bSDK Project -# 2019 - 2021 Lameguy64 / Meido-Tek Productions - -## Settings - -PSN00BSDK_LIBS ?= ../../../libpsn00b - -# Edit this to point to psn00bsdk-setup.mk, or copy that over to your project's -# root folder (it only depends on environment variables). -include ../../../psn00bsdk-setup.mk - -# Project target name -TARGET = vagsample - -## Files - -# Searches for C, C++ and S (assembler) files in local directory -CFILES = $(notdir $(wildcard *.c)) -CPPFILES= $(notdir $(wildcard *.cpp)) -AFILES = $(notdir $(wildcard *.s)) - -# Create names for object files -OFILES = $(addprefix build/,$(CFILES:.c=.o)) \ - $(addprefix build/,$(CPPFILES:.cpp=.o)) \ - $(addprefix build/,$(AFILES:.s=.o)) - -# Project specific includes and libraries -# (use -I for include dirs, -L for library dirs, -l for static libraries) -INCLUDE += -LIBDIRS += -LIBS += - -## Build rules - -#all: iso -all: build/$(TARGET) - -iso: build/$(TARGET) resources - $(MKPSXISO) -y -q iso.xml - -resources: - # Add commands to build/convert your assets here - #$(LZPACK) data.xml - -build/$(TARGET): $(OFILES) - @mkdir -p $(dir $@) - $(LD) $(LDFLAGS_EXE) $(LIBDIRS) $^ $(LIBS) -o $@ - $(NM) -f posix -l -n $@ >$@.map - $(ELF2X) -q $@ $@.exe - -build/%.o: %.c - @mkdir -p $(dir $@) - $(CC) $(CFLAGS_EXE) $(INCLUDE) -c $< -o $@ - -build/%.o: %.cpp - @mkdir -p $(dir $@) - $(CXX) $(CPPFLAGS_EXE) $(INCLUDE) -c $< -o $@ - -build/%.o: %.s - @mkdir -p $(dir $@) - $(CC) $(AFLAGS_EXE) $(INCLUDE) -c $< -o $@ - -clean: - rm -rf build diff --git a/examples/system/dynlink/main.c b/examples/system/dynlink/main.c index 70314da..33f6f44 100644 --- a/examples/system/dynlink/main.c +++ b/examples/system/dynlink/main.c @@ -62,7 +62,9 @@ const void *const DO_NOT_STRIP[] __attribute__((section(".dummy"))) = { &InitGeom, &RotMatrix, &TransMatrix, - &MulMatrix0 + &MulMatrix0, + &GetTimInfo, + &LoadImage }; static const char *const DLL_FILENAMES[] = { @@ -141,7 +143,7 @@ void load_dll(const char *filename) { dll = dlopen(filename, RTLD_LAZY); if (!dll) - SHOW_ERROR("FAILED TO LOAD %s\n%s\n", filename, dlerror()); + SHOW_ERROR("FAILED TO LOAD %s\nERROR=%d\n", filename, (int32_t) dlerror()); dll_api.init = dlsym(dll, "init"); dll_api.render = dlsym(dll, "render"); @@ -168,12 +170,12 @@ int main(int argc, const char* argv[]) { SHOW_STATUS("LOADING SYMBOL MAP\n"); if (!DL_LoadSymbolMap("cdrom:MAIN.MAP;1")) - SHOW_ERROR("FAILED TO LOAD SYMBOL MAP\n%s\n", dlerror()); + SHOW_ERROR("FAILED TO LOAD SYMBOL MAP\nERROR=%d\n", (int32_t) dlerror()); // Try to obtain a reference to a local function. void (*_display)() = DL_GetSymbolByName("display"); if (!_display) - SHOW_ERROR("FAILED TO LOOK UP LOCAL FUNCTION\n%s\n", dlerror()); + SHOW_ERROR("FAILED TO LOOK UP LOCAL FUNCTION\nERROR=%d\n", (int32_t) dlerror()); printf("Symbol map test, display() @ %08x\n", _display); diff --git a/libpsn00b/CMakeLists.txt b/libpsn00b/CMakeLists.txt index 52914d3..083208a 100644 --- a/libpsn00b/CMakeLists.txt +++ b/libpsn00b/CMakeLists.txt @@ -41,10 +41,17 @@ foreach(_library IN LISTS PSN00BSDK_LIBRARIES) endforeach() # Extract libgcc's contents and merge them into libc after building. +# Unfortunately glob expressions won't work on Windows, so we have to manually +# enumerate the contents of libgcc and save the list to a temporary file (as it +# is too long to be passed directly on the command line). This is actually the +# most reliable way I found to do this (I tried $<TARGET_OBJECTS> to no avail). add_custom_command( TARGET c POST_BUILD + COMMAND ${CMAKE_AR} t ${PSN00BSDK_LIBGCC} $<ANGLE-R>_libgcc.txt COMMAND ${CMAKE_AR} x ${PSN00BSDK_LIBGCC} - COMMAND ${CMAKE_AR} rsuU $<TARGET_FILE:c> *.o + COMMAND ${CMAKE_AR} q $<TARGET_FILE:c> \@_libgcc.txt + COMMAND ${CMAKE_AR} s $<TARGET_FILE:c> + #COMMAND ${CMAKE_AR} rsuU $<TARGET_FILE:c> *.o COMMENT "Merging libgcc contents into SDK libc" ) diff --git a/libpsn00b/cmake/internal_setup.cmake b/libpsn00b/cmake/internal_setup.cmake index e3d4be7..377afbc 100644 --- a/libpsn00b/cmake/internal_setup.cmake +++ b/libpsn00b/cmake/internal_setup.cmake @@ -79,6 +79,12 @@ function(psn00bsdk_add_executable name type) message(FATAL_ERROR "Invalid executable type: ${type} (must be STATIC or DYNAMIC)") endif() + # Throw an error if elf2x was not found (which should never happen if the + # SDK is installed properly). + if(ELF2X STREQUAL "ELF2X-NOTFOUND") + message(FATAL_ERROR "Failed to locate elf2x. Check your PATH environment variable.") + endif() + add_executable (${name} ${ARGN}) target_link_libraries(${name} psn00bsdk_${_type}_exe) set_target_properties(${name} PROPERTIES PREFIX "" SUFFIX ".elf") @@ -146,6 +152,13 @@ endfunction() # [additional options passed to add_custom_target()] # ) function(psn00bsdk_add_cd_image name image_name config_file) + # Throw an error if mkpsxiso was not found. Performing this check manually + # (instead of just marking mkpsxiso as required) allows simple projects to + # be built even if mkpsxiso is not installed. + if(MKPSXISO STREQUAL "MKPSXISO-NOTFOUND") + message(FATAL_ERROR "Failed to locate mkpsxiso. If mkpsxiso wasn't installed alongside the SDK, check your PATH environment variable.") + endif() + set(CD_IMAGE_NAME ${image_name}) configure_file(${config_file} _gen_${config_file}) @@ -158,4 +171,6 @@ function(psn00bsdk_add_cd_image name image_name config_file) ) endfunction() -## Helper functions for assets (TODO) +## Helper functions for assets + +# TODO: add them diff --git a/libpsn00b/include/dlfcn.h b/libpsn00b/include/dlfcn.h index 0c51821..b3a5cec 100644 --- a/libpsn00b/include/dlfcn.h +++ b/libpsn00b/include/dlfcn.h @@ -20,6 +20,23 @@ #define RTLD_DEFAULT ((DLL *) 0) +typedef enum _DL_Error { + RTLD_E_NONE = 0, // No error + RTLD_E_FILE_OPEN = 1, // Unable to find or open file + RTLD_E_FILE_ALLOC = 2, // Unable to allocate buffer to load file into + RTLD_E_FILE_READ = 3, // Failed to read file + RTLD_E_NO_MAP = 4, // No symbol map has been loaded yet + RTLD_E_MAP_ALLOC = 5, // Unable to allocate symbol map structures + RTLD_E_NO_SYMBOLS = 6, // No symbols found in symbol map + RTLD_E_DLL_NULL = 7, // Unable to initialize DLL from null pointer + RTLD_E_DLL_ALLOC = 8, // Unable to allocate DLL metadata structures + RTLD_E_DLL_FORMAT = 9, // Unsupported DLL type or format + RTLD_E_NO_FILE_API = 10, // psxetc has been built without file support + RTLD_E_MAP_SYMBOL = 11, // Symbol not found in symbol map + RTLD_E_DLL_SYMBOL = 12, // Symbol not found in DLL + RTLD_E_HASH_LOOKUP = 13 // Hash table lookup failed due to internal error +} DL_Error; + typedef enum _DL_ResolveMode { RTLD_LAZY = 1, // Resolve functions when they are first called (default) RTLD_NOW = 2 // Resolve all symbols immediately on load @@ -161,13 +178,13 @@ void dlclose(DLL *dll); */ void *dlsym(DLL *dll, const char *name); /** - * @brief Returns a string describing the last error that occurred, or null if - * no error has occurred since the last call to dlerror() (i.e. calling this + * @brief Returns a code describing the last error that occurred, or DL_E_NONE + * if no error has occurred since the last call to dlerror() (i.e. calling this * also resets the internal error flags). * - * @return NULL or pointer to const string + * @return NULL or member of DL_Error enum */ -const char *const dlerror(void); +DL_Error dlerror(void); #ifdef __cplusplus } diff --git a/libpsn00b/psxetc/dl.c b/libpsn00b/psxetc/dl.c index e405efc..586e7e1 100644 --- a/libpsn00b/psxetc/dl.c +++ b/libpsn00b/psxetc/dl.c @@ -48,42 +48,19 @@ typedef struct { void *ptr; } MapEntry; -typedef enum { - ERR_NONE, - ERR_FILE, - ERR_FILE_MALLOC, - ERR_FILE_READ, - ERR_NO_MAP, - ERR_MAP_MALLOC, - ERR_NO_SYMBOLS, - ERR_DLL_NULL, - ERR_DLL_MALLOC, - ERR_DLL_FORMAT, - ERR_NO_FILE_API, - ERR_MAP_SYMBOL, - ERR_DLL_SYMBOL -} ErrorCode; +typedef struct { + uint32_t nbucket; + uint32_t nchain; + + MapEntry *entries; + uint32_t *bucket; + uint32_t *chain; +} SymbolMap; /* Data */ -static const char *const DL_ERROR_MESSAGES[] = { - "Unable to find file", - "Unable to allocate buffer to load file into", - "Failed to read file", - "No symbol map has been loaded yet", - "Unable to allocate symbol map structures", - "No symbols found in symbol map", - "Unable to initialize DLL from null pointer", - "Unable to allocate DLL metadata structures", - "Unsupported DLL type or format", - "psxetc has been built without file support", - "Symbol not found in symbol map", - "Symbol not found in DLL" -}; - -static ErrorCode _error_code = ERR_NONE; -static uint32_t *_map_hash_table = 0; -static MapEntry *_map_entry_table = 0; +static DL_Error _error_code = RTLD_E_NONE; +static SymbolMap _symbol_map; // Accessed by _dl_resolve_helper, stores the pointer to the current resolver // function. Can be changed using DL_SetResolveCallback(). @@ -93,18 +70,14 @@ void *(*_dl_resolve_callback)(DLL *, const char *) = 0; #ifdef DEBUG #define _LOG(...) printf(__VA_ARGS__) -#define _ERROR(code, ret) { \ - _LOG("psxetc: ERROR! %s\n", DL_ERROR_MESSAGES[code - 1]); \ - _error_code = code; \ - return ret; \ -} #else #define _LOG(...) +#endif + #define _ERROR(code, ret) { \ _error_code = code; \ return ret; \ } -#endif void _dl_resolve_wrapper(void); @@ -161,8 +134,10 @@ static uint32_t _elf_hash(const char *str) { #ifdef USE_FILE_API static uint8_t *_load_file(const char *filename, size_t *size_output) { int32_t fd = open(filename, 1); - if (fd < 0) - _ERROR(ERR_FILE, 0); + if (fd < 0) { + _LOG("psxetc: Can't open %s, error = %d\n", filename, fd); + _ERROR(RTLD_E_FILE_OPEN, 0); + } // Extract file size from the file's associated control block. // https://problemkaputt.de/psx-spx.htm#biosmemorymap @@ -170,8 +145,10 @@ static uint8_t *_load_file(const char *filename, size_t *size_output) { size_t size = fcb[fd].filesize; uint8_t *buffer = malloc(size); - if (!buffer) - _ERROR(ERR_FILE_MALLOC, 0); + if (!buffer) { + _LOG("psxetc: Unable to allocate %d bytes for %s\n", size, filename); + _ERROR(RTLD_E_FILE_ALLOC, 0); + } _LOG("psxetc: Loading %s (%d bytes)..", filename, size); @@ -181,7 +158,9 @@ static uint8_t *_load_file(const char *filename, size_t *size_output) { if (length <= 0) { close(fd); free(buffer); - _ERROR(ERR_FILE_READ, 0); + + _LOG("failed, error = %d\n", length); + _ERROR(RTLD_E_FILE_READ, 0); } _LOG("."); @@ -213,20 +192,29 @@ int32_t DL_ParseSymbolMap(const char *ptr, size_t size) { // TODO: find a way to calculate the optimal number of hash table "buckets" // in order to minimize hash table size - uint32_t nbucket = entries; - _LOG("psxetc: Predicted %d entries, %d hash buckets\n", entries, nbucket); + _symbol_map.nbucket = entries; + _symbol_map.nchain = entries; + _LOG( + "psxetc: Allocating nbucket = %d, nchain = %d\n", + _symbol_map.nbucket, + entries + ); // Allocate an entry table to store parsed symbols in, and an associated // hash table (same format as .hash section, with 8-byte header). - _map_hash_table = malloc(sizeof(uint32_t) * (2 + nbucket + entries)); - _map_entry_table = malloc(sizeof(MapEntry) * entries); - if (!_map_hash_table || !_map_entry_table) - _ERROR(ERR_MAP_MALLOC, -1); + _symbol_map.entries = malloc(sizeof(MapEntry) * entries); + _symbol_map.bucket = malloc(sizeof(uint32_t) * _symbol_map.nbucket); + _symbol_map.chain = malloc(sizeof(uint32_t) * entries); - _map_hash_table[0] = nbucket; - _map_hash_table[1] = entries; - for (uint32_t i = 0; i < (nbucket + entries); i++) - _map_hash_table[2 + i] = 0xffffffff; + if (!_symbol_map.entries || !_symbol_map.bucket || !_symbol_map.chain) { + _LOG("psxetc: Unable to allocate symbol map table\n"); + _ERROR(RTLD_E_MAP_ALLOC, -1); + } + + for (uint32_t i = 0; i < _symbol_map.nbucket; i++) + _symbol_map.bucket[i] = 0xffffffff; + for (uint32_t i = 0; i < entries; i++) + _symbol_map.chain[i] = 0xffffffff; // Go again through the symbol map and fill in the hash table. uint32_t index = 0; @@ -254,7 +242,7 @@ int32_t DL_ParseSymbolMap(const char *ptr, size_t size) { void *address = (void *) address64; char _type = toupper(type_string[0]); uint32_t hash = _elf_hash(name); - uint32_t hash_mod = hash % nbucket; + uint32_t hash_mod = hash % _symbol_map.nbucket; if (address && ( (_type == 'T') || // .text @@ -270,15 +258,15 @@ int32_t DL_ParseSymbolMap(const char *ptr, size_t size) { name ); - MapEntry *entry = &(_map_entry_table[index]); + MapEntry *entry = &(_symbol_map.entries[index]); entry->hash = hash; entry->ptr = address; // Append a reference to the entry to the hash table's chain // for the current hash_mod. I can't explain this properly. - uint32_t *hash_entry = &(_map_hash_table[2 + hash_mod]); + uint32_t *hash_entry = &(_symbol_map.bucket[hash_mod]); while (*hash_entry != 0xffffffff) - hash_entry = &(_map_hash_table[2 + nbucket + *hash_entry]); + hash_entry = &(_symbol_map.chain[*hash_entry]); *hash_entry = index; index++; @@ -291,9 +279,9 @@ int32_t DL_ParseSymbolMap(const char *ptr, size_t size) { pos++; } - _LOG("psxetc: Parsed %d symbols from map\n", entries); + _LOG("psxetc: Parsed %d symbols\n", entries); if (!entries) - _ERROR(ERR_NO_SYMBOLS, -1); + _ERROR(RTLD_E_NO_SYMBOLS, -1); return entries; } @@ -310,42 +298,54 @@ int32_t DL_LoadSymbolMap(const char *filename) { return entries; #else - _ERROR(ERR_NO_FILE_API, -1); + _ERROR(RTLD_E_NO_FILE_API, -1); #endif } void DL_UnloadSymbolMap(void) { - if (!_map_hash_table) + if (!_symbol_map.entries) return; - free(_map_hash_table); - free(_map_entry_table); - _map_hash_table = 0; + free(_symbol_map.entries); + free(_symbol_map.bucket); + free(_symbol_map.chain); + _symbol_map.entries = 0; } void *DL_GetSymbolByName(const char *name) { - if (!_map_hash_table) - _ERROR(ERR_NO_MAP, 0); + if (!_symbol_map.entries) { + _LOG("psxetc: Attempted lookup with no map loaded\n"); + _ERROR(RTLD_E_NO_MAP, 0); + } // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html - uint32_t nbucket = _map_hash_table[0]; uint32_t hash = _elf_hash(name); - uint32_t hash_mod = hash % nbucket; + uint32_t hash_mod = hash % _symbol_map.nbucket; // Go through the hash table's chain until the symbol hash matches the one // calculated. - for (uint32_t i = _map_hash_table[2 + hash_mod]; i;) { - MapEntry *entry = &(_map_entry_table[i]); + for (uint32_t i = _symbol_map.bucket[hash_mod]; i != 0xffffffff;) { + if (i >= _symbol_map.nchain) { + _LOG( + "psxetc: GetSymbolByName() index out of bounds (i = %d, n = %d)\n", + i, + _symbol_map.nchain + ); + _ERROR(RTLD_E_HASH_LOOKUP, 0); + } + + MapEntry *entry = &(_symbol_map.entries[i]); if (hash == entry->hash) { _LOG("psxetc: Map lookup [%s = %08x]\n", name, entry->ptr); return entry->ptr; } - i = _map_hash_table[2 + nbucket + i]; + i = _symbol_map.chain[i]; } - _ERROR(ERR_MAP_SYMBOL, 0); + _LOG("psxetc: Map lookup [%s not found]\n", name); + _ERROR(RTLD_E_MAP_SYMBOL, 0); } void DL_SetResolveCallback(void *(*callback)(DLL *, const char *)) { @@ -356,11 +356,13 @@ void DL_SetResolveCallback(void *(*callback)(DLL *, const char *)) { DLL *dlinit(void *ptr, size_t size, DL_ResolveMode mode) { if (!ptr) - _ERROR(ERR_DLL_NULL, 0); + _ERROR(RTLD_E_DLL_NULL, 0); DLL *dll = malloc(sizeof(DLL)); - if (!dll) - _ERROR(ERR_DLL_MALLOC, 0); + if (!dll) { + _LOG("psxetc: Unable to allocate DLL struct\n"); + _ERROR(RTLD_E_DLL_ALLOC, 0); + } dll->ptr = ptr; dll->malloc_ptr = 0; @@ -417,7 +419,9 @@ DLL *dlinit(void *ptr, size_t size, DL_ResolveMode mode) { // Only 16-byte symbol table entries are supported. if (dyn->d_un.d_val != sizeof(Elf32_Sym)) { free(dll); - _ERROR(ERR_DLL_FORMAT, 0); + + _LOG("psxetc: Invalid symtab entry size %d\n", dyn->d_un.d_val); + _ERROR(RTLD_E_DLL_FORMAT, 0); } break; @@ -428,7 +432,9 @@ DLL *dlinit(void *ptr, size_t size, DL_ResolveMode mode) { // Versions other than 1 are unsupported (do they even exist?). if (dyn->d_un.d_val != 1) { free(dll); - _ERROR(ERR_DLL_FORMAT, 0); + + _LOG("psxetc: Invalid DLL version %d\n", dyn->d_un.d_val); + _ERROR(RTLD_E_DLL_FORMAT, 0); } break; @@ -439,7 +445,9 @@ DLL *dlinit(void *ptr, size_t size, DL_ResolveMode mode) { // Shortcut pointers (whatever they are) are not supported. if (dyn->d_un.d_val & RHF_QUICKSTART) { free(dll); - _ERROR(ERR_DLL_FORMAT, 0); + + _LOG("psxetc: Invalid flags\n"); + _ERROR(RTLD_E_DLL_FORMAT, 0); } break; @@ -458,7 +466,9 @@ DLL *dlinit(void *ptr, size_t size, DL_ResolveMode mode) { // be easy enough to support them, but why? if (dyn->d_un.d_val) { free(dll); - _ERROR(ERR_DLL_FORMAT, 0); + + _LOG("psxetc: Invalid base address %08x\n", dyn->d_un.d_val); + _ERROR(RTLD_E_DLL_FORMAT, 0); } break; @@ -558,7 +568,7 @@ DLL *dlinit(void *ptr, size_t size, DL_ResolveMode mode) { if (!dll->got[2 + j]) { free(dll); - _ERROR(ERR_MAP_SYMBOL, 0); + _ERROR(RTLD_E_MAP_SYMBOL, 0); } } @@ -600,7 +610,7 @@ DLL *dlopen(const char *filename, DL_ResolveMode mode) { return dll; #else - _ERROR(ERR_NO_FILE_API, 0); + _ERROR(RTLD_E_NO_FILE_API, 0); #endif } @@ -633,13 +643,21 @@ void *dlsym(DLL *dll, const char *name) { //return _dl_resolve_callback(RTLD_DEFAULT, name); // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html - const uint32_t *hash_table = dll->hash; - uint32_t nbucket = hash_table[0]; - uint32_t hash_mod = _elf_hash(name) % nbucket; + uint32_t nbucket = dll->hash[0]; + uint32_t nchain = dll->hash[1]; + const uint32_t *bucket = &(dll->hash[2]); + const uint32_t *chain = &(dll->hash[2 + nbucket]); + + uint32_t hash_mod = _elf_hash(name) % nbucket; // Go through the hash table's chain until the symbol name matches the one // provided. - for (uint32_t i = hash_table[2 + hash_mod]; i;) { + for (uint32_t i = bucket[hash_mod]; i != 0xffffffff;) { + if (i >= nchain) { + _LOG("psxetc: dlsym() index out of bounds (i = %d, n = %d)\n", i, nchain); + _ERROR(RTLD_E_HASH_LOOKUP, 0); + } + Elf32_Sym *sym = &(dll->symtab[i]); const char *_name = &(dll->strtab[sym->st_name]); @@ -648,18 +666,16 @@ void *dlsym(DLL *dll, const char *name) { return sym->st_value; } - i = hash_table[2 + nbucket + i]; + i = chain[i]; } - _ERROR(ERR_DLL_SYMBOL, 0); + _LOG("psxetc: DLL lookup [%s not found]\n", name); + _ERROR(RTLD_E_DLL_SYMBOL, 0); } -const char *const dlerror(void) { - uint32_t last = _error_code; - _error_code = ERR_NONE; - - if (last) - return DL_ERROR_MESSAGES[last - 1]; +DL_Error dlerror(void) { + DL_Error last = _error_code; + _error_code = RTLD_E_NONE; - return 0; + return last; } diff --git a/libpsn00b/readme.txt b/libpsn00b/readme.txt index 9f6ebc6..cfff733 100644 --- a/libpsn00b/readme.txt +++ b/libpsn00b/readme.txt @@ -11,11 +11,11 @@ from scratch in a mix of C and hand optimized assembly language for optimal software performance. These libraries can only be compiled using a build of GCC that targets -mipsel-unknown-elf. Compiler version shouldn't matter much and it should work -fine with GCC 9.1.0, though 7.4.0 is the recommended version as that is what -LibPSn00b is most tested most on. +mipsel-unknown-elf (or mipsel-none-elf). Compiler version shouldn't matter +much and it should work fine with the latest GCC release, though 7.4.0 is the +recommended version as that is what LibPSn00b is most tested most on. + - Brief summary of libraries: libc - Standard C library. Covers only a small subset of the full @@ -33,7 +33,8 @@ Brief summary of libraries: psxapi - Provides function calls for using functions provided by the PS1 BIOS. - psxetc - Provides some miscellaneous features such as debug font. + psxetc - Provides some miscellaneous features used by the other libraries + as well as a dynamic linker for loading DLLs at runtime. psxspu - SPU library (work in progress). Currently supports hardware init, sample data upload via DMA and playing sound samples. @@ -49,7 +50,12 @@ Brief summary of libraries: must be covered in the changelog.txt file. -Compiling (OUTDATED): +Compiling: + + Refer to INSTALL.md in the parent directory for up-to-date installation + instructions. + + --- THE SECTION BELOW IS OUTDATED AND ONLY KEPT FOR REFERENCE --- To compile the LibPSn00b libraries, you will first need a working GCC toolchain which you can either build yourself as described in the @@ -94,6 +100,10 @@ Documentation: library documents. It may be wise to export the document as a PDF document for easier viewing. + The PSn00bSDK CMake toolchain script also defines several macros and + helpers that can be used in project build scripts, documented in + cmake_reference.md. + Contributing: @@ -138,4 +148,12 @@ LibPSn00b Library to-do list: psxpress - MDEC and data decompression library. May use DEFLATE or LZ77 for compressing MDEC data instead of Huffman as used in the official libraries. It may yield better compression which may - potentially result in higher quality FMVs.
\ No newline at end of file + potentially result in higher quality FMVs. + + psxexp - Support library for various devices connected to the serial or + expansion port, including both official ones (e.g. PCMCIA cards + and IDE drives used by some PS1-based arcade systems) as well + as cheat devices, RAM expanders or even ESP8266/ESP32 wireless + modules. May also include APIs for accessing the filesystem on + a connected drive (possibly by overriding psxcd and psxmcrd + functions) or reliably transferring data from/to a PC. diff --git a/toolchain.txt b/toolchain.txt deleted file mode 100644 index d34d54b..0000000 --- a/toolchain.txt +++ /dev/null @@ -1,171 +0,0 @@ -If you wish to build the toolchain yourself, beware that this process can get -pretty tedious unless you have a powerful enough computer with plenty of -threads to spare. A system with at least 4 to 8 total threads or more is -recommended. You may have better success building the toolchain in ArchLinux -especially if you want to build the most recent versions of GCC. Debian -Buster however, may have about the same chance of success in building as -using ArchLinux. - -It is recommended to stick with GCC 7.4.0 and Binutils 2.31 as this is the -version PSn00bSDK is most tested with. Though generally, it doesn't hurt to -try out newer versions and has been known to work flawlessly without issue. - -These instructions are only for Linux, though they can be adapted for -building in Windows using MSys2 or Cygwin64. Beware that libgcc cannot -be built under Windows as the libgcc build process depends on symlinks -which is not supported by the operating system and is not wrapped by Msys2 -and Cygwin64. The only workaround is to first build GCC in Linux then copy -the libgcc.a library from it to the Windows build. - - -Make sure the following packages are installed prior to building: -* make -* build-essential or build-essentials, if you don't have GCC installed yet - (a host toolchain is required to build the rest of the SDK anyway). - - -Building binutils: - -Binutils must be built first as GCC depends on binutils built for the same -target architecture. In this case, building binutils and GCC to target -mipsel-unknown-elf. - -* Download binutils source files at ftp://ftp.gnu.org. Choose a version you - wish to use with PSn00bSDK. The reference version that PSn00bSDK is tested - with most is 2.31. - -* Extract the contents of the archive, preferably in a directory named - "toolchain" (or whatever). - -* Create a directory named binutils-build inside the toolchain directory. Do - not create it inside the binutils directory with the source files. - -* Enter the binutils-build directory and configure binutils from there - with the following command line: - -../binutils-<version>/configure --prefix=/usr/local/mipsel-unknown-elf \ ---target=mipsel-unknown-elf --disable-docs --disable-nls --with-float=soft - -Replace <version> with the version of binutils you wish to use. You may also -want to change the prefix argument to a path you prefer to have the toolchain -installed to (ie. somewhere within your home directory so you wouldn't need -root privileges to install). - -* Run `make -j 4` to compile binutils (-j specifies how many simultaneous - jobs to spawn at once, set it equal to the number of cores/threads your - system has available to speed up compiling). - -* Run `make install-strip` to install binutils to the path specified by the - --prefix argument (root privileges are required if the prefix points to - /usr/local). - - -Building gcc: - -With binutils built it should be possible to build the GCC toolchain. Since -GCC is considerably larger than binutils, compile time is going to be much -longer so it's going to take a longer while to build this. - -* Download gcc source files at ftp://ftp.gnu.org and choose the version you - wish to use with PSn00bSDK. The reference version that PSn00bSDK is tested - with most is 7.4.0. - -* Extract it to the same toolchain directory you extracted binutils in. - -* Enter the extracted "gcc-<version>" folder and run the following command - from there to download the required dependencies: - -./contrib/download_prerequisites - -* Go back and create a directory named gcc-build inside the toolchain - directory. - -* Enter the gcc-build directory and configure gcc from there with the - following command line: - -../gcc-<version>/configure --prefix=/usr/local/mipsel-unknown-elf \ ---target=mipsel-unknown-elf --disable-docs --disable-nls --disable-libada \ ---disable-libssp --disable-libquadmath --disable-libstdc++-v3 \ ---with-float=soft --enable-languages=c,c++ --with-gnu-as --with-gnu-ld - -Replace <version> with the version of gcc you downloaded. The prefix path -must match to what you've specified for binutils earlier, if you've decided -to use a different prefix path for binutils. - -When building under Windows you must additionally specify --disable-libgcc, -as libgcc cannot be built under Windows as it needs symlinks which are not -supported by the operating system. - -* Run make in the same manner you built binutils with. - -* Run `make install-strip` to install gcc to the path specified by --prefix - (root privileges are required if the prefix points to /usr/local). - -* Add a path to the bin directory of the toolchain into your PATH environment - variable by adding the following line in your .bashrc or .bash_profile file: - -export PATH=$PATH:/usr/local/mipsel-unknown-elf/bin - -Under Windows, you'll have to add the path to the PATH environment variable -through System Properties. - - -Note regarding C++ support: - -C++ support in PSn00bSDK, besides compile-time features like constexpr, only -goes as far as basic classes, namespaces and the ability to dynamically create -and delete class objects at any point of the program. The required dependencies -are supplied by libc of libpsn00b. - -Standard C++ libraries are not implemented and likely never going to be -implemented due to bloat concerns that it may introduce. Besides, the official -SDK lacks full C++ support as well. - -If you're trying to compile with C++ code and you get a linker error about -undefined vtables, try specifying --fno-rtti to the g++ command line. - ------------------------------------------------------------------------------ -Updating the ldscript (NO LONGER REQUIRED as PSn00bSDK now ships with its own -linker scripts, the section below is only kept for reference): - -The following changes used to be required in order for basic C++ functionality -to work in older PSn00bSDK versions. The changes define the constructor and -deconstructor sections which are required for the relevant support functions -in PSn00bSDK's libc library to be linked properly for C++. - -* Go to mipsel-unknown-elf/lib/ldscripts in the toolchain directory. - -* Open elf32elmip.x in any text editor. - -* Locate the .text definition (with the {} brackets) and add the following - inside the bracket block: - -__CTOR_LIST__ = .; -___CTOR_LIST__ = .; -LONG (((__CTOR_END__ - __CTOR_LIST__) / 4) - 2) -KEEP (*(SORT (.ctors.*))) -KEEP (*(.ctors)) -LONG (0x00000000) -__CTOR_END__ = .; -. = ALIGN (0x10); - -__DTOR_LIST__ = .; -___DTOR_LIST__ = .; -LONG (((__DTOR_END__ - __DTOR_LIST__) / 4) - 2) -KEEP (*(SORT (.dtors.*))) -KEEP (*(.dtors)) -LONG (0x00000000) -__DTOR_END__ = .; -. = ALIGN (0x10); - -* Save script changes. - -Since there's no known way (at least to me -Lameguy64) to configure GCC such -that it uses the modified ldscript as the default, you must specify the -modified script during the linking stage of your projects with the -T -argument, when invoking mipsel-unknown-elf-ld. - -Alternatively, you can make a copy of the ldscript file and modify it within -your project directory. This is especially useful if your project uses code -overlays. - |
