mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-01-22 11:43:39 +01:00
Add main executable compression, bump to 0.4.1
This commit is contained in:
parent
d1bd2869b0
commit
e836f665f4
@ -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
|
||||
)
|
||||
|
@ -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
22
src/boot/crt0.s
Normal 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
76
src/boot/main.cpp
Normal 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
94
src/common/args.cpp
Normal 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
59
src/common/args.hpp
Normal 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);
|
||||
};
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
);
|
||||
|
@ -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
115
src/launcher/launcher.cpp
Normal 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
42
src/launcher/launcher.hpp
Normal 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);
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user