Add main executable compression, bump to 0.4.1

This commit is contained in:
spicyjpeg 2024-04-04 14:39:52 +02:00
parent d1bd2869b0
commit e836f665f4
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
19 changed files with 750 additions and 474 deletions

View File

@ -6,7 +6,7 @@ set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/cmake/toolchain.cmake")
project(
cart_tool_private
LANGUAGES C CXX ASM
VERSION 0.4.0
VERSION 0.4.1
DESCRIPTION "Konami System 573 security cartridge tool"
)
@ -31,7 +31,7 @@ set(cdVolumeName "CART_TOOL_${_version}")
add_library(
common OBJECT
src/libc/crt0.c
#src/libc/crt0.c
src/libc/cxxsupport.cpp
src/libc/malloc.c
src/libc/memset.s
@ -49,10 +49,11 @@ target_include_directories(
src/libc
)
target_compile_definitions(common PUBLIC VERSION="${PROJECT_VERSION}")
link_libraries(common)
function(addExecutable name stackTop)
function(addExecutable name address stackTop)
add_executable(${name} ${ARGN})
target_link_libraries(${name} PRIVATE common)
target_link_options(${name} PRIVATE -Ttext=0x${address})
add_custom_command(
TARGET ${name} POST_BUILD
@ -70,8 +71,12 @@ endfunction()
## Main executable
# IMPORTANT: these addresses assume the boot executable's size (including code,
# heap and stack allocations as well as the resource archive) is <448 KB
# (0x70000 bytes).
addExecutable(
main 801dfff0
main 80080000 801dfff0
src/common/args.cpp
src/common/file.cpp
src/common/gpu.cpp
src/common/gpufont.cpp
@ -82,6 +87,7 @@ addExecutable(
src/common/rom.cpp
src/common/spu.cpp
src/common/util.cpp
src/libc/crt0.c
src/main/cart.cpp
src/main/cartdata.cpp
src/main/cartio.cpp
@ -109,7 +115,6 @@ target_compile_definitions(
$<IF:$<CONFIG:Debug>,
ENABLE_LOGGING=1
ENABLE_FILE_WRITING=1
#ENABLE_ARGV=1
ENABLE_LOG_BUFFER=1
ENABLE_PS1_CONTROLLER=1
#ENABLE_X76F100_DRIVER=1
@ -118,7 +123,6 @@ target_compile_definitions(
,
ENABLE_LOGGING=1
ENABLE_FILE_WRITING=1
#ENABLE_ARGV=1
ENABLE_LOG_BUFFER=1
ENABLE_PS1_CONTROLLER=1
#ENABLE_X76F100_DRIVER=1
@ -127,42 +131,52 @@ target_compile_definitions(
>
)
list(APPEND packageContents main.psexe)
## Executable launchers
## Boot stub and executable launchers
# NOTE: in order to make sure -Os is passed after -Og or -O3 (see
# cmake/setup.cmake) and thus overrides it, it must be added to a separate
# target rather than directly to the executables.
add_library(launcherFlags INTERFACE)
target_compile_options(launcherFlags INTERFACE -Os)
add_library(bootFlags INTERFACE)
target_compile_options(bootFlags INTERFACE -Os)
target_compile_definitions(
launcherFlags INTERFACE
$<IF:$<CONFIG:Debug>,
#ENABLE_LOGGING=1
,
bootFlags INTERFACE
$<$<CONFIG:Debug>:
#ENABLE_ARGV=1
#ENABLE_LOGGING=1
>
)
addExecutable(
boot 80010000 0
src/boot/crt0.s
src/boot/main.cpp
src/common/util.cpp
)
target_link_libraries(boot PRIVATE bootFlags)
list(APPEND packageContents boot.psexe)
function(addLauncher address stackTop)
addExecutable(
launcher${address} ${stackTop}
launcher${address} ${address} ${stackTop}
src/common/args.cpp
src/common/ide.cpp
src/common/ideglue.cpp
src/common/io.cpp
src/common/util.cpp
src/launcher/launcher.cpp
src/launcher/main.cpp
src/libc/crt0.c
src/vendor/ff.c
src/vendor/ffunicode.c
src/vendor/printf.c
)
target_link_options(launcher${address} PRIVATE -Ttext=0x${address})
target_link_libraries(launcher${address} PRIVATE launcherFlags)
target_link_libraries(launcher${address} PRIVATE bootFlags)
endfunction()
# Note that the launchers must be <40 KB (0xa000 bytes) in order for this to
# work properly.
# IMPORTANT: these addresses assume the launcher's total size (including code,
# heap and stack allocations, but excluding the executable header) is <32 KB
# (0x8000 bytes).
addLauncher(801f8000 801ffff0)
addLauncher(803f8000 803ffff0)
@ -218,13 +232,14 @@ add_custom_command(
resources.json
assets/app.palette.json
assets/app.strings.json
main
launcher801f8000
launcher803f8000
COMMENT "Building resource archive"
VERBATIM
)
addBinaryFile(
main _resources _resourcesSize
boot _resourceArchive _resourceArchiveLength
"${PROJECT_BINARY_DIR}/resources.zip"
)
@ -248,7 +263,7 @@ if(EXISTS "${XORRISO_PATH}")
-system_id "PLAYSTATION"
-preparer_id "CART_TOOL BUILD SCRIPT"
-map readme.txt README.TXT
-map main.psexe PSX.EXE
-map boot.psexe PSX.EXE
-clone PSX.EXE GSE.NXX
-clone PSX.EXE NSE.GXX
-clone PSX.EXE OSE.FXX
@ -269,7 +284,7 @@ if(EXISTS "${XORRISO_PATH}")
-clone PSX.EXE TSY.AXD
-clone PSX.EXE TSZ.AXC
OUTPUT "${releaseName}.iso"
DEPENDS main
DEPENDS boot
COMMENT "Building CD-ROM image"
VERBATIM
)

View File

@ -1,4 +1,21 @@
[
{
"type": "binary",
"name": "binaries/main.psexe.lz4",
"source": "main.psexe",
"compression": "lz4"
},
{
"type": "binary",
"name": "binaries/launcher801f8000.psexe",
"source": "launcher801f8000.psexe"
},
{
"type": "binary",
"name": "binaries/launcher803f8000.psexe",
"source": "launcher803f8000.psexe"
},
{
"type": "tim",
"name": "assets/textures/background.tim",
@ -21,40 +38,40 @@
"source": "${PROJECT_SOURCE_DIR}/assets/textures/font.metrics.json"
},
{
"type": "binary",
"name": "assets/sounds/about.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/about.vag",
"compress": null
"type": "binary",
"name": "assets/sounds/about.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/about.vag",
"compression": "none"
},
{
"type": "binary",
"name": "assets/sounds/alert.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/alert.vag",
"compress": null
"type": "binary",
"name": "assets/sounds/alert.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/alert.vag",
"compression": "none"
},
{
"type": "binary",
"name": "assets/sounds/move.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/move.vag",
"compress": null
"type": "binary",
"name": "assets/sounds/move.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/move.vag",
"compression": "none"
},
{
"type": "binary",
"name": "assets/sounds/enter.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/enter.vag",
"compress": null
"type": "binary",
"name": "assets/sounds/enter.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/enter.vag",
"compression": "none"
},
{
"type": "binary",
"name": "assets/sounds/exit.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/exit.vag",
"compress": null
"type": "binary",
"name": "assets/sounds/exit.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/exit.vag",
"compression": "none"
},
{
"type": "binary",
"name": "assets/sounds/click.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/click.vag",
"compress": null
"type": "binary",
"name": "assets/sounds/click.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/click.vag",
"compression": "none"
},
{
"type": "palette",
@ -90,16 +107,5 @@
"type": "binary",
"name": "data/zs01.cartdb",
"source": "${PROJECT_SOURCE_DIR}/data/zs01.cartdb"
},
{
"type": "binary",
"name": "launchers/801f8000.psexe",
"source": "launcher801f8000.psexe"
},
{
"type": "binary",
"name": "launchers/803f8000.psexe",
"source": "launcher803f8000.psexe"
}
]

22
src/boot/crt0.s Normal file
View File

@ -0,0 +1,22 @@
.set noreorder
.set _STACK_SIZE, 0x100
# We're going to override ps1-bare-metal's _start() with a minimal version that
# skips .bss initialization (getting rid of memset() in the process) and moves
# the stack to a statically allocated buffer.
.section .text._start, "ax", @progbits
.global _start
.type _start, @function
_start:
la $gp, _gp
j main
addiu $sp, $gp, %gprel(_stackBuffer) + _STACK_SIZE - 16
.section .sbss._stackBuffer, "aw"
.type _stackBuffer, @object
_stackBuffer:
.space _STACK_SIZE

76
src/boot/main.cpp Normal file
View File

@ -0,0 +1,76 @@
#include <stddef.h>
#include <stdint.h>
#include "common/io.hpp"
#include "common/util.hpp"
extern "C" const uint8_t _resourceArchive[];
extern "C" const size_t _resourceArchiveLength;
static char _ptrArg[]{ "resource.ptr=xxxxxxxx" };
static char _lengthArg[]{ "resource.length=xxxxxxxx" };
struct [[gnu::packed]] ZIPFileHeader {
public:
uint32_t magic;
uint16_t version, flags, compType;
uint16_t fileTime, fileDate;
uint32_t crc, compLength, uncompLength;
uint16_t nameLength, extraLength;
inline bool validateMagic(void) const {
return (magic == 0x04034b50);
}
inline size_t getHeaderLength(void) const {
return sizeof(ZIPFileHeader) + nameLength + extraLength;
}
};
int main(int argc, const char **argv) {
//io::init();
// Parse the header of the archive's first entry manually. This avoids
// pulling in miniz and bloating the binary.
// NOTE: this assumes the main executable is always the first file in the
// archive.
auto zipHeader = reinterpret_cast<const ZIPFileHeader *>(_resourceArchive);
auto ptr = &_resourceArchive[zipHeader->getHeaderLength()];
auto compLength = zipHeader->compLength;
//assert(zipHeader->validateMagic());
//assert(!zipHeader->compType);
// Decompress only the header to determine where to place the binary in
// memory, then rerun the decompressor on the entire executable.
util::ExecutableHeader exeHeader;
util::decompressLZ4(
reinterpret_cast<uint8_t *>(&exeHeader), ptr, sizeof(exeHeader),
compLength
);
auto offset = exeHeader.textOffset - util::EXECUTABLE_BODY_OFFSET;
auto length = exeHeader.textLength + util::EXECUTABLE_BODY_OFFSET;
util::decompressLZ4(
reinterpret_cast<uint8_t *>(offset), ptr, length, compLength
);
util::ExecutableLoader loader(exeHeader, nullptr);
util::hexValueToString(
&_ptrArg[13], reinterpret_cast<uint32_t>(_resourceArchive), 8
);
loader.addArgument(_ptrArg);
util::hexValueToString(&_lengthArg[16], _resourceArchiveLength, 8);
loader.addArgument(_lengthArg);
#ifdef ENABLE_ARGV
for (; argc > 0; argc--)
loader.addArgument(*(argv++));
#endif
io::clearWatchdog();
loader.run();
return 0;
}

94
src/common/args.cpp Normal file
View File

@ -0,0 +1,94 @@
#include <stddef.h>
#include <stdlib.h>
#include "common/args.hpp"
#include "common/util.hpp"
namespace args {
/* Command line argument parsers */
bool CommonArgs::parseArgument(const char *arg) {
if (!arg)
return false;
switch (util::hash(arg, VALUE_SEPARATOR)) {
#if 0
case "boot.rom"_h:
LOG("boot.rom=%s", &arg[9]);
return true;
case "boot.from"_h:
LOG("boot.from=%s", &arg[10]);
return true;
#endif
case "console"_h:
baudRate = int(strtol(&arg[8], nullptr, 0));
return true;
default:
return false;
}
}
bool MainArgs::parseArgument(const char *arg) {
if (!arg)
return false;
switch (util::hash(arg, VALUE_SEPARATOR)) {
case "screen.width"_h:
screenWidth = int(strtol(&arg[13], nullptr, 0));
return true;
case "screen.height"_h:
screenHeight = int(strtol(&arg[14], nullptr, 0));
return true;
case "screen.interlace"_h:
forceInterlace = bool(strtol(&arg[17], nullptr, 0));
return true;
// Allow the default assets to be overridden by passing a pointer to an
// in-memory ZIP file as a command-line argument.
case "resource.ptr"_h:
resourcePtr = reinterpret_cast<const void *>(
strtol(&arg[13], nullptr, 16)
);
return true;
case "resource.length"_h:
resourceLength = size_t(strtol(&arg[16], nullptr, 16));
return true;
default:
return CommonArgs::parseArgument(arg);
}
}
bool ExecutableLauncherArgs::parseArgument(const char *arg) {
if (!arg)
return false;
switch (util::hash(arg, VALUE_SEPARATOR)) {
case "launcher.drive"_h:
drive = &arg[15];
return true;
case "launcher.path"_h:
path = &arg[14];
return true;
case "launcher.arg"_h:
if (argCount >= int(util::countOf(executableArgs)))
return false;
executableArgs[argCount++] = &arg[13];
return true;
default:
return CommonArgs::parseArgument(arg);
}
}
}

59
src/common/args.hpp Normal file
View File

@ -0,0 +1,59 @@
#pragma once
#include <stddef.h>
#include "common/util.hpp"
namespace args {
/* Command line argument parsers */
static constexpr char VALUE_SEPARATOR = '=';
static constexpr int DEFAULT_BAUD_RATE = 115200;
static constexpr int DEFAULT_SCREEN_WIDTH = 320;
static constexpr int DEFAULT_SCREEN_HEIGHT = 240;
class CommonArgs {
public:
int baudRate;
#ifdef NDEBUG
inline CommonArgs(void)
: baudRate(0) {}
#else
// Enable serial port logging by default in debug builds.
inline CommonArgs(void)
: baudRate(DEFAULT_BAUD_RATE) {}
#endif
bool parseArgument(const char *arg);
};
class MainArgs : public CommonArgs {
public:
int screenWidth, screenHeight;
bool forceInterlace;
const void *resourcePtr;
size_t resourceLength;
inline MainArgs(void)
: screenWidth(DEFAULT_SCREEN_WIDTH), screenHeight(DEFAULT_SCREEN_HEIGHT),
forceInterlace(false), resourcePtr(nullptr), resourceLength(0) {}
bool parseArgument(const char *arg);
};
class ExecutableLauncherArgs : public CommonArgs {
public:
int argCount;
const char *drive, *path;
const char *executableArgs[util::MAX_EXECUTABLE_ARGS];
inline ExecutableLauncherArgs(void)
: argCount(0), drive(nullptr), path(nullptr) {}
bool parseArgument(const char *arg);
};
}

View File

@ -201,7 +201,7 @@ enum FlashIdentifier : uint16_t {
bool FlashRegion::hasBootExecutable(void) const {
// FIXME: this implementation will not detect executables that cross bank
// boundaries (but it shouldn't matter as executables must be <4 MB anyway)
auto data = reinterpret_cast<const uint8_t *>(ptr + FLASH_EXE_OFFSET);
auto data = reinterpret_cast<const uint8_t *>(ptr + FLASH_EXECUTABLE_OFFSET);
auto crcPtr = reinterpret_cast<const uint32_t *>(ptr + FLASH_CRC_OFFSET);
auto table = reinterpret_cast<const uint32_t *>(CACHE_BASE);

View File

@ -11,9 +11,9 @@ namespace rom {
/* ROM region dumpers */
static constexpr size_t FLASH_BANK_LENGTH = 0x400000;
static constexpr uint32_t FLASH_CRC_OFFSET = 0x20;
static constexpr uint32_t FLASH_EXE_OFFSET = 0x24;
static constexpr size_t FLASH_BANK_LENGTH = 0x400000;
static constexpr uint32_t FLASH_CRC_OFFSET = 0x20;
static constexpr uint32_t FLASH_EXECUTABLE_OFFSET = 0x24;
class Driver;

View File

@ -95,6 +95,60 @@ void Logger::log(const char *format, ...) {
enableInterrupts();
}
/* LZ4 decompressor */
void decompressLZ4(
uint8_t *output, const uint8_t *input, size_t maxOutputLength,
size_t inputLength
) {
auto outputEnd = &output[maxOutputLength];
auto inputEnd = &input[inputLength];
while (input < inputEnd) {
uint8_t token = *(input++);
// Copy literals from the input stream.
int literalLength = token >> 4;
if (literalLength == 0xf) {
uint8_t addend;
do {
addend = *(input++);
literalLength += addend;
} while (addend == 0xff);
}
for (; literalLength && (output < outputEnd); literalLength--)
*(output++) = *(input++);
if (input >= inputEnd)
break;
int offset = input[0] | (input[1] << 8);
input += 2;
// Copy from previously decompressed data. Note that this *must* be done
// one byte at a time, as the compressor relies on out-of-bounds copies
// repeating the last byte.
int copyLength = token & 0xf;
if (copyLength == 0xf) {
uint8_t addend;
do {
addend = *(input++);
copyLength += addend;
} while (addend == 0xff);
}
auto copySource = output - offset;
copyLength += 4;
for (; copyLength && (output < outputEnd); copyLength--)
*(output++) = *(copySource++);
}
}
/* CRC calculation */
static constexpr uint8_t _CRC8_POLY = 0x8c;
@ -176,7 +230,19 @@ extern "C" uint32_t mz_crc32(uint32_t crc, const uint8_t *data, size_t length) {
static const char _HEX_CHARSET[]{ "0123456789ABCDEF" };
size_t hexToString(char *output, const uint8_t *input, size_t length, char sep) {
size_t hexValueToString(char *output, uint32_t value, size_t numDigits) {
output += numDigits;
*output = 0;
for (size_t i = numDigits; i; i--, value >>= 4)
*(--output) = _HEX_CHARSET[value & 0xf];
return numDigits;
}
size_t hexToString(
char *output, const uint8_t *input, size_t length, char separator
) {
size_t outLength = 0;
for (; length; length--) {
@ -185,8 +251,8 @@ size_t hexToString(char *output, const uint8_t *input, size_t length, char sep)
*(output++) = _HEX_CHARSET[value >> 4];
*(output++) = _HEX_CHARSET[value & 0xf];
if (sep && (length > 1)) {
*(output++) = sep;
if (separator && (length > 1)) {
*(output++) = separator;
outLength += 3;
} else {
outLength += 2;
@ -264,42 +330,43 @@ ExecutableLoader::ExecutableLoader(
if (!stackTop)
stackTop = defaultStackTop;
_argListPtr = reinterpret_cast<char **>(uintptr_t(stackTop) & ~7)
_argListPtr = reinterpret_cast<const char **>(uintptr_t(stackTop) & ~7)
- MAX_EXECUTABLE_ARGS;
_currentStackPtr = reinterpret_cast<char *>(_argListPtr);
}
void ExecutableLoader::addArgument(const char *arg) {
void ExecutableLoader::copyArgument(const char *arg) {
// Command-line arguments must be copied to the top of the new stack in
// order to ensure the executable is going to be able to access them at any
// time.
size_t length = __builtin_strlen(arg) + 1;
size_t aligned = (length + 7) & ~7;
_currentStackPtr -= aligned;
_argListPtr[_argCount++] = _currentStackPtr;
size_t length = __builtin_strlen(arg) + 1;
_currentStackPtr -= (length + 7) & ~7;
addArgument(_currentStackPtr);
__builtin_memcpy(_currentStackPtr, arg, length);
//assert(_argCount <= MAX_EXECUTABLE_ARGS);
}
[[noreturn]] void ExecutableLoader::run(void) {
[[noreturn]] void ExecutableLoader::run(
int rawArgc, const char *const *rawArgv
) {
disableInterrupts();
flushCache();
register int a0 __asm__("a0") = _argCount;
register char **a1 __asm__("a1") = _argListPtr;
register uintptr_t gp __asm__("gp") = _header.initialGP;
register int a0 __asm__("a0") = rawArgc;
register const char *const *a1 __asm__("a1") = rawArgv;
register uintptr_t gp __asm__("gp") = _header.initialGP;
// Changing the stack pointer and return address is not something that
// should be done in a C++ function, but hopefully it's fine here since
// we're jumping out right after setting it.
__asm__ volatile(
".set push\n"
".set noreorder\n"
"li $ra, %0\n"
"jr %1\n"
"addiu $sp, %2, -8\n"
".set reorder\n"
".set pop\n"
:: "i"(DEV2_BASE), "r"(_header.entryPoint), "r"(_currentStackPtr),
"r"(a0), "r"(a1), "r"(gp)
);

View File

@ -298,24 +298,43 @@ class ExecutableLoader {
private:
const ExecutableHeader &_header;
int _argCount;
char **_argListPtr;
char *_currentStackPtr;
int _argCount;
const char **_argListPtr;
char *_currentStackPtr;
public:
inline void addArgument(const char *arg) {
_argListPtr[_argCount++] = arg;
}
[[noreturn]] inline void run(void) {
run(_argCount, _argListPtr);
}
ExecutableLoader(const ExecutableHeader &header, void *defaultStackTop);
void addArgument(const char *arg);
[[noreturn]] void run(void);
void copyArgument(const char *arg);
[[noreturn]] void run(int rawArgc, const char *const *rawArgv);
};
/* Other APIs */
static inline size_t getLZ4InPlaceMargin(size_t inputLength) {
return (inputLength >> 8) + 32;
}
void decompressLZ4(
uint8_t *output, const uint8_t *input, size_t maxOutputLength,
size_t inputLength
);
uint8_t dsCRC8(const uint8_t *data, size_t length);
uint16_t zsCRC16(const uint8_t *data, size_t length);
uint32_t zipCRC32(const uint8_t *data, size_t length, uint32_t crc = 0);
void initZipCRC32(void);
size_t hexToString(char *output, const uint8_t *input, size_t length, char sep = 0);
size_t hexValueToString(char *output, uint32_t value, size_t numDigits = 8);
size_t hexToString(
char *output, const uint8_t *input, size_t length, char separator = 0
);
size_t serialNumberToString(char *output, const uint8_t *input);
size_t traceIDToString(char *output, const uint8_t *input);
size_t encodeBase41(char *output, const uint8_t *input, size_t length);

115
src/launcher/launcher.cpp Normal file
View File

@ -0,0 +1,115 @@
#include <stddef.h>
#include <stdint.h>
#include "common/ide.hpp"
#include "common/util.hpp"
#include "launcher/launcher.hpp"
#include "vendor/ff.h"
LauncherError ExecutableLauncher::openFile(void) {
if (!args.drive || !args.path) {
LOG("required arguments missing");
return INVALID_ARGS;
}
// The drive index is always a single digit, so there is no need to pull in
// strtol() here.
int drive = args.drive[0] - '0';
if (drive < 0 || drive > 1) {
LOG("invalid drive ID: %d", drive);
return INVALID_ARGS;
}
if (ide::devices[drive].enumerate()) {
LOG("IDE init failed, drive=%s", args.drive);
return DRIVE_ERROR;
}
auto error = f_mount(&_fs, args.drive, 1);
if (error) {
LOG("FAT mount failed, code=%d, drive=%s", error, args.drive);
return FAT_ERROR;
}
f_chdrive(args.drive);
error = f_open(&_file, args.path, FA_READ);
if (error) {
LOG("open failed, code=%d, path=%s", error, args.path);
return FILE_ERROR;
}
return NO_ERROR;
}
LauncherError ExecutableLauncher::parseHeader(uint64_t offset) {
LOG("parsing header, offset=0x%lx", offset);
auto error = f_lseek(&_file, offset);
if (error) {
LOG("seek to header failed, code=%d, path=%s", error, args.path);
return FILE_ERROR;
}
size_t length;
error = f_read(&_file, &_header, sizeof(_header), &length);
if (error) {
LOG("header read failed, code=%d, path=%s", error, args.path);
return FILE_ERROR;
}
if (length != sizeof(_header)) {
LOG("invalid header length: %d", length);
return INVALID_FILE;
}
if (!_header.validateMagic()) {
LOG("invalid executable magic");
return INVALID_FILE;
}
_bodyOffset = offset + util::EXECUTABLE_BODY_OFFSET;
return NO_ERROR;
}
LauncherError ExecutableLauncher::loadBody(void) {
auto error = f_lseek(&_file, _bodyOffset);
if (error) {
LOG("seek to body failed, code=%d, path=%s", error, args.path);
return FILE_ERROR;
}
size_t length;
error = f_read(&_file, _header.getTextPtr(), _header.textLength, &length);
if (error) {
LOG("body read failed, code=%d, path=%s", error, args.path);
return FILE_ERROR;
}
if (length != _header.textLength) {
LOG("invalid body length: %d", length);
return INVALID_FILE;
}
return NO_ERROR;
}
void ExecutableLauncher::closeFile(void) {
if (_file.obj.fs)
f_close(&_file);
if (_fs.fs_type)
f_unmount(args.drive);
}
extern "C" uint8_t _textStart[];
[[noreturn]] void ExecutableLauncher::run(void) {
util::ExecutableLoader loader(_header, _textStart - 16);
for (int i = 0; i < args.argCount; i++)
loader.copyArgument(args.executableArgs[i]);
loader.run();
}

42
src/launcher/launcher.hpp Normal file
View File

@ -0,0 +1,42 @@
#pragma once
#include <stdint.h>
#include "common/args.hpp"
#include "common/util.hpp"
#include "vendor/ff.h"
enum LauncherError {
NO_ERROR = 0,
INVALID_ARGS = 1,
DRIVE_ERROR = 2,
FAT_ERROR = 3,
FILE_ERROR = 4,
INVALID_FILE = 5
};
class ExecutableLauncher {
private:
// Using the FatFs API directly (rather than through file::FATProvider)
// yields a smaller executable as it avoids pulling in malloc.
FATFS _fs;
FIL _file;
util::ExecutableHeader _header;
uint64_t _bodyOffset;
public:
args::ExecutableLauncherArgs args;
inline ExecutableLauncher(void) {
_fs.fs_type = 0;
_file.obj.fs = nullptr;
}
LauncherError openFile(void);
LauncherError parseHeader(uint64_t offset = 0);
LauncherError loadBody(void);
void closeFile(void);
[[noreturn]] void run(void);
};

View File

@ -1,237 +1,56 @@
#include <stdio.h>
#include <stdlib.h>
#include "common/ide.hpp"
#include "common/args.hpp"
#include "common/io.hpp"
#include "common/rom.hpp"
#include "common/util.hpp"
#include "ps1/system.h"
#include "vendor/ff.h"
#include "launcher/launcher.hpp"
extern "C" uint8_t _textStart[];
class Settings {
public:
int baudRate, argCount;
const char *drive, *path;
const char *args[util::MAX_EXECUTABLE_ARGS];
inline Settings(void)
: baudRate(0), argCount(0), drive(nullptr), path(nullptr) {}
bool parse(const char *arg);
static const uint32_t _EXECUTABLE_OFFSETS[]{
0,
rom::FLASH_EXECUTABLE_OFFSET,
util::EXECUTABLE_BODY_OFFSET
};
bool Settings::parse(const char *arg) {
if (!arg)
return false;
switch (util::hash(arg, '=')) {
#if 0
case "boot.rom"_h:
LOG("boot.rom=%s", &arg[9]);
return true;
case "boot.from"_h:
LOG("boot.from=%s", &arg[10]);
return true;
case "console"_h:
// Disabled to avoid pulling in strtol.
baudRate = int(strtol(&arg[8], nullptr, 0));
return true;
#endif
case "launcher.drive"_h:
drive = &arg[15];
return true;
case "launcher.path"_h:
path = &arg[14];
return true;
case "launcher.arg"_h:
if (argCount >= int(util::countOf(args)))
return false;
args[argCount++] = &arg[13];
return true;
default:
return false;
}
}
class Launcher {
private:
Settings &_settings;
// Using the FatFs API directly (rather than through file::FATProvider)
// yields a smaller executable as it avoids pulling in malloc.
FATFS _fs;
FIL _file;
util::ExecutableHeader _header;
public:
inline Launcher(Settings &settings)
: _settings(settings) {
_fs.fs_type = 0;
_file.obj.fs = nullptr;
}
inline ~Launcher(void) {
exit();
}
bool openFile(void);
bool readHeader(uint64_t offset);
bool readBody(void);
void exit(void);
[[noreturn]] void run(void);
};
bool Launcher::openFile(void) {
if (!_settings.drive || !_settings.path) {
LOG("required arguments missing");
return false;
}
// As long as it works...
int drive = _settings.drive[0] - '0';
if (drive < 0 || drive > 1) {
LOG("invalid drive ID");
return false;
}
if (ide::devices[drive].enumerate()) {
LOG("IDE init failed, drive=%s", _settings.drive);
return false;
}
if (f_mount(&_fs, _settings.drive, 1)) {
LOG("FAT mount failed, drive=%s", _settings.drive);
return false;
}
f_chdrive(_settings.drive);
if (f_open(&_file, _settings.path, FA_READ)) {
LOG("open failed, path=%s", _settings.path);
return false;
}
return true;
}
bool Launcher::readHeader(uint64_t offset) {
size_t length;
if (f_lseek(&_file, offset)) {
LOG("seek to header failed, path=%s", _settings.path);
return false;
}
if (f_read(&_file, &_header, sizeof(_header), &length)) {
LOG("header read failed, path=%s", _settings.path);
return false;
}
if (length != sizeof(_header)) {
LOG("invalid header length %d", length);
return false;
}
if (!_header.validateMagic()) {
LOG("invalid executable magic");
return false;
}
if (f_lseek(&_file, offset + util::EXECUTABLE_BODY_OFFSET)) {
LOG("seek to body failed, path=%s", _settings.path);
return false;
}
LOG("ptr=0x%08x, length=0x%x", _header.textOffset, _header.textLength);
return true;
}
bool Launcher::readBody(void) {
size_t length;
if (f_read(&_file, _header.getTextPtr(), _header.textLength, &length)) {
LOG("body read failed, path=%s", _settings.path);
return false;
}
if (length != _header.textLength) {
LOG("invalid body length %d", length);
return false;
}
return true;
}
void Launcher::exit(void) {
if (_file.obj.fs)
f_close(&_file);
if (_fs.fs_type)
f_unmount(_settings.drive);
//uninstallExceptionHandler();
}
[[noreturn]] void Launcher::run(void) {
util::ExecutableLoader loader(_header, _textStart - 16);
for (int i = 0; i < _settings.argCount; i++)
loader.addArgument(_settings.args[i]);
exit();
loader.run();
}
int main(int argc, const char **argv) {
#if 0
// Exception handling code bloats the binary significantly (especially in
// debug builds, as it pulls in the crash handler), so the watchdog is
// cleared manually instead.
installExceptionHandler();
io::init();
setInterruptHandler([](void *dummy) {
if (acknowledgeInterrupt(IRQ_VSYNC))
io::clearWatchdog();
}, nullptr);
IRQ_MASK = 1 << IRQ_VSYNC;
enableInterrupts();
#endif
Settings settings;
Launcher launcher(settings);
#ifndef NDEBUG
// Enable serial port logging by default in debug builds.
settings.baudRate = 115200;
#endif
ExecutableLauncher launcher;
for (; argc > 0; argc--)
settings.parse(*(argv++));
launcher.args.parseArgument(*(argv++));
//util::logger.setupSyslog(settings.baudRate);
#ifdef ENABLE_LOGGING
util::logger.setupSyslog(launcher.args.baudRate);
#endif
auto error = launcher.openFile();
io::clearWatchdog();
if (!launcher.openFile())
return 1;
io::clearWatchdog();
if (!launcher.readHeader(0)) {
// If the file is not an executable, check if it is a flash image that
// contains an executable. Note that the CRC32 is not validated.
if (!launcher.readHeader(rom::FLASH_EXE_OFFSET))
return 2;
if (error)
goto _exit;
// Check for the presence of an executable at several different offsets
// within the file before giving up.
for (auto offset : _EXECUTABLE_OFFSETS) {
error = launcher.parseHeader(offset);
io::clearWatchdog();
if (error == INVALID_FILE)
continue;
if (error)
goto _exit;
error = launcher.loadBody();
io::clearWatchdog();
if (error)
goto _exit;
launcher.closeFile();
launcher.run();
}
io::clearWatchdog();
if (!launcher.readBody())
return 3;
io::clearWatchdog();
launcher.run();
return 0;
_exit:
launcher.closeFile();
return error;
}

View File

@ -333,14 +333,13 @@ char *strtok(char *restrict str, const char *restrict delim) {
long long strtoll(const char *restrict str, char **restrict str_end, int base) {
if (!str)
return 0;
while (isspace(*str))
str++;
int negative = (*str == '-');
if (negative)
str++;
char sign = *str;
if ((sign == '+') || (sign == '-'))
str++;
while (isspace(*str))
str++;
@ -349,7 +348,7 @@ long long strtoll(const char *restrict str, char **restrict str_end, int base) {
long long value = 0;
if (*str == '0') {
int _base;
int foundBase;
switch (str[1]) {
case 0:
@ -357,32 +356,32 @@ long long strtoll(const char *restrict str, char **restrict str_end, int base) {
case 'X':
case 'x':
_base = 16;
str += 2;
foundBase = 16;
str += 2;
break;
case 'O':
case 'o':
_base = 8;
str += 2;
foundBase = 8;
str += 2;
break;
case 'B':
case 'b':
_base = 2;
str += 2;
foundBase = 2;
str += 2;
break;
default:
// Numbers starting with a zero are *not* interpreted as octal
// unless base = 8.
_base = 0;
foundBase = 0;
str++;
}
if (!base)
base = _base;
else if (base != _base)
base = foundBase;
else if (foundBase && (base != foundBase))
return 0;
}
@ -420,7 +419,7 @@ _exit:
if (str_end)
*str_end = (char *) str;
return negative ? (-value) : value;
return (sign == '-') ? (-value) : value;
}
long strtol(const char *restrict str, char **restrict str_end, int base) {

View File

@ -67,16 +67,22 @@ public:
static const Launcher _LAUNCHERS[]{
{
.path = "launchers/801f8000.psexe",
.path = "binaries/launcher801f8000.psexe",
.loadOffset = 0x801f8000,
.length = 0x8000
}, {
.path = "launchers/803f8000.psexe",
.path = "binaries/launcher803f8000.psexe",
.loadOffset = 0x803f8000,
.length = 0x8000
}
};
static const uint32_t _EXECUTABLE_OFFSETS[]{
0,
rom::FLASH_EXECUTABLE_OFFSET,
util::EXECUTABLE_BODY_OFFSET
};
bool App::_executableWorker(void) {
_workerStatus.update(0, 1, WSTR("App.executableWorker.init"));
@ -87,27 +93,32 @@ bool App::_executableWorker(void) {
goto _fileOpenError;
util::ExecutableHeader header;
size_t length;
length = _file->read(&header, sizeof(header));
if (length != sizeof(header))
goto _fileError;
if (!header.validateMagic()) {
// If the file is not an executable, check if it is a flash image that
// contains an executable. Note that the CRC32 is not validated.
_file->seek(rom::FLASH_EXE_OFFSET);
length = _file->read(&header, sizeof(header));
// Check for the presence of an executable at several different offsets
// within the file before giving up.
for (auto offset : _EXECUTABLE_OFFSETS) {
_file->seek(offset);
size_t length = _file->read(&header, sizeof(header));
if (length != sizeof(header))
goto _fileError;
if (!header.validateMagic())
goto _fileError;
break;
if (header.validateMagic())
goto _validFile;
}
delete _file;
_fileOpenError:
_messageScreen.setMessage(
MESSAGE_ERROR, _filePickerScreen,
WSTR("App.executableWorker.fileError"), path
);
_workerStatus.setNextScreen(_messageScreen);
return false;
_validFile:
delete _file;
uintptr_t executableEnd, stackTop;
executableEnd = header.textOffset + header.textLength;
@ -153,18 +164,6 @@ bool App::_executableWorker(void) {
);
data.destroy();
util::ExecutableLoader loader(
header, reinterpret_cast<void *>(launcherEnd)
);
char arg[128];
snprintf(
arg, sizeof(arg), "launcher.drive=%s", _fileProvider.getDriveString()
);
loader.addArgument(arg);
snprintf(arg, sizeof(arg), "launcher.path=%s", path);
loader.addArgument(arg);
// All destructors must be invoked manually as we are not returning to
// main() before starting the new executable.
_unloadCartData();
@ -175,6 +174,18 @@ bool App::_executableWorker(void) {
_fileProvider.close();
util::ExecutableLoader loader(
header, reinterpret_cast<void *>(launcherEnd)
);
char arg[128];
snprintf(
arg, sizeof(arg), "launcher.drive=%s", _fileProvider.getDriveString()
);
loader.copyArgument(arg);
snprintf(arg, sizeof(arg), "launcher.path=%s", path);
loader.copyArgument(arg);
uninstallExceptionHandler();
loader.run();
}
@ -186,17 +197,6 @@ bool App::_executableWorker(void) {
);
_workerStatus.setNextScreen(_messageScreen);
return false;
_fileError:
delete _file;
_fileOpenError:
_messageScreen.setMessage(
MESSAGE_ERROR, _filePickerScreen,
WSTR("App.executableWorker.fileError"), path
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
bool App::_atapiEjectWorker(void) {

View File

@ -1,6 +1,5 @@
#include <stdio.h>
#include <stdlib.h>
#include "common/args.hpp"
#include "common/file.hpp"
#include "common/gpu.hpp"
#include "common/io.hpp"
@ -11,72 +10,6 @@
#include "ps1/gpucmd.h"
#include "ps1/system.h"
extern "C" const uint8_t _resources[];
extern "C" const size_t _resourcesSize;
class Settings {
public:
int width, height;
bool forceInterlace;
int baudRate;
const void *resPtr;
size_t resLength;
inline Settings(void)
: width(320), height(240), forceInterlace(false), baudRate(0),
resPtr(nullptr), resLength(0) {}
bool parse(const char *arg);
};
bool Settings::parse(const char *arg) {
if (!arg)
return false;
switch (util::hash(arg, '=')) {
#if 0
case "boot.rom"_h:
LOG("boot.rom=%s", &arg[9]);
return true;
case "boot.from"_h:
LOG("boot.from=%s", &arg[10]);
return true;
#endif
case "console"_h:
baudRate = int(strtol(&arg[8], nullptr, 0));
return true;
case "screen.width"_h:
width = int(strtol(&arg[13], nullptr, 0));
return true;
case "screen.height"_h:
height = int(strtol(&arg[14], nullptr, 0));
return true;
case "screen.interlace"_h:
forceInterlace = bool(strtol(&arg[17], nullptr, 0));
return true;
// Allow the default assets to be overridden by passing a pointer to an
// in-memory ZIP file as a command-line argument.
case "resources.ptr"_h:
resPtr = reinterpret_cast<const void *>(
strtol(&arg[14], nullptr, 16)
);
return true;
case "resources.length"_h:
resLength = size_t(strtol(&arg[17], nullptr, 16));
return true;
default:
return false;
}
}
int main(int argc, const char **argv) {
installExceptionHandler();
gpu::init();
@ -84,37 +17,29 @@ int main(int argc, const char **argv) {
io::init();
util::initZipCRC32();
Settings settings;
args::MainArgs args;
#ifndef NDEBUG
// Enable serial port logging by default in debug builds.
settings.baudRate = 115200;
#endif
#ifdef ENABLE_ARGV
for (; argc > 0; argc--)
settings.parse(*(argv++));
args.parseArgument(*(argv++));
#ifdef ENABLE_LOGGING
util::logger.setupSyslog(args.baudRate);
#endif
util::logger.setupSyslog(settings.baudRate);
// Load the resource archive, first from memory if a pointer was given and
// then from the HDD. If both attempts fail, fall back to the archive
// embedded into the executable.
auto resourceProvider = new file::ZIPProvider;
if (settings.resPtr && settings.resLength) {
if (resourceProvider->init(settings.resPtr, settings.resLength))
goto _resourceInitDone;
// A pointer to the resource archive is always provided on the command line
// by the boot stub.
if (!args.resourcePtr || !args.resourceLength) {
LOG("required arguments missing");
return 1;
}
resourceProvider->init(_resources, _resourcesSize);
auto resourceProvider = new file::ZIPProvider;
_resourceInitDone:
resourceProvider->init(args.resourcePtr, args.resourceLength);
io::clearWatchdog();
auto gpuCtx = new gpu::Context(
GP1_MODE_NTSC, settings.width, settings.height, settings.forceInterlace
GP1_MODE_NTSC, args.screenWidth, args.screenHeight, args.forceInterlace
);
auto uiCtx = new ui::Context(*gpuCtx);
auto app = new App(*uiCtx, *resourceProvider);

View File

@ -28,10 +28,9 @@
/* Internal state */
static uint32_t _savedBreakpointVector[4];
static uint32_t _savedExceptionVector[4];
static VoidFunction _flushCache = 0;
static Thread _mainThread;
static uint32_t _savedBreakpointVector[4];
static uint32_t _savedExceptionVector[4];
static Thread _mainThread;
ArgFunction interruptHandler = 0;
void *interruptHandlerArg = 0;
@ -41,6 +40,12 @@ Thread *nextThread = &_mainThread;
/* Exception handler setup */
static inline void _flushCache(void) {
// This is the only function that must always run from the BIOS ROM as it
// temporarily disables main RAM.
BIOS_API_TABLE[0x44]();
}
void _exceptionVector(void);
void installExceptionHandler(void) {
@ -54,11 +59,6 @@ void installExceptionHandler(void) {
// Disable interrupts and the GTE at the COP0 side.
cop0_setSR(COP0_SR_CU0);
// Grab a direct pointer to the BIOS function to flush the instruction
// cache. This is the only function that must always run from the BIOS ROM
// as it temporarily disables main RAM.
_flushCache = BIOS_API_TABLE[0x44];
// Overwrite the default breakpoint and exception handlers placed into RAM
// by the BIOS with a function that will jump to our custom handler.
__builtin_memcpy(_savedBreakpointVector, BIOS_BP_VECTOR, 16);
@ -100,9 +100,6 @@ void setInterruptHandler(ArgFunction func, void *arg) {
}
void flushCache(void) {
if (!_flushCache)
_flushCache = BIOS_API_TABLE[0x44];
bool enable = disableInterrupts();
_flushCache();

View File

@ -13,7 +13,7 @@ from struct import Struct
from typing import Any, ByteString, Generator, Mapping, Sequence
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
import numpy
import lz4.block, numpy
from numpy import ndarray
from PIL import Image
@ -346,18 +346,20 @@ def createParser() -> ArgumentParser:
help = "Show this help message and exit"
)
group = parser.add_argument_group("ZIP compression options")
group = parser.add_argument_group("Compression options")
group.add_argument(
"-c", "--compress-level",
type = int,
default = 9,
help = "Set default gzip compression level (default 9)",
metavar = "0-9"
"-c", "--compression",
type = str,
choices = ( "none", "deflate", "lz4" ),
default = "deflate",
help = "Set default compression algorithm (default DEFLATE)"
)
group.add_argument(
"-n", "--no-compression",
action = "store_true",
help = "Forcefully disable gzip compression for all files"
"-l", "--compress-level",
type = int,
default = 9,
help = "Set default DEFLATE and LZ4 compression level (default 9)",
metavar = "0-9"
)
group = parser.add_argument_group("File paths")
@ -449,14 +451,32 @@ def main():
case _type:
raise KeyError(f"unsupported asset type '{_type}'")
gzipLevel: int | None = asset.get("compress", args.compress_level)
disallow: bool = \
(len(data) < 1024) or (gzipLevel is None) or args.no_compression
compressLevel: int | None = \
asset.get("compressLevel", args.compress_level)
_zip.writestr(
asset["name"], data,
ZIP_STORED if disallow else ZIP_DEFLATED, gzipLevel
)
match asset.get("compression", args.compression).strip():
case "none" | None:
_zip.writestr(asset["name"], data, ZIP_STORED)
case "deflate":
_zip.writestr(
asset["name"], data, ZIP_DEFLATED, compressLevel
)
case "lz4":
# ZIP archives do not "officially" support LZ4 compression,
# so the entry is stored as an uncompressed file.
compressed: bytes = lz4.block.compress(
data,
mode = "high_compression",
compression = compressLevel,
store_size = False
)
_zip.writestr(asset["name"], compressed, ZIP_STORED)
case _type:
raise KeyError(f"unsupported compression type '{_type}'")
if __name__ == "__main__":
main()

View File

@ -2,5 +2,6 @@
# py -m pip install -r tools/requirements.txt (Windows)
# sudo pip install -r tools/requirements.txt (Linux/macOS)
lz4 >= 4.3.2
numpy >= 1.19.4
Pillow >= 8.2.0