mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-01-22 19:52:05 +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(
|
project(
|
||||||
cart_tool_private
|
cart_tool_private
|
||||||
LANGUAGES C CXX ASM
|
LANGUAGES C CXX ASM
|
||||||
VERSION 0.4.0
|
VERSION 0.4.1
|
||||||
DESCRIPTION "Konami System 573 security cartridge tool"
|
DESCRIPTION "Konami System 573 security cartridge tool"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ set(cdVolumeName "CART_TOOL_${_version}")
|
|||||||
|
|
||||||
add_library(
|
add_library(
|
||||||
common OBJECT
|
common OBJECT
|
||||||
src/libc/crt0.c
|
#src/libc/crt0.c
|
||||||
src/libc/cxxsupport.cpp
|
src/libc/cxxsupport.cpp
|
||||||
src/libc/malloc.c
|
src/libc/malloc.c
|
||||||
src/libc/memset.s
|
src/libc/memset.s
|
||||||
@ -49,10 +49,11 @@ target_include_directories(
|
|||||||
src/libc
|
src/libc
|
||||||
)
|
)
|
||||||
target_compile_definitions(common PUBLIC VERSION="${PROJECT_VERSION}")
|
target_compile_definitions(common PUBLIC VERSION="${PROJECT_VERSION}")
|
||||||
|
link_libraries(common)
|
||||||
|
|
||||||
function(addExecutable name stackTop)
|
function(addExecutable name address stackTop)
|
||||||
add_executable(${name} ${ARGN})
|
add_executable(${name} ${ARGN})
|
||||||
target_link_libraries(${name} PRIVATE common)
|
target_link_options(${name} PRIVATE -Ttext=0x${address})
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
TARGET ${name} POST_BUILD
|
TARGET ${name} POST_BUILD
|
||||||
@ -70,8 +71,12 @@ endfunction()
|
|||||||
|
|
||||||
## Main executable
|
## 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(
|
addExecutable(
|
||||||
main 801dfff0
|
main 80080000 801dfff0
|
||||||
|
src/common/args.cpp
|
||||||
src/common/file.cpp
|
src/common/file.cpp
|
||||||
src/common/gpu.cpp
|
src/common/gpu.cpp
|
||||||
src/common/gpufont.cpp
|
src/common/gpufont.cpp
|
||||||
@ -82,6 +87,7 @@ addExecutable(
|
|||||||
src/common/rom.cpp
|
src/common/rom.cpp
|
||||||
src/common/spu.cpp
|
src/common/spu.cpp
|
||||||
src/common/util.cpp
|
src/common/util.cpp
|
||||||
|
src/libc/crt0.c
|
||||||
src/main/cart.cpp
|
src/main/cart.cpp
|
||||||
src/main/cartdata.cpp
|
src/main/cartdata.cpp
|
||||||
src/main/cartio.cpp
|
src/main/cartio.cpp
|
||||||
@ -109,7 +115,6 @@ target_compile_definitions(
|
|||||||
$<IF:$<CONFIG:Debug>,
|
$<IF:$<CONFIG:Debug>,
|
||||||
ENABLE_LOGGING=1
|
ENABLE_LOGGING=1
|
||||||
ENABLE_FILE_WRITING=1
|
ENABLE_FILE_WRITING=1
|
||||||
#ENABLE_ARGV=1
|
|
||||||
ENABLE_LOG_BUFFER=1
|
ENABLE_LOG_BUFFER=1
|
||||||
ENABLE_PS1_CONTROLLER=1
|
ENABLE_PS1_CONTROLLER=1
|
||||||
#ENABLE_X76F100_DRIVER=1
|
#ENABLE_X76F100_DRIVER=1
|
||||||
@ -118,7 +123,6 @@ target_compile_definitions(
|
|||||||
,
|
,
|
||||||
ENABLE_LOGGING=1
|
ENABLE_LOGGING=1
|
||||||
ENABLE_FILE_WRITING=1
|
ENABLE_FILE_WRITING=1
|
||||||
#ENABLE_ARGV=1
|
|
||||||
ENABLE_LOG_BUFFER=1
|
ENABLE_LOG_BUFFER=1
|
||||||
ENABLE_PS1_CONTROLLER=1
|
ENABLE_PS1_CONTROLLER=1
|
||||||
#ENABLE_X76F100_DRIVER=1
|
#ENABLE_X76F100_DRIVER=1
|
||||||
@ -127,42 +131,52 @@ target_compile_definitions(
|
|||||||
>
|
>
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND packageContents main.psexe)
|
## Boot stub and executable launchers
|
||||||
|
|
||||||
## Executable launchers
|
|
||||||
|
|
||||||
# NOTE: in order to make sure -Os is passed after -Og or -O3 (see
|
# 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
|
# cmake/setup.cmake) and thus overrides it, it must be added to a separate
|
||||||
# target rather than directly to the executables.
|
# target rather than directly to the executables.
|
||||||
add_library(launcherFlags INTERFACE)
|
add_library(bootFlags INTERFACE)
|
||||||
target_compile_options(launcherFlags INTERFACE -Os)
|
target_compile_options(bootFlags INTERFACE -Os)
|
||||||
target_compile_definitions(
|
target_compile_definitions(
|
||||||
launcherFlags INTERFACE
|
bootFlags INTERFACE
|
||||||
$<IF:$<CONFIG:Debug>,
|
$<$<CONFIG:Debug>:
|
||||||
#ENABLE_LOGGING=1
|
#ENABLE_ARGV=1
|
||||||
,
|
|
||||||
#ENABLE_LOGGING=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)
|
function(addLauncher address stackTop)
|
||||||
addExecutable(
|
addExecutable(
|
||||||
launcher${address} ${stackTop}
|
launcher${address} ${address} ${stackTop}
|
||||||
|
src/common/args.cpp
|
||||||
src/common/ide.cpp
|
src/common/ide.cpp
|
||||||
src/common/ideglue.cpp
|
src/common/ideglue.cpp
|
||||||
src/common/io.cpp
|
src/common/io.cpp
|
||||||
src/common/util.cpp
|
src/common/util.cpp
|
||||||
|
src/launcher/launcher.cpp
|
||||||
src/launcher/main.cpp
|
src/launcher/main.cpp
|
||||||
|
src/libc/crt0.c
|
||||||
src/vendor/ff.c
|
src/vendor/ff.c
|
||||||
src/vendor/ffunicode.c
|
src/vendor/ffunicode.c
|
||||||
src/vendor/printf.c
|
src/vendor/printf.c
|
||||||
)
|
)
|
||||||
target_link_options(launcher${address} PRIVATE -Ttext=0x${address})
|
target_link_libraries(launcher${address} PRIVATE bootFlags)
|
||||||
target_link_libraries(launcher${address} PRIVATE launcherFlags)
|
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# Note that the launchers must be <40 KB (0xa000 bytes) in order for this to
|
# IMPORTANT: these addresses assume the launcher's total size (including code,
|
||||||
# work properly.
|
# heap and stack allocations, but excluding the executable header) is <32 KB
|
||||||
|
# (0x8000 bytes).
|
||||||
addLauncher(801f8000 801ffff0)
|
addLauncher(801f8000 801ffff0)
|
||||||
addLauncher(803f8000 803ffff0)
|
addLauncher(803f8000 803ffff0)
|
||||||
|
|
||||||
@ -218,13 +232,14 @@ add_custom_command(
|
|||||||
resources.json
|
resources.json
|
||||||
assets/app.palette.json
|
assets/app.palette.json
|
||||||
assets/app.strings.json
|
assets/app.strings.json
|
||||||
|
main
|
||||||
launcher801f8000
|
launcher801f8000
|
||||||
launcher803f8000
|
launcher803f8000
|
||||||
COMMENT "Building resource archive"
|
COMMENT "Building resource archive"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
addBinaryFile(
|
addBinaryFile(
|
||||||
main _resources _resourcesSize
|
boot _resourceArchive _resourceArchiveLength
|
||||||
"${PROJECT_BINARY_DIR}/resources.zip"
|
"${PROJECT_BINARY_DIR}/resources.zip"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -248,7 +263,7 @@ if(EXISTS "${XORRISO_PATH}")
|
|||||||
-system_id "PLAYSTATION"
|
-system_id "PLAYSTATION"
|
||||||
-preparer_id "CART_TOOL BUILD SCRIPT"
|
-preparer_id "CART_TOOL BUILD SCRIPT"
|
||||||
-map readme.txt README.TXT
|
-map readme.txt README.TXT
|
||||||
-map main.psexe PSX.EXE
|
-map boot.psexe PSX.EXE
|
||||||
-clone PSX.EXE GSE.NXX
|
-clone PSX.EXE GSE.NXX
|
||||||
-clone PSX.EXE NSE.GXX
|
-clone PSX.EXE NSE.GXX
|
||||||
-clone PSX.EXE OSE.FXX
|
-clone PSX.EXE OSE.FXX
|
||||||
@ -269,7 +284,7 @@ if(EXISTS "${XORRISO_PATH}")
|
|||||||
-clone PSX.EXE TSY.AXD
|
-clone PSX.EXE TSY.AXD
|
||||||
-clone PSX.EXE TSZ.AXC
|
-clone PSX.EXE TSZ.AXC
|
||||||
OUTPUT "${releaseName}.iso"
|
OUTPUT "${releaseName}.iso"
|
||||||
DEPENDS main
|
DEPENDS boot
|
||||||
COMMENT "Building CD-ROM image"
|
COMMENT "Building CD-ROM image"
|
||||||
VERBATIM
|
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",
|
"type": "tim",
|
||||||
"name": "assets/textures/background.tim",
|
"name": "assets/textures/background.tim",
|
||||||
@ -21,40 +38,40 @@
|
|||||||
"source": "${PROJECT_SOURCE_DIR}/assets/textures/font.metrics.json"
|
"source": "${PROJECT_SOURCE_DIR}/assets/textures/font.metrics.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "binary",
|
"type": "binary",
|
||||||
"name": "assets/sounds/about.vag",
|
"name": "assets/sounds/about.vag",
|
||||||
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/about.vag",
|
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/about.vag",
|
||||||
"compress": null
|
"compression": "none"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "binary",
|
"type": "binary",
|
||||||
"name": "assets/sounds/alert.vag",
|
"name": "assets/sounds/alert.vag",
|
||||||
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/alert.vag",
|
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/alert.vag",
|
||||||
"compress": null
|
"compression": "none"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "binary",
|
"type": "binary",
|
||||||
"name": "assets/sounds/move.vag",
|
"name": "assets/sounds/move.vag",
|
||||||
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/move.vag",
|
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/move.vag",
|
||||||
"compress": null
|
"compression": "none"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "binary",
|
"type": "binary",
|
||||||
"name": "assets/sounds/enter.vag",
|
"name": "assets/sounds/enter.vag",
|
||||||
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/enter.vag",
|
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/enter.vag",
|
||||||
"compress": null
|
"compression": "none"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "binary",
|
"type": "binary",
|
||||||
"name": "assets/sounds/exit.vag",
|
"name": "assets/sounds/exit.vag",
|
||||||
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/exit.vag",
|
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/exit.vag",
|
||||||
"compress": null
|
"compression": "none"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "binary",
|
"type": "binary",
|
||||||
"name": "assets/sounds/click.vag",
|
"name": "assets/sounds/click.vag",
|
||||||
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/click.vag",
|
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/click.vag",
|
||||||
"compress": null
|
"compression": "none"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "palette",
|
"type": "palette",
|
||||||
@ -90,16 +107,5 @@
|
|||||||
"type": "binary",
|
"type": "binary",
|
||||||
"name": "data/zs01.cartdb",
|
"name": "data/zs01.cartdb",
|
||||||
"source": "${PROJECT_SOURCE_DIR}/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 {
|
bool FlashRegion::hasBootExecutable(void) const {
|
||||||
// FIXME: this implementation will not detect executables that cross bank
|
// FIXME: this implementation will not detect executables that cross bank
|
||||||
// boundaries (but it shouldn't matter as executables must be <4 MB anyway)
|
// 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 crcPtr = reinterpret_cast<const uint32_t *>(ptr + FLASH_CRC_OFFSET);
|
||||||
auto table = reinterpret_cast<const uint32_t *>(CACHE_BASE);
|
auto table = reinterpret_cast<const uint32_t *>(CACHE_BASE);
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ namespace rom {
|
|||||||
|
|
||||||
/* ROM region dumpers */
|
/* ROM region dumpers */
|
||||||
|
|
||||||
static constexpr size_t FLASH_BANK_LENGTH = 0x400000;
|
static constexpr size_t FLASH_BANK_LENGTH = 0x400000;
|
||||||
static constexpr uint32_t FLASH_CRC_OFFSET = 0x20;
|
static constexpr uint32_t FLASH_CRC_OFFSET = 0x20;
|
||||||
static constexpr uint32_t FLASH_EXE_OFFSET = 0x24;
|
static constexpr uint32_t FLASH_EXECUTABLE_OFFSET = 0x24;
|
||||||
|
|
||||||
class Driver;
|
class Driver;
|
||||||
|
|
||||||
|
@ -95,6 +95,60 @@ void Logger::log(const char *format, ...) {
|
|||||||
enableInterrupts();
|
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 */
|
/* CRC calculation */
|
||||||
|
|
||||||
static constexpr uint8_t _CRC8_POLY = 0x8c;
|
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" };
|
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;
|
size_t outLength = 0;
|
||||||
|
|
||||||
for (; length; length--) {
|
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 >> 4];
|
||||||
*(output++) = _HEX_CHARSET[value & 0xf];
|
*(output++) = _HEX_CHARSET[value & 0xf];
|
||||||
|
|
||||||
if (sep && (length > 1)) {
|
if (separator && (length > 1)) {
|
||||||
*(output++) = sep;
|
*(output++) = separator;
|
||||||
outLength += 3;
|
outLength += 3;
|
||||||
} else {
|
} else {
|
||||||
outLength += 2;
|
outLength += 2;
|
||||||
@ -264,42 +330,43 @@ ExecutableLoader::ExecutableLoader(
|
|||||||
if (!stackTop)
|
if (!stackTop)
|
||||||
stackTop = defaultStackTop;
|
stackTop = defaultStackTop;
|
||||||
|
|
||||||
_argListPtr = reinterpret_cast<char **>(uintptr_t(stackTop) & ~7)
|
_argListPtr = reinterpret_cast<const char **>(uintptr_t(stackTop) & ~7)
|
||||||
- MAX_EXECUTABLE_ARGS;
|
- MAX_EXECUTABLE_ARGS;
|
||||||
_currentStackPtr = reinterpret_cast<char *>(_argListPtr);
|
_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
|
// 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
|
// order to ensure the executable is going to be able to access them at any
|
||||||
// time.
|
// time.
|
||||||
size_t length = __builtin_strlen(arg) + 1;
|
size_t length = __builtin_strlen(arg) + 1;
|
||||||
size_t aligned = (length + 7) & ~7;
|
_currentStackPtr -= (length + 7) & ~7;
|
||||||
|
|
||||||
_currentStackPtr -= aligned;
|
|
||||||
_argListPtr[_argCount++] = _currentStackPtr;
|
|
||||||
|
|
||||||
|
addArgument(_currentStackPtr);
|
||||||
__builtin_memcpy(_currentStackPtr, arg, length);
|
__builtin_memcpy(_currentStackPtr, arg, length);
|
||||||
//assert(_argCount <= MAX_EXECUTABLE_ARGS);
|
//assert(_argCount <= MAX_EXECUTABLE_ARGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void ExecutableLoader::run(void) {
|
[[noreturn]] void ExecutableLoader::run(
|
||||||
|
int rawArgc, const char *const *rawArgv
|
||||||
|
) {
|
||||||
disableInterrupts();
|
disableInterrupts();
|
||||||
flushCache();
|
flushCache();
|
||||||
|
|
||||||
register int a0 __asm__("a0") = _argCount;
|
register int a0 __asm__("a0") = rawArgc;
|
||||||
register char **a1 __asm__("a1") = _argListPtr;
|
register const char *const *a1 __asm__("a1") = rawArgv;
|
||||||
register uintptr_t gp __asm__("gp") = _header.initialGP;
|
register uintptr_t gp __asm__("gp") = _header.initialGP;
|
||||||
|
|
||||||
// Changing the stack pointer and return address is not something that
|
// 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
|
// should be done in a C++ function, but hopefully it's fine here since
|
||||||
// we're jumping out right after setting it.
|
// we're jumping out right after setting it.
|
||||||
__asm__ volatile(
|
__asm__ volatile(
|
||||||
|
".set push\n"
|
||||||
".set noreorder\n"
|
".set noreorder\n"
|
||||||
"li $ra, %0\n"
|
"li $ra, %0\n"
|
||||||
"jr %1\n"
|
"jr %1\n"
|
||||||
"addiu $sp, %2, -8\n"
|
"addiu $sp, %2, -8\n"
|
||||||
".set reorder\n"
|
".set pop\n"
|
||||||
:: "i"(DEV2_BASE), "r"(_header.entryPoint), "r"(_currentStackPtr),
|
:: "i"(DEV2_BASE), "r"(_header.entryPoint), "r"(_currentStackPtr),
|
||||||
"r"(a0), "r"(a1), "r"(gp)
|
"r"(a0), "r"(a1), "r"(gp)
|
||||||
);
|
);
|
||||||
|
@ -298,24 +298,43 @@ class ExecutableLoader {
|
|||||||
private:
|
private:
|
||||||
const ExecutableHeader &_header;
|
const ExecutableHeader &_header;
|
||||||
|
|
||||||
int _argCount;
|
int _argCount;
|
||||||
char **_argListPtr;
|
const char **_argListPtr;
|
||||||
char *_currentStackPtr;
|
char *_currentStackPtr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
inline void addArgument(const char *arg) {
|
||||||
|
_argListPtr[_argCount++] = arg;
|
||||||
|
}
|
||||||
|
[[noreturn]] inline void run(void) {
|
||||||
|
run(_argCount, _argListPtr);
|
||||||
|
}
|
||||||
|
|
||||||
ExecutableLoader(const ExecutableHeader &header, void *defaultStackTop);
|
ExecutableLoader(const ExecutableHeader &header, void *defaultStackTop);
|
||||||
void addArgument(const char *arg);
|
void copyArgument(const char *arg);
|
||||||
[[noreturn]] void run(void);
|
[[noreturn]] void run(int rawArgc, const char *const *rawArgv);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Other APIs */
|
/* 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);
|
uint8_t dsCRC8(const uint8_t *data, size_t length);
|
||||||
uint16_t zsCRC16(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);
|
uint32_t zipCRC32(const uint8_t *data, size_t length, uint32_t crc = 0);
|
||||||
void initZipCRC32(void);
|
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 serialNumberToString(char *output, const uint8_t *input);
|
||||||
size_t traceIDToString(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);
|
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 "common/args.hpp"
|
||||||
#include <stdlib.h>
|
|
||||||
#include "common/ide.hpp"
|
|
||||||
#include "common/io.hpp"
|
#include "common/io.hpp"
|
||||||
#include "common/rom.hpp"
|
#include "common/rom.hpp"
|
||||||
#include "common/util.hpp"
|
#include "common/util.hpp"
|
||||||
#include "ps1/system.h"
|
#include "launcher/launcher.hpp"
|
||||||
#include "vendor/ff.h"
|
|
||||||
|
|
||||||
extern "C" uint8_t _textStart[];
|
static const uint32_t _EXECUTABLE_OFFSETS[]{
|
||||||
|
0,
|
||||||
class Settings {
|
rom::FLASH_EXECUTABLE_OFFSET,
|
||||||
public:
|
util::EXECUTABLE_BODY_OFFSET
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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) {
|
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();
|
io::init();
|
||||||
|
|
||||||
setInterruptHandler([](void *dummy) {
|
ExecutableLauncher launcher;
|
||||||
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
|
|
||||||
|
|
||||||
for (; argc > 0; argc--)
|
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();
|
io::clearWatchdog();
|
||||||
if (!launcher.openFile())
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
io::clearWatchdog();
|
if (error)
|
||||||
if (!launcher.readHeader(0)) {
|
goto _exit;
|
||||||
// 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.
|
// Check for the presence of an executable at several different offsets
|
||||||
if (!launcher.readHeader(rom::FLASH_EXE_OFFSET))
|
// within the file before giving up.
|
||||||
return 2;
|
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();
|
_exit:
|
||||||
if (!launcher.readBody())
|
launcher.closeFile();
|
||||||
return 3;
|
return error;
|
||||||
|
|
||||||
io::clearWatchdog();
|
|
||||||
launcher.run();
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
long long strtoll(const char *restrict str, char **restrict str_end, int base) {
|
||||||
if (!str)
|
if (!str)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
while (isspace(*str))
|
while (isspace(*str))
|
||||||
str++;
|
str++;
|
||||||
|
|
||||||
int negative = (*str == '-');
|
char sign = *str;
|
||||||
if (negative)
|
|
||||||
str++;
|
|
||||||
|
|
||||||
|
if ((sign == '+') || (sign == '-'))
|
||||||
|
str++;
|
||||||
while (isspace(*str))
|
while (isspace(*str))
|
||||||
str++;
|
str++;
|
||||||
|
|
||||||
@ -349,7 +348,7 @@ long long strtoll(const char *restrict str, char **restrict str_end, int base) {
|
|||||||
long long value = 0;
|
long long value = 0;
|
||||||
|
|
||||||
if (*str == '0') {
|
if (*str == '0') {
|
||||||
int _base;
|
int foundBase;
|
||||||
|
|
||||||
switch (str[1]) {
|
switch (str[1]) {
|
||||||
case 0:
|
case 0:
|
||||||
@ -357,32 +356,32 @@ long long strtoll(const char *restrict str, char **restrict str_end, int base) {
|
|||||||
|
|
||||||
case 'X':
|
case 'X':
|
||||||
case 'x':
|
case 'x':
|
||||||
_base = 16;
|
foundBase = 16;
|
||||||
str += 2;
|
str += 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'O':
|
case 'O':
|
||||||
case 'o':
|
case 'o':
|
||||||
_base = 8;
|
foundBase = 8;
|
||||||
str += 2;
|
str += 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'B':
|
case 'B':
|
||||||
case 'b':
|
case 'b':
|
||||||
_base = 2;
|
foundBase = 2;
|
||||||
str += 2;
|
str += 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Numbers starting with a zero are *not* interpreted as octal
|
// Numbers starting with a zero are *not* interpreted as octal
|
||||||
// unless base = 8.
|
// unless base = 8.
|
||||||
_base = 0;
|
foundBase = 0;
|
||||||
str++;
|
str++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!base)
|
if (!base)
|
||||||
base = _base;
|
base = foundBase;
|
||||||
else if (base != _base)
|
else if (foundBase && (base != foundBase))
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,7 +419,7 @@ _exit:
|
|||||||
if (str_end)
|
if (str_end)
|
||||||
*str_end = (char *) str;
|
*str_end = (char *) str;
|
||||||
|
|
||||||
return negative ? (-value) : value;
|
return (sign == '-') ? (-value) : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
long strtol(const char *restrict str, char **restrict str_end, int base) {
|
long strtol(const char *restrict str, char **restrict str_end, int base) {
|
||||||
|
@ -67,16 +67,22 @@ public:
|
|||||||
|
|
||||||
static const Launcher _LAUNCHERS[]{
|
static const Launcher _LAUNCHERS[]{
|
||||||
{
|
{
|
||||||
.path = "launchers/801f8000.psexe",
|
.path = "binaries/launcher801f8000.psexe",
|
||||||
.loadOffset = 0x801f8000,
|
.loadOffset = 0x801f8000,
|
||||||
.length = 0x8000
|
.length = 0x8000
|
||||||
}, {
|
}, {
|
||||||
.path = "launchers/803f8000.psexe",
|
.path = "binaries/launcher803f8000.psexe",
|
||||||
.loadOffset = 0x803f8000,
|
.loadOffset = 0x803f8000,
|
||||||
.length = 0x8000
|
.length = 0x8000
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const uint32_t _EXECUTABLE_OFFSETS[]{
|
||||||
|
0,
|
||||||
|
rom::FLASH_EXECUTABLE_OFFSET,
|
||||||
|
util::EXECUTABLE_BODY_OFFSET
|
||||||
|
};
|
||||||
|
|
||||||
bool App::_executableWorker(void) {
|
bool App::_executableWorker(void) {
|
||||||
_workerStatus.update(0, 1, WSTR("App.executableWorker.init"));
|
_workerStatus.update(0, 1, WSTR("App.executableWorker.init"));
|
||||||
|
|
||||||
@ -87,27 +93,32 @@ bool App::_executableWorker(void) {
|
|||||||
goto _fileOpenError;
|
goto _fileOpenError;
|
||||||
|
|
||||||
util::ExecutableHeader header;
|
util::ExecutableHeader header;
|
||||||
size_t length;
|
|
||||||
|
|
||||||
length = _file->read(&header, sizeof(header));
|
// Check for the presence of an executable at several different offsets
|
||||||
|
// within the file before giving up.
|
||||||
if (length != sizeof(header))
|
for (auto offset : _EXECUTABLE_OFFSETS) {
|
||||||
goto _fileError;
|
_file->seek(offset);
|
||||||
|
size_t length = _file->read(&header, sizeof(header));
|
||||||
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));
|
|
||||||
|
|
||||||
if (length != sizeof(header))
|
if (length != sizeof(header))
|
||||||
goto _fileError;
|
break;
|
||||||
if (!header.validateMagic())
|
if (header.validateMagic())
|
||||||
goto _fileError;
|
goto _validFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete _file;
|
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;
|
uintptr_t executableEnd, stackTop;
|
||||||
|
|
||||||
executableEnd = header.textOffset + header.textLength;
|
executableEnd = header.textOffset + header.textLength;
|
||||||
@ -153,18 +164,6 @@ bool App::_executableWorker(void) {
|
|||||||
);
|
);
|
||||||
data.destroy();
|
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
|
// All destructors must be invoked manually as we are not returning to
|
||||||
// main() before starting the new executable.
|
// main() before starting the new executable.
|
||||||
_unloadCartData();
|
_unloadCartData();
|
||||||
@ -175,6 +174,18 @@ bool App::_executableWorker(void) {
|
|||||||
|
|
||||||
_fileProvider.close();
|
_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();
|
uninstallExceptionHandler();
|
||||||
loader.run();
|
loader.run();
|
||||||
}
|
}
|
||||||
@ -186,17 +197,6 @@ bool App::_executableWorker(void) {
|
|||||||
);
|
);
|
||||||
_workerStatus.setNextScreen(_messageScreen);
|
_workerStatus.setNextScreen(_messageScreen);
|
||||||
return false;
|
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) {
|
bool App::_atapiEjectWorker(void) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include "common/args.hpp"
|
||||||
#include <stdlib.h>
|
|
||||||
#include "common/file.hpp"
|
#include "common/file.hpp"
|
||||||
#include "common/gpu.hpp"
|
#include "common/gpu.hpp"
|
||||||
#include "common/io.hpp"
|
#include "common/io.hpp"
|
||||||
@ -11,72 +10,6 @@
|
|||||||
#include "ps1/gpucmd.h"
|
#include "ps1/gpucmd.h"
|
||||||
#include "ps1/system.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) {
|
int main(int argc, const char **argv) {
|
||||||
installExceptionHandler();
|
installExceptionHandler();
|
||||||
gpu::init();
|
gpu::init();
|
||||||
@ -84,37 +17,29 @@ int main(int argc, const char **argv) {
|
|||||||
io::init();
|
io::init();
|
||||||
util::initZipCRC32();
|
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--)
|
for (; argc > 0; argc--)
|
||||||
settings.parse(*(argv++));
|
args.parseArgument(*(argv++));
|
||||||
|
|
||||||
|
#ifdef ENABLE_LOGGING
|
||||||
|
util::logger.setupSyslog(args.baudRate);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
util::logger.setupSyslog(settings.baudRate);
|
// A pointer to the resource archive is always provided on the command line
|
||||||
|
// by the boot stub.
|
||||||
// Load the resource archive, first from memory if a pointer was given and
|
if (!args.resourcePtr || !args.resourceLength) {
|
||||||
// then from the HDD. If both attempts fail, fall back to the archive
|
LOG("required arguments missing");
|
||||||
// embedded into the executable.
|
return 1;
|
||||||
auto resourceProvider = new file::ZIPProvider;
|
|
||||||
|
|
||||||
if (settings.resPtr && settings.resLength) {
|
|
||||||
if (resourceProvider->init(settings.resPtr, settings.resLength))
|
|
||||||
goto _resourceInitDone;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceProvider->init(_resources, _resourcesSize);
|
auto resourceProvider = new file::ZIPProvider;
|
||||||
|
|
||||||
_resourceInitDone:
|
resourceProvider->init(args.resourcePtr, args.resourceLength);
|
||||||
io::clearWatchdog();
|
io::clearWatchdog();
|
||||||
|
|
||||||
auto gpuCtx = new gpu::Context(
|
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 uiCtx = new ui::Context(*gpuCtx);
|
||||||
auto app = new App(*uiCtx, *resourceProvider);
|
auto app = new App(*uiCtx, *resourceProvider);
|
||||||
|
@ -28,10 +28,9 @@
|
|||||||
|
|
||||||
/* Internal state */
|
/* Internal state */
|
||||||
|
|
||||||
static uint32_t _savedBreakpointVector[4];
|
static uint32_t _savedBreakpointVector[4];
|
||||||
static uint32_t _savedExceptionVector[4];
|
static uint32_t _savedExceptionVector[4];
|
||||||
static VoidFunction _flushCache = 0;
|
static Thread _mainThread;
|
||||||
static Thread _mainThread;
|
|
||||||
|
|
||||||
ArgFunction interruptHandler = 0;
|
ArgFunction interruptHandler = 0;
|
||||||
void *interruptHandlerArg = 0;
|
void *interruptHandlerArg = 0;
|
||||||
@ -41,6 +40,12 @@ Thread *nextThread = &_mainThread;
|
|||||||
|
|
||||||
/* Exception handler setup */
|
/* 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 _exceptionVector(void);
|
||||||
|
|
||||||
void installExceptionHandler(void) {
|
void installExceptionHandler(void) {
|
||||||
@ -54,11 +59,6 @@ void installExceptionHandler(void) {
|
|||||||
// Disable interrupts and the GTE at the COP0 side.
|
// Disable interrupts and the GTE at the COP0 side.
|
||||||
cop0_setSR(COP0_SR_CU0);
|
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
|
// Overwrite the default breakpoint and exception handlers placed into RAM
|
||||||
// by the BIOS with a function that will jump to our custom handler.
|
// by the BIOS with a function that will jump to our custom handler.
|
||||||
__builtin_memcpy(_savedBreakpointVector, BIOS_BP_VECTOR, 16);
|
__builtin_memcpy(_savedBreakpointVector, BIOS_BP_VECTOR, 16);
|
||||||
@ -100,9 +100,6 @@ void setInterruptHandler(ArgFunction func, void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void flushCache(void) {
|
void flushCache(void) {
|
||||||
if (!_flushCache)
|
|
||||||
_flushCache = BIOS_API_TABLE[0x44];
|
|
||||||
|
|
||||||
bool enable = disableInterrupts();
|
bool enable = disableInterrupts();
|
||||||
|
|
||||||
_flushCache();
|
_flushCache();
|
||||||
|
@ -13,7 +13,7 @@ from struct import Struct
|
|||||||
from typing import Any, ByteString, Generator, Mapping, Sequence
|
from typing import Any, ByteString, Generator, Mapping, Sequence
|
||||||
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
|
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
|
||||||
|
|
||||||
import numpy
|
import lz4.block, numpy
|
||||||
from numpy import ndarray
|
from numpy import ndarray
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
@ -346,18 +346,20 @@ def createParser() -> ArgumentParser:
|
|||||||
help = "Show this help message and exit"
|
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(
|
group.add_argument(
|
||||||
"-c", "--compress-level",
|
"-c", "--compression",
|
||||||
type = int,
|
type = str,
|
||||||
default = 9,
|
choices = ( "none", "deflate", "lz4" ),
|
||||||
help = "Set default gzip compression level (default 9)",
|
default = "deflate",
|
||||||
metavar = "0-9"
|
help = "Set default compression algorithm (default DEFLATE)"
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"-n", "--no-compression",
|
"-l", "--compress-level",
|
||||||
action = "store_true",
|
type = int,
|
||||||
help = "Forcefully disable gzip compression for all files"
|
default = 9,
|
||||||
|
help = "Set default DEFLATE and LZ4 compression level (default 9)",
|
||||||
|
metavar = "0-9"
|
||||||
)
|
)
|
||||||
|
|
||||||
group = parser.add_argument_group("File paths")
|
group = parser.add_argument_group("File paths")
|
||||||
@ -449,14 +451,32 @@ def main():
|
|||||||
case _type:
|
case _type:
|
||||||
raise KeyError(f"unsupported asset type '{_type}'")
|
raise KeyError(f"unsupported asset type '{_type}'")
|
||||||
|
|
||||||
gzipLevel: int | None = asset.get("compress", args.compress_level)
|
compressLevel: int | None = \
|
||||||
disallow: bool = \
|
asset.get("compressLevel", args.compress_level)
|
||||||
(len(data) < 1024) or (gzipLevel is None) or args.no_compression
|
|
||||||
|
|
||||||
_zip.writestr(
|
match asset.get("compression", args.compression).strip():
|
||||||
asset["name"], data,
|
case "none" | None:
|
||||||
ZIP_STORED if disallow else ZIP_DEFLATED, gzipLevel
|
_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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -2,5 +2,6 @@
|
|||||||
# py -m pip install -r tools/requirements.txt (Windows)
|
# py -m pip install -r tools/requirements.txt (Windows)
|
||||||
# sudo pip install -r tools/requirements.txt (Linux/macOS)
|
# sudo pip install -r tools/requirements.txt (Linux/macOS)
|
||||||
|
|
||||||
|
lz4 >= 4.3.2
|
||||||
numpy >= 1.19.4
|
numpy >= 1.19.4
|
||||||
Pillow >= 8.2.0
|
Pillow >= 8.2.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user