Fix inconsistent EOL
This commit is contained in:
@@ -1,103 +1,103 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"name": "JabyEngine",
|
||||
"path": ".",
|
||||
},
|
||||
{
|
||||
"name": "Include",
|
||||
"path": "..\\..\\include"
|
||||
},
|
||||
{
|
||||
"name": "Root",
|
||||
"path": "..\\.."
|
||||
}
|
||||
],
|
||||
"tasks": {
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "make",
|
||||
"type": "shell",
|
||||
"command": "../../scripts/podman_jaby_engine.sh ../../:src/Library make ${input:target} BUILD_PROFILE=${input:build cfg} PSX_TV_FORMAT=${input:tv format} CUSTOM_CONFIG=${input:config options}",
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "make_all",
|
||||
"type": "shell",
|
||||
"command": "../../scripts/podman_jaby_engine.sh ../../:src/Library make -f MakeAll.mk ${input:target prefix}_${input:target} BUILD_PROFILE=${input:build cfg} CUSTOM_CONFIG=${input:config options}",
|
||||
"group": "build"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "build cfg",
|
||||
"type": "pickString",
|
||||
"options": ["debug", "release"],
|
||||
"default": "release",
|
||||
"description": "build configuration"
|
||||
},
|
||||
{
|
||||
"id": "tv format",
|
||||
"type": "pickString",
|
||||
"options": ["PAL", "NTSC"],
|
||||
"default": "PAL",
|
||||
"description": "TV format to use"
|
||||
},
|
||||
{
|
||||
"id": "config options",
|
||||
"type": "command",
|
||||
"command": "shellCommand.execute",
|
||||
"args": {
|
||||
"command": "echo \"|<Default>\" && ls -d */",
|
||||
"cwd": "${workspaceFolder}/../../config",
|
||||
"fieldSeparator": "|"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "target prefix",
|
||||
"type": "pickString",
|
||||
"options": ["jabyengine", "all_jabyengine"],
|
||||
"default": "jabyengine",
|
||||
"description": "To build JabyEngine or JabyEngine with all configs"
|
||||
},
|
||||
{
|
||||
"id": "target",
|
||||
"type": "pickString",
|
||||
"options": ["all", "clean", "rebuild"],
|
||||
"default": "all",
|
||||
"description": "build target",
|
||||
}
|
||||
]
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": ["augustocdias.tasks-shell-input", "cantonios.project-templates"]
|
||||
},
|
||||
"settings": {
|
||||
"cmake.configureOnOpen": false,
|
||||
"C_Cpp.default.includePath": [
|
||||
"include",
|
||||
"../../include"
|
||||
],
|
||||
"C_Cpp.default.compilerPath": "",
|
||||
"C_Cpp.default.cStandard": "c17",
|
||||
"C_Cpp.default.cppStandard": "c++20",
|
||||
"C_Cpp.default.intelliSenseMode": "linux-gcc-x86",
|
||||
"C_Cpp.default.compilerArgs": [
|
||||
],
|
||||
"C_Cpp.default.defines": [
|
||||
"JABYENGINE_PAL",
|
||||
"__DEBUG_SPU_MMU__",
|
||||
"__friends=public"
|
||||
],
|
||||
"files.exclude": {
|
||||
"**/*.o": true,
|
||||
"**/*.dep": true
|
||||
},
|
||||
"files.associations": {
|
||||
"stdio.h": "c",
|
||||
"TUTO0.C": "cpp",
|
||||
"MAIN.C": "cpp"
|
||||
}
|
||||
}
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"name": "JabyEngine",
|
||||
"path": ".",
|
||||
},
|
||||
{
|
||||
"name": "Include",
|
||||
"path": "..\\..\\include"
|
||||
},
|
||||
{
|
||||
"name": "Root",
|
||||
"path": "..\\.."
|
||||
}
|
||||
],
|
||||
"tasks": {
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "make",
|
||||
"type": "shell",
|
||||
"command": "../../scripts/podman_jaby_engine.sh ../../:src/Library make ${input:target} BUILD_PROFILE=${input:build cfg} PSX_TV_FORMAT=${input:tv format} CUSTOM_CONFIG=${input:config options}",
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "make_all",
|
||||
"type": "shell",
|
||||
"command": "../../scripts/podman_jaby_engine.sh ../../:src/Library make -f MakeAll.mk ${input:target prefix}_${input:target} BUILD_PROFILE=${input:build cfg} CUSTOM_CONFIG=${input:config options}",
|
||||
"group": "build"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "build cfg",
|
||||
"type": "pickString",
|
||||
"options": ["debug", "release"],
|
||||
"default": "release",
|
||||
"description": "build configuration"
|
||||
},
|
||||
{
|
||||
"id": "tv format",
|
||||
"type": "pickString",
|
||||
"options": ["PAL", "NTSC"],
|
||||
"default": "PAL",
|
||||
"description": "TV format to use"
|
||||
},
|
||||
{
|
||||
"id": "config options",
|
||||
"type": "command",
|
||||
"command": "shellCommand.execute",
|
||||
"args": {
|
||||
"command": "echo \"|<Default>\" && ls -d */",
|
||||
"cwd": "${workspaceFolder}/../../config",
|
||||
"fieldSeparator": "|"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "target prefix",
|
||||
"type": "pickString",
|
||||
"options": ["jabyengine", "all_jabyengine"],
|
||||
"default": "jabyengine",
|
||||
"description": "To build JabyEngine or JabyEngine with all configs"
|
||||
},
|
||||
{
|
||||
"id": "target",
|
||||
"type": "pickString",
|
||||
"options": ["all", "clean", "rebuild"],
|
||||
"default": "all",
|
||||
"description": "build target",
|
||||
}
|
||||
]
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": ["augustocdias.tasks-shell-input", "cantonios.project-templates"]
|
||||
},
|
||||
"settings": {
|
||||
"cmake.configureOnOpen": false,
|
||||
"C_Cpp.default.includePath": [
|
||||
"include",
|
||||
"../../include"
|
||||
],
|
||||
"C_Cpp.default.compilerPath": "",
|
||||
"C_Cpp.default.cStandard": "c17",
|
||||
"C_Cpp.default.cppStandard": "c++20",
|
||||
"C_Cpp.default.intelliSenseMode": "linux-gcc-x86",
|
||||
"C_Cpp.default.compilerArgs": [
|
||||
],
|
||||
"C_Cpp.default.defines": [
|
||||
"JABYENGINE_PAL",
|
||||
"__DEBUG_SPU_MMU__",
|
||||
"__friends=public"
|
||||
],
|
||||
"files.exclude": {
|
||||
"**/*.o": true,
|
||||
"**/*.dep": true
|
||||
},
|
||||
"files.associations": {
|
||||
"stdio.h": "c",
|
||||
"TUTO0.C": "cpp",
|
||||
"MAIN.C": "cpp"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
define make_one
|
||||
$(MAKE) $1 PSX_TV_FORMAT=PAL CUSTOM_CONFIG=$2
|
||||
$(MAKE) $1 PSX_TV_FORMAT=NTSC CUSTOM_CONFIG=$2
|
||||
endef
|
||||
|
||||
define make_all
|
||||
$(call make_one,$1,)
|
||||
$(foreach config,$2,$(call make_one,$1,$(config)))
|
||||
endef
|
||||
|
||||
config_files = $(shell cd ../../config && ls -d */)
|
||||
|
||||
jabyengine_%:
|
||||
$(call make_one,$*,$(CUSTOM_CONFIG))
|
||||
|
||||
all_jabyengine_%:
|
||||
$(call make_all,$*,$(config_files))
|
||||
|
||||
all:
|
||||
$(call make_all,all,$(config_files))
|
||||
|
||||
clean:
|
||||
$(call make_all,clean,$(config_files))
|
||||
|
||||
rebuild:
|
||||
define make_one
|
||||
$(MAKE) $1 PSX_TV_FORMAT=PAL CUSTOM_CONFIG=$2
|
||||
$(MAKE) $1 PSX_TV_FORMAT=NTSC CUSTOM_CONFIG=$2
|
||||
endef
|
||||
|
||||
define make_all
|
||||
$(call make_one,$1,)
|
||||
$(foreach config,$2,$(call make_one,$1,$(config)))
|
||||
endef
|
||||
|
||||
config_files = $(shell cd ../../config && ls -d */)
|
||||
|
||||
jabyengine_%:
|
||||
$(call make_one,$*,$(CUSTOM_CONFIG))
|
||||
|
||||
all_jabyengine_%:
|
||||
$(call make_all,$*,$(config_files))
|
||||
|
||||
all:
|
||||
$(call make_all,all,$(config_files))
|
||||
|
||||
clean:
|
||||
$(call make_all,clean,$(config_files))
|
||||
|
||||
rebuild:
|
||||
$(call make_all,rebuild,$(config_files))
|
||||
@@ -1,51 +1,51 @@
|
||||
include ../../mkfile/common/RebuildTarget.mk
|
||||
JABY_ENGINE_DIR = ../../
|
||||
|
||||
ARTIFACT = libJabyEngine_$(PSX_TV_FORMAT)
|
||||
|
||||
SPLASH_IMAGE = src/BootLoader/splash_image_pal_boot.hpp
|
||||
SPLASH_IMAGE_NTSC = src/BootLoader/splash_image_ntsc_boot.hpp
|
||||
|
||||
CCFLAGS += -I../../include -D__friends=public
|
||||
CCFLAGS += -save-temps=obj
|
||||
|
||||
include ../../mkfile/common/CustomConfigHelper.mk
|
||||
CONFIG_NAME = $(PLATFORM)-$(BUILD_PROFILE)/$(CUSTOM_CONFIG)
|
||||
|
||||
include ../../mkfile/common/Wildcard.mk
|
||||
SRCS = $(call rwildcard, src, c cpp s)
|
||||
|
||||
include ../../mkfile/Makefile
|
||||
LIB_DIR = ../../lib/$(CONFIG_NAME)
|
||||
|
||||
MAIN_LIB_OBJS = $(filter-out $(MAIN_BOOT_OBJ) $(OVERLAY_BOOT_OBJ),$(OBJS))
|
||||
|
||||
#$(info $$var is [${MAIN_BOOT_OBJ}])
|
||||
#$(info $$var2 is [${MAIN_LIB_OBJS}])
|
||||
|
||||
#Linking rule
|
||||
$(TARGET).a: $(MAIN_LIB_OBJS) $(SPLASH_IMAGE)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AR) rcs $(TARGET).a $(MAIN_LIB_OBJS)
|
||||
|
||||
#Copy rules
|
||||
$(LIB_DIR)/$(ARTIFACT).a: $(TARGET).a
|
||||
@mkdir -p $(LIB_DIR)
|
||||
cp $(TARGET).a $(LIB_DIR)/$(ARTIFACT).a
|
||||
|
||||
# rule to make the boot image
|
||||
$(SPLASH_IMAGE): ressources/Splash.png
|
||||
psxfileconv --lz4 $< simple-tim full16 | cpp_out --name SplashScreen -o $@
|
||||
|
||||
$(SPLASH_IMAGE_NTSC): ressources/Splash_ntsc.png
|
||||
psxfileconv --lz4 $< simple-tim full16 | cpp_out --name SplashScreen -o $@
|
||||
|
||||
#Rules section for default compilation and linking
|
||||
all: $(SPLASH_IMAGE) $(SPLASH_IMAGE_NTSC) $(LIB_DIR)/$(ARTIFACT).a
|
||||
|
||||
clean:
|
||||
rm -fr $(SPLASH_IMAGE)
|
||||
rm -fr $(SPLASH_IMAGE_NTSC)
|
||||
rm -fr $(OUTPUT_DIR)
|
||||
rm -fr gcm.cache
|
||||
include ../../mkfile/common/RebuildTarget.mk
|
||||
JABY_ENGINE_DIR = ../../
|
||||
|
||||
ARTIFACT = libJabyEngine_$(PSX_TV_FORMAT)
|
||||
|
||||
SPLASH_IMAGE = src/BootLoader/splash_image_pal_boot.hpp
|
||||
SPLASH_IMAGE_NTSC = src/BootLoader/splash_image_ntsc_boot.hpp
|
||||
|
||||
CCFLAGS += -I../../include -D__friends=public
|
||||
CCFLAGS += -save-temps=obj
|
||||
|
||||
include ../../mkfile/common/CustomConfigHelper.mk
|
||||
CONFIG_NAME = $(PLATFORM)-$(BUILD_PROFILE)/$(CUSTOM_CONFIG)
|
||||
|
||||
include ../../mkfile/common/Wildcard.mk
|
||||
SRCS = $(call rwildcard, src, c cpp s)
|
||||
|
||||
include ../../mkfile/Makefile
|
||||
LIB_DIR = ../../lib/$(CONFIG_NAME)
|
||||
|
||||
MAIN_LIB_OBJS = $(filter-out $(MAIN_BOOT_OBJ) $(OVERLAY_BOOT_OBJ),$(OBJS))
|
||||
|
||||
#$(info $$var is [${MAIN_BOOT_OBJ}])
|
||||
#$(info $$var2 is [${MAIN_LIB_OBJS}])
|
||||
|
||||
#Linking rule
|
||||
$(TARGET).a: $(MAIN_LIB_OBJS) $(SPLASH_IMAGE)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AR) rcs $(TARGET).a $(MAIN_LIB_OBJS)
|
||||
|
||||
#Copy rules
|
||||
$(LIB_DIR)/$(ARTIFACT).a: $(TARGET).a
|
||||
@mkdir -p $(LIB_DIR)
|
||||
cp $(TARGET).a $(LIB_DIR)/$(ARTIFACT).a
|
||||
|
||||
# rule to make the boot image
|
||||
$(SPLASH_IMAGE): ressources/Splash.png
|
||||
psxfileconv --lz4 $< simple-tim full16 | cpp_out --name SplashScreen -o $@
|
||||
|
||||
$(SPLASH_IMAGE_NTSC): ressources/Splash_ntsc.png
|
||||
psxfileconv --lz4 $< simple-tim full16 | cpp_out --name SplashScreen -o $@
|
||||
|
||||
#Rules section for default compilation and linking
|
||||
all: $(SPLASH_IMAGE) $(SPLASH_IMAGE_NTSC) $(LIB_DIR)/$(ARTIFACT).a
|
||||
|
||||
clean:
|
||||
rm -fr $(SPLASH_IMAGE)
|
||||
rm -fr $(SPLASH_IMAGE_NTSC)
|
||||
rm -fr $(OUTPUT_DIR)
|
||||
rm -fr gcm.cache
|
||||
rm -fr $(LIB_DIR)/$(ARTIFACT).a
|
||||
@@ -1,44 +1,44 @@
|
||||
#pragma once
|
||||
#include <PSX/System/IOPorts/dma_io.hpp>
|
||||
#include <PSX/SPU/spu.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace SPU {
|
||||
namespace internal {
|
||||
struct DMA {
|
||||
static void wait() {
|
||||
DMA_IO::SPU.wait();
|
||||
while(SPU_IO::StatusRegister.read().is_set(SPU_IO_Values::StatusRegister::TransferBusy));
|
||||
}
|
||||
|
||||
static void end() {
|
||||
SPU_IO::ControlRegister.set_transfer_mode(SPU_IO_Values::ControlRegister::Stop);
|
||||
}
|
||||
|
||||
struct Receive {
|
||||
static void prepare() {
|
||||
end();
|
||||
SPU_IO::DataTransferControl.write(SPU_IO_Values::DataTransferControl::NormalTransferMode());
|
||||
SPU_IO::ControlRegister.set_transfer_mode(SPU_IO_Values::ControlRegister::Stop);
|
||||
}
|
||||
|
||||
static void set_src(uintptr_t adr) {
|
||||
DMA_IO::SPU.set_adr(adr);
|
||||
}
|
||||
|
||||
static void set_dst(SPU::SRAMAdr adr) {
|
||||
SPU_IO::SRAMTransferAdr.write(adr);
|
||||
SPU_IO::ControlRegister.set_transfer_mode(SPU_IO_Values::ControlRegister::DMAWrite);
|
||||
}
|
||||
|
||||
static void start(uint16_t blockCount, uint16_t wordsPerBlock = 0x10) {
|
||||
using SyncMode1 = DMA_IO_Values::BCR::SyncMode1;
|
||||
|
||||
DMA_IO::SPU.block_ctrl.write(DMA_IO_Values::BCR::from(SyncMode1::BlockSize.with(wordsPerBlock), SyncMode1::BlockAmount.with(blockCount)));
|
||||
DMA_IO::SPU.channel_ctrl.write(DMA_IO_Values::CHCHR::StartSPUReceive());
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma once
|
||||
#include <PSX/System/IOPorts/dma_io.hpp>
|
||||
#include <PSX/SPU/spu.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace SPU {
|
||||
namespace internal {
|
||||
struct DMA {
|
||||
static void wait() {
|
||||
DMA_IO::SPU.wait();
|
||||
while(SPU_IO::StatusRegister.read().is_set(SPU_IO_Values::StatusRegister::TransferBusy));
|
||||
}
|
||||
|
||||
static void end() {
|
||||
SPU_IO::ControlRegister.set_transfer_mode(SPU_IO_Values::ControlRegister::Stop);
|
||||
}
|
||||
|
||||
struct Receive {
|
||||
static void prepare() {
|
||||
end();
|
||||
SPU_IO::DataTransferControl.write(SPU_IO_Values::DataTransferControl::NormalTransferMode());
|
||||
SPU_IO::ControlRegister.set_transfer_mode(SPU_IO_Values::ControlRegister::Stop);
|
||||
}
|
||||
|
||||
static void set_src(uintptr_t adr) {
|
||||
DMA_IO::SPU.set_adr(adr);
|
||||
}
|
||||
|
||||
static void set_dst(SPU::SRAMAdr adr) {
|
||||
SPU_IO::SRAMTransferAdr.write(adr);
|
||||
SPU_IO::ControlRegister.set_transfer_mode(SPU_IO_Values::ControlRegister::DMAWrite);
|
||||
}
|
||||
|
||||
static void start(uint16_t blockCount, uint16_t wordsPerBlock = 0x10) {
|
||||
using SyncMode1 = DMA_IO_Values::BCR::SyncMode1;
|
||||
|
||||
DMA_IO::SPU.block_ctrl.write(DMA_IO_Values::BCR::from(SyncMode1::BlockSize.with(wordsPerBlock), SyncMode1::BlockAmount.with(blockCount)));
|
||||
DMA_IO::SPU.channel_ctrl.write(DMA_IO_Values::CHCHR::StartSPUReceive());
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
#include <PSX/jabyengine.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace SPU_MMU {
|
||||
const uint8_t* allocate(uint8_t voice, size_t size);
|
||||
void deallocate(uint8_t voice);
|
||||
}
|
||||
#pragma once
|
||||
#include <PSX/jabyengine.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace SPU_MMU {
|
||||
const uint8_t* allocate(uint8_t voice, size_t size);
|
||||
void deallocate(uint8_t voice);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,51 @@
|
||||
#pragma once
|
||||
#include "threads.hpp"
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Callback {
|
||||
namespace internal {
|
||||
static void execute_callback(Thread::Handle thread_handle, uint32_t parm) {
|
||||
if(CurrentThread::is_me(MainThread::Handle)) {
|
||||
CurrentThread::replace_with(thread_handle);
|
||||
CurrentThread::force_a0(parm);
|
||||
}
|
||||
SysCall::ReturnFromException();
|
||||
}
|
||||
|
||||
static uint32_t resume_callback(Thread::Handle handle) {
|
||||
SysCall::ChangeThread(handle);
|
||||
asm("sw $a0, %0" : "=m"(handle));
|
||||
return handle;
|
||||
}
|
||||
|
||||
namespace VSync {
|
||||
static constexpr size_t StackSize = 64;
|
||||
|
||||
extern SysCall::ThreadHandle thread_handle;
|
||||
extern uint32_t stack[StackSize];
|
||||
void routine();
|
||||
|
||||
static void [[deprecated("Currently not in use")]] execute() {
|
||||
execute_callback(VSync::thread_handle, 0);
|
||||
}
|
||||
}
|
||||
|
||||
namespace CD {
|
||||
static constexpr size_t StackSize = 256;
|
||||
|
||||
extern Thread::Handle thread_handle;
|
||||
extern uint32_t stack[StackSize];
|
||||
void routine(uint32_t irq);
|
||||
|
||||
static void execute(uint32_t irq) {
|
||||
execute_callback(CD::thread_handle, irq);
|
||||
}
|
||||
|
||||
static uint32_t resume() {
|
||||
return resume_callback(MainThread::Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma once
|
||||
#include "threads.hpp"
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Callback {
|
||||
namespace internal {
|
||||
static void execute_callback(Thread::Handle thread_handle, uint32_t parm) {
|
||||
if(CurrentThread::is_me(MainThread::Handle)) {
|
||||
CurrentThread::replace_with(thread_handle);
|
||||
CurrentThread::force_a0(parm);
|
||||
}
|
||||
SysCall::ReturnFromException();
|
||||
}
|
||||
|
||||
static uint32_t resume_callback(Thread::Handle handle) {
|
||||
SysCall::ChangeThread(handle);
|
||||
asm("sw $a0, %0" : "=m"(handle));
|
||||
return handle;
|
||||
}
|
||||
|
||||
namespace VSync {
|
||||
static constexpr size_t StackSize = 64;
|
||||
|
||||
extern SysCall::ThreadHandle thread_handle;
|
||||
extern uint32_t stack[StackSize];
|
||||
void routine();
|
||||
|
||||
static void [[deprecated("Currently not in use")]] execute() {
|
||||
execute_callback(VSync::thread_handle, 0);
|
||||
}
|
||||
}
|
||||
|
||||
namespace CD {
|
||||
static constexpr size_t StackSize = 256;
|
||||
|
||||
extern Thread::Handle thread_handle;
|
||||
extern uint32_t stack[StackSize];
|
||||
void routine(uint32_t irq);
|
||||
|
||||
static void execute(uint32_t irq) {
|
||||
execute_callback(CD::thread_handle, irq);
|
||||
}
|
||||
|
||||
static uint32_t resume() {
|
||||
return resume_callback(MainThread::Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
#pragma once
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
struct Thread {
|
||||
using Handle = SysCall::ThreadHandle;
|
||||
|
||||
static constexpr uint32_t idx_from_handle(SysCall::ThreadHandle thread) {
|
||||
return thread & 0xFFFF;
|
||||
}
|
||||
|
||||
static uintptr_t get_pic_of(Handle handle) {
|
||||
return table_of_tables.threads[idx_from_handle(handle)].epc;
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
static Handle create(void(*function)(uint32_t), uint32_t(&stack)[N]) {
|
||||
return SysCall::OpenThread(reinterpret_cast<void(*)()>(function), &stack[N - 1], SysCall::get_gp());
|
||||
}
|
||||
|
||||
static void set_kernel_mode_for(SysCall::ThreadHandle handle) {
|
||||
table_of_tables.threads[idx_from_handle(handle)].sr = 0x0;
|
||||
}
|
||||
|
||||
static void set_user_mode_for(SysCall::ThreadHandle handle) {
|
||||
table_of_tables.threads[idx_from_handle(handle)].sr = 0x40000404;
|
||||
}
|
||||
};
|
||||
|
||||
struct CurrentThread {
|
||||
static uintptr_t get_pic() {
|
||||
return table_of_tables.processes->current_tcb->epc;
|
||||
}
|
||||
|
||||
static void force_a0(uint32_t a0) {
|
||||
table_of_tables.processes->current_tcb->reg[4] = a0;
|
||||
}
|
||||
|
||||
static bool is_me(Thread::Handle handle) {
|
||||
return table_of_tables.processes->current_tcb == &table_of_tables.threads[Thread::idx_from_handle(handle)];
|
||||
}
|
||||
|
||||
static void replace_with(Thread::Handle handle) {
|
||||
table_of_tables.processes->current_tcb = &table_of_tables.threads[Thread::idx_from_handle(handle)];
|
||||
}
|
||||
};
|
||||
|
||||
struct MainThread {
|
||||
static constexpr const Thread::Handle Handle = 0xFF000000;
|
||||
|
||||
static uintptr_t get_pic() {
|
||||
return table_of_tables.threads[0].epc;
|
||||
}
|
||||
};
|
||||
#pragma once
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
struct Thread {
|
||||
using Handle = SysCall::ThreadHandle;
|
||||
|
||||
static constexpr uint32_t idx_from_handle(SysCall::ThreadHandle thread) {
|
||||
return thread & 0xFFFF;
|
||||
}
|
||||
|
||||
static uintptr_t get_pic_of(Handle handle) {
|
||||
return table_of_tables.threads[idx_from_handle(handle)].epc;
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
static Handle create(void(*function)(uint32_t), uint32_t(&stack)[N]) {
|
||||
return SysCall::OpenThread(reinterpret_cast<void(*)()>(function), &stack[N - 1], SysCall::get_gp());
|
||||
}
|
||||
|
||||
static void set_kernel_mode_for(SysCall::ThreadHandle handle) {
|
||||
table_of_tables.threads[idx_from_handle(handle)].sr = 0x0;
|
||||
}
|
||||
|
||||
static void set_user_mode_for(SysCall::ThreadHandle handle) {
|
||||
table_of_tables.threads[idx_from_handle(handle)].sr = 0x40000404;
|
||||
}
|
||||
};
|
||||
|
||||
struct CurrentThread {
|
||||
static uintptr_t get_pic() {
|
||||
return table_of_tables.processes->current_tcb->epc;
|
||||
}
|
||||
|
||||
static void force_a0(uint32_t a0) {
|
||||
table_of_tables.processes->current_tcb->reg[4] = a0;
|
||||
}
|
||||
|
||||
static bool is_me(Thread::Handle handle) {
|
||||
return table_of_tables.processes->current_tcb == &table_of_tables.threads[Thread::idx_from_handle(handle)];
|
||||
}
|
||||
|
||||
static void replace_with(Thread::Handle handle) {
|
||||
table_of_tables.processes->current_tcb = &table_of_tables.threads[Thread::idx_from_handle(handle)];
|
||||
}
|
||||
};
|
||||
|
||||
struct MainThread {
|
||||
static constexpr const Thread::Handle Handle = 0xFF000000;
|
||||
|
||||
static uintptr_t get_pic() {
|
||||
return table_of_tables.threads[0].epc;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,38 +1,38 @@
|
||||
#pragma once
|
||||
#include <PSX/Auxiliary/bits.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace MIPS {
|
||||
struct SR {
|
||||
static constexpr auto IEc = Bit(0); // Current Interrupt Enable (0=Disable, 1=Enable)
|
||||
static constexpr auto KUc = Bit(1); // Current Kernel/User Mode (0=Kernel, 1=User)
|
||||
static constexpr auto IEp = Bit(2); // Previous Interrupt Disable
|
||||
static constexpr auto KUp = Bit(3); // Previous Kernal/User Mode
|
||||
static constexpr auto IEo = Bit(4); // Old Interrupt Disable
|
||||
static constexpr auto KUo = Bit(5); // Old Kernal/User Mode
|
||||
static constexpr auto Im = BitRange::from_to(8, 15); // 8 bit interrupt mask fields. When set the corresponding interrupts are allowed to cause an exception.
|
||||
static constexpr auto Isc = Bit(16); // Isolate Cache (0=No, 1=Isolate) When isolated, all load and store operations are targetted to the Data cache, and never the main memory. (Used by PSX Kernel, in combination with Port FFFE0130h)
|
||||
static constexpr auto Swc = Bit(17); // Swc Swapped cache mode (0=Normal, 1=Swapped) Instruction cache will act as Data cache and vice versa. Use only with Isc to access & invalidate Instr. cache entries. (Not used by PSX Kernel)
|
||||
static constexpr auto PZ = Bit(18); // PZ When set cache parity bits are written as 0.
|
||||
static constexpr auto CM = Bit(19); // CM Shows the result of the last load operation with the D-cache isolated. It gets set if the cache really contained data for the addressed memory location.
|
||||
static constexpr auto PE = Bit(20); // Cache parity error (Does not cause exception)
|
||||
static constexpr auto TS = Bit(21); // TLB shutdown. Gets set if a programm address simultaneously matches 2 TLB entries. (initial value on reset allows to detect extended CPU version?)
|
||||
static constexpr auto BEV = Bit(22); // Boot exception vectors in RAM/ROM (0=RAM/KSEG0, 1=ROM/KSEG1)
|
||||
static constexpr auto RE = Bit(25); // Reverse endianness (0=Normal endianness, 1=Reverse endianness) Reverses the byte order in which data is stored in memory. (lo-hi -> hi-lo) (Has affect only to User mode, not to Kernal mode) (?) (The bit doesn't exist in PSX ?)
|
||||
static constexpr auto CU0 = Bit(28); // COP0 Enable (0=Enable only in Kernal Mode, 1=Kernal and User Mode)
|
||||
static constexpr auto CU1 = Bit(29); // COP1 Enable (0=Disable, 1=Enable) (none such in PSX)
|
||||
static constexpr auto CU2 = Bit(30); // COP2 Enable (0=Disable, 1=Enable) (GTE in PSX)
|
||||
static constexpr auto CU3 = Bit(31); // COP3 Enable (0=Disable, 1=Enable) (none such in PSX)
|
||||
|
||||
static uint32_t read() {
|
||||
uint32_t sr;
|
||||
__asm__("mfc0 %0, $12" : "=rm"(sr));
|
||||
return sr;
|
||||
}
|
||||
|
||||
static void write(uint32_t sr) {
|
||||
__asm__("mtc0 %0, $12" :: "rm"(sr));
|
||||
}
|
||||
};
|
||||
}
|
||||
#pragma once
|
||||
#include <PSX/Auxiliary/bits.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace MIPS {
|
||||
struct SR {
|
||||
static constexpr auto IEc = Bit(0); // Current Interrupt Enable (0=Disable, 1=Enable)
|
||||
static constexpr auto KUc = Bit(1); // Current Kernel/User Mode (0=Kernel, 1=User)
|
||||
static constexpr auto IEp = Bit(2); // Previous Interrupt Disable
|
||||
static constexpr auto KUp = Bit(3); // Previous Kernal/User Mode
|
||||
static constexpr auto IEo = Bit(4); // Old Interrupt Disable
|
||||
static constexpr auto KUo = Bit(5); // Old Kernal/User Mode
|
||||
static constexpr auto Im = BitRange::from_to(8, 15); // 8 bit interrupt mask fields. When set the corresponding interrupts are allowed to cause an exception.
|
||||
static constexpr auto Isc = Bit(16); // Isolate Cache (0=No, 1=Isolate) When isolated, all load and store operations are targetted to the Data cache, and never the main memory. (Used by PSX Kernel, in combination with Port FFFE0130h)
|
||||
static constexpr auto Swc = Bit(17); // Swc Swapped cache mode (0=Normal, 1=Swapped) Instruction cache will act as Data cache and vice versa. Use only with Isc to access & invalidate Instr. cache entries. (Not used by PSX Kernel)
|
||||
static constexpr auto PZ = Bit(18); // PZ When set cache parity bits are written as 0.
|
||||
static constexpr auto CM = Bit(19); // CM Shows the result of the last load operation with the D-cache isolated. It gets set if the cache really contained data for the addressed memory location.
|
||||
static constexpr auto PE = Bit(20); // Cache parity error (Does not cause exception)
|
||||
static constexpr auto TS = Bit(21); // TLB shutdown. Gets set if a programm address simultaneously matches 2 TLB entries. (initial value on reset allows to detect extended CPU version?)
|
||||
static constexpr auto BEV = Bit(22); // Boot exception vectors in RAM/ROM (0=RAM/KSEG0, 1=ROM/KSEG1)
|
||||
static constexpr auto RE = Bit(25); // Reverse endianness (0=Normal endianness, 1=Reverse endianness) Reverses the byte order in which data is stored in memory. (lo-hi -> hi-lo) (Has affect only to User mode, not to Kernal mode) (?) (The bit doesn't exist in PSX ?)
|
||||
static constexpr auto CU0 = Bit(28); // COP0 Enable (0=Enable only in Kernal Mode, 1=Kernal and User Mode)
|
||||
static constexpr auto CU1 = Bit(29); // COP1 Enable (0=Disable, 1=Enable) (none such in PSX)
|
||||
static constexpr auto CU2 = Bit(30); // COP2 Enable (0=Disable, 1=Enable) (GTE in PSX)
|
||||
static constexpr auto CU3 = Bit(31); // COP3 Enable (0=Disable, 1=Enable) (none such in PSX)
|
||||
|
||||
static uint32_t read() {
|
||||
uint32_t sr;
|
||||
__asm__("mfc0 %0, $12" : "=rm"(sr));
|
||||
return sr;
|
||||
}
|
||||
|
||||
static void write(uint32_t sr) {
|
||||
__asm__("mtc0 %0, $12" :: "rm"(sr));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,39 @@
|
||||
#pragma once
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/IOPorts/periphery_io.hpp>
|
||||
#include <PSX/Periphery/periphery.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
extern "C" void busy_loop(int count);
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Periphery {
|
||||
using namespace Periphery_IO;
|
||||
|
||||
static void connect_to(uint16_t port) {
|
||||
JOY_CTRL.write(Periphery_IO_Values::JOY_CTRL::create_for(port));
|
||||
busy_loop(500);
|
||||
}
|
||||
|
||||
static void close_connection() {
|
||||
JOY_CTRL.write(Periphery_IO_Values::JOY_CTRL::close());
|
||||
}
|
||||
|
||||
static void send_byte(uint8_t byte) {
|
||||
while(!JOY_STAT.is_ready_transfer());
|
||||
JOY_TX_DATA.write(Periphery_IO_Values::JOY_TX_DATA::create(byte));
|
||||
}
|
||||
|
||||
static uint8_t read_byte() {
|
||||
while(!JOY_STAT.has_response());
|
||||
return JOY_RX_DATA.read().raw;
|
||||
}
|
||||
|
||||
static void acknowledge() {
|
||||
while(JOY_STAT.read().is_set(Periphery_IO_Values::JOY_STAT::ACKIrqLow));
|
||||
JOY_CTRL.write(JOY_CTRL.read().set(Periphery_IO_Values::JOY_CTRL::ACK));
|
||||
|
||||
Interrupt::ack_irq(Interrupt::Periphery);
|
||||
}
|
||||
}
|
||||
#pragma once
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/IOPorts/periphery_io.hpp>
|
||||
#include <PSX/Periphery/periphery.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
extern "C" void busy_loop(int count);
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Periphery {
|
||||
using namespace Periphery_IO;
|
||||
|
||||
static void connect_to(uint16_t port) {
|
||||
JOY_CTRL.write(Periphery_IO_Values::JOY_CTRL::create_for(port));
|
||||
busy_loop(500);
|
||||
}
|
||||
|
||||
static void close_connection() {
|
||||
JOY_CTRL.write(Periphery_IO_Values::JOY_CTRL::close());
|
||||
}
|
||||
|
||||
static void send_byte(uint8_t byte) {
|
||||
while(!JOY_STAT.is_ready_transfer());
|
||||
JOY_TX_DATA.write(Periphery_IO_Values::JOY_TX_DATA::create(byte));
|
||||
}
|
||||
|
||||
static uint8_t read_byte() {
|
||||
while(!JOY_STAT.has_response());
|
||||
return JOY_RX_DATA.read().raw;
|
||||
}
|
||||
|
||||
static void acknowledge() {
|
||||
while(JOY_STAT.read().is_set(Periphery_IO_Values::JOY_STAT::ACKIrqLow));
|
||||
JOY_CTRL.write(JOY_CTRL.read().set(Periphery_IO_Values::JOY_CTRL::ACK));
|
||||
|
||||
Interrupt::ack_irq(Interrupt::Periphery);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
# inline_n.h
|
||||
This header is directly lifted from psx-redux and not my creation. I will try to give it my personal touch but the original implementation will be always from [pcsx-redux](https://github.com/grumpycoders/pcsx-redux) and the **grumpycoders**.
|
||||
|
||||
# GTEMAC.H
|
||||
Great for reference! It is under `psyq\include\GTEMAC.H`
|
||||
|
||||
# LIBGTE.H
|
||||
# inline_n.h
|
||||
This header is directly lifted from psx-redux and not my creation. I will try to give it my personal touch but the original implementation will be always from [pcsx-redux](https://github.com/grumpycoders/pcsx-redux) and the **grumpycoders**.
|
||||
|
||||
# GTEMAC.H
|
||||
Great for reference! It is under `psyq\include\GTEMAC.H`
|
||||
|
||||
# LIBGTE.H
|
||||
Not so helpful but can be found under `psyq\include\LIBGTE.H`
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,54 +1,54 @@
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/Audio/CDDA.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CDDA {
|
||||
namespace CD = JabyEngine::CD::internal;
|
||||
|
||||
static CD::BCDTimeStamp last_track;
|
||||
CD::BCDTimeStamp playing_track;
|
||||
|
||||
TrackList get_tracks() {
|
||||
CD::Command::send_wait_response(CD_IO::Command::GetTN);
|
||||
|
||||
const auto stat = CD_IO::PortIndex0::ResponseFifo.read();
|
||||
const auto first = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
const auto end = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
const auto last_track = (end - first) + 1;
|
||||
|
||||
if(last_track == 1) {
|
||||
return TrackList::empty();
|
||||
}
|
||||
|
||||
return TrackList{.first_track = 2, .last_track = static_cast<uint8_t>(last_track)};
|
||||
}
|
||||
|
||||
void play(uint8_t track) {
|
||||
CD::enable_CDDA();
|
||||
CD::Command::send_wait_response(CD_IO::Command::GetTD, track);
|
||||
const auto stats = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
playing_track.min = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
playing_track.sec = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
|
||||
CD::Command::send(CD_IO::Command::SetLoc, playing_track.min, playing_track.sec, 0x0_u8);
|
||||
CD::Command::send(CD_IO::Command::Play);
|
||||
// The PS3 does not support playing a track by track id
|
||||
//CD::Command::send(CD_IO::Command::Play, track);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
CD::pause();
|
||||
}
|
||||
|
||||
void push_play() {
|
||||
stop();
|
||||
last_track = CD::get_loc();
|
||||
}
|
||||
|
||||
void pop_play() {
|
||||
CD::enable_CDDA();
|
||||
CD::Command::send(CD_IO::Command::SetLoc, last_track.min, last_track.sec, last_track.sector);
|
||||
CD::Command::send(CD_IO::Command::Play);
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/Audio/CDDA.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CDDA {
|
||||
namespace CD = JabyEngine::CD::internal;
|
||||
|
||||
static CD::BCDTimeStamp last_track;
|
||||
CD::BCDTimeStamp playing_track;
|
||||
|
||||
TrackList get_tracks() {
|
||||
CD::Command::send_wait_response(CD_IO::Command::GetTN);
|
||||
|
||||
const auto stat = CD_IO::PortIndex0::ResponseFifo.read();
|
||||
const auto first = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
const auto end = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
const auto last_track = (end - first) + 1;
|
||||
|
||||
if(last_track == 1) {
|
||||
return TrackList::empty();
|
||||
}
|
||||
|
||||
return TrackList{.first_track = 2, .last_track = static_cast<uint8_t>(last_track)};
|
||||
}
|
||||
|
||||
void play(uint8_t track) {
|
||||
CD::enable_CDDA();
|
||||
CD::Command::send_wait_response(CD_IO::Command::GetTD, track);
|
||||
const auto stats = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
playing_track.min = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
playing_track.sec = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
|
||||
CD::Command::send(CD_IO::Command::SetLoc, playing_track.min, playing_track.sec, 0x0_u8);
|
||||
CD::Command::send(CD_IO::Command::Play);
|
||||
// The PS3 does not support playing a track by track id
|
||||
//CD::Command::send(CD_IO::Command::Play, track);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
CD::pause();
|
||||
}
|
||||
|
||||
void push_play() {
|
||||
stop();
|
||||
last_track = CD::get_loc();
|
||||
}
|
||||
|
||||
void pop_play() {
|
||||
CD::enable_CDDA();
|
||||
CD::Command::send(CD_IO::Command::SetLoc, last_track.min, last_track.sec, last_track.sector);
|
||||
CD::Command::send(CD_IO::Command::Play);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,68 @@
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include "../../internal-include/CD/cd_types.hpp"
|
||||
#include <PSX/Audio/CDXA.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CDXA {
|
||||
namespace CD = JabyEngine::CD::internal;
|
||||
|
||||
static struct {
|
||||
CD::BCDTimeStamp start_loc;
|
||||
CD::BCDTimeStamp last_loc;
|
||||
bool double_speed;
|
||||
uint8_t channel;
|
||||
} setting;
|
||||
|
||||
CD::State interrupt_handler(uint8_t irq) {
|
||||
switch(irq) {
|
||||
case CD_IO::Interrupt::DataReady: {
|
||||
// The IRQ stack is 0x1000 bytes large so this should fit
|
||||
const auto xa_file = CD::copy_from_sector<CD::RawXADataSector>();
|
||||
if(setting.channel == xa_file.sub_header.channel_number) {
|
||||
CD::set_loc(setting.start_loc);
|
||||
CD::Command::send_no_wait(CD_IO::Command::ReadS);
|
||||
}
|
||||
} break;
|
||||
|
||||
case CD_IO::Interrupt::DiskError:
|
||||
return CD::State::Error;
|
||||
};
|
||||
return CD::State::XAMode;
|
||||
}
|
||||
|
||||
void play(const volatile AutoLBAEntry* lba, uint8_t rel_lba_idx, uint8_t channel, bool double_speed) {
|
||||
setting.start_loc = CD::BCDTimeStamp::from(lba[rel_lba_idx].get_lba());
|
||||
setting.double_speed = double_speed;
|
||||
|
||||
CD::enable_CDXA(double_speed); //< Activates PortIndex0
|
||||
set_channel(channel);
|
||||
CD::Command::send(CD_IO::Command::SetLoc, setting.start_loc.min, setting.start_loc.sec, setting.start_loc.sector);
|
||||
CD::Command::send(CD_IO::Command::ReadS);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
CD::pause();
|
||||
}
|
||||
|
||||
void set_channel(uint8_t channel) {
|
||||
static constexpr uint8_t File = 1;
|
||||
|
||||
CD::Command::send(CD_IO::Command::Filter, File, channel);
|
||||
setting.channel = channel;
|
||||
}
|
||||
|
||||
void push_play() {
|
||||
stop();
|
||||
setting.last_loc = CD::get_loc();
|
||||
CD::current_state = CD::State::Ready;
|
||||
}
|
||||
|
||||
void pop_play() {
|
||||
CD::enable_CDXA(setting.double_speed); //< Activates PortIndex0
|
||||
|
||||
set_channel(setting.channel);
|
||||
CD::Command::send(CD_IO::Command::SetLoc, setting.last_loc.min, setting.last_loc.sec, setting.last_loc.sector);
|
||||
CD::Command::send(CD_IO::Command::ReadS);
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include "../../internal-include/CD/cd_types.hpp"
|
||||
#include <PSX/Audio/CDXA.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CDXA {
|
||||
namespace CD = JabyEngine::CD::internal;
|
||||
|
||||
static struct {
|
||||
CD::BCDTimeStamp start_loc;
|
||||
CD::BCDTimeStamp last_loc;
|
||||
bool double_speed;
|
||||
uint8_t channel;
|
||||
} setting;
|
||||
|
||||
CD::State interrupt_handler(uint8_t irq) {
|
||||
switch(irq) {
|
||||
case CD_IO::Interrupt::DataReady: {
|
||||
// The IRQ stack is 0x1000 bytes large so this should fit
|
||||
const auto xa_file = CD::copy_from_sector<CD::RawXADataSector>();
|
||||
if(setting.channel == xa_file.sub_header.channel_number) {
|
||||
CD::set_loc(setting.start_loc);
|
||||
CD::Command::send_no_wait(CD_IO::Command::ReadS);
|
||||
}
|
||||
} break;
|
||||
|
||||
case CD_IO::Interrupt::DiskError:
|
||||
return CD::State::Error;
|
||||
};
|
||||
return CD::State::XAMode;
|
||||
}
|
||||
|
||||
void play(const volatile AutoLBAEntry* lba, uint8_t rel_lba_idx, uint8_t channel, bool double_speed) {
|
||||
setting.start_loc = CD::BCDTimeStamp::from(lba[rel_lba_idx].get_lba());
|
||||
setting.double_speed = double_speed;
|
||||
|
||||
CD::enable_CDXA(double_speed); //< Activates PortIndex0
|
||||
set_channel(channel);
|
||||
CD::Command::send(CD_IO::Command::SetLoc, setting.start_loc.min, setting.start_loc.sec, setting.start_loc.sector);
|
||||
CD::Command::send(CD_IO::Command::ReadS);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
CD::pause();
|
||||
}
|
||||
|
||||
void set_channel(uint8_t channel) {
|
||||
static constexpr uint8_t File = 1;
|
||||
|
||||
CD::Command::send(CD_IO::Command::Filter, File, channel);
|
||||
setting.channel = channel;
|
||||
}
|
||||
|
||||
void push_play() {
|
||||
stop();
|
||||
setting.last_loc = CD::get_loc();
|
||||
CD::current_state = CD::State::Ready;
|
||||
}
|
||||
|
||||
void pop_play() {
|
||||
CD::enable_CDXA(setting.double_speed); //< Activates PortIndex0
|
||||
|
||||
set_channel(setting.channel);
|
||||
CD::Command::send(CD_IO::Command::SetLoc, setting.last_loc.min, setting.last_loc.sec, setting.last_loc.sector);
|
||||
CD::Command::send(CD_IO::Command::ReadS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,73 @@
|
||||
#pragma once
|
||||
#include "../../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include "bios_font_types.hpp"
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
// | ! | " | # | $ | % | & | ' | ( | ) | * | + | , | - | . | / | 0 |
|
||||
// | 8149 | 8168 | 8194 | 8190 | 8193 | 8195 | 8166 | 8169 | 816A | 8196 | 817B | 8143 | 817C | 8144 | 815E | 8240 |
|
||||
// | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? | @ |
|
||||
// | 8251 | 8252 | 8253 | 8254 | 8255 | 8256 | 8257 | 8258 | 8259 | 8146 | 8147 | 8183 | 8181 | 8184 | 8148 | 8197 |
|
||||
// | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P |
|
||||
// | 8260 | 8261 | 8262 | 8263 | 8264 | 8265 | 8266 | 8267 | 8268 | 8269 | 826A | 826B | 826C | 826D | 826E | 826F |
|
||||
// | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ | ` |
|
||||
// | 8270 | 8271 | 8272 | 8273 | 8274 | 8275 | 8276 | 8277 | 8278 | 8279 | 816D | 815F | 816E | 814F | 8151 | 814D |
|
||||
// | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
|
||||
// | 8281 | 8282 | 8283 | 8284 | 8285 | 8286 | 8287 | 8288 | 8289 | 828A | 828B | 828C | 828D | 828E | 828F | 8290 |
|
||||
// | q | r | s | t | u | v | w | x | y | z | { | | | } | ~ | | |
|
||||
// | 8291 | 8292 | 8293 | 8294 | 8295 | 8296 | 8297 | 8298 | 8299 | 829A | 816F | 8162 | 8170 | 8160 | | |
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GPU {
|
||||
namespace SJIS {
|
||||
// base: 0x8100
|
||||
static const SpecialChar Specials[] = { // ToDo: Can we split this into 4 arrays? Would that help somehow?
|
||||
// { ! } { " } { # } { $ } { % } { & } { ' } { ( } { ) } { * } { + } { , } { - } { . } { / }
|
||||
{0x49, 0}, {0x68, 1}, {0x94, 2}, {0x90, 3}, {0x93, 4}, {0x95, 5}, {0x66, 6}, {0x69, 7}, {0x6A, 8}, {0x96, 9}, {0x7B, 10}, {0x43, 11}, {0x7C, 12}, {0x44, 13}, {0x5E, 14},
|
||||
// { : } { ; } { < } { = } { > } { ? } { @ }
|
||||
{0x46, 25}, {0x47, 26}, {0x83, 27}, {0x81, 28}, {0x84, 29}, {0x48, 30}, {0x97, 31},
|
||||
// { [ } { \ } { ] } { ^ } { _ } { ` }
|
||||
{0x6D, 58}, {0x5F, 59}, {0x6E, 60}, {0x4F, 61}, {0x51, 62}, {0x4D, 63},
|
||||
// { { } { | } { } } { ~ }
|
||||
{0x6F, 90}, {0x62, 91}, {0x70, 92}, {0x60, 93}
|
||||
};
|
||||
|
||||
// base: 0x8200
|
||||
static const RangeChar AlphaNumeric[] = {
|
||||
// { 0 }
|
||||
{{0x4F, 15}, 1},
|
||||
// { 1 - 9 }
|
||||
{{0x50, 16}, 9},
|
||||
// { A - Z }
|
||||
{{0x60, 32}, 26},
|
||||
// { a - z }
|
||||
{{0x81, 64}, 26}
|
||||
};
|
||||
|
||||
static void load(const PositionU16& start_pos) {
|
||||
FontBuffer font_buffer;
|
||||
const auto load_special_chars = [&font_buffer]<size_t Size>(const PositionU16& pos, const SpecialChar(&special_chars)[Size]) {
|
||||
for(const auto& special_char : special_chars) {
|
||||
font_buffer.load_to(pos.add(FontBuffer::vram_offset(special_char.tile_id)), SysCall::Krom2RawAdd(0x8100 | special_char.base_offset));
|
||||
}
|
||||
};
|
||||
const auto load_alpha_num = [&font_buffer]<size_t Size>(const PositionU16& pos, const RangeChar(&range_char)[Size]) {
|
||||
for(const auto& range : range_char) {
|
||||
const auto end_tile = range.start_char.tile_id + range.length;
|
||||
auto sjis_code = 0x8200 | range.start_char.base_offset;
|
||||
|
||||
for(uint16_t tile_id = range.start_char.tile_id; tile_id < end_tile; tile_id++,sjis_code++) {
|
||||
font_buffer.load_to(pos.add(FontBuffer::vram_offset(tile_id)), SysCall::Krom2RawAdd(sjis_code));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
font_buffer.setup();
|
||||
|
||||
GPU::internal::DMA::Receive::prepare();
|
||||
load_special_chars(start_pos, Specials);
|
||||
load_alpha_num(start_pos, AlphaNumeric);
|
||||
font_buffer.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma once
|
||||
#include "../../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include "bios_font_types.hpp"
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
// | ! | " | # | $ | % | & | ' | ( | ) | * | + | , | - | . | / | 0 |
|
||||
// | 8149 | 8168 | 8194 | 8190 | 8193 | 8195 | 8166 | 8169 | 816A | 8196 | 817B | 8143 | 817C | 8144 | 815E | 8240 |
|
||||
// | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? | @ |
|
||||
// | 8251 | 8252 | 8253 | 8254 | 8255 | 8256 | 8257 | 8258 | 8259 | 8146 | 8147 | 8183 | 8181 | 8184 | 8148 | 8197 |
|
||||
// | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P |
|
||||
// | 8260 | 8261 | 8262 | 8263 | 8264 | 8265 | 8266 | 8267 | 8268 | 8269 | 826A | 826B | 826C | 826D | 826E | 826F |
|
||||
// | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ | ` |
|
||||
// | 8270 | 8271 | 8272 | 8273 | 8274 | 8275 | 8276 | 8277 | 8278 | 8279 | 816D | 815F | 816E | 814F | 8151 | 814D |
|
||||
// | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
|
||||
// | 8281 | 8282 | 8283 | 8284 | 8285 | 8286 | 8287 | 8288 | 8289 | 828A | 828B | 828C | 828D | 828E | 828F | 8290 |
|
||||
// | q | r | s | t | u | v | w | x | y | z | { | | | } | ~ | | |
|
||||
// | 8291 | 8292 | 8293 | 8294 | 8295 | 8296 | 8297 | 8298 | 8299 | 829A | 816F | 8162 | 8170 | 8160 | | |
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GPU {
|
||||
namespace SJIS {
|
||||
// base: 0x8100
|
||||
static const SpecialChar Specials[] = { // ToDo: Can we split this into 4 arrays? Would that help somehow?
|
||||
// { ! } { " } { # } { $ } { % } { & } { ' } { ( } { ) } { * } { + } { , } { - } { . } { / }
|
||||
{0x49, 0}, {0x68, 1}, {0x94, 2}, {0x90, 3}, {0x93, 4}, {0x95, 5}, {0x66, 6}, {0x69, 7}, {0x6A, 8}, {0x96, 9}, {0x7B, 10}, {0x43, 11}, {0x7C, 12}, {0x44, 13}, {0x5E, 14},
|
||||
// { : } { ; } { < } { = } { > } { ? } { @ }
|
||||
{0x46, 25}, {0x47, 26}, {0x83, 27}, {0x81, 28}, {0x84, 29}, {0x48, 30}, {0x97, 31},
|
||||
// { [ } { \ } { ] } { ^ } { _ } { ` }
|
||||
{0x6D, 58}, {0x5F, 59}, {0x6E, 60}, {0x4F, 61}, {0x51, 62}, {0x4D, 63},
|
||||
// { { } { | } { } } { ~ }
|
||||
{0x6F, 90}, {0x62, 91}, {0x70, 92}, {0x60, 93}
|
||||
};
|
||||
|
||||
// base: 0x8200
|
||||
static const RangeChar AlphaNumeric[] = {
|
||||
// { 0 }
|
||||
{{0x4F, 15}, 1},
|
||||
// { 1 - 9 }
|
||||
{{0x50, 16}, 9},
|
||||
// { A - Z }
|
||||
{{0x60, 32}, 26},
|
||||
// { a - z }
|
||||
{{0x81, 64}, 26}
|
||||
};
|
||||
|
||||
static void load(const PositionU16& start_pos) {
|
||||
FontBuffer font_buffer;
|
||||
const auto load_special_chars = [&font_buffer]<size_t Size>(const PositionU16& pos, const SpecialChar(&special_chars)[Size]) {
|
||||
for(const auto& special_char : special_chars) {
|
||||
font_buffer.load_to(pos.add(FontBuffer::vram_offset(special_char.tile_id)), SysCall::Krom2RawAdd(0x8100 | special_char.base_offset));
|
||||
}
|
||||
};
|
||||
const auto load_alpha_num = [&font_buffer]<size_t Size>(const PositionU16& pos, const RangeChar(&range_char)[Size]) {
|
||||
for(const auto& range : range_char) {
|
||||
const auto end_tile = range.start_char.tile_id + range.length;
|
||||
auto sjis_code = 0x8200 | range.start_char.base_offset;
|
||||
|
||||
for(uint16_t tile_id = range.start_char.tile_id; tile_id < end_tile; tile_id++,sjis_code++) {
|
||||
font_buffer.load_to(pos.add(FontBuffer::vram_offset(tile_id)), SysCall::Krom2RawAdd(sjis_code));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
font_buffer.setup();
|
||||
|
||||
GPU::internal::DMA::Receive::prepare();
|
||||
load_special_chars(start_pos, Specials);
|
||||
load_alpha_num(start_pos, AlphaNumeric);
|
||||
font_buffer.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +1,98 @@
|
||||
#pragma once
|
||||
#include "../../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include <PSX/GPU/gpu_auto_load_font.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GPU {
|
||||
namespace SJIS {
|
||||
struct FontBuffer {
|
||||
// A line of 16 Pixel
|
||||
struct Line {
|
||||
LookUpColor4 pixel[BIOS_Font::Size.width/4];
|
||||
|
||||
void load(uint16_t bit_pattern) {
|
||||
static constexpr auto BitsPerRound = 4;
|
||||
|
||||
size_t px_idx = 0;
|
||||
for(uint16_t shift = 1 << 15; shift; shift = shift >> BitsPerRound) {
|
||||
uint8_t lu_ids[BitsPerRound];
|
||||
|
||||
for(size_t n = 0; n < BitsPerRound; n++) {
|
||||
lu_ids[n] = (bit_pattern & (shift >> n)) ? 1 : 0;
|
||||
}
|
||||
|
||||
this->pixel[px_idx++] = LookUpColor4::create(lu_ids);
|
||||
}
|
||||
}
|
||||
|
||||
static PositionU16 vram_offset(uint16_t tile_id) {
|
||||
return tile_id_for16<PositionU16>(tile_id, SizeU16::create(4, 16));
|
||||
}
|
||||
};
|
||||
|
||||
// Letters are actually only 16x15 but should be treated 16x16
|
||||
struct Letter {
|
||||
PositionU16 position;
|
||||
Line lines[BIOS_Font::Size.height - 1];
|
||||
Line empty_line;
|
||||
|
||||
void setup() {
|
||||
this->empty_line.load(0);
|
||||
}
|
||||
|
||||
void load_to(const PositionU16& pos, const uint16_t* bit_map) {
|
||||
this->position = pos;
|
||||
for(auto& cur_line : this->lines) {
|
||||
cur_line.load(__builtin_bswap16(*bit_map++));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// v double buffer do not change size without adjusting
|
||||
Letter letter_buffer[2];
|
||||
uint8_t free_idx;
|
||||
|
||||
void setup() {
|
||||
for(auto& letter : this->letter_buffer) {
|
||||
letter.setup();
|
||||
}
|
||||
this->free_idx = 0;
|
||||
GPU::internal::DMA::Receive::prepare();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
GPU::internal::DMA::wait();
|
||||
GPU::internal::DMA::end();
|
||||
}
|
||||
|
||||
void load_to(const PositionU16& pos, const uint16_t* bit_map) {
|
||||
auto& cur_letter = this->letter_buffer[this->free_idx];
|
||||
this->free_idx ^= 1;
|
||||
|
||||
cur_letter.load_to(pos, bit_map);
|
||||
GPU::internal::DMA::wait();
|
||||
GPU::internal::DMA::Receive::set_src(reinterpret_cast<uintptr_t>(cur_letter.lines));
|
||||
// v 4 Pixel per byte
|
||||
GPU::internal::DMA::Receive::set_dst(cur_letter.position, SizeU16::create(16/4, 16));
|
||||
GPU::internal::DMA::Receive::start(sizeof(Line) >> 2);
|
||||
}
|
||||
|
||||
static PositionU16 vram_offset(uint16_t tile_id) {
|
||||
return Line::vram_offset(tile_id);
|
||||
}
|
||||
};
|
||||
|
||||
struct SpecialChar {
|
||||
uint8_t base_offset;
|
||||
uint8_t tile_id;
|
||||
};
|
||||
|
||||
struct RangeChar {
|
||||
SpecialChar start_char;
|
||||
uint8_t length;
|
||||
};
|
||||
|
||||
static_assert(BIOS_Font::Size == SizeU16::create(16, 16));
|
||||
}
|
||||
}
|
||||
#pragma once
|
||||
#include "../../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include <PSX/GPU/gpu_auto_load_font.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GPU {
|
||||
namespace SJIS {
|
||||
struct FontBuffer {
|
||||
// A line of 16 Pixel
|
||||
struct Line {
|
||||
LookUpColor4 pixel[BIOS_Font::Size.width/4];
|
||||
|
||||
void load(uint16_t bit_pattern) {
|
||||
static constexpr auto BitsPerRound = 4;
|
||||
|
||||
size_t px_idx = 0;
|
||||
for(uint16_t shift = 1 << 15; shift; shift = shift >> BitsPerRound) {
|
||||
uint8_t lu_ids[BitsPerRound];
|
||||
|
||||
for(size_t n = 0; n < BitsPerRound; n++) {
|
||||
lu_ids[n] = (bit_pattern & (shift >> n)) ? 1 : 0;
|
||||
}
|
||||
|
||||
this->pixel[px_idx++] = LookUpColor4::create(lu_ids);
|
||||
}
|
||||
}
|
||||
|
||||
static PositionU16 vram_offset(uint16_t tile_id) {
|
||||
return tile_id_for16<PositionU16>(tile_id, SizeU16::create(4, 16));
|
||||
}
|
||||
};
|
||||
|
||||
// Letters are actually only 16x15 but should be treated 16x16
|
||||
struct Letter {
|
||||
PositionU16 position;
|
||||
Line lines[BIOS_Font::Size.height - 1];
|
||||
Line empty_line;
|
||||
|
||||
void setup() {
|
||||
this->empty_line.load(0);
|
||||
}
|
||||
|
||||
void load_to(const PositionU16& pos, const uint16_t* bit_map) {
|
||||
this->position = pos;
|
||||
for(auto& cur_line : this->lines) {
|
||||
cur_line.load(__builtin_bswap16(*bit_map++));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// v double buffer do not change size without adjusting
|
||||
Letter letter_buffer[2];
|
||||
uint8_t free_idx;
|
||||
|
||||
void setup() {
|
||||
for(auto& letter : this->letter_buffer) {
|
||||
letter.setup();
|
||||
}
|
||||
this->free_idx = 0;
|
||||
GPU::internal::DMA::Receive::prepare();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
GPU::internal::DMA::wait();
|
||||
GPU::internal::DMA::end();
|
||||
}
|
||||
|
||||
void load_to(const PositionU16& pos, const uint16_t* bit_map) {
|
||||
auto& cur_letter = this->letter_buffer[this->free_idx];
|
||||
this->free_idx ^= 1;
|
||||
|
||||
cur_letter.load_to(pos, bit_map);
|
||||
GPU::internal::DMA::wait();
|
||||
GPU::internal::DMA::Receive::set_src(reinterpret_cast<uintptr_t>(cur_letter.lines));
|
||||
// v 4 Pixel per byte
|
||||
GPU::internal::DMA::Receive::set_dst(cur_letter.position, SizeU16::create(16/4, 16));
|
||||
GPU::internal::DMA::Receive::start(sizeof(Line) >> 2);
|
||||
}
|
||||
|
||||
static PositionU16 vram_offset(uint16_t tile_id) {
|
||||
return Line::vram_offset(tile_id);
|
||||
}
|
||||
};
|
||||
|
||||
struct SpecialChar {
|
||||
uint8_t base_offset;
|
||||
uint8_t tile_id;
|
||||
};
|
||||
|
||||
struct RangeChar {
|
||||
SpecialChar start_char;
|
||||
uint8_t length;
|
||||
};
|
||||
|
||||
static_assert(BIOS_Font::Size == SizeU16::create(16, 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
#include "../../internal-include/BootLoader/boot_loader.hpp"
|
||||
#include "../../internal-include/System/callbacks_internal.hpp"
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace Callbacks {
|
||||
namespace InternalCallback = JabyEngine::Callback::internal;
|
||||
|
||||
void setup() {
|
||||
// We do not use threads anymore but keep the code for it
|
||||
/*SysCall::EnterCriticalSection();
|
||||
/*InternalCallback::VSync::thread_handle = SysCall::OpenThread(
|
||||
InternalCallback::VSync::routine,
|
||||
&InternalCallback::VSync::stack[InternalCallback::VSync::StackSize - 1],
|
||||
SysCall::get_gp()
|
||||
);
|
||||
Thread::set_user_mode_for(InternalCallback::VSync::thread_handle);
|
||||
|
||||
InternalCallback::CD::thread_handle = Thread::create(InternalCallback::CD::routine, InternalCallback::CD::stack);
|
||||
Thread::set_user_mode_for(InternalCallback::CD::thread_handle);
|
||||
SysCall::ExitCriticalSection();*/
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/BootLoader/boot_loader.hpp"
|
||||
#include "../../internal-include/System/callbacks_internal.hpp"
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace Callbacks {
|
||||
namespace InternalCallback = JabyEngine::Callback::internal;
|
||||
|
||||
void setup() {
|
||||
// We do not use threads anymore but keep the code for it
|
||||
/*SysCall::EnterCriticalSection();
|
||||
/*InternalCallback::VSync::thread_handle = SysCall::OpenThread(
|
||||
InternalCallback::VSync::routine,
|
||||
&InternalCallback::VSync::stack[InternalCallback::VSync::StackSize - 1],
|
||||
SysCall::get_gp()
|
||||
);
|
||||
Thread::set_user_mode_for(InternalCallback::VSync::thread_handle);
|
||||
|
||||
InternalCallback::CD::thread_handle = Thread::create(InternalCallback::CD::routine, InternalCallback::CD::stack);
|
||||
Thread::set_user_mode_for(InternalCallback::CD::thread_handle);
|
||||
SysCall::ExitCriticalSection();*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,42 @@
|
||||
#include "../../internal-include/BootLoader/boot_loader.hpp"
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/IOPorts/memory_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CD {
|
||||
namespace internal {
|
||||
extern SysCall::InterruptCallback irq_callback;
|
||||
}
|
||||
}
|
||||
|
||||
namespace boot {
|
||||
namespace CD {
|
||||
using JabyEngine::CD::internal::Command;
|
||||
|
||||
void setup() {
|
||||
static constexpr auto DebugX = 1;
|
||||
static constexpr auto DebugY = 1;
|
||||
static constexpr auto DebugScale = 1.0;
|
||||
|
||||
__debug_boot_color_at(::JabyEngine::GPU::Color24::White(), DebugX, DebugY, DebugScale);
|
||||
SysCall::EnterCriticalSection();
|
||||
Memory_IO::COM_DELAY.write(Memory_IO_Values::COM_DELAY::create());
|
||||
Memory_IO::CD_DELAY.write(Memory_IO_Values::CD_DELAY::create());
|
||||
|
||||
SysCall::SysEnqIntRP(SysCall::Priority::CdromIoIrq, &::JabyEngine::CD::internal::irq_callback);
|
||||
|
||||
CD_IO::PortIndex1::change_to();
|
||||
CD_IO::Interrupt::ack_extended(CD_IO::PortIndex1::InterruptFlag);
|
||||
|
||||
// TODO: Verify this on real HW
|
||||
CD_IO::Interrupt::enable_extended(CD_IO::PortIndex1::InterruptEnable);
|
||||
|
||||
Interrupt::enable_irq(Interrupt::CDROM);
|
||||
Interrupt::ack_irq(Interrupt::CDROM);
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/BootLoader/boot_loader.hpp"
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/IOPorts/memory_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CD {
|
||||
namespace internal {
|
||||
extern SysCall::InterruptCallback irq_callback;
|
||||
}
|
||||
}
|
||||
|
||||
namespace boot {
|
||||
namespace CD {
|
||||
using JabyEngine::CD::internal::Command;
|
||||
|
||||
void setup() {
|
||||
static constexpr auto DebugX = 1;
|
||||
static constexpr auto DebugY = 1;
|
||||
static constexpr auto DebugScale = 1.0;
|
||||
|
||||
__debug_boot_color_at(::JabyEngine::GPU::Color24::White(), DebugX, DebugY, DebugScale);
|
||||
SysCall::EnterCriticalSection();
|
||||
Memory_IO::COM_DELAY.write(Memory_IO_Values::COM_DELAY::create());
|
||||
Memory_IO::CD_DELAY.write(Memory_IO_Values::CD_DELAY::create());
|
||||
|
||||
SysCall::SysEnqIntRP(SysCall::Priority::CdromIoIrq, &::JabyEngine::CD::internal::irq_callback);
|
||||
|
||||
CD_IO::PortIndex1::change_to();
|
||||
CD_IO::Interrupt::ack_extended(CD_IO::PortIndex1::InterruptFlag);
|
||||
|
||||
// TODO: Verify this on real HW
|
||||
CD_IO::Interrupt::enable_extended(CD_IO::PortIndex1::InterruptEnable);
|
||||
|
||||
Interrupt::enable_irq(Interrupt::CDROM);
|
||||
Interrupt::ack_irq(Interrupt::CDROM);
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
#include <PSX/System/IOPorts/dma_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace DMA {
|
||||
void setup() {
|
||||
static constexpr auto EnableDMA = DMA_IO_Values::DPCR::from(
|
||||
DMA_IO_Values::DPCR::SPU.turn_on(3), DMA_IO_Values::DPCR::GPU.turn_on(3), DMA_IO_Values::DPCR::CDROM.turn_on(3)
|
||||
);
|
||||
|
||||
DMA_IO::DPCR.write(EnableDMA);
|
||||
DMA_IO::DICR.write(DMA_IO_Values::DICR::empty());
|
||||
// ACK IRQ
|
||||
DMA_IO::DICR.write(DMA_IO::DICR.read());
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
Interrupt::disable_irq(Interrupt::DMA);
|
||||
Interrupt::ack_irq(Interrupt::DMA);
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
#include <PSX/System/IOPorts/dma_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace DMA {
|
||||
void setup() {
|
||||
static constexpr auto EnableDMA = DMA_IO_Values::DPCR::from(
|
||||
DMA_IO_Values::DPCR::SPU.turn_on(3), DMA_IO_Values::DPCR::GPU.turn_on(3), DMA_IO_Values::DPCR::CDROM.turn_on(3)
|
||||
);
|
||||
|
||||
DMA_IO::DPCR.write(EnableDMA);
|
||||
DMA_IO::DICR.write(DMA_IO_Values::DICR::empty());
|
||||
// ACK IRQ
|
||||
DMA_IO::DICR.write(DMA_IO::DICR.read());
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
Interrupt::disable_irq(Interrupt::DMA);
|
||||
Interrupt::ack_irq(Interrupt::DMA);
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +1,109 @@
|
||||
#include "../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/Auxiliary/lz4_decompressor.hpp>
|
||||
#include <PSX/File/Processor/file_processor.hpp>
|
||||
#include <PSX/GPU/gpu.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
#ifdef JABYENGINE_PAL
|
||||
#include "splash_image_pal_boot.hpp"
|
||||
#else
|
||||
#include "splash_image_ntsc_boot.hpp"
|
||||
#endif //JABYENGINE_PAL
|
||||
|
||||
// Concept for switchting BIOS Fonts...?
|
||||
#include "BIOSFont/ascii_bios_font.hpp"
|
||||
|
||||
extern "C" uint32_t __boot_loader_end;
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GPU {
|
||||
namespace internal {
|
||||
extern SysCall::InterruptCallback irq_callback;
|
||||
}
|
||||
|
||||
namespace SJIS {
|
||||
void load_clut(const PositionU16& dst_cord) {
|
||||
struct CLUT {
|
||||
CPU2VRAM cmd;
|
||||
Color data[16];
|
||||
};
|
||||
const CLUT clut {
|
||||
.cmd = CPU2VRAM::create(AreaU16::create(dst_cord, GPU::SizeU16::create(16, 1))),
|
||||
.data = {
|
||||
Color::Black(), Color::Grey(),
|
||||
Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(),
|
||||
Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(),
|
||||
}
|
||||
};
|
||||
|
||||
GPU::internal::render(reinterpret_cast<const uint32_t*>(&clut), sizeof(CLUT)/sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace boot {
|
||||
namespace GPU {
|
||||
using namespace JabyEngine::GPU;
|
||||
|
||||
static void configure_display() {
|
||||
GPU_IO::GP1.set_display_mode(::JabyEngine::GPU::internal::Display::DisplayMode);
|
||||
GPU::Display::set_offset(0, 0);
|
||||
}
|
||||
|
||||
static size_t decompress_logo() {
|
||||
LZ4Decompressor lz4_decomp(reinterpret_cast<uint8_t*>(&__boot_loader_end));
|
||||
|
||||
const auto [progress, bytes_ready] = lz4_decomp.process(ArrayRange(SplashScreen, sizeof(SplashScreen)), true);
|
||||
switch(progress) {
|
||||
case Progress::InProgress:
|
||||
printf("Decompressing still in progress... %llu\n", bytes_ready);
|
||||
break;
|
||||
|
||||
case Progress::Error:
|
||||
printf("Error decompressing!!!\n");
|
||||
break;
|
||||
|
||||
case Progress::Done:
|
||||
printf("Done decompressing: %llu Bytes ready\n", bytes_ready);
|
||||
break;
|
||||
}
|
||||
|
||||
return bytes_ready;
|
||||
}
|
||||
|
||||
void display_logo() {
|
||||
static constexpr uint16_t TexturePageHeight = 256;
|
||||
const auto bytes_ready = decompress_logo();
|
||||
|
||||
// Upload SplashScreen picture
|
||||
auto state = FileProcessor::create(&__boot_loader_end, SimpleTIM::create(32, 0, 0, 0));
|
||||
state.process(bytes_ready);
|
||||
|
||||
// Now load the BIOS font to the specified location
|
||||
SJIS::load(BIOS_Font::TextureLoadPos);
|
||||
SJIS::load_clut(BIOS_Font::CLUTLoadPos);
|
||||
|
||||
// Duplicate DisplayBuffer content
|
||||
::JabyEngine::GPU::internal::copy_vram_to_vram({PositionU16::create(0, TexturePageHeight), SizeU16::create(Display::Width, TexturePageHeight)}, PositionU16::create(0, 0));
|
||||
|
||||
Display::enable();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
GPU_IO::GP1.reset();
|
||||
configure_display();
|
||||
::JabyEngine::GPU::internal::Display::exchange_buffer_and_display();
|
||||
|
||||
GPU::internal::wait_ready_for_CMD();
|
||||
GPU::internal::quick_fill_fast(Color24::Black(), {PositionU16::create(0, 0), SizeU16::create(Display::Width, Display::Height)});
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
SysCall::SysEnqIntRP(SysCall::Priority::VblankIrq, &::JabyEngine::GPU::internal::irq_callback);
|
||||
Interrupt::enable_irq(Interrupt::VBlank);
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/Auxiliary/lz4_decompressor.hpp>
|
||||
#include <PSX/File/Processor/file_processor.hpp>
|
||||
#include <PSX/GPU/gpu.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
#ifdef JABYENGINE_PAL
|
||||
#include "splash_image_pal_boot.hpp"
|
||||
#else
|
||||
#include "splash_image_ntsc_boot.hpp"
|
||||
#endif //JABYENGINE_PAL
|
||||
|
||||
// Concept for switchting BIOS Fonts...?
|
||||
#include "BIOSFont/ascii_bios_font.hpp"
|
||||
|
||||
extern "C" uint32_t __boot_loader_end;
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GPU {
|
||||
namespace internal {
|
||||
extern SysCall::InterruptCallback irq_callback;
|
||||
}
|
||||
|
||||
namespace SJIS {
|
||||
void load_clut(const PositionU16& dst_cord) {
|
||||
struct CLUT {
|
||||
CPU2VRAM cmd;
|
||||
Color data[16];
|
||||
};
|
||||
const CLUT clut {
|
||||
.cmd = CPU2VRAM::create(AreaU16::create(dst_cord, GPU::SizeU16::create(16, 1))),
|
||||
.data = {
|
||||
Color::Black(), Color::Grey(),
|
||||
Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(),
|
||||
Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(), Color::Black(),
|
||||
}
|
||||
};
|
||||
|
||||
GPU::internal::render(reinterpret_cast<const uint32_t*>(&clut), sizeof(CLUT)/sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace boot {
|
||||
namespace GPU {
|
||||
using namespace JabyEngine::GPU;
|
||||
|
||||
static void configure_display() {
|
||||
GPU_IO::GP1.set_display_mode(::JabyEngine::GPU::internal::Display::DisplayMode);
|
||||
GPU::Display::set_offset(0, 0);
|
||||
}
|
||||
|
||||
static size_t decompress_logo() {
|
||||
LZ4Decompressor lz4_decomp(reinterpret_cast<uint8_t*>(&__boot_loader_end));
|
||||
|
||||
const auto [progress, bytes_ready] = lz4_decomp.process(ArrayRange(SplashScreen, sizeof(SplashScreen)), true);
|
||||
switch(progress) {
|
||||
case Progress::InProgress:
|
||||
printf("Decompressing still in progress... %llu\n", bytes_ready);
|
||||
break;
|
||||
|
||||
case Progress::Error:
|
||||
printf("Error decompressing!!!\n");
|
||||
break;
|
||||
|
||||
case Progress::Done:
|
||||
printf("Done decompressing: %llu Bytes ready\n", bytes_ready);
|
||||
break;
|
||||
}
|
||||
|
||||
return bytes_ready;
|
||||
}
|
||||
|
||||
void display_logo() {
|
||||
static constexpr uint16_t TexturePageHeight = 256;
|
||||
const auto bytes_ready = decompress_logo();
|
||||
|
||||
// Upload SplashScreen picture
|
||||
auto state = FileProcessor::create(&__boot_loader_end, SimpleTIM::create(32, 0, 0, 0));
|
||||
state.process(bytes_ready);
|
||||
|
||||
// Now load the BIOS font to the specified location
|
||||
SJIS::load(BIOS_Font::TextureLoadPos);
|
||||
SJIS::load_clut(BIOS_Font::CLUTLoadPos);
|
||||
|
||||
// Duplicate DisplayBuffer content
|
||||
::JabyEngine::GPU::internal::copy_vram_to_vram({PositionU16::create(0, TexturePageHeight), SizeU16::create(Display::Width, TexturePageHeight)}, PositionU16::create(0, 0));
|
||||
|
||||
Display::enable();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
GPU_IO::GP1.reset();
|
||||
configure_display();
|
||||
::JabyEngine::GPU::internal::Display::exchange_buffer_and_display();
|
||||
|
||||
GPU::internal::wait_ready_for_CMD();
|
||||
GPU::internal::quick_fill_fast(Color24::Black(), {PositionU16::create(0, 0), SizeU16::create(Display::Width, Display::Height)});
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
SysCall::SysEnqIntRP(SysCall::Priority::VblankIrq, &::JabyEngine::GPU::internal::irq_callback);
|
||||
Interrupt::enable_irq(Interrupt::VBlank);
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
#include "../../internal-include/mipscalls.hpp"
|
||||
#include <PSX/GTE/gte.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace GTE {
|
||||
void setup() {
|
||||
SysCall::EnterCriticalSection();
|
||||
const auto sr = bit::set(MIPS::SR::read(), MIPS::SR::CU2);
|
||||
MIPS::SR::write(sr);
|
||||
SysCall::ExitCriticalSection();
|
||||
|
||||
JabyEngine::GTE::set_geom_offset(0, 0);
|
||||
JabyEngine::GTE::set_geom_screen(512);
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/mipscalls.hpp"
|
||||
#include <PSX/GTE/gte.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace GTE {
|
||||
void setup() {
|
||||
SysCall::EnterCriticalSection();
|
||||
const auto sr = bit::set(MIPS::SR::read(), MIPS::SR::CU2);
|
||||
MIPS::SR::write(sr);
|
||||
SysCall::ExitCriticalSection();
|
||||
|
||||
JabyEngine::GTE::set_geom_offset(0, 0);
|
||||
JabyEngine::GTE::set_geom_screen(512);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
#include "../../internal-include/periphery_internal.hpp"
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace Periphery {
|
||||
void setup() {
|
||||
Periphery_IO::JOY_MODE.write(Periphery_IO_Values::JOY_MODE::create());
|
||||
Periphery_IO::JOY_BAUD.write(Periphery_IO_Values::JOY_BAUD::create());
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
Interrupt::disable_irq(Interrupt::Periphery);
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/periphery_internal.hpp"
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace Periphery {
|
||||
void setup() {
|
||||
Periphery_IO::JOY_MODE.write(Periphery_IO_Values::JOY_MODE::create());
|
||||
Periphery_IO::JOY_BAUD.write(Periphery_IO_Values::JOY_BAUD::create());
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
Interrupt::disable_irq(Interrupt::Periphery);
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +1,88 @@
|
||||
#include <PSX/Auxiliary/math_helper.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <string.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace BIOS {
|
||||
using Version = JabyEngine::BIOS::Version;
|
||||
|
||||
static uint32_t get_bcd_version() {
|
||||
return *reinterpret_cast<uint32_t*>(0xBFC00100);
|
||||
}
|
||||
|
||||
static const char* get_kernel_maker_str() {
|
||||
return reinterpret_cast<const char*>(0xBFC00108);
|
||||
}
|
||||
|
||||
static const char* get_version_str() {
|
||||
const char* kernel_maker = get_kernel_maker_str();
|
||||
const char* ver_start = kernel_maker + (strlen(kernel_maker) + 1);
|
||||
|
||||
while(*ver_start == 0) {
|
||||
ver_start++;
|
||||
}
|
||||
return ver_start;
|
||||
}
|
||||
|
||||
static Version::Type get_raw_bios_type(const char* version_str) {
|
||||
static const auto str_length = []<size_t N>(const char (&name)[N]) -> size_t {
|
||||
return N - 1;
|
||||
};
|
||||
const char dev_str[] = "DTL-";
|
||||
const char ps1_str[] = "CEX-";
|
||||
const char ps_compatible_str[] = "PS compatible mode";
|
||||
const char no$psx_str[] = "no$psx";
|
||||
const char xebra_str[] = "XEBRA";
|
||||
|
||||
const struct {
|
||||
const char* name;
|
||||
size_t name_length;
|
||||
Version::Type type;
|
||||
} bioses[] = {
|
||||
// Sorted by likeliness
|
||||
{.name = ps1_str, .name_length = str_length(ps1_str), .type = Version::Type::PS1},
|
||||
{.name = ps_compatible_str, .name_length = str_length(ps_compatible_str), .type = Version::Type::PSCompatible},
|
||||
{.name = no$psx_str, .name_length = str_length(no$psx_str), .type = Version::Type::No$psx},
|
||||
{.name = xebra_str, .name_length = str_length(xebra_str), .type = Version::Type::XEBRA},
|
||||
{.name = dev_str, .name_length = str_length(dev_str), .type = Version::Type::Devboard}
|
||||
};
|
||||
|
||||
for(const auto& bios : bioses) {
|
||||
if(strncmp(version_str, bios.name, bios.name_length) == 0) {
|
||||
return bios.type;
|
||||
}
|
||||
}
|
||||
return Version::Type::Unkown;
|
||||
}
|
||||
|
||||
static Version::Type refine_bios_type(Version::Type type) {
|
||||
if(type == Version::Type::PSCompatible) {
|
||||
const auto bios_year_bcd = get_bcd_version() >> 16;
|
||||
return bios_year_bcd == 0x2011 ? Version::Type::PS3 : Version::Type::PS2;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static Version get_bios_version() {
|
||||
Version version;
|
||||
const auto date_bcd = get_bcd_version();
|
||||
const char*const version_str = get_version_str();
|
||||
|
||||
version.date.day = from_bcd(static_cast<uint8_t>(date_bcd & 0xFF));
|
||||
version.date.month = from_bcd(static_cast<uint8_t>((date_bcd >> 8) & 0xFF));
|
||||
version.date.year = from_bcd(static_cast<uint16_t>(date_bcd >> 16));
|
||||
version.type = refine_bios_type(get_raw_bios_type(version_str));
|
||||
version.kernel_maker = get_kernel_maker_str();
|
||||
version.version_str = version_str;
|
||||
version.gui_version = reinterpret_cast<const char*>(0xBFC7FF32);
|
||||
version.copyright = version.gui_version + (strlen(version.gui_version) + 1);
|
||||
return version;
|
||||
}
|
||||
|
||||
void identify() {
|
||||
const_cast<JabyEngine::BIOS::Version&>(JabyEngine::BIOS::version) = get_bios_version();
|
||||
}
|
||||
}
|
||||
}
|
||||
#include <PSX/Auxiliary/math_helper.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <string.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace boot {
|
||||
namespace BIOS {
|
||||
using Version = JabyEngine::BIOS::Version;
|
||||
|
||||
static uint32_t get_bcd_version() {
|
||||
return *reinterpret_cast<uint32_t*>(0xBFC00100);
|
||||
}
|
||||
|
||||
static const char* get_kernel_maker_str() {
|
||||
return reinterpret_cast<const char*>(0xBFC00108);
|
||||
}
|
||||
|
||||
static const char* get_version_str() {
|
||||
const char* kernel_maker = get_kernel_maker_str();
|
||||
const char* ver_start = kernel_maker + (strlen(kernel_maker) + 1);
|
||||
|
||||
while(*ver_start == 0) {
|
||||
ver_start++;
|
||||
}
|
||||
return ver_start;
|
||||
}
|
||||
|
||||
static Version::Type get_raw_bios_type(const char* version_str) {
|
||||
static const auto str_length = []<size_t N>(const char (&name)[N]) -> size_t {
|
||||
return N - 1;
|
||||
};
|
||||
const char dev_str[] = "DTL-";
|
||||
const char ps1_str[] = "CEX-";
|
||||
const char ps_compatible_str[] = "PS compatible mode";
|
||||
const char no$psx_str[] = "no$psx";
|
||||
const char xebra_str[] = "XEBRA";
|
||||
|
||||
const struct {
|
||||
const char* name;
|
||||
size_t name_length;
|
||||
Version::Type type;
|
||||
} bioses[] = {
|
||||
// Sorted by likeliness
|
||||
{.name = ps1_str, .name_length = str_length(ps1_str), .type = Version::Type::PS1},
|
||||
{.name = ps_compatible_str, .name_length = str_length(ps_compatible_str), .type = Version::Type::PSCompatible},
|
||||
{.name = no$psx_str, .name_length = str_length(no$psx_str), .type = Version::Type::No$psx},
|
||||
{.name = xebra_str, .name_length = str_length(xebra_str), .type = Version::Type::XEBRA},
|
||||
{.name = dev_str, .name_length = str_length(dev_str), .type = Version::Type::Devboard}
|
||||
};
|
||||
|
||||
for(const auto& bios : bioses) {
|
||||
if(strncmp(version_str, bios.name, bios.name_length) == 0) {
|
||||
return bios.type;
|
||||
}
|
||||
}
|
||||
return Version::Type::Unkown;
|
||||
}
|
||||
|
||||
static Version::Type refine_bios_type(Version::Type type) {
|
||||
if(type == Version::Type::PSCompatible) {
|
||||
const auto bios_year_bcd = get_bcd_version() >> 16;
|
||||
return bios_year_bcd == 0x2011 ? Version::Type::PS3 : Version::Type::PS2;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static Version get_bios_version() {
|
||||
Version version;
|
||||
const auto date_bcd = get_bcd_version();
|
||||
const char*const version_str = get_version_str();
|
||||
|
||||
version.date.day = from_bcd(static_cast<uint8_t>(date_bcd & 0xFF));
|
||||
version.date.month = from_bcd(static_cast<uint8_t>((date_bcd >> 8) & 0xFF));
|
||||
version.date.year = from_bcd(static_cast<uint16_t>(date_bcd >> 16));
|
||||
version.type = refine_bios_type(get_raw_bios_type(version_str));
|
||||
version.kernel_maker = get_kernel_maker_str();
|
||||
version.version_str = version_str;
|
||||
version.gui_version = reinterpret_cast<const char*>(0xBFC7FF32);
|
||||
version.copyright = version.gui_version + (strlen(version.gui_version) + 1);
|
||||
return version;
|
||||
}
|
||||
|
||||
void identify() {
|
||||
const_cast<JabyEngine::BIOS::Version&>(JabyEngine::BIOS::version) = get_bios_version();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,33 @@
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <PSX/Timer/high_res_timer.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Timer {
|
||||
extern SysCall::InterruptCallback irq_callback;
|
||||
}
|
||||
|
||||
namespace boot {
|
||||
namespace Timer {
|
||||
using namespace JabyEngine::Timer;
|
||||
|
||||
void setup() {
|
||||
using namespace Timer_IO;
|
||||
using namespace Timer_IO_Values;
|
||||
|
||||
static constexpr auto Mode = CounterMode::from(CounterMode::FreeRun, Counter2::SyncMode::FreeRun, CounterMode::ResetAfterTarget, CounterMode::IRQAtTarget, CounterMode::IRQEveryTime, CounterMode::IRQPulse, Counter2::Source::System_Clock_Div_8);
|
||||
|
||||
// We disable the IRQ here so it can be enabled by user demand later
|
||||
// Having the interrupt fire every 10ms will slow us down slightly so we only do it on demand
|
||||
Interrupt::disable_irq(Interrupt::Timer2);
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
SysCall::SysEnqIntRP(SysCall::Priority::Timer2Irq, &irq_callback);
|
||||
SysCall::ExitCriticalSection();
|
||||
|
||||
Counter2.set_target_value(HighResTime::TicksFor10ms);
|
||||
Counter2.set_mode(Mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <PSX/Timer/high_res_timer.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Timer {
|
||||
extern SysCall::InterruptCallback irq_callback;
|
||||
}
|
||||
|
||||
namespace boot {
|
||||
namespace Timer {
|
||||
using namespace JabyEngine::Timer;
|
||||
|
||||
void setup() {
|
||||
using namespace Timer_IO;
|
||||
using namespace Timer_IO_Values;
|
||||
|
||||
static constexpr auto Mode = CounterMode::from(CounterMode::FreeRun, Counter2::SyncMode::FreeRun, CounterMode::ResetAfterTarget, CounterMode::IRQAtTarget, CounterMode::IRQEveryTime, CounterMode::IRQPulse, Counter2::Source::System_Clock_Div_8);
|
||||
|
||||
// We disable the IRQ here so it can be enabled by user demand later
|
||||
// Having the interrupt fire every 10ms will slow us down slightly so we only do it on demand
|
||||
Interrupt::disable_irq(Interrupt::Timer2);
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
SysCall::SysEnqIntRP(SysCall::Priority::Timer2Irq, &irq_callback);
|
||||
SysCall::ExitCriticalSection();
|
||||
|
||||
Counter2.set_target_value(HighResTime::TicksFor10ms);
|
||||
Counter2.set_mode(Mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +1,130 @@
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include "../../internal-include/System/callbacks_internal.hpp"
|
||||
#include <PSX/System/IOPorts/dma_io.hpp>
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CD {
|
||||
namespace internal {
|
||||
extern SectorBufferAllocator sector_allocator;
|
||||
extern File cur_file;
|
||||
|
||||
static constexpr auto AudioSectorMode = CD_IO_Values::Mode::from(CD_IO_Values::Mode::SingleSpeed, CD_IO_Values::Mode::AutoPauseTrack, CD_IO_Values::Mode::CDDA);
|
||||
static constexpr auto DataSectorMode = CD_IO_Values::Mode::from(CD_IO_Values::Mode::DoubleSpeed, CD_IO_Values::Mode::DataSector);
|
||||
static constexpr auto XAAudioSectorMode = CD_IO_Values::Mode::from(CD_IO_Values::Mode::SingleSpeed, CD_IO_Values::Mode::XADPCM, CD_IO_Values::Mode::WholeSector, CD_IO_Values::Mode::UseXAFilter);
|
||||
|
||||
namespace IRQ {
|
||||
SysCall::InterruptVerifierResult verifier();
|
||||
void handler(uint32_t);
|
||||
}
|
||||
|
||||
auto irq_callback = SysCall::InterruptCallback::from(IRQ::verifier, IRQ::handler);
|
||||
State current_state = State::Ready;
|
||||
uint8_t irq_bit_pending = CD_IO::Interrupt::None;
|
||||
|
||||
static void read_sector_dma(uint32_t* dst, size_t bytes) {
|
||||
static const auto WaitSectorReady = []() {
|
||||
while(!CD_IO::IndexStatus.read().is_set(CD_IO_Values::IndexStatus::HasDataFifoData));
|
||||
};
|
||||
|
||||
static const auto ReadSector = [](uint32_t* dst, size_t bytes) {
|
||||
DMA_IO::CDROM.set_adr(reinterpret_cast<uintptr_t>(dst));
|
||||
DMA_IO::CDROM.block_ctrl.write(DMA_IO_Values::BCR::SyncMode0::for_cd(bytes >> 2));
|
||||
DMA_IO::CDROM.channel_ctrl.write(DMA_IO_Values::CHCHR::StartCDROM());
|
||||
|
||||
DMA_IO::CDROM.wait();
|
||||
CD_IO::PortIndex0::change_to();
|
||||
|
||||
CD_IO::PortIndex0::Request.write(CD_IO_Values::Request::reset());
|
||||
};
|
||||
|
||||
WaitSectorReady();
|
||||
ReadSector(dst, bytes);
|
||||
}
|
||||
|
||||
static BCDTimeStamp send_read_cmd(uint32_t lba, CD_IO::Command::Desc cmd) {
|
||||
const auto loc = BCDTimeStamp::from(lba);
|
||||
|
||||
Command::send(CD_IO::Command::SetLoc, loc.min, loc.sec, loc.sector);
|
||||
Command::send(cmd);
|
||||
return loc;
|
||||
}
|
||||
|
||||
static void send_read_n(uint32_t lba) {
|
||||
send_read_cmd(lba, CD_IO::Command::ReadN);
|
||||
current_state = State::Reading;
|
||||
}
|
||||
|
||||
void read_file(AutoLBAEntry file_info, const SectorBufferAllocator& buffer_allocator) {
|
||||
cur_file.set_from(file_info);
|
||||
sector_allocator = buffer_allocator;
|
||||
|
||||
enable_CD();
|
||||
send_read_n(cur_file.cur_lba);
|
||||
}
|
||||
|
||||
void end_read_file() {
|
||||
sector_allocator = SectorBufferAllocator::invalid();
|
||||
}
|
||||
|
||||
void continue_reading() {
|
||||
if(current_state == State::BufferFull) {
|
||||
CD_IO::PortIndex0::change_to();
|
||||
send_read_n(cur_file.cur_lba);
|
||||
}
|
||||
}
|
||||
|
||||
void copy_from_sector(uint32_t* dst, size_t bytes) {
|
||||
read_sector_dma(dst, bytes);
|
||||
}
|
||||
|
||||
BCDTimeStamp get_loc() {
|
||||
Command::send_wait_response(CD_IO::Command::GetLocP);
|
||||
|
||||
// XEBRA does not support the mirror register so we go for the original...
|
||||
CD_IO::PortIndex1::change_to();
|
||||
const auto track = CD_IO::PortIndex1::ResponseFifo.read().raw; // track number (AAh=Lead-out area) (FFh=unknown, toc, none?)
|
||||
const auto index = CD_IO::PortIndex1::ResponseFifo.read().raw; // index number (Usually 01h)
|
||||
const auto mm = CD_IO::PortIndex1::ResponseFifo.read().raw; // minute number within track (00h and up)
|
||||
const auto ss = CD_IO::PortIndex1::ResponseFifo.read().raw; // second number within track (00h to 59h)
|
||||
const auto sect = CD_IO::PortIndex1::ResponseFifo.read().raw; // sector number within track (00h to 74h)
|
||||
|
||||
const auto min = CD_IO::PortIndex1::ResponseFifo.read().raw; // minute number on entire disk (00h and up)
|
||||
const auto sec = CD_IO::PortIndex1::ResponseFifo.read().raw; // second number on entire disk (00h to 59h)
|
||||
const auto sectors = CD_IO::PortIndex1::ResponseFifo.read().raw; // sector number on entire disk (00h to 74h)
|
||||
|
||||
return BCDTimeStamp{min, sec, sectors};
|
||||
}
|
||||
|
||||
BCDTimeStamp get_locL() {
|
||||
Command::send_wait_response(CD_IO::Command::GetLocL);
|
||||
|
||||
const auto min = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
const auto sec = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
const auto sector = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
|
||||
return BCDTimeStamp{min, sec, sector};
|
||||
}
|
||||
|
||||
void enable_CD() {
|
||||
Command::send(CD_IO::Command::SetMode, DataSectorMode);
|
||||
}
|
||||
|
||||
void enable_CDDA() {
|
||||
Command::send(CD_IO::Command::SetMode, AudioSectorMode);
|
||||
}
|
||||
|
||||
void enable_CDXA(bool double_speed) {
|
||||
static constexpr uint8_t SingleSpeedBit = 0x0;
|
||||
static constexpr uint8_t DoubleSpeedBit = static_cast<uint8_t>(CD_IO_Values::Mode::DoubleSpeed);
|
||||
|
||||
const uint8_t mode = XAAudioSectorMode.raw | (double_speed ? DoubleSpeedBit : SingleSpeedBit);
|
||||
|
||||
Command::send(CD_IO::Command::SetMode, mode);
|
||||
current_state = State::XAMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include "../../internal-include/System/callbacks_internal.hpp"
|
||||
#include <PSX/System/IOPorts/dma_io.hpp>
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CD {
|
||||
namespace internal {
|
||||
extern SectorBufferAllocator sector_allocator;
|
||||
extern File cur_file;
|
||||
|
||||
static constexpr auto AudioSectorMode = CD_IO_Values::Mode::from(CD_IO_Values::Mode::SingleSpeed, CD_IO_Values::Mode::AutoPauseTrack, CD_IO_Values::Mode::CDDA);
|
||||
static constexpr auto DataSectorMode = CD_IO_Values::Mode::from(CD_IO_Values::Mode::DoubleSpeed, CD_IO_Values::Mode::DataSector);
|
||||
static constexpr auto XAAudioSectorMode = CD_IO_Values::Mode::from(CD_IO_Values::Mode::SingleSpeed, CD_IO_Values::Mode::XADPCM, CD_IO_Values::Mode::WholeSector, CD_IO_Values::Mode::UseXAFilter);
|
||||
|
||||
namespace IRQ {
|
||||
SysCall::InterruptVerifierResult verifier();
|
||||
void handler(uint32_t);
|
||||
}
|
||||
|
||||
auto irq_callback = SysCall::InterruptCallback::from(IRQ::verifier, IRQ::handler);
|
||||
State current_state = State::Ready;
|
||||
uint8_t irq_bit_pending = CD_IO::Interrupt::None;
|
||||
|
||||
static void read_sector_dma(uint32_t* dst, size_t bytes) {
|
||||
static const auto WaitSectorReady = []() {
|
||||
while(!CD_IO::IndexStatus.read().is_set(CD_IO_Values::IndexStatus::HasDataFifoData));
|
||||
};
|
||||
|
||||
static const auto ReadSector = [](uint32_t* dst, size_t bytes) {
|
||||
DMA_IO::CDROM.set_adr(reinterpret_cast<uintptr_t>(dst));
|
||||
DMA_IO::CDROM.block_ctrl.write(DMA_IO_Values::BCR::SyncMode0::for_cd(bytes >> 2));
|
||||
DMA_IO::CDROM.channel_ctrl.write(DMA_IO_Values::CHCHR::StartCDROM());
|
||||
|
||||
DMA_IO::CDROM.wait();
|
||||
CD_IO::PortIndex0::change_to();
|
||||
|
||||
CD_IO::PortIndex0::Request.write(CD_IO_Values::Request::reset());
|
||||
};
|
||||
|
||||
WaitSectorReady();
|
||||
ReadSector(dst, bytes);
|
||||
}
|
||||
|
||||
static BCDTimeStamp send_read_cmd(uint32_t lba, CD_IO::Command::Desc cmd) {
|
||||
const auto loc = BCDTimeStamp::from(lba);
|
||||
|
||||
Command::send(CD_IO::Command::SetLoc, loc.min, loc.sec, loc.sector);
|
||||
Command::send(cmd);
|
||||
return loc;
|
||||
}
|
||||
|
||||
static void send_read_n(uint32_t lba) {
|
||||
send_read_cmd(lba, CD_IO::Command::ReadN);
|
||||
current_state = State::Reading;
|
||||
}
|
||||
|
||||
void read_file(AutoLBAEntry file_info, const SectorBufferAllocator& buffer_allocator) {
|
||||
cur_file.set_from(file_info);
|
||||
sector_allocator = buffer_allocator;
|
||||
|
||||
enable_CD();
|
||||
send_read_n(cur_file.cur_lba);
|
||||
}
|
||||
|
||||
void end_read_file() {
|
||||
sector_allocator = SectorBufferAllocator::invalid();
|
||||
}
|
||||
|
||||
void continue_reading() {
|
||||
if(current_state == State::BufferFull) {
|
||||
CD_IO::PortIndex0::change_to();
|
||||
send_read_n(cur_file.cur_lba);
|
||||
}
|
||||
}
|
||||
|
||||
void copy_from_sector(uint32_t* dst, size_t bytes) {
|
||||
read_sector_dma(dst, bytes);
|
||||
}
|
||||
|
||||
BCDTimeStamp get_loc() {
|
||||
Command::send_wait_response(CD_IO::Command::GetLocP);
|
||||
|
||||
// XEBRA does not support the mirror register so we go for the original...
|
||||
CD_IO::PortIndex1::change_to();
|
||||
const auto track = CD_IO::PortIndex1::ResponseFifo.read().raw; // track number (AAh=Lead-out area) (FFh=unknown, toc, none?)
|
||||
const auto index = CD_IO::PortIndex1::ResponseFifo.read().raw; // index number (Usually 01h)
|
||||
const auto mm = CD_IO::PortIndex1::ResponseFifo.read().raw; // minute number within track (00h and up)
|
||||
const auto ss = CD_IO::PortIndex1::ResponseFifo.read().raw; // second number within track (00h to 59h)
|
||||
const auto sect = CD_IO::PortIndex1::ResponseFifo.read().raw; // sector number within track (00h to 74h)
|
||||
|
||||
const auto min = CD_IO::PortIndex1::ResponseFifo.read().raw; // minute number on entire disk (00h and up)
|
||||
const auto sec = CD_IO::PortIndex1::ResponseFifo.read().raw; // second number on entire disk (00h to 59h)
|
||||
const auto sectors = CD_IO::PortIndex1::ResponseFifo.read().raw; // sector number on entire disk (00h to 74h)
|
||||
|
||||
return BCDTimeStamp{min, sec, sectors};
|
||||
}
|
||||
|
||||
BCDTimeStamp get_locL() {
|
||||
Command::send_wait_response(CD_IO::Command::GetLocL);
|
||||
|
||||
const auto min = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
const auto sec = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
const auto sector = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
||||
|
||||
return BCDTimeStamp{min, sec, sector};
|
||||
}
|
||||
|
||||
void enable_CD() {
|
||||
Command::send(CD_IO::Command::SetMode, DataSectorMode);
|
||||
}
|
||||
|
||||
void enable_CDDA() {
|
||||
Command::send(CD_IO::Command::SetMode, AudioSectorMode);
|
||||
}
|
||||
|
||||
void enable_CDXA(bool double_speed) {
|
||||
static constexpr uint8_t SingleSpeedBit = 0x0;
|
||||
static constexpr uint8_t DoubleSpeedBit = static_cast<uint8_t>(CD_IO_Values::Mode::DoubleSpeed);
|
||||
|
||||
const uint8_t mode = XAAudioSectorMode.raw | (double_speed ? DoubleSpeedBit : SingleSpeedBit);
|
||||
|
||||
Command::send(CD_IO::Command::SetMode, mode);
|
||||
current_state = State::XAMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +1,93 @@
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/System/IOPorts/dma_io.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CDDA {
|
||||
extern CD::internal::BCDTimeStamp playing_track;
|
||||
}
|
||||
|
||||
namespace CDXA {
|
||||
CD::internal::State interrupt_handler(uint8_t irq);
|
||||
}
|
||||
|
||||
namespace CD {
|
||||
namespace internal {
|
||||
SectorBufferAllocator sector_allocator;
|
||||
File cur_file;
|
||||
|
||||
namespace IRQ {
|
||||
static void process(uint32_t irq) {
|
||||
if(current_state != State::XAMode) {
|
||||
switch(irq) {
|
||||
case CD_IO::Interrupt::DataReady: {
|
||||
// Obtain sector content here
|
||||
auto* sector = sector_allocator.allocate_sector();
|
||||
if(sector) {
|
||||
//Now obtain sector
|
||||
copy_from_sector(sector->data, CD_IO::DataSector::SizeBytes);
|
||||
|
||||
if(cur_file.done_processing()) {
|
||||
current_state = State::Done;
|
||||
Command::send_no_wait(CD_IO::Command::Pause);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
current_state = State::BufferFull;
|
||||
Command::send_no_wait(CD_IO::Command::Pause);
|
||||
}
|
||||
} break;
|
||||
|
||||
case CD_IO::Interrupt::DataEnd: {
|
||||
set_loc(CDDA::playing_track);
|
||||
Command::send_no_wait(CD_IO::Command::Play);
|
||||
} break;
|
||||
|
||||
case CD_IO::Interrupt::DiskError: {
|
||||
current_state = State::Error;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
current_state = CDXA::interrupt_handler(irq);
|
||||
}
|
||||
}
|
||||
|
||||
//######################################################################################################################
|
||||
|
||||
SysCall::InterruptVerifierResult verifier() {
|
||||
if(Interrupt::is_irq(Interrupt::CDROM)) {
|
||||
Interrupt::ack_irq(Interrupt::CDROM);
|
||||
return SysCall::InterruptVerifierResult::ExecuteHandler;
|
||||
}
|
||||
|
||||
else {
|
||||
return SysCall::InterruptVerifierResult::SkipHandler;
|
||||
}
|
||||
}
|
||||
|
||||
void handler(uint32_t x) {
|
||||
const auto old_status = CD_IO::IndexStatus.read();
|
||||
|
||||
CD_IO::PortIndex1::change_to();
|
||||
const auto cur_irq = CD_IO::Interrupt::get_type(CD_IO::PortIndex1::InterruptFlag);
|
||||
CD_IO::PortIndex1::change_to();
|
||||
CD_IO::Interrupt::ack_extended(CD_IO::PortIndex1::InterruptFlag);
|
||||
|
||||
irq_bit_pending = bit::clear(irq_bit_pending, cur_irq);
|
||||
|
||||
CD_IO::PortIndex0::change_to();
|
||||
if(cur_irq == CD_IO::Interrupt::DataReady) {
|
||||
CD_IO::PortIndex0::Request.write(CD_IO_Values::Request::want_data());
|
||||
}
|
||||
|
||||
process(cur_irq);
|
||||
// No masking required because we can only write bit 0 - 2
|
||||
CD_IO::IndexStatus.write(old_status);
|
||||
return SysCall::ReturnFromException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/System/IOPorts/dma_io.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace CDDA {
|
||||
extern CD::internal::BCDTimeStamp playing_track;
|
||||
}
|
||||
|
||||
namespace CDXA {
|
||||
CD::internal::State interrupt_handler(uint8_t irq);
|
||||
}
|
||||
|
||||
namespace CD {
|
||||
namespace internal {
|
||||
SectorBufferAllocator sector_allocator;
|
||||
File cur_file;
|
||||
|
||||
namespace IRQ {
|
||||
static void process(uint32_t irq) {
|
||||
if(current_state != State::XAMode) {
|
||||
switch(irq) {
|
||||
case CD_IO::Interrupt::DataReady: {
|
||||
// Obtain sector content here
|
||||
auto* sector = sector_allocator.allocate_sector();
|
||||
if(sector) {
|
||||
//Now obtain sector
|
||||
copy_from_sector(sector->data, CD_IO::DataSector::SizeBytes);
|
||||
|
||||
if(cur_file.done_processing()) {
|
||||
current_state = State::Done;
|
||||
Command::send_no_wait(CD_IO::Command::Pause);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
current_state = State::BufferFull;
|
||||
Command::send_no_wait(CD_IO::Command::Pause);
|
||||
}
|
||||
} break;
|
||||
|
||||
case CD_IO::Interrupt::DataEnd: {
|
||||
set_loc(CDDA::playing_track);
|
||||
Command::send_no_wait(CD_IO::Command::Play);
|
||||
} break;
|
||||
|
||||
case CD_IO::Interrupt::DiskError: {
|
||||
current_state = State::Error;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
current_state = CDXA::interrupt_handler(irq);
|
||||
}
|
||||
}
|
||||
|
||||
//######################################################################################################################
|
||||
|
||||
SysCall::InterruptVerifierResult verifier() {
|
||||
if(Interrupt::is_irq(Interrupt::CDROM)) {
|
||||
Interrupt::ack_irq(Interrupt::CDROM);
|
||||
return SysCall::InterruptVerifierResult::ExecuteHandler;
|
||||
}
|
||||
|
||||
else {
|
||||
return SysCall::InterruptVerifierResult::SkipHandler;
|
||||
}
|
||||
}
|
||||
|
||||
void handler(uint32_t x) {
|
||||
const auto old_status = CD_IO::IndexStatus.read();
|
||||
|
||||
CD_IO::PortIndex1::change_to();
|
||||
const auto cur_irq = CD_IO::Interrupt::get_type(CD_IO::PortIndex1::InterruptFlag);
|
||||
CD_IO::PortIndex1::change_to();
|
||||
CD_IO::Interrupt::ack_extended(CD_IO::PortIndex1::InterruptFlag);
|
||||
|
||||
irq_bit_pending = bit::clear(irq_bit_pending, cur_irq);
|
||||
|
||||
CD_IO::PortIndex0::change_to();
|
||||
if(cur_irq == CD_IO::Interrupt::DataReady) {
|
||||
CD_IO::PortIndex0::Request.write(CD_IO_Values::Request::want_data());
|
||||
}
|
||||
|
||||
process(cur_irq);
|
||||
// No masking required because we can only write bit 0 - 2
|
||||
CD_IO::IndexStatus.write(old_status);
|
||||
return SysCall::ReturnFromException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +1,67 @@
|
||||
#include "tim_helper.hpp"
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace TIMFileProcessor {
|
||||
namespace {
|
||||
void set_gpu_receive(const uint32_t* src, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
GPU::internal::DMA::Receive::prepare();
|
||||
GPU::internal::DMA::Receive::set_dst(PositionU16::create(x, y), SizeU16::create(w, h));
|
||||
GPU::internal::DMA::Receive::set_src(reinterpret_cast<const uintptr_t>(src));
|
||||
}
|
||||
|
||||
size_t set_gpu_receive_data(const uint32_t* src, const AreaU16& dst) {
|
||||
const auto width = dst.size.width;
|
||||
const auto height = dst.size.height;
|
||||
|
||||
set_gpu_receive(src, dst.position.x, dst.position.y, width, height);
|
||||
return (width*height)/2;
|
||||
}
|
||||
|
||||
Progress parse_data(State::CDDataProcessor& data_proc, GenericTIM& generic_tim) {
|
||||
const auto [words_to_use, is_last] = Helper::DMA::WordsReady::calculate(data_proc, generic_tim.words_left);
|
||||
const auto words_used = Helper::DMA::send_words<GPU::internal::DMA>(words_to_use, is_last);
|
||||
|
||||
generic_tim.words_left -= words_used;
|
||||
data_proc.processed(words_used*sizeof(uint32_t));
|
||||
return is_last ? Progress::Done : Progress::InProgress;
|
||||
}
|
||||
|
||||
Progress switch_state_parse_data(State::CDDataProcessor& data_proc, GenericTIM& generic_tim) {
|
||||
const auto result = generic_tim.pre_data_parsing(data_proc);
|
||||
if(result == Progress::Done) {
|
||||
generic_tim.words_left = TIMFileProcessor::set_gpu_receive_data(reinterpret_cast<const uint32_t*>(data_proc.data_adr), generic_tim.tex_area);
|
||||
return Helper::exchange_and_execute_process_function<TIMFileProcessor::GenericTIM>(TIMFileProcessor::parse_data, data_proc, generic_tim);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Progress parse_clut(State::CDDataProcessor& data_proc, GenericTIM& generic_tim) {
|
||||
if(const auto result = TIMFileProcessor::parse_data(data_proc, generic_tim); result != Progress::Done) {
|
||||
return result;
|
||||
}
|
||||
|
||||
else {
|
||||
return switch_state_parse_data(data_proc, generic_tim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Progress parse_header(State::CDDataProcessor& data_proc, GenericTIM& generic_tim) {
|
||||
const auto result = generic_tim.parse_header(data_proc);
|
||||
if(result == Progress::Done) {
|
||||
//Check if we have a clut to care about
|
||||
if(generic_tim.has_clut()) {
|
||||
//CLUTs are 16bit full color anyway
|
||||
generic_tim.words_left = TIMFileProcessor::set_gpu_receive_data(reinterpret_cast<const uint32_t*>(data_proc.data_adr), generic_tim.clut_area);
|
||||
return Helper::exchange_and_execute_process_function(parse_clut, data_proc, generic_tim);
|
||||
}
|
||||
|
||||
else {
|
||||
return switch_state_parse_data(data_proc, generic_tim);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#include "tim_helper.hpp"
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace TIMFileProcessor {
|
||||
namespace {
|
||||
void set_gpu_receive(const uint32_t* src, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
GPU::internal::DMA::Receive::prepare();
|
||||
GPU::internal::DMA::Receive::set_dst(PositionU16::create(x, y), SizeU16::create(w, h));
|
||||
GPU::internal::DMA::Receive::set_src(reinterpret_cast<const uintptr_t>(src));
|
||||
}
|
||||
|
||||
size_t set_gpu_receive_data(const uint32_t* src, const AreaU16& dst) {
|
||||
const auto width = dst.size.width;
|
||||
const auto height = dst.size.height;
|
||||
|
||||
set_gpu_receive(src, dst.position.x, dst.position.y, width, height);
|
||||
return (width*height)/2;
|
||||
}
|
||||
|
||||
Progress parse_data(State::CDDataProcessor& data_proc, GenericTIM& generic_tim) {
|
||||
const auto [words_to_use, is_last] = Helper::DMA::WordsReady::calculate(data_proc, generic_tim.words_left);
|
||||
const auto words_used = Helper::DMA::send_words<GPU::internal::DMA>(words_to_use, is_last);
|
||||
|
||||
generic_tim.words_left -= words_used;
|
||||
data_proc.processed(words_used*sizeof(uint32_t));
|
||||
return is_last ? Progress::Done : Progress::InProgress;
|
||||
}
|
||||
|
||||
Progress switch_state_parse_data(State::CDDataProcessor& data_proc, GenericTIM& generic_tim) {
|
||||
const auto result = generic_tim.pre_data_parsing(data_proc);
|
||||
if(result == Progress::Done) {
|
||||
generic_tim.words_left = TIMFileProcessor::set_gpu_receive_data(reinterpret_cast<const uint32_t*>(data_proc.data_adr), generic_tim.tex_area);
|
||||
return Helper::exchange_and_execute_process_function<TIMFileProcessor::GenericTIM>(TIMFileProcessor::parse_data, data_proc, generic_tim);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Progress parse_clut(State::CDDataProcessor& data_proc, GenericTIM& generic_tim) {
|
||||
if(const auto result = TIMFileProcessor::parse_data(data_proc, generic_tim); result != Progress::Done) {
|
||||
return result;
|
||||
}
|
||||
|
||||
else {
|
||||
return switch_state_parse_data(data_proc, generic_tim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Progress parse_header(State::CDDataProcessor& data_proc, GenericTIM& generic_tim) {
|
||||
const auto result = generic_tim.parse_header(data_proc);
|
||||
if(result == Progress::Done) {
|
||||
//Check if we have a clut to care about
|
||||
if(generic_tim.has_clut()) {
|
||||
//CLUTs are 16bit full color anyway
|
||||
generic_tim.words_left = TIMFileProcessor::set_gpu_receive_data(reinterpret_cast<const uint32_t*>(data_proc.data_adr), generic_tim.clut_area);
|
||||
return Helper::exchange_and_execute_process_function(parse_clut, data_proc, generic_tim);
|
||||
}
|
||||
|
||||
else {
|
||||
return switch_state_parse_data(data_proc, generic_tim);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
#pragma once
|
||||
#include "../../../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include <PSX/File/file_processor_helper.hpp>
|
||||
#include <PSX/GPU/gpu_types.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace TIMFileProcessor {
|
||||
using namespace FileProcessor;
|
||||
using namespace GPU;
|
||||
|
||||
struct GenericTIM {
|
||||
AreaU16 clut_area;
|
||||
AreaU16 tex_area;
|
||||
size_t words_left; //32bit values
|
||||
|
||||
bool has_clut() const {
|
||||
return this->clut_area.size.width > 0;
|
||||
}
|
||||
|
||||
virtual Progress parse_header(State::CDDataProcessor& data_proc) = 0;
|
||||
virtual Progress pre_data_parsing(State::CDDataProcessor& data_proc) = 0;
|
||||
};
|
||||
|
||||
Progress parse_header(State::CDDataProcessor& data_proc, GenericTIM& generic_tim);
|
||||
}
|
||||
#pragma once
|
||||
#include "../../../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include <PSX/File/file_processor_helper.hpp>
|
||||
#include <PSX/GPU/gpu_types.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace TIMFileProcessor {
|
||||
using namespace FileProcessor;
|
||||
using namespace GPU;
|
||||
|
||||
struct GenericTIM {
|
||||
AreaU16 clut_area;
|
||||
AreaU16 tex_area;
|
||||
size_t words_left; //32bit values
|
||||
|
||||
bool has_clut() const {
|
||||
return this->clut_area.size.width > 0;
|
||||
}
|
||||
|
||||
virtual Progress parse_header(State::CDDataProcessor& data_proc) = 0;
|
||||
virtual Progress pre_data_parsing(State::CDDataProcessor& data_proc) = 0;
|
||||
};
|
||||
|
||||
Progress parse_header(State::CDDataProcessor& data_proc, GenericTIM& generic_tim);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,61 @@
|
||||
#include "tim_helper.hpp"
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace FileProcessor {
|
||||
namespace {
|
||||
using GPU::AreaU16;
|
||||
|
||||
struct BlockInfo {
|
||||
uint32_t size;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint16_t w;
|
||||
uint16_t h;
|
||||
};
|
||||
|
||||
struct TIMState : public TIMFileProcessor::GenericTIM {
|
||||
static TIMState create() {
|
||||
TIMState state;
|
||||
return state;
|
||||
}
|
||||
|
||||
virtual Progress parse_header(State::CDDataProcessor& data_proc) override {
|
||||
static constexpr auto HEADER_SIZE = 2*sizeof(uint32_t);
|
||||
|
||||
if(data_proc.data_bytes >= (HEADER_SIZE + sizeof(BlockInfo))) {
|
||||
static constexpr auto HAS_CLUT_BIT = (0x1 << 3);
|
||||
|
||||
data_proc.processed(sizeof(uint32_t));
|
||||
const auto flag = data_proc.simple_read_r<uint32_t>();
|
||||
|
||||
if(flag & HAS_CLUT_BIT) {
|
||||
const auto block_info = data_proc.simple_read_r<BlockInfo>();
|
||||
this->clut_area = AreaU16::create(block_info.x, block_info.y, block_info.w, block_info.h);
|
||||
}
|
||||
|
||||
else {
|
||||
this->clut_area = AreaU16::create(0, 0, 0, 0);
|
||||
}
|
||||
return Progress::Done;
|
||||
}
|
||||
return Progress::Error;
|
||||
}
|
||||
|
||||
virtual Progress pre_data_parsing(State::CDDataProcessor& data_proc) {
|
||||
if(data_proc.data_bytes >= sizeof(BlockInfo)) {
|
||||
const auto block_info = data_proc.simple_read_r<BlockInfo>();
|
||||
this->tex_area = AreaU16::create(block_info.x, block_info.y, block_info.w, block_info.h);
|
||||
return Progress::Done;
|
||||
}
|
||||
return Progress::InProgress;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
State create(const uint32_t* data_adr, const TIM& file) {
|
||||
using Callback = Progress(*)(State::CDDataProcessor& data_proc, TIMState& simple_tim);
|
||||
return State::from(TIMState::create(), data_adr, reinterpret_cast<Callback>(TIMFileProcessor::parse_header));
|
||||
}
|
||||
}
|
||||
#include "tim_helper.hpp"
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace FileProcessor {
|
||||
namespace {
|
||||
using GPU::AreaU16;
|
||||
|
||||
struct BlockInfo {
|
||||
uint32_t size;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint16_t w;
|
||||
uint16_t h;
|
||||
};
|
||||
|
||||
struct TIMState : public TIMFileProcessor::GenericTIM {
|
||||
static TIMState create() {
|
||||
TIMState state;
|
||||
return state;
|
||||
}
|
||||
|
||||
virtual Progress parse_header(State::CDDataProcessor& data_proc) override {
|
||||
static constexpr auto HEADER_SIZE = 2*sizeof(uint32_t);
|
||||
|
||||
if(data_proc.data_bytes >= (HEADER_SIZE + sizeof(BlockInfo))) {
|
||||
static constexpr auto HAS_CLUT_BIT = (0x1 << 3);
|
||||
|
||||
data_proc.processed(sizeof(uint32_t));
|
||||
const auto flag = data_proc.simple_read_r<uint32_t>();
|
||||
|
||||
if(flag & HAS_CLUT_BIT) {
|
||||
const auto block_info = data_proc.simple_read_r<BlockInfo>();
|
||||
this->clut_area = AreaU16::create(block_info.x, block_info.y, block_info.w, block_info.h);
|
||||
}
|
||||
|
||||
else {
|
||||
this->clut_area = AreaU16::create(0, 0, 0, 0);
|
||||
}
|
||||
return Progress::Done;
|
||||
}
|
||||
return Progress::Error;
|
||||
}
|
||||
|
||||
virtual Progress pre_data_parsing(State::CDDataProcessor& data_proc) {
|
||||
if(data_proc.data_bytes >= sizeof(BlockInfo)) {
|
||||
const auto block_info = data_proc.simple_read_r<BlockInfo>();
|
||||
this->tex_area = AreaU16::create(block_info.x, block_info.y, block_info.w, block_info.h);
|
||||
return Progress::Done;
|
||||
}
|
||||
return Progress::InProgress;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
State create(const uint32_t* data_adr, const TIM& file) {
|
||||
using Callback = Progress(*)(State::CDDataProcessor& data_proc, TIMState& simple_tim);
|
||||
return State::from(TIMState::create(), data_adr, reinterpret_cast<Callback>(TIMFileProcessor::parse_header));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +1,127 @@
|
||||
#include "../../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/File/Processor/cd_file_processor.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
static constexpr auto DisabledCircularBufferSize = 512;
|
||||
|
||||
void CDFileProcessor :: start_cur_job(const AutoLBAEntry* lba, const BufferConfiguration& buf_cfg) {
|
||||
using CD::internal::SectorBufferAllocator;
|
||||
const auto configurate_for = [this](const CDFile& file, const BufferConfiguration& buf_cfg, bool is_lz4) -> FileProcessor::State {
|
||||
const uint32_t* data_adr = [this](const CDFile& file, const BufferConfiguration& buf_cfg, bool is_lz4) -> const uint32_t* {
|
||||
const auto disable_lz4 = [this](uint32_t* work_area, size_t size, uint32_t* overwrite_dst = nullptr) -> uint32_t* {
|
||||
reinterpret_cast<uint8_t*>(this->circular_buffer.setup(reinterpret_cast<CD_IO::DataSector*>(work_area), size));
|
||||
|
||||
this->lz4_decomp.disable();
|
||||
return overwrite_dst ? overwrite_dst : reinterpret_cast<uint32_t*>(work_area);
|
||||
};
|
||||
|
||||
const auto enable_lz4 = [this](uint32_t* work_area, size_t size, uint32_t* override_dst = nullptr) -> uint32_t* {
|
||||
uint8_t* dst_adr = reinterpret_cast<uint8_t*>(this->circular_buffer.setup(reinterpret_cast<CD_IO::DataSector*>(work_area), size));
|
||||
|
||||
if(override_dst) {
|
||||
dst_adr = reinterpret_cast<uint8_t*>(override_dst);
|
||||
}
|
||||
|
||||
this->lz4_decomp.setup(dst_adr);
|
||||
return reinterpret_cast<uint32_t*>(dst_adr);
|
||||
};
|
||||
|
||||
if(file.type == CDFileType::CopyTo) {
|
||||
return is_lz4 ? enable_lz4(buf_cfg.adr, buf_cfg.sector_count, file.payload.overlay.dst) : disable_lz4(file.payload.copy_to.dst, DisabledCircularBufferSize);
|
||||
}
|
||||
|
||||
else {
|
||||
return is_lz4 ? enable_lz4(buf_cfg.adr, buf_cfg.sector_count) : disable_lz4(buf_cfg.adr, DisabledCircularBufferSize);
|
||||
}
|
||||
}(file, buf_cfg, is_lz4);
|
||||
|
||||
switch(file.type) {
|
||||
case CDFileType::CopyTo:
|
||||
return FileProcessor::create(data_adr, Nothing());
|
||||
|
||||
case CDFileType::SimpleTIM:
|
||||
return FileProcessor::create(data_adr, file.payload.simple_tim);
|
||||
|
||||
case CDFileType::SonyTIM:
|
||||
return FileProcessor::create(data_adr, file.payload.tim);
|
||||
|
||||
case CDFileType::SonyVAG:
|
||||
return FileProcessor::create(data_adr, file.payload.vag);
|
||||
|
||||
default:
|
||||
return FileProcessor::create_custom(data_adr, static_cast<CDFileType_t>(file.type) - static_cast<CDFileType_t>(CDFileType::Custom), file.payload);
|
||||
}
|
||||
};
|
||||
|
||||
const auto& cur_job = *this->jobs.files;
|
||||
const auto& cur_lba = lba[cur_job.rel_lba_idx];
|
||||
|
||||
this->file_state = configurate_for(cur_job, buf_cfg, cur_lba.is_lz4());
|
||||
CD::internal::read_file(cur_lba, SectorBufferAllocator::create(this,
|
||||
[](void* ctx) -> CD_IO::DataSector* {
|
||||
CDFileProcessor &self = *reinterpret_cast<CDFileProcessor*>(ctx);
|
||||
return self.circular_buffer.allocate();
|
||||
}));
|
||||
|
||||
//printf(">>> %i.) CD needs to load LBA: %i -> %i (is LZ4: [%s])\n", cur_job.rel_lba_idx, cur_lba.get_lba(), cur_lba.get_size_in_sectors(), cur_lba.is_lz4() ? "Yes" : "No");
|
||||
}
|
||||
|
||||
bool CDFileProcessor :: process_data() {
|
||||
while(this->circular_buffer.has_data()) {
|
||||
ArrayRange<const uint8_t> cur_sector(reinterpret_cast<uint8_t*>(this->circular_buffer.get_next()->data), CD_IO::DataSector::SizeBytes);
|
||||
|
||||
// v We can not know if there will be more data or not - but it also doesn't matter much for us
|
||||
const auto result = this->lz4_decomp.process(cur_sector, false);
|
||||
this->circular_buffer.pop();
|
||||
|
||||
if(result) {
|
||||
// Process the data in the tmp_area
|
||||
if(this->file_state.process(result.bytes_ready) == Progress::Error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDFileProcessor :: setup(const volatile AutoLBAEntry* lba, JobArray jobs, const BufferConfiguration& buf_cfg) {
|
||||
this->jobs = jobs;
|
||||
CDFileProcessor::start_cur_job(const_cast<const AutoLBAEntry*>(lba), buf_cfg);
|
||||
}
|
||||
|
||||
void CDFileProcessor :: shutdown() {
|
||||
CD::internal::end_read_file();
|
||||
}
|
||||
|
||||
Progress CDFileProcessor :: process() {
|
||||
const auto cur_state = CD::internal::read_current_state();
|
||||
CDFileProcessor::process_data();
|
||||
switch(cur_state) {
|
||||
case CD::internal::State::Done:
|
||||
/*
|
||||
We are done now!
|
||||
The user decides if he wants the next value
|
||||
*/
|
||||
//while(this->file_state.process(0) != Progress::Done);
|
||||
return Progress::Done;
|
||||
|
||||
case CD::internal::State::BufferFull:
|
||||
/* We processed data and unpause the CD drive */
|
||||
CD::internal::continue_reading();
|
||||
return Progress::InProgress;
|
||||
|
||||
case CD::internal::State::Reading:
|
||||
return Progress::InProgress;
|
||||
|
||||
case CD::internal::State::Error:
|
||||
default:
|
||||
/* Error for real */
|
||||
return Progress::Error;
|
||||
}
|
||||
}
|
||||
#include "../../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/File/Processor/cd_file_processor.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
static constexpr auto DisabledCircularBufferSize = 512;
|
||||
|
||||
void CDFileProcessor :: start_cur_job(const AutoLBAEntry* lba, const BufferConfiguration& buf_cfg) {
|
||||
using CD::internal::SectorBufferAllocator;
|
||||
const auto configurate_for = [this](const CDFile& file, const BufferConfiguration& buf_cfg, bool is_lz4) -> FileProcessor::State {
|
||||
const uint32_t* data_adr = [this](const CDFile& file, const BufferConfiguration& buf_cfg, bool is_lz4) -> const uint32_t* {
|
||||
const auto disable_lz4 = [this](uint32_t* work_area, size_t size, uint32_t* overwrite_dst = nullptr) -> uint32_t* {
|
||||
reinterpret_cast<uint8_t*>(this->circular_buffer.setup(reinterpret_cast<CD_IO::DataSector*>(work_area), size));
|
||||
|
||||
this->lz4_decomp.disable();
|
||||
return overwrite_dst ? overwrite_dst : reinterpret_cast<uint32_t*>(work_area);
|
||||
};
|
||||
|
||||
const auto enable_lz4 = [this](uint32_t* work_area, size_t size, uint32_t* override_dst = nullptr) -> uint32_t* {
|
||||
uint8_t* dst_adr = reinterpret_cast<uint8_t*>(this->circular_buffer.setup(reinterpret_cast<CD_IO::DataSector*>(work_area), size));
|
||||
|
||||
if(override_dst) {
|
||||
dst_adr = reinterpret_cast<uint8_t*>(override_dst);
|
||||
}
|
||||
|
||||
this->lz4_decomp.setup(dst_adr);
|
||||
return reinterpret_cast<uint32_t*>(dst_adr);
|
||||
};
|
||||
|
||||
if(file.type == CDFileType::CopyTo) {
|
||||
return is_lz4 ? enable_lz4(buf_cfg.adr, buf_cfg.sector_count, file.payload.overlay.dst) : disable_lz4(file.payload.copy_to.dst, DisabledCircularBufferSize);
|
||||
}
|
||||
|
||||
else {
|
||||
return is_lz4 ? enable_lz4(buf_cfg.adr, buf_cfg.sector_count) : disable_lz4(buf_cfg.adr, DisabledCircularBufferSize);
|
||||
}
|
||||
}(file, buf_cfg, is_lz4);
|
||||
|
||||
switch(file.type) {
|
||||
case CDFileType::CopyTo:
|
||||
return FileProcessor::create(data_adr, Nothing());
|
||||
|
||||
case CDFileType::SimpleTIM:
|
||||
return FileProcessor::create(data_adr, file.payload.simple_tim);
|
||||
|
||||
case CDFileType::SonyTIM:
|
||||
return FileProcessor::create(data_adr, file.payload.tim);
|
||||
|
||||
case CDFileType::SonyVAG:
|
||||
return FileProcessor::create(data_adr, file.payload.vag);
|
||||
|
||||
default:
|
||||
return FileProcessor::create_custom(data_adr, static_cast<CDFileType_t>(file.type) - static_cast<CDFileType_t>(CDFileType::Custom), file.payload);
|
||||
}
|
||||
};
|
||||
|
||||
const auto& cur_job = *this->jobs.files;
|
||||
const auto& cur_lba = lba[cur_job.rel_lba_idx];
|
||||
|
||||
this->file_state = configurate_for(cur_job, buf_cfg, cur_lba.is_lz4());
|
||||
CD::internal::read_file(cur_lba, SectorBufferAllocator::create(this,
|
||||
[](void* ctx) -> CD_IO::DataSector* {
|
||||
CDFileProcessor &self = *reinterpret_cast<CDFileProcessor*>(ctx);
|
||||
return self.circular_buffer.allocate();
|
||||
}));
|
||||
|
||||
//printf(">>> %i.) CD needs to load LBA: %i -> %i (is LZ4: [%s])\n", cur_job.rel_lba_idx, cur_lba.get_lba(), cur_lba.get_size_in_sectors(), cur_lba.is_lz4() ? "Yes" : "No");
|
||||
}
|
||||
|
||||
bool CDFileProcessor :: process_data() {
|
||||
while(this->circular_buffer.has_data()) {
|
||||
ArrayRange<const uint8_t> cur_sector(reinterpret_cast<uint8_t*>(this->circular_buffer.get_next()->data), CD_IO::DataSector::SizeBytes);
|
||||
|
||||
// v We can not know if there will be more data or not - but it also doesn't matter much for us
|
||||
const auto result = this->lz4_decomp.process(cur_sector, false);
|
||||
this->circular_buffer.pop();
|
||||
|
||||
if(result) {
|
||||
// Process the data in the tmp_area
|
||||
if(this->file_state.process(result.bytes_ready) == Progress::Error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDFileProcessor :: setup(const volatile AutoLBAEntry* lba, JobArray jobs, const BufferConfiguration& buf_cfg) {
|
||||
this->jobs = jobs;
|
||||
CDFileProcessor::start_cur_job(const_cast<const AutoLBAEntry*>(lba), buf_cfg);
|
||||
}
|
||||
|
||||
void CDFileProcessor :: shutdown() {
|
||||
CD::internal::end_read_file();
|
||||
}
|
||||
|
||||
Progress CDFileProcessor :: process() {
|
||||
const auto cur_state = CD::internal::read_current_state();
|
||||
CDFileProcessor::process_data();
|
||||
switch(cur_state) {
|
||||
case CD::internal::State::Done:
|
||||
/*
|
||||
We are done now!
|
||||
The user decides if he wants the next value
|
||||
*/
|
||||
//while(this->file_state.process(0) != Progress::Done);
|
||||
return Progress::Done;
|
||||
|
||||
case CD::internal::State::BufferFull:
|
||||
/* We processed data and unpause the CD drive */
|
||||
CD::internal::continue_reading();
|
||||
return Progress::InProgress;
|
||||
|
||||
case CD::internal::State::Reading:
|
||||
return Progress::InProgress;
|
||||
|
||||
case CD::internal::State::Error:
|
||||
default:
|
||||
/* Error for real */
|
||||
return Progress::Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
#include <PSX/File/file_processor_helper.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace FileProcessor {
|
||||
State __weak create_custom(const uint32_t* data_adr, const CDFileType_t& file_type, const CDFile::Payload& payload) {
|
||||
return FileProcessor::create(data_adr, Nothing());
|
||||
}
|
||||
}
|
||||
#include <PSX/File/file_processor_helper.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace FileProcessor {
|
||||
State __weak create_custom(const uint32_t* data_adr, const CDFileType_t& file_type, const CDFile::Payload& payload) {
|
||||
return FileProcessor::create(data_adr, Nothing());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,79 @@
|
||||
#include "../../../internal-include/SPU/spu_internal.hpp"
|
||||
#include <PSX/Auxiliary/big_endian.hpp>
|
||||
#include <PSX/Auxiliary/word_helper.hpp>
|
||||
#include <PSX/File/file_processor_helper.hpp>
|
||||
#include <PSX/SPU/spu.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace FileProcessor {
|
||||
struct VAGHeader {
|
||||
char id[4];
|
||||
uint32_t version;
|
||||
uint32_t reserved;
|
||||
uint32_t sample_size;
|
||||
uint32_t sample_frequency;
|
||||
uint8_t reserved_2[12];
|
||||
char name[16];
|
||||
|
||||
constexpr uint32_t get_version() const {
|
||||
return read_be(this->version);
|
||||
}
|
||||
|
||||
constexpr uint32_t get_sample_size() const {
|
||||
return read_be(this->sample_size);
|
||||
}
|
||||
|
||||
constexpr uint32_t get_sample_frequency() const {
|
||||
return read_be(this->sample_frequency);
|
||||
}
|
||||
};
|
||||
|
||||
struct VAGState {
|
||||
uint32_t voice_id;
|
||||
size_t words_left;
|
||||
SPU::SimpleVolume inital_vol;
|
||||
|
||||
static constexpr VAGState create(uint32_t voice_id, SPU::SimpleVolume inital_vol) {
|
||||
return VAGState{.voice_id = voice_id, .words_left = 0, .inital_vol = inital_vol};
|
||||
}
|
||||
};
|
||||
|
||||
static Progress parse_sample(State::CDDataProcessor& data_proc, VAGState& state) {
|
||||
const auto [words_to_use, is_last] = Helper::DMA::WordsReady::calculate(data_proc, state.words_left);
|
||||
const auto words_used = Helper::DMA::send_words<SPU::internal::DMA>(words_to_use, is_last);
|
||||
|
||||
state.words_left -= words_used;
|
||||
|
||||
data_proc.processed(words_used*sizeof(uint32_t));
|
||||
return is_last ? Progress::Done : Progress::InProgress;
|
||||
}
|
||||
|
||||
static Progress parse_header(State::CDDataProcessor& data_proc, VAGState& state) {
|
||||
if(data_proc.data_bytes >= sizeof(VAGHeader)) {
|
||||
const auto& header = *reinterpret_cast<const VAGHeader*>(data_proc.data_adr);
|
||||
const auto words = bytes_to_words(header.get_sample_size());
|
||||
const auto bytes = words_to_bytes(words);
|
||||
|
||||
state.words_left = words;
|
||||
|
||||
auto sram_adr = SPU::voice[state.voice_id].allocate(SPU_IO_Values::SampleRate::from_HZ(header.get_sample_frequency()), bytes);
|
||||
|
||||
SPU::voice[state.voice_id].set_volume(state.inital_vol, state.inital_vol);
|
||||
|
||||
data_proc.processed(sizeof(VAGHeader));
|
||||
SPU::internal::DMA::Receive::prepare();
|
||||
SPU::internal::DMA::Receive::set_dst(sram_adr);
|
||||
SPU::internal::DMA::Receive::set_src(reinterpret_cast<uintptr_t>(data_proc.data_adr));
|
||||
|
||||
return Helper::exchange_and_execute_process_function(parse_sample, data_proc, state);
|
||||
}
|
||||
|
||||
return Progress::InProgress;
|
||||
}
|
||||
|
||||
State create(const uint32_t* data_adr, const VAG& file) {
|
||||
return State::from(VAGState::create(file.voice_number, file.inital_stereo_vol), data_adr, parse_header);
|
||||
}
|
||||
}
|
||||
#include "../../../internal-include/SPU/spu_internal.hpp"
|
||||
#include <PSX/Auxiliary/big_endian.hpp>
|
||||
#include <PSX/Auxiliary/word_helper.hpp>
|
||||
#include <PSX/File/file_processor_helper.hpp>
|
||||
#include <PSX/SPU/spu.hpp>
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace FileProcessor {
|
||||
struct VAGHeader {
|
||||
char id[4];
|
||||
uint32_t version;
|
||||
uint32_t reserved;
|
||||
uint32_t sample_size;
|
||||
uint32_t sample_frequency;
|
||||
uint8_t reserved_2[12];
|
||||
char name[16];
|
||||
|
||||
constexpr uint32_t get_version() const {
|
||||
return read_be(this->version);
|
||||
}
|
||||
|
||||
constexpr uint32_t get_sample_size() const {
|
||||
return read_be(this->sample_size);
|
||||
}
|
||||
|
||||
constexpr uint32_t get_sample_frequency() const {
|
||||
return read_be(this->sample_frequency);
|
||||
}
|
||||
};
|
||||
|
||||
struct VAGState {
|
||||
uint32_t voice_id;
|
||||
size_t words_left;
|
||||
SPU::SimpleVolume inital_vol;
|
||||
|
||||
static constexpr VAGState create(uint32_t voice_id, SPU::SimpleVolume inital_vol) {
|
||||
return VAGState{.voice_id = voice_id, .words_left = 0, .inital_vol = inital_vol};
|
||||
}
|
||||
};
|
||||
|
||||
static Progress parse_sample(State::CDDataProcessor& data_proc, VAGState& state) {
|
||||
const auto [words_to_use, is_last] = Helper::DMA::WordsReady::calculate(data_proc, state.words_left);
|
||||
const auto words_used = Helper::DMA::send_words<SPU::internal::DMA>(words_to_use, is_last);
|
||||
|
||||
state.words_left -= words_used;
|
||||
|
||||
data_proc.processed(words_used*sizeof(uint32_t));
|
||||
return is_last ? Progress::Done : Progress::InProgress;
|
||||
}
|
||||
|
||||
static Progress parse_header(State::CDDataProcessor& data_proc, VAGState& state) {
|
||||
if(data_proc.data_bytes >= sizeof(VAGHeader)) {
|
||||
const auto& header = *reinterpret_cast<const VAGHeader*>(data_proc.data_adr);
|
||||
const auto words = bytes_to_words(header.get_sample_size());
|
||||
const auto bytes = words_to_bytes(words);
|
||||
|
||||
state.words_left = words;
|
||||
|
||||
auto sram_adr = SPU::voice[state.voice_id].allocate(SPU_IO_Values::SampleRate::from_HZ(header.get_sample_frequency()), bytes);
|
||||
|
||||
SPU::voice[state.voice_id].set_volume(state.inital_vol, state.inital_vol);
|
||||
|
||||
data_proc.processed(sizeof(VAGHeader));
|
||||
SPU::internal::DMA::Receive::prepare();
|
||||
SPU::internal::DMA::Receive::set_dst(sram_adr);
|
||||
SPU::internal::DMA::Receive::set_src(reinterpret_cast<uintptr_t>(data_proc.data_adr));
|
||||
|
||||
return Helper::exchange_and_execute_process_function(parse_sample, data_proc, state);
|
||||
}
|
||||
|
||||
return Progress::InProgress;
|
||||
}
|
||||
|
||||
State create(const uint32_t* data_adr, const VAG& file) {
|
||||
return State::from(VAGState::create(file.voice_number, file.inital_stereo_vol), data_adr, parse_header);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +1,119 @@
|
||||
#include "../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include <PSX/Timer/frame_timer.hpp>
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GPU {
|
||||
uint8_t Display :: current_id = 0;
|
||||
namespace internal {
|
||||
#ifdef __SUPPORT_PS3__
|
||||
uintptr_t DMA :: MADR = 0;
|
||||
#endif // __SUPPORT_PS3__
|
||||
|
||||
static SysCall::InterruptVerifierResult interrupt_verifier();
|
||||
static void interrupt_handler(uint32_t);
|
||||
|
||||
auto irq_callback = SysCall::InterruptCallback::from(interrupt_verifier, interrupt_handler);
|
||||
VSyncCallback vsync_callback = nullptr;
|
||||
static uint8_t vsync_counter = 0;
|
||||
bool vsync_lock_callback = false;
|
||||
|
||||
static SysCall::InterruptVerifierResult interrupt_verifier() {
|
||||
if(Interrupt::is_irq(Interrupt::VBlank)) {
|
||||
Interrupt::ack_irq(Interrupt::VBlank);
|
||||
return SysCall::InterruptVerifierResult::ExecuteHandler;
|
||||
}
|
||||
|
||||
else {
|
||||
return SysCall::InterruptVerifierResult::SkipHandler;
|
||||
}
|
||||
}
|
||||
|
||||
static void interrupt_handler(uint32_t) {
|
||||
vsync_counter++;
|
||||
MasterTime::value++;
|
||||
|
||||
if(vsync_callback && !vsync_lock_callback) {
|
||||
vsync_callback();
|
||||
}
|
||||
//Callback::internal::VSync::execute();
|
||||
SysCall::ReturnFromException();
|
||||
}
|
||||
|
||||
uint32_t Display :: exchange_buffer_and_display() {
|
||||
static constexpr uint16_t TexturePageHeight = 256;
|
||||
|
||||
const uint16_t draw_area_y = (TexturePageHeight*PublicDisplay::current_id);
|
||||
|
||||
GPU::internal::set_draw_area(GPU::PositionU16::create(0, draw_area_y));
|
||||
PublicDisplay::current_id ^= 1;
|
||||
GPU_IO::GP1.set_display_area(GPU::PositionU16::create(0, static_cast<uint16_t>((TexturePageHeight*PublicDisplay::current_id))));
|
||||
return draw_area_y;
|
||||
}
|
||||
|
||||
void wait_vsync(uint8_t syncs) {
|
||||
volatile auto& vsync_count = reinterpret_cast<volatile uint8_t&>(vsync_counter);
|
||||
|
||||
const uint8_t vsync_dst_value = vsync_count + syncs;
|
||||
while(vsync_count != vsync_dst_value);
|
||||
}
|
||||
|
||||
void render(const uint32_t* data, size_t words) {
|
||||
wait_ready_for_CMD();
|
||||
for(size_t n = 0; n < words; n++) {
|
||||
GPU_IO::GP0.write({data[n]});
|
||||
}
|
||||
}
|
||||
|
||||
void render_dma(const uint32_t* data) {
|
||||
// DPCR already enabled
|
||||
DMA_IO::GPU.wait();
|
||||
GPU_IO::GP1.set_dma_direction(GPU_IO_Values::GPUSTAT::DMADirection::CPU2GPU);
|
||||
DMA_IO::GPU.set_adr(reinterpret_cast<uintptr_t>(data));
|
||||
DMA_IO::GPU.block_ctrl.write(DMA_IO_Values::BCR::SyncMode2::for_gpu_cmd());
|
||||
|
||||
wait_ready_for_CMD();
|
||||
DMA_IO::GPU.channel_ctrl.write(DMA_IO_Values::CHCHR::StartGPULinked());
|
||||
}
|
||||
}
|
||||
|
||||
void Display :: set_offset(int16_t x, int16_t y) {
|
||||
// Does not matter really - The original offset is good enough
|
||||
#ifdef __ADJUST_PS3_SCREEN_OFFSET__
|
||||
static constexpr auto PS3_CorrectionX = 2;
|
||||
static constexpr auto PS3_CorrectionY = 1;
|
||||
#else
|
||||
static constexpr auto PS3_CorrectionX = 0;
|
||||
static constexpr auto PS3_CorrectionY = 0;
|
||||
#endif // __ADJUST_PS3_SCREEN_OFFSET__
|
||||
|
||||
x += (internal::Display::DisplayRange.x + Configuration::DisplayDefaultOffset.x);
|
||||
y += (internal::Display::DisplayRange.y + Configuration::DisplayDefaultOffset.y);
|
||||
|
||||
GPU_IO::GP1.set_horizontal_display_range((x << 3), (x + Display::Width + PS3_CorrectionX) << 3);
|
||||
GPU_IO::GP1.set_vertical_display_range( y, y + Display::Height + PS3_CorrectionY);
|
||||
}
|
||||
|
||||
void set_vsync_callback(VSyncCallback callback) {
|
||||
internal::vsync_callback = callback;
|
||||
}
|
||||
|
||||
void swap_buffers(bool clear_screen) {
|
||||
const int16_t draw_offset_y = internal::Display::exchange_buffer_and_display();
|
||||
internal::set_draw_offset(GPU::PositionI16::create(0, draw_offset_y));
|
||||
if(clear_screen) {
|
||||
internal::quick_fill_fast(Color24::Black(), AreaU16::create(0, static_cast<uint16_t>(draw_offset_y), Display::Width, Display::Height));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t swap_buffers_vsync(uint8_t syncs, bool clear_screen) {
|
||||
// Waits for finish FIFO
|
||||
internal::wait_ready_for_CMD();
|
||||
internal::wait_vsync(syncs);
|
||||
|
||||
swap_buffers(clear_screen);
|
||||
return Display::current_id;
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/GPU/gpu_internal.hpp"
|
||||
#include <PSX/Timer/frame_timer.hpp>
|
||||
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GPU {
|
||||
uint8_t Display :: current_id = 0;
|
||||
namespace internal {
|
||||
#ifdef __SUPPORT_PS3__
|
||||
uintptr_t DMA :: MADR = 0;
|
||||
#endif // __SUPPORT_PS3__
|
||||
|
||||
static SysCall::InterruptVerifierResult interrupt_verifier();
|
||||
static void interrupt_handler(uint32_t);
|
||||
|
||||
auto irq_callback = SysCall::InterruptCallback::from(interrupt_verifier, interrupt_handler);
|
||||
VSyncCallback vsync_callback = nullptr;
|
||||
static uint8_t vsync_counter = 0;
|
||||
bool vsync_lock_callback = false;
|
||||
|
||||
static SysCall::InterruptVerifierResult interrupt_verifier() {
|
||||
if(Interrupt::is_irq(Interrupt::VBlank)) {
|
||||
Interrupt::ack_irq(Interrupt::VBlank);
|
||||
return SysCall::InterruptVerifierResult::ExecuteHandler;
|
||||
}
|
||||
|
||||
else {
|
||||
return SysCall::InterruptVerifierResult::SkipHandler;
|
||||
}
|
||||
}
|
||||
|
||||
static void interrupt_handler(uint32_t) {
|
||||
vsync_counter++;
|
||||
MasterTime::value++;
|
||||
|
||||
if(vsync_callback && !vsync_lock_callback) {
|
||||
vsync_callback();
|
||||
}
|
||||
//Callback::internal::VSync::execute();
|
||||
SysCall::ReturnFromException();
|
||||
}
|
||||
|
||||
uint32_t Display :: exchange_buffer_and_display() {
|
||||
static constexpr uint16_t TexturePageHeight = 256;
|
||||
|
||||
const uint16_t draw_area_y = (TexturePageHeight*PublicDisplay::current_id);
|
||||
|
||||
GPU::internal::set_draw_area(GPU::PositionU16::create(0, draw_area_y));
|
||||
PublicDisplay::current_id ^= 1;
|
||||
GPU_IO::GP1.set_display_area(GPU::PositionU16::create(0, static_cast<uint16_t>((TexturePageHeight*PublicDisplay::current_id))));
|
||||
return draw_area_y;
|
||||
}
|
||||
|
||||
void wait_vsync(uint8_t syncs) {
|
||||
volatile auto& vsync_count = reinterpret_cast<volatile uint8_t&>(vsync_counter);
|
||||
|
||||
const uint8_t vsync_dst_value = vsync_count + syncs;
|
||||
while(vsync_count != vsync_dst_value);
|
||||
}
|
||||
|
||||
void render(const uint32_t* data, size_t words) {
|
||||
wait_ready_for_CMD();
|
||||
for(size_t n = 0; n < words; n++) {
|
||||
GPU_IO::GP0.write({data[n]});
|
||||
}
|
||||
}
|
||||
|
||||
void render_dma(const uint32_t* data) {
|
||||
// DPCR already enabled
|
||||
DMA_IO::GPU.wait();
|
||||
GPU_IO::GP1.set_dma_direction(GPU_IO_Values::GPUSTAT::DMADirection::CPU2GPU);
|
||||
DMA_IO::GPU.set_adr(reinterpret_cast<uintptr_t>(data));
|
||||
DMA_IO::GPU.block_ctrl.write(DMA_IO_Values::BCR::SyncMode2::for_gpu_cmd());
|
||||
|
||||
wait_ready_for_CMD();
|
||||
DMA_IO::GPU.channel_ctrl.write(DMA_IO_Values::CHCHR::StartGPULinked());
|
||||
}
|
||||
}
|
||||
|
||||
void Display :: set_offset(int16_t x, int16_t y) {
|
||||
// Does not matter really - The original offset is good enough
|
||||
#ifdef __ADJUST_PS3_SCREEN_OFFSET__
|
||||
static constexpr auto PS3_CorrectionX = 2;
|
||||
static constexpr auto PS3_CorrectionY = 1;
|
||||
#else
|
||||
static constexpr auto PS3_CorrectionX = 0;
|
||||
static constexpr auto PS3_CorrectionY = 0;
|
||||
#endif // __ADJUST_PS3_SCREEN_OFFSET__
|
||||
|
||||
x += (internal::Display::DisplayRange.x + Configuration::DisplayDefaultOffset.x);
|
||||
y += (internal::Display::DisplayRange.y + Configuration::DisplayDefaultOffset.y);
|
||||
|
||||
GPU_IO::GP1.set_horizontal_display_range((x << 3), (x + Display::Width + PS3_CorrectionX) << 3);
|
||||
GPU_IO::GP1.set_vertical_display_range( y, y + Display::Height + PS3_CorrectionY);
|
||||
}
|
||||
|
||||
void set_vsync_callback(VSyncCallback callback) {
|
||||
internal::vsync_callback = callback;
|
||||
}
|
||||
|
||||
void swap_buffers(bool clear_screen) {
|
||||
const int16_t draw_offset_y = internal::Display::exchange_buffer_and_display();
|
||||
internal::set_draw_offset(GPU::PositionI16::create(0, draw_offset_y));
|
||||
if(clear_screen) {
|
||||
internal::quick_fill_fast(Color24::Black(), AreaU16::create(0, static_cast<uint16_t>(draw_offset_y), Display::Width, Display::Height));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t swap_buffers_vsync(uint8_t syncs, bool clear_screen) {
|
||||
// Waits for finish FIFO
|
||||
internal::wait_ready_for_CMD();
|
||||
internal::wait_vsync(syncs);
|
||||
|
||||
swap_buffers(clear_screen);
|
||||
return Display::current_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +1,129 @@
|
||||
#include <PSX/GTE/gte.hpp>
|
||||
|
||||
static int32_t hisin(int32_t value) {
|
||||
static constexpr int32_t qN = 13;
|
||||
static constexpr int32_t qA = 12;
|
||||
static constexpr int32_t B = 19900;
|
||||
static constexpr int32_t C = 3516;
|
||||
|
||||
const auto c = value << (30 - qN); // Semi-circle info into carry.
|
||||
|
||||
value -= 1<<qN; // sine -> cosine calc
|
||||
value = value << (31 - qN); // Mask with PI
|
||||
value = value >> (31 - qN); // Note: SIGNED shift! (to qN)
|
||||
value = value*value >> (2*qN - 14); // x=x^2 To Q14
|
||||
|
||||
auto result = B - (value*C >> 14); // B - x^2*C
|
||||
result = (1 << qA) - (value*result >> 16); // A - x^2*(B-x^2*C)
|
||||
|
||||
return c >= 0 ? result : -result;
|
||||
}
|
||||
|
||||
gte_float sin(deg_t value) {
|
||||
return gte_float{.raw = hisin(value.raw)};
|
||||
}
|
||||
|
||||
gte_float cos(deg_t value) {
|
||||
return gte_float{.raw = hisin(value.raw + (deg_t::full_circle/4))};
|
||||
}
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GTE {
|
||||
namespace MatrixHelper {
|
||||
struct SinCosPair {
|
||||
int16_t sin;
|
||||
int16_t cos;
|
||||
|
||||
static SinCosPair create_for(deg_t value) {
|
||||
return SinCosPair{
|
||||
.sin = static_cast<int16_t>(::sin(value)),
|
||||
.cos = static_cast<int16_t>(::cos(value)),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static MATRIX Stack[StackSize];
|
||||
static MATRIX* FreeStackEntry = Stack;
|
||||
|
||||
ROTMATRIX& multiply_matrix(const ROTMATRIX& m0, const ROTMATRIX& m1, ROTMATRIX& result) {
|
||||
set_rot_matrix(m0);
|
||||
|
||||
JabyEngine::GTE::ldclmv(m1, 0);
|
||||
JabyEngine::GTE::rtir();
|
||||
JabyEngine::GTE::stclmv(result, 0);
|
||||
|
||||
JabyEngine::GTE::ldclmv(m1, 1);
|
||||
JabyEngine::GTE::rtir();
|
||||
JabyEngine::GTE::stclmv(result, 1);
|
||||
|
||||
JabyEngine::GTE::ldclmv(m1, 2);
|
||||
JabyEngine::GTE::rtir();
|
||||
JabyEngine::GTE::stclmv(result, 2);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void set_matrix(const MATRIX& matrix) {
|
||||
set_rot_matrix(matrix.rotation);
|
||||
set_trans_vector(matrix.transfer);
|
||||
}
|
||||
|
||||
MATRIX get_matrix() {
|
||||
MATRIX matrix;
|
||||
|
||||
get_rot_matrix(matrix.rotation);
|
||||
get_trans_vector(matrix.transfer);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
void push_matrix() {
|
||||
*FreeStackEntry = get_matrix();
|
||||
FreeStackEntry++;
|
||||
}
|
||||
|
||||
void push_matrix_and_set(const MATRIX& matrix) {
|
||||
push_matrix();
|
||||
set_matrix(matrix);
|
||||
}
|
||||
|
||||
void pop_matrix() {
|
||||
FreeStackEntry--;
|
||||
set_matrix(*FreeStackEntry);
|
||||
}
|
||||
|
||||
MATRIX get_and_pop_matrix() {
|
||||
const auto matrix = get_matrix();
|
||||
pop_matrix();
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
ROTMATRIX ROTMATRIX :: rotated(deg_t x, deg_t y, deg_t z) {
|
||||
using namespace MatrixHelper;
|
||||
|
||||
const auto sincos_x = SinCosPair::create_for(x);
|
||||
const auto sincos_y = SinCosPair::create_for(y);
|
||||
const auto sincos_z = SinCosPair::create_for(z);
|
||||
|
||||
auto rotX = ROTMATRIX::identity();
|
||||
rotX.matrix[1][1] = sincos_x.cos; rotX.matrix[1][2] = -sincos_x.sin;
|
||||
rotX.matrix[2][1] = sincos_x.sin; rotX.matrix[2][2] = sincos_x.cos;
|
||||
|
||||
auto rotY = ROTMATRIX::identity();
|
||||
rotY.matrix[0][0] = sincos_y.cos; rotY.matrix[0][2] = sincos_y.sin;
|
||||
rotY.matrix[2][0] = -sincos_y.sin; rotY.matrix[2][2] = sincos_y.cos;
|
||||
|
||||
auto rotZ = ROTMATRIX::identity();
|
||||
rotZ.matrix[0][0] = sincos_z.cos; rotZ.matrix[0][1] = -sincos_z.sin;
|
||||
rotZ.matrix[1][0] = sincos_z.sin; rotZ.matrix[1][1] = sincos_z.cos;
|
||||
|
||||
push_matrix();
|
||||
multiply_matrix(rotX, rotY, rotX);
|
||||
multiply_matrix(rotX, rotZ, rotX);
|
||||
pop_matrix();
|
||||
|
||||
return rotX;
|
||||
}
|
||||
}
|
||||
#include <PSX/GTE/gte.hpp>
|
||||
|
||||
static int32_t hisin(int32_t value) {
|
||||
static constexpr int32_t qN = 13;
|
||||
static constexpr int32_t qA = 12;
|
||||
static constexpr int32_t B = 19900;
|
||||
static constexpr int32_t C = 3516;
|
||||
|
||||
const auto c = value << (30 - qN); // Semi-circle info into carry.
|
||||
|
||||
value -= 1<<qN; // sine -> cosine calc
|
||||
value = value << (31 - qN); // Mask with PI
|
||||
value = value >> (31 - qN); // Note: SIGNED shift! (to qN)
|
||||
value = value*value >> (2*qN - 14); // x=x^2 To Q14
|
||||
|
||||
auto result = B - (value*C >> 14); // B - x^2*C
|
||||
result = (1 << qA) - (value*result >> 16); // A - x^2*(B-x^2*C)
|
||||
|
||||
return c >= 0 ? result : -result;
|
||||
}
|
||||
|
||||
gte_float sin(deg_t value) {
|
||||
return gte_float{.raw = hisin(value.raw)};
|
||||
}
|
||||
|
||||
gte_float cos(deg_t value) {
|
||||
return gte_float{.raw = hisin(value.raw + (deg_t::full_circle/4))};
|
||||
}
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace GTE {
|
||||
namespace MatrixHelper {
|
||||
struct SinCosPair {
|
||||
int16_t sin;
|
||||
int16_t cos;
|
||||
|
||||
static SinCosPair create_for(deg_t value) {
|
||||
return SinCosPair{
|
||||
.sin = static_cast<int16_t>(::sin(value)),
|
||||
.cos = static_cast<int16_t>(::cos(value)),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static MATRIX Stack[StackSize];
|
||||
static MATRIX* FreeStackEntry = Stack;
|
||||
|
||||
ROTMATRIX& multiply_matrix(const ROTMATRIX& m0, const ROTMATRIX& m1, ROTMATRIX& result) {
|
||||
set_rot_matrix(m0);
|
||||
|
||||
JabyEngine::GTE::ldclmv(m1, 0);
|
||||
JabyEngine::GTE::rtir();
|
||||
JabyEngine::GTE::stclmv(result, 0);
|
||||
|
||||
JabyEngine::GTE::ldclmv(m1, 1);
|
||||
JabyEngine::GTE::rtir();
|
||||
JabyEngine::GTE::stclmv(result, 1);
|
||||
|
||||
JabyEngine::GTE::ldclmv(m1, 2);
|
||||
JabyEngine::GTE::rtir();
|
||||
JabyEngine::GTE::stclmv(result, 2);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void set_matrix(const MATRIX& matrix) {
|
||||
set_rot_matrix(matrix.rotation);
|
||||
set_trans_vector(matrix.transfer);
|
||||
}
|
||||
|
||||
MATRIX get_matrix() {
|
||||
MATRIX matrix;
|
||||
|
||||
get_rot_matrix(matrix.rotation);
|
||||
get_trans_vector(matrix.transfer);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
void push_matrix() {
|
||||
*FreeStackEntry = get_matrix();
|
||||
FreeStackEntry++;
|
||||
}
|
||||
|
||||
void push_matrix_and_set(const MATRIX& matrix) {
|
||||
push_matrix();
|
||||
set_matrix(matrix);
|
||||
}
|
||||
|
||||
void pop_matrix() {
|
||||
FreeStackEntry--;
|
||||
set_matrix(*FreeStackEntry);
|
||||
}
|
||||
|
||||
MATRIX get_and_pop_matrix() {
|
||||
const auto matrix = get_matrix();
|
||||
pop_matrix();
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
ROTMATRIX ROTMATRIX :: rotated(deg_t x, deg_t y, deg_t z) {
|
||||
using namespace MatrixHelper;
|
||||
|
||||
const auto sincos_x = SinCosPair::create_for(x);
|
||||
const auto sincos_y = SinCosPair::create_for(y);
|
||||
const auto sincos_z = SinCosPair::create_for(z);
|
||||
|
||||
auto rotX = ROTMATRIX::identity();
|
||||
rotX.matrix[1][1] = sincos_x.cos; rotX.matrix[1][2] = -sincos_x.sin;
|
||||
rotX.matrix[2][1] = sincos_x.sin; rotX.matrix[2][2] = sincos_x.cos;
|
||||
|
||||
auto rotY = ROTMATRIX::identity();
|
||||
rotY.matrix[0][0] = sincos_y.cos; rotY.matrix[0][2] = sincos_y.sin;
|
||||
rotY.matrix[2][0] = -sincos_y.sin; rotY.matrix[2][2] = sincos_y.cos;
|
||||
|
||||
auto rotZ = ROTMATRIX::identity();
|
||||
rotZ.matrix[0][0] = sincos_z.cos; rotZ.matrix[0][1] = -sincos_z.sin;
|
||||
rotZ.matrix[1][0] = sincos_z.sin; rotZ.matrix[1][1] = sincos_z.cos;
|
||||
|
||||
push_matrix();
|
||||
multiply_matrix(rotX, rotY, rotX);
|
||||
multiply_matrix(rotX, rotZ, rotX);
|
||||
pop_matrix();
|
||||
|
||||
return rotX;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +1,136 @@
|
||||
#include "../../internal-include/periphery_internal.hpp"
|
||||
#include <PSX/Periphery/periphery.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Periphery {
|
||||
// Controllers are checked every alternating frame
|
||||
static uint8_t cur_controller_port = 0;
|
||||
RawController controller[PortCount][DeviceCount];
|
||||
|
||||
struct ControllerHelper {
|
||||
static bool is_config(const RawController &cont) {
|
||||
return (static_cast<uint8_t>(cont.header.state) & 0xF);
|
||||
}
|
||||
|
||||
static void advance_config(RawController &cont) {
|
||||
cont.header.state = static_cast<RawController::State>((static_cast<uint32_t>(cont.header.state) << 1));
|
||||
}
|
||||
|
||||
static uint8_t* raw_device_data(RawController &cont) {
|
||||
return reinterpret_cast<uint8_t*>(&cont.button.currentState);
|
||||
}
|
||||
};
|
||||
|
||||
static void set_config_command(RawController::State state, uint8_t (&header)[3], uint8_t (&data)[6]) {
|
||||
static constexpr uint32_t CMDIDX = 1;
|
||||
|
||||
switch(state) {
|
||||
case RawController::State::EnterConfigMode:
|
||||
static constexpr uint8_t EnterCFGModeCMD = 0x1;
|
||||
|
||||
header[CMDIDX] = 0x43;
|
||||
data[0] = EnterCFGModeCMD;
|
||||
break;
|
||||
|
||||
case RawController::State::LockAnalog:
|
||||
static constexpr uint8_t valID = 0;
|
||||
static constexpr uint8_t selID = 1;
|
||||
|
||||
header[CMDIDX] = 0x44;
|
||||
data[valID] = static_cast<uint8_t>(LED::State::On);
|
||||
data[selID] = static_cast<uint8_t>(LED::Lock::On);
|
||||
break;
|
||||
|
||||
case RawController::State::UnlockRumble:
|
||||
header[CMDIDX] = 0x4D;
|
||||
|
||||
data[1] = 0x1;
|
||||
for(int32_t n = 2; n < 6; n++) {
|
||||
data[n] = 0xFF;
|
||||
}
|
||||
break;
|
||||
|
||||
case RawController::State::ExitConfigMode:
|
||||
header[CMDIDX] = 0x43;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t send_data(const uint8_t* header, uint8_t* headerResponse, const uint8_t* data, uint8_t* response) {
|
||||
const uint8_t *src = header;
|
||||
uint8_t *dst = headerResponse;
|
||||
size_t size = 3;
|
||||
|
||||
for(size_t n = 0; n < size; n++) {
|
||||
Periphery::send_byte(*src);
|
||||
Periphery::acknowledge();
|
||||
*dst = Periphery::read_byte();
|
||||
|
||||
if(n == 2) {
|
||||
const uint8_t id = headerResponse[1];
|
||||
size += (id == 0xFF) ? 0 : ((id & 0xF) << 1);
|
||||
|
||||
src = data;
|
||||
dst = response;
|
||||
}
|
||||
|
||||
else {
|
||||
src++;
|
||||
dst++;
|
||||
}
|
||||
|
||||
busy_loop(15);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void query_controller() {
|
||||
static constexpr auto TypeIDX = 1;
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
Periphery::connect_to(cur_controller_port);
|
||||
for(uint32_t id = 0; id < Periphery::DeviceCount; id++) {
|
||||
auto &cur_controller = controller[cur_controller_port][id];
|
||||
|
||||
uint8_t header[] = {static_cast<uint8_t>((id + 1)), 0x42, 0x0};
|
||||
uint8_t data[] = {cur_controller.header.rumble0, cur_controller.header.rumble1, 0x0, 0x0, 0x0, 0x0};
|
||||
|
||||
// Can this move to the if??
|
||||
set_config_command(cur_controller.header.state, header, data);
|
||||
if(ControllerHelper::is_config(cur_controller)) {
|
||||
send_data(header, header, data, data);
|
||||
ControllerHelper::advance_config(cur_controller);
|
||||
}
|
||||
|
||||
else {
|
||||
cur_controller.button.exchange_state();
|
||||
|
||||
send_data(header, header, data, ControllerHelper::raw_device_data(cur_controller));
|
||||
const bool isValidController = (header[TypeIDX] != 0xFF);
|
||||
|
||||
if(header[TypeIDX] != cur_controller.header.type) {
|
||||
cur_controller.header.type = header[TypeIDX];
|
||||
cur_controller.header.state = isValidController ? RawController::State::EnterConfigMode : RawController::State::Disconnected;
|
||||
|
||||
/*if(!isValidController)
|
||||
{
|
||||
printf("Disconnected!\n");
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
printf("ID: 0x%02X 0x%04X\n", header[TypeIDX], contData.button);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
Periphery::close_connection();
|
||||
|
||||
if(Configuration::Periphery::include_portB()) {
|
||||
cur_controller_port ^= 0x1;
|
||||
}
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/periphery_internal.hpp"
|
||||
#include <PSX/Periphery/periphery.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Periphery {
|
||||
// Controllers are checked every alternating frame
|
||||
static uint8_t cur_controller_port = 0;
|
||||
RawController controller[PortCount][DeviceCount];
|
||||
|
||||
struct ControllerHelper {
|
||||
static bool is_config(const RawController &cont) {
|
||||
return (static_cast<uint8_t>(cont.header.state) & 0xF);
|
||||
}
|
||||
|
||||
static void advance_config(RawController &cont) {
|
||||
cont.header.state = static_cast<RawController::State>((static_cast<uint32_t>(cont.header.state) << 1));
|
||||
}
|
||||
|
||||
static uint8_t* raw_device_data(RawController &cont) {
|
||||
return reinterpret_cast<uint8_t*>(&cont.button.currentState);
|
||||
}
|
||||
};
|
||||
|
||||
static void set_config_command(RawController::State state, uint8_t (&header)[3], uint8_t (&data)[6]) {
|
||||
static constexpr uint32_t CMDIDX = 1;
|
||||
|
||||
switch(state) {
|
||||
case RawController::State::EnterConfigMode:
|
||||
static constexpr uint8_t EnterCFGModeCMD = 0x1;
|
||||
|
||||
header[CMDIDX] = 0x43;
|
||||
data[0] = EnterCFGModeCMD;
|
||||
break;
|
||||
|
||||
case RawController::State::LockAnalog:
|
||||
static constexpr uint8_t valID = 0;
|
||||
static constexpr uint8_t selID = 1;
|
||||
|
||||
header[CMDIDX] = 0x44;
|
||||
data[valID] = static_cast<uint8_t>(LED::State::On);
|
||||
data[selID] = static_cast<uint8_t>(LED::Lock::On);
|
||||
break;
|
||||
|
||||
case RawController::State::UnlockRumble:
|
||||
header[CMDIDX] = 0x4D;
|
||||
|
||||
data[1] = 0x1;
|
||||
for(int32_t n = 2; n < 6; n++) {
|
||||
data[n] = 0xFF;
|
||||
}
|
||||
break;
|
||||
|
||||
case RawController::State::ExitConfigMode:
|
||||
header[CMDIDX] = 0x43;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t send_data(const uint8_t* header, uint8_t* headerResponse, const uint8_t* data, uint8_t* response) {
|
||||
const uint8_t *src = header;
|
||||
uint8_t *dst = headerResponse;
|
||||
size_t size = 3;
|
||||
|
||||
for(size_t n = 0; n < size; n++) {
|
||||
Periphery::send_byte(*src);
|
||||
Periphery::acknowledge();
|
||||
*dst = Periphery::read_byte();
|
||||
|
||||
if(n == 2) {
|
||||
const uint8_t id = headerResponse[1];
|
||||
size += (id == 0xFF) ? 0 : ((id & 0xF) << 1);
|
||||
|
||||
src = data;
|
||||
dst = response;
|
||||
}
|
||||
|
||||
else {
|
||||
src++;
|
||||
dst++;
|
||||
}
|
||||
|
||||
busy_loop(15);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void query_controller() {
|
||||
static constexpr auto TypeIDX = 1;
|
||||
|
||||
SysCall::EnterCriticalSection();
|
||||
Periphery::connect_to(cur_controller_port);
|
||||
for(uint32_t id = 0; id < Periphery::DeviceCount; id++) {
|
||||
auto &cur_controller = controller[cur_controller_port][id];
|
||||
|
||||
uint8_t header[] = {static_cast<uint8_t>((id + 1)), 0x42, 0x0};
|
||||
uint8_t data[] = {cur_controller.header.rumble0, cur_controller.header.rumble1, 0x0, 0x0, 0x0, 0x0};
|
||||
|
||||
// Can this move to the if??
|
||||
set_config_command(cur_controller.header.state, header, data);
|
||||
if(ControllerHelper::is_config(cur_controller)) {
|
||||
send_data(header, header, data, data);
|
||||
ControllerHelper::advance_config(cur_controller);
|
||||
}
|
||||
|
||||
else {
|
||||
cur_controller.button.exchange_state();
|
||||
|
||||
send_data(header, header, data, ControllerHelper::raw_device_data(cur_controller));
|
||||
const bool isValidController = (header[TypeIDX] != 0xFF);
|
||||
|
||||
if(header[TypeIDX] != cur_controller.header.type) {
|
||||
cur_controller.header.type = header[TypeIDX];
|
||||
cur_controller.header.state = isValidController ? RawController::State::EnterConfigMode : RawController::State::Disconnected;
|
||||
|
||||
/*if(!isValidController)
|
||||
{
|
||||
printf("Disconnected!\n");
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
printf("ID: 0x%02X 0x%04X\n", header[TypeIDX], contData.button);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
Periphery::close_connection();
|
||||
|
||||
if(Configuration::Periphery::include_portB()) {
|
||||
cur_controller_port ^= 0x1;
|
||||
}
|
||||
SysCall::ExitCriticalSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,31 @@
|
||||
#include "../../internal-include/SPU/spu_internal.hpp"
|
||||
#include "../../internal-include/SPU/spu_mmu.hpp"
|
||||
#include <PSX/System/IOPorts/spu_io.hpp>
|
||||
#include <PSX/SPU/spu.hpp>
|
||||
#include <stddef.hpp>
|
||||
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace SPU {
|
||||
SRAMAdr Voice :: allocate(size_t size) {
|
||||
Voice::stop();
|
||||
const auto voice_id = Voice::get_id();
|
||||
const auto adr = SRAMAdr{static_cast<SRAMAdr::UnderlyingType>(reinterpret_cast<uintptr_t>(SPU_MMU::allocate(voice_id, size)))};
|
||||
|
||||
SPU_IO::Voice[voice_id].adr.write(adr);
|
||||
return adr;
|
||||
}
|
||||
|
||||
SRAMAdr Voice :: allocate(SPU_IO_Values::SampleRate frequency, size_t size) {
|
||||
const auto result = Voice::allocate(size);
|
||||
Voice::set_sample_rate(frequency);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Voice :: deallocate() {
|
||||
Voice::stop();
|
||||
SPU_MMU::deallocate(Voice::get_id());
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/SPU/spu_internal.hpp"
|
||||
#include "../../internal-include/SPU/spu_mmu.hpp"
|
||||
#include <PSX/System/IOPorts/spu_io.hpp>
|
||||
#include <PSX/SPU/spu.hpp>
|
||||
#include <stddef.hpp>
|
||||
|
||||
#include <stdio.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace SPU {
|
||||
SRAMAdr Voice :: allocate(size_t size) {
|
||||
Voice::stop();
|
||||
const auto voice_id = Voice::get_id();
|
||||
const auto adr = SRAMAdr{static_cast<SRAMAdr::UnderlyingType>(reinterpret_cast<uintptr_t>(SPU_MMU::allocate(voice_id, size)))};
|
||||
|
||||
SPU_IO::Voice[voice_id].adr.write(adr);
|
||||
return adr;
|
||||
}
|
||||
|
||||
SRAMAdr Voice :: allocate(SPU_IO_Values::SampleRate frequency, size_t size) {
|
||||
const auto result = Voice::allocate(size);
|
||||
Voice::set_sample_rate(frequency);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Voice :: deallocate() {
|
||||
Voice::stop();
|
||||
SPU_MMU::deallocate(Voice::get_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +1,136 @@
|
||||
#include "../../internal-include/SPU/spu_mmu.hpp"
|
||||
#include <PSX/System/IOPorts/spu_io.hpp>
|
||||
#include <PSX/Auxiliary/math_helper.hpp>
|
||||
#include <PSX/SPU/spu.hpp>
|
||||
#include <PSX/jabyengine_config.hpp>
|
||||
#include <stddef.hpp>
|
||||
#ifdef __DEBUG_SPU_MMU__
|
||||
#include <stdio.hpp>
|
||||
#endif // __DEBUG_SPU_MMU__
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace SPU_MMU {
|
||||
namespace SPU_MemoryMap = SPU_IO_Values::MemoryMap;
|
||||
|
||||
struct SPUMemory {
|
||||
const uint8_t* adr;
|
||||
size_t size;
|
||||
|
||||
static SPUMemory create(size_t size) {
|
||||
return SPUMemory{.adr = reinterpret_cast<const uint8_t*>(SPU_MemoryMap::ADPCM), .size = size};
|
||||
}
|
||||
|
||||
constexpr void clear() {
|
||||
this->adr = nullptr;
|
||||
this->size = 0;
|
||||
}
|
||||
|
||||
constexpr const uint8_t* get_end_adr() const {
|
||||
return (this->adr + this->size);
|
||||
}
|
||||
|
||||
constexpr bool is_free() const {
|
||||
return this->adr == nullptr;
|
||||
}
|
||||
|
||||
constexpr bool intersects(const SPUMemory& other) const {
|
||||
const auto* min = max_of(this->adr, other.adr);
|
||||
const auto* max = min_of(this->get_end_adr(), other.get_end_adr());
|
||||
return min < max;
|
||||
}
|
||||
};
|
||||
|
||||
struct AllocatedVoice {
|
||||
SPUMemory memory;
|
||||
AllocatedVoice* next_entry;
|
||||
};
|
||||
|
||||
struct VoiceManager {
|
||||
struct Iterator {
|
||||
AllocatedVoice* *prev_voice;
|
||||
AllocatedVoice* current_voice;
|
||||
|
||||
void next() {
|
||||
this->prev_voice = &this->current_voice->next_entry;
|
||||
this->current_voice = this->current_voice->next_entry;
|
||||
}
|
||||
|
||||
bool has_next() const {
|
||||
return this->current_voice;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return Iterator::has_next();
|
||||
}
|
||||
};
|
||||
|
||||
AllocatedVoice allocated_voice_buffer[SPU_IO::VoiceCount + SPU_IO::ReverbCount] = {0};
|
||||
AllocatedVoice* first_allocated_voice = nullptr;
|
||||
|
||||
Iterator iterator() {
|
||||
return Iterator{.prev_voice = &this->first_allocated_voice, .current_voice = this->first_allocated_voice};
|
||||
}
|
||||
|
||||
AllocatedVoice& get_voice(uint8_t id) {
|
||||
return this->allocated_voice_buffer[id];
|
||||
}
|
||||
};
|
||||
|
||||
static VoiceManager voice_mgr;
|
||||
static const uint8_t* reverb_adr = reinterpret_cast<const uint8_t*>(SPU_MemoryMap::End);
|
||||
// ^ change this to allocate reverb
|
||||
|
||||
using MemoryFoundCallback = const uint8_t* (AllocatedVoice& new_entry, AllocatedVoice* &prev_entry, AllocatedVoice* next_entry);
|
||||
|
||||
static const uint8_t* verify_and_add(AllocatedVoice& new_entry, AllocatedVoice* &prev_entry, AllocatedVoice* next_entry) {
|
||||
if(new_entry.memory.adr >= reverb_adr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
prev_entry = &new_entry;
|
||||
new_entry.next_entry = next_entry;
|
||||
return new_entry.memory.adr;
|
||||
}
|
||||
|
||||
static const uint8_t* find_first_fit(AllocatedVoice &new_entry, MemoryFoundCallback memory_found) {
|
||||
auto iterator = voice_mgr.iterator();
|
||||
while(iterator) {
|
||||
if(!iterator.current_voice->memory.intersects(new_entry.memory)) {
|
||||
break;
|
||||
}
|
||||
new_entry.memory.adr = iterator.current_voice->memory.get_end_adr();
|
||||
iterator.next();
|
||||
}
|
||||
|
||||
return memory_found(new_entry, *iterator.prev_voice, iterator.current_voice);
|
||||
}
|
||||
|
||||
const uint8_t* allocate(uint8_t voice, size_t size) {
|
||||
auto& voice_entry = voice_mgr.get_voice(voice);
|
||||
if(!voice_entry.memory.is_free()) {
|
||||
deallocate(voice);
|
||||
}
|
||||
|
||||
voice_entry.memory = SPUMemory::create(size);
|
||||
const auto* mem_adr = find_first_fit(voice_entry, verify_and_add);
|
||||
#ifdef __DEBUG_SPU_MMU__
|
||||
printf("SPU: Allocated %i @0x%p to 0x%p (%i bytes)\n", voice, mem_adr, (mem_adr + size), size);
|
||||
#endif // __DEBUG_SPU_MMU__
|
||||
return mem_adr;
|
||||
}
|
||||
|
||||
void deallocate(uint8_t voice) {
|
||||
auto* voice_adr = &voice_mgr.get_voice(voice);
|
||||
auto iterator = voice_mgr.iterator();
|
||||
|
||||
voice_adr->memory.clear();
|
||||
while(iterator) {
|
||||
if(iterator.current_voice == voice_adr) {
|
||||
*iterator.prev_voice = voice_adr->next_entry;
|
||||
break;
|
||||
}
|
||||
iterator.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/SPU/spu_mmu.hpp"
|
||||
#include <PSX/System/IOPorts/spu_io.hpp>
|
||||
#include <PSX/Auxiliary/math_helper.hpp>
|
||||
#include <PSX/SPU/spu.hpp>
|
||||
#include <PSX/jabyengine_config.hpp>
|
||||
#include <stddef.hpp>
|
||||
#ifdef __DEBUG_SPU_MMU__
|
||||
#include <stdio.hpp>
|
||||
#endif // __DEBUG_SPU_MMU__
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace SPU_MMU {
|
||||
namespace SPU_MemoryMap = SPU_IO_Values::MemoryMap;
|
||||
|
||||
struct SPUMemory {
|
||||
const uint8_t* adr;
|
||||
size_t size;
|
||||
|
||||
static SPUMemory create(size_t size) {
|
||||
return SPUMemory{.adr = reinterpret_cast<const uint8_t*>(SPU_MemoryMap::ADPCM), .size = size};
|
||||
}
|
||||
|
||||
constexpr void clear() {
|
||||
this->adr = nullptr;
|
||||
this->size = 0;
|
||||
}
|
||||
|
||||
constexpr const uint8_t* get_end_adr() const {
|
||||
return (this->adr + this->size);
|
||||
}
|
||||
|
||||
constexpr bool is_free() const {
|
||||
return this->adr == nullptr;
|
||||
}
|
||||
|
||||
constexpr bool intersects(const SPUMemory& other) const {
|
||||
const auto* min = max_of(this->adr, other.adr);
|
||||
const auto* max = min_of(this->get_end_adr(), other.get_end_adr());
|
||||
return min < max;
|
||||
}
|
||||
};
|
||||
|
||||
struct AllocatedVoice {
|
||||
SPUMemory memory;
|
||||
AllocatedVoice* next_entry;
|
||||
};
|
||||
|
||||
struct VoiceManager {
|
||||
struct Iterator {
|
||||
AllocatedVoice* *prev_voice;
|
||||
AllocatedVoice* current_voice;
|
||||
|
||||
void next() {
|
||||
this->prev_voice = &this->current_voice->next_entry;
|
||||
this->current_voice = this->current_voice->next_entry;
|
||||
}
|
||||
|
||||
bool has_next() const {
|
||||
return this->current_voice;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return Iterator::has_next();
|
||||
}
|
||||
};
|
||||
|
||||
AllocatedVoice allocated_voice_buffer[SPU_IO::VoiceCount + SPU_IO::ReverbCount] = {0};
|
||||
AllocatedVoice* first_allocated_voice = nullptr;
|
||||
|
||||
Iterator iterator() {
|
||||
return Iterator{.prev_voice = &this->first_allocated_voice, .current_voice = this->first_allocated_voice};
|
||||
}
|
||||
|
||||
AllocatedVoice& get_voice(uint8_t id) {
|
||||
return this->allocated_voice_buffer[id];
|
||||
}
|
||||
};
|
||||
|
||||
static VoiceManager voice_mgr;
|
||||
static const uint8_t* reverb_adr = reinterpret_cast<const uint8_t*>(SPU_MemoryMap::End);
|
||||
// ^ change this to allocate reverb
|
||||
|
||||
using MemoryFoundCallback = const uint8_t* (AllocatedVoice& new_entry, AllocatedVoice* &prev_entry, AllocatedVoice* next_entry);
|
||||
|
||||
static const uint8_t* verify_and_add(AllocatedVoice& new_entry, AllocatedVoice* &prev_entry, AllocatedVoice* next_entry) {
|
||||
if(new_entry.memory.adr >= reverb_adr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
prev_entry = &new_entry;
|
||||
new_entry.next_entry = next_entry;
|
||||
return new_entry.memory.adr;
|
||||
}
|
||||
|
||||
static const uint8_t* find_first_fit(AllocatedVoice &new_entry, MemoryFoundCallback memory_found) {
|
||||
auto iterator = voice_mgr.iterator();
|
||||
while(iterator) {
|
||||
if(!iterator.current_voice->memory.intersects(new_entry.memory)) {
|
||||
break;
|
||||
}
|
||||
new_entry.memory.adr = iterator.current_voice->memory.get_end_adr();
|
||||
iterator.next();
|
||||
}
|
||||
|
||||
return memory_found(new_entry, *iterator.prev_voice, iterator.current_voice);
|
||||
}
|
||||
|
||||
const uint8_t* allocate(uint8_t voice, size_t size) {
|
||||
auto& voice_entry = voice_mgr.get_voice(voice);
|
||||
if(!voice_entry.memory.is_free()) {
|
||||
deallocate(voice);
|
||||
}
|
||||
|
||||
voice_entry.memory = SPUMemory::create(size);
|
||||
const auto* mem_adr = find_first_fit(voice_entry, verify_and_add);
|
||||
#ifdef __DEBUG_SPU_MMU__
|
||||
printf("SPU: Allocated %i @0x%p to 0x%p (%i bytes)\n", voice, mem_adr, (mem_adr + size), size);
|
||||
#endif // __DEBUG_SPU_MMU__
|
||||
return mem_adr;
|
||||
}
|
||||
|
||||
void deallocate(uint8_t voice) {
|
||||
auto* voice_adr = &voice_mgr.get_voice(voice);
|
||||
auto iterator = voice_mgr.iterator();
|
||||
|
||||
voice_adr->memory.clear();
|
||||
while(iterator) {
|
||||
if(iterator.current_voice == voice_adr) {
|
||||
*iterator.prev_voice = voice_adr->next_entry;
|
||||
break;
|
||||
}
|
||||
iterator.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,44 @@
|
||||
#include "../../internal-include/System/callbacks_internal.hpp"
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/System/callbacks.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Callback {
|
||||
// Marked deprecated currently
|
||||
/*using Function = VSyncCallback::Function;
|
||||
Function VSyncCallback :: callback = nullptr;*/
|
||||
|
||||
namespace internal {
|
||||
namespace VSync {
|
||||
SysCall::ThreadHandle thread_handle = 0;
|
||||
uint32_t stack[StackSize] = {0};
|
||||
|
||||
void routine() {
|
||||
while(true) {
|
||||
// Marked deprecated currently
|
||||
/*if(VSyncCallback::callback) {
|
||||
VSyncCallback::callback();
|
||||
}*/
|
||||
SysCall::ChangeThread(MainThread::Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace CD {
|
||||
namespace CD_IRQ = JabyEngine::CD::internal::IRQ;
|
||||
|
||||
SysCall::ThreadHandle thread_handle = 0;
|
||||
uint32_t stack[StackSize];
|
||||
|
||||
void routine(uint32_t irq) {
|
||||
while(true) {
|
||||
const auto old_status = CD_IO::IndexStatus.read();
|
||||
CD_IRQ::process(irq);
|
||||
CD_IO::IndexStatus.write(old_status);
|
||||
irq = Callback::internal::CD::resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "../../internal-include/System/callbacks_internal.hpp"
|
||||
#include "../../internal-include/CD/cd_internal.hpp"
|
||||
#include <PSX/System/callbacks.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace Callback {
|
||||
// Marked deprecated currently
|
||||
/*using Function = VSyncCallback::Function;
|
||||
Function VSyncCallback :: callback = nullptr;*/
|
||||
|
||||
namespace internal {
|
||||
namespace VSync {
|
||||
SysCall::ThreadHandle thread_handle = 0;
|
||||
uint32_t stack[StackSize] = {0};
|
||||
|
||||
void routine() {
|
||||
while(true) {
|
||||
// Marked deprecated currently
|
||||
/*if(VSyncCallback::callback) {
|
||||
VSyncCallback::callback();
|
||||
}*/
|
||||
SysCall::ChangeThread(MainThread::Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace CD {
|
||||
namespace CD_IRQ = JabyEngine::CD::internal::IRQ;
|
||||
|
||||
SysCall::ThreadHandle thread_handle = 0;
|
||||
uint32_t stack[StackSize];
|
||||
|
||||
void routine(uint32_t irq) {
|
||||
while(true) {
|
||||
const auto old_status = CD_IO::IndexStatus.read();
|
||||
CD_IRQ::process(irq);
|
||||
CD_IO::IndexStatus.write(old_status);
|
||||
irq = Callback::internal::CD::resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,63 @@
|
||||
#include <string.hpp>
|
||||
|
||||
int strncmp(const char* s1, const char* s2, size_t n) {
|
||||
if(n == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
do {
|
||||
if(*s1 != *s2++) {
|
||||
return (*(unsigned char *)s1 - *(unsigned char *)--s2);
|
||||
}
|
||||
|
||||
if(*s1++ == 0) {
|
||||
break;
|
||||
}
|
||||
} while(--n != 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t strlen(const char *str) {
|
||||
const char* end = str;
|
||||
|
||||
for(; *end; ++end);
|
||||
return(end - str);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void* core_memset(T* dst, int val, size_t &len) {
|
||||
while(len >= sizeof(T)) {
|
||||
*dst++ = val;
|
||||
len -= sizeof(T);
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
static void* simple_memset(void* dest, int val, size_t len) {
|
||||
core_memset(static_cast<uint8_t*>(dest), val, len);
|
||||
return dest;
|
||||
}
|
||||
|
||||
static void* cplx_memset(void* dst, int val, size_t len) {
|
||||
void*const org_dst = dst;
|
||||
|
||||
if(reinterpret_cast<uintptr_t>(dst) & 0x2 == 0) {
|
||||
dst = core_memset(static_cast<uint32_t*>(dst), val, len);
|
||||
}
|
||||
|
||||
if(reinterpret_cast<uintptr_t>(dst) & 0x1 == 0) {
|
||||
dst = core_memset(static_cast<uint16_t*>(dst), val, len);
|
||||
}
|
||||
|
||||
if(len > 0) {
|
||||
simple_memset(dst, val, len);
|
||||
}
|
||||
return org_dst;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
void* memset(void* dest, int val, size_t len) {
|
||||
return simple_memset(dest, val, len);
|
||||
}
|
||||
#include <string.hpp>
|
||||
|
||||
int strncmp(const char* s1, const char* s2, size_t n) {
|
||||
if(n == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
do {
|
||||
if(*s1 != *s2++) {
|
||||
return (*(unsigned char *)s1 - *(unsigned char *)--s2);
|
||||
}
|
||||
|
||||
if(*s1++ == 0) {
|
||||
break;
|
||||
}
|
||||
} while(--n != 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t strlen(const char *str) {
|
||||
const char* end = str;
|
||||
|
||||
for(; *end; ++end);
|
||||
return(end - str);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void* core_memset(T* dst, int val, size_t &len) {
|
||||
while(len >= sizeof(T)) {
|
||||
*dst++ = val;
|
||||
len -= sizeof(T);
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
static void* simple_memset(void* dest, int val, size_t len) {
|
||||
core_memset(static_cast<uint8_t*>(dest), val, len);
|
||||
return dest;
|
||||
}
|
||||
|
||||
static void* cplx_memset(void* dst, int val, size_t len) {
|
||||
void*const org_dst = dst;
|
||||
|
||||
if(reinterpret_cast<uintptr_t>(dst) & 0x2 == 0) {
|
||||
dst = core_memset(static_cast<uint32_t*>(dst), val, len);
|
||||
}
|
||||
|
||||
if(reinterpret_cast<uintptr_t>(dst) & 0x1 == 0) {
|
||||
dst = core_memset(static_cast<uint16_t*>(dst), val, len);
|
||||
}
|
||||
|
||||
if(len > 0) {
|
||||
simple_memset(dst, val, len);
|
||||
}
|
||||
return org_dst;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
void* memset(void* dest, int val, size_t len) {
|
||||
return simple_memset(dest, val, len);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <PSX/Auxiliary/math_helper.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace BIOS {
|
||||
const Version version = {0};
|
||||
}
|
||||
#include <PSX/Auxiliary/math_helper.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
namespace BIOS {
|
||||
const Version version = {0};
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,27 @@
|
||||
#include <PSX/System/IOPorts/timer_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <PSX/Timer/high_res_timer.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
volatile uint16_t HighResTime :: global_counter_10ms = 0;
|
||||
|
||||
namespace Timer {
|
||||
static SysCall::InterruptVerifierResult interrupt_verifier() {
|
||||
if(Interrupt::is_irq(Interrupt::Timer2)) {
|
||||
Interrupt::ack_irq(Interrupt::Timer2);
|
||||
return SysCall::InterruptVerifierResult::ExecuteHandler;
|
||||
}
|
||||
|
||||
else {
|
||||
return SysCall::InterruptVerifierResult::SkipHandler;
|
||||
}
|
||||
}
|
||||
|
||||
static void interrupt_handler(uint32_t) {
|
||||
HighResTime::global_counter_10ms = HighResTime::global_counter_10ms + 1;
|
||||
SysCall::ReturnFromException();
|
||||
}
|
||||
|
||||
auto irq_callback = SysCall::InterruptCallback::from(interrupt_verifier, interrupt_handler);
|
||||
}
|
||||
#include <PSX/System/IOPorts/timer_io.hpp>
|
||||
#include <PSX/System/syscalls.hpp>
|
||||
#include <PSX/Timer/high_res_timer.hpp>
|
||||
|
||||
namespace JabyEngine {
|
||||
volatile uint16_t HighResTime :: global_counter_10ms = 0;
|
||||
|
||||
namespace Timer {
|
||||
static SysCall::InterruptVerifierResult interrupt_verifier() {
|
||||
if(Interrupt::is_irq(Interrupt::Timer2)) {
|
||||
Interrupt::ack_irq(Interrupt::Timer2);
|
||||
return SysCall::InterruptVerifierResult::ExecuteHandler;
|
||||
}
|
||||
|
||||
else {
|
||||
return SysCall::InterruptVerifierResult::SkipHandler;
|
||||
}
|
||||
}
|
||||
|
||||
static void interrupt_handler(uint32_t) {
|
||||
HighResTime::global_counter_10ms = HighResTime::global_counter_10ms + 1;
|
||||
SysCall::ReturnFromException();
|
||||
}
|
||||
|
||||
auto irq_callback = SysCall::InterruptCallback::from(interrupt_verifier, interrupt_handler);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
.set push
|
||||
.set noreorder
|
||||
.section .text, "ax", @progbits
|
||||
.align 2
|
||||
.global _ZN10JabyEngine7SysCall6printfEPKcz
|
||||
.type _ZN10JabyEngine7SysCall6printfEPKcz, @function
|
||||
|
||||
_ZN10JabyEngine7SysCall6printfEPKcz:
|
||||
li $t2, 0xa0
|
||||
jr $t2
|
||||
li $t1, 0x3f
|
||||
.set push
|
||||
.set noreorder
|
||||
.section .text, "ax", @progbits
|
||||
.align 2
|
||||
.global _ZN10JabyEngine7SysCall6printfEPKcz
|
||||
.type _ZN10JabyEngine7SysCall6printfEPKcz, @function
|
||||
|
||||
_ZN10JabyEngine7SysCall6printfEPKcz:
|
||||
li $t2, 0xa0
|
||||
jr $t2
|
||||
li $t1, 0x3f
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
BUILD_PROFILE ?= debug
|
||||
CARGO_CMD ?= build
|
||||
WINDOWS_TARGET ?= x86_64-pc-windows-gnu
|
||||
UNIX_TARGET ?= x86_64-unknown-linux-gnu
|
||||
|
||||
WINDOWS_ARTIFACT = ./target/$(WINDOWS_TARGET)/$(BUILD_PROFILE)/$(ARTIFACT).exe
|
||||
UNIX_ARTIFACT = ./target/$(UNIX_TARGET)/$(BUILD_PROFILE)/$(ARTIFACT)
|
||||
|
||||
define cp_artifact
|
||||
$(if $(findstring build,$(CARGO_CMD)),
|
||||
@mkdir -p $(dir $(1))
|
||||
@cp $(1) $(2)
|
||||
)
|
||||
endef
|
||||
|
||||
define cargo_windows_default
|
||||
$(if $(findstring upgrade,$(CARGO_CMD)),
|
||||
cargo $(CARGO_CMD) --$(BUILD_PROFILE),
|
||||
|
||||
cargo $(CARGO_CMD) --$(BUILD_PROFILE) --target=$(WINDOWS_TARGET)
|
||||
$(call cp_artifact,$(WINDOWS_ARTIFACT), ../../../bin/$(ARTIFACT).exe)
|
||||
)
|
||||
endef
|
||||
|
||||
define cargo_unix_default
|
||||
$(if $(findstring upgrade,$(CARGO_CMD)),
|
||||
cargo $(CARGO_CMD) --$(BUILD_PROFILE),
|
||||
|
||||
cargo $(CARGO_CMD) --$(BUILD_PROFILE) --target=$(UNIX_TARGET)
|
||||
$(call cp_artifact,$(UNIX_ARTIFACT), ../../../bin/$(ARTIFACT))
|
||||
)
|
||||
endef
|
||||
|
||||
BUILD_PROFILE ?= debug
|
||||
CARGO_CMD ?= build
|
||||
WINDOWS_TARGET ?= x86_64-pc-windows-gnu
|
||||
UNIX_TARGET ?= x86_64-unknown-linux-gnu
|
||||
|
||||
WINDOWS_ARTIFACT = ./target/$(WINDOWS_TARGET)/$(BUILD_PROFILE)/$(ARTIFACT).exe
|
||||
UNIX_ARTIFACT = ./target/$(UNIX_TARGET)/$(BUILD_PROFILE)/$(ARTIFACT)
|
||||
|
||||
define cp_artifact
|
||||
$(if $(findstring build,$(CARGO_CMD)),
|
||||
@mkdir -p $(dir $(1))
|
||||
@cp $(1) $(2)
|
||||
)
|
||||
endef
|
||||
|
||||
define cargo_windows_default
|
||||
$(if $(findstring upgrade,$(CARGO_CMD)),
|
||||
cargo $(CARGO_CMD) --$(BUILD_PROFILE),
|
||||
|
||||
cargo $(CARGO_CMD) --$(BUILD_PROFILE) --target=$(WINDOWS_TARGET)
|
||||
$(call cp_artifact,$(WINDOWS_ARTIFACT), ../../../bin/$(ARTIFACT).exe)
|
||||
)
|
||||
endef
|
||||
|
||||
define cargo_unix_default
|
||||
$(if $(findstring upgrade,$(CARGO_CMD)),
|
||||
cargo $(CARGO_CMD) --$(BUILD_PROFILE),
|
||||
|
||||
cargo $(CARGO_CMD) --$(BUILD_PROFILE) --target=$(UNIX_TARGET)
|
||||
$(call cp_artifact,$(UNIX_ARTIFACT), ../../../bin/$(ARTIFACT))
|
||||
)
|
||||
endef
|
||||
|
||||
# Windows build requires "rustup target add x86_64-pc-windows-gnu" and "sudo apt-get install mingw-w64"
|
||||
@@ -1,9 +1,9 @@
|
||||
SUPPORT_PROJECTS = cdtypes tool_helper
|
||||
PROJECTS = cpp_out psxfileconv mkoverlay psxcdgen_ex psxreadmap wslpath
|
||||
.PHONY: $(PROJECTS)
|
||||
|
||||
$(foreach var,$(PROJECTS),$(var)):
|
||||
$(MAKE) -C ./$@ $(MAKECMDGOALS)
|
||||
|
||||
all-windows: $(PROJECTS)
|
||||
SUPPORT_PROJECTS = cdtypes tool_helper
|
||||
PROJECTS = cpp_out psxfileconv mkoverlay psxcdgen_ex psxreadmap wslpath
|
||||
.PHONY: $(PROJECTS)
|
||||
|
||||
$(foreach var,$(PROJECTS),$(var)):
|
||||
$(MAKE) -C ./$@ $(MAKECMDGOALS)
|
||||
|
||||
all-windows: $(PROJECTS)
|
||||
all: $(PROJECTS)
|
||||
@@ -1,20 +1,20 @@
|
||||
<ISO_Project>
|
||||
<Description>
|
||||
<Publisher>Jaby Wuff</Publisher>
|
||||
<!--<License>../Tests/ISO_Planschbecken.xml</License>-->
|
||||
</Description>
|
||||
<Track>
|
||||
<File name="Miau.txt" padded_size="4096">../Tests/Test.mk</File>
|
||||
<File name="Miau2.txt">../Tests/Test.mk</File>
|
||||
<File name="SYSTEM.CNF">../Tests/Test.mk</File>
|
||||
<File name="SCES_003.90">../Tests/Test.mk</File>
|
||||
<Audiofile>../Tests/ISO_Planschbecken.xml</Audiofile>
|
||||
<Directory name="Wuff">
|
||||
<File name="Miau.txt">../Tests/ISO_Planschbecken.xml</File>
|
||||
<Directory name="Sub" hidden="true">
|
||||
<File name="SubM.txt">../Tests/ISO_Planschbecken.xml</File>
|
||||
</Directory>
|
||||
</Directory>
|
||||
<Overlay name="Main.ovl" lba_source="../../../../JabyAdventure/application/src/MainState/LBAs.hpp">../../../../JabyAdventure/application/bin/PSX-release/Overlay.main_area</Overlay>
|
||||
</Track>
|
||||
<ISO_Project>
|
||||
<Description>
|
||||
<Publisher>Jaby Wuff</Publisher>
|
||||
<!--<License>../Tests/ISO_Planschbecken.xml</License>-->
|
||||
</Description>
|
||||
<Track>
|
||||
<File name="Miau.txt" padded_size="4096">../Tests/Test.mk</File>
|
||||
<File name="Miau2.txt">../Tests/Test.mk</File>
|
||||
<File name="SYSTEM.CNF">../Tests/Test.mk</File>
|
||||
<File name="SCES_003.90">../Tests/Test.mk</File>
|
||||
<Audiofile>../Tests/ISO_Planschbecken.xml</Audiofile>
|
||||
<Directory name="Wuff">
|
||||
<File name="Miau.txt">../Tests/ISO_Planschbecken.xml</File>
|
||||
<Directory name="Sub" hidden="true">
|
||||
<File name="SubM.txt">../Tests/ISO_Planschbecken.xml</File>
|
||||
</Directory>
|
||||
</Directory>
|
||||
<Overlay name="Main.ovl" lba_source="../../../../JabyAdventure/application/src/MainState/LBAs.hpp">../../../../JabyAdventure/application/bin/PSX-release/Overlay.main_area</Overlay>
|
||||
</Track>
|
||||
</ISO_Project>
|
||||
@@ -1,58 +1,58 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"cargo_task": [
|
||||
"./ all!All",
|
||||
"cpp_out all!cpp_out",
|
||||
"mkoverlay all!mkoverlay",
|
||||
"psxcdgen_ex all!psxcdgen_ex",
|
||||
"psxcdread all!psxcdread",
|
||||
"psxfileconv all!psxfileconv",
|
||||
"psxreadmap all!psxreadmap",
|
||||
"wslpath all!wslpath",
|
||||
]
|
||||
},
|
||||
"tasks": {
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "cargo",
|
||||
"type": "shell",
|
||||
"group": {
|
||||
"kind": "build"
|
||||
},
|
||||
"command": "../../scripts/podman_jaby_engine.sh ../../:src/Tools make -C ${input:linux_cargo_task} CARGO_CMD=${input:cargo cmd} BUILD_PROFILE=${input:build cfg}",
|
||||
"problemMatcher": []
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "linux_cargo_task",
|
||||
"type": "command",
|
||||
"command": "shellCommand.execute",
|
||||
"args": {
|
||||
"command": "echo ${config:cargo_task} | tr , \\\\n",
|
||||
"fieldSeparator": "!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "build cfg",
|
||||
"type": "pickString",
|
||||
"options": ["debug", "release", "compatible", "incompatible"],
|
||||
"default": "release",
|
||||
"description": "build configuration"
|
||||
},
|
||||
{
|
||||
"id": "cargo cmd",
|
||||
"type":"pickString",
|
||||
"options": ["build", "check", "upgrade", "clean", "run", "tree"],
|
||||
"default": "build",
|
||||
"description": "cargo command to run"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"cargo_task": [
|
||||
"./ all!All",
|
||||
"cpp_out all!cpp_out",
|
||||
"mkoverlay all!mkoverlay",
|
||||
"psxcdgen_ex all!psxcdgen_ex",
|
||||
"psxcdread all!psxcdread",
|
||||
"psxfileconv all!psxfileconv",
|
||||
"psxreadmap all!psxreadmap",
|
||||
"wslpath all!wslpath",
|
||||
]
|
||||
},
|
||||
"tasks": {
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "cargo",
|
||||
"type": "shell",
|
||||
"group": {
|
||||
"kind": "build"
|
||||
},
|
||||
"command": "../../scripts/podman_jaby_engine.sh ../../:src/Tools make -C ${input:linux_cargo_task} CARGO_CMD=${input:cargo cmd} BUILD_PROFILE=${input:build cfg}",
|
||||
"problemMatcher": []
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "linux_cargo_task",
|
||||
"type": "command",
|
||||
"command": "shellCommand.execute",
|
||||
"args": {
|
||||
"command": "echo ${config:cargo_task} | tr , \\\\n",
|
||||
"fieldSeparator": "!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "build cfg",
|
||||
"type": "pickString",
|
||||
"options": ["debug", "release", "compatible", "incompatible"],
|
||||
"default": "release",
|
||||
"description": "build configuration"
|
||||
},
|
||||
{
|
||||
"id": "cargo cmd",
|
||||
"type":"pickString",
|
||||
"options": ["build", "check", "upgrade", "clean", "run", "tree"],
|
||||
"default": "build",
|
||||
"description": "cargo command to run"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = cdtypes
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = cdtypes
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
||||
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = cpp_out
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = cpp_out
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
||||
@@ -1,52 +1,52 @@
|
||||
use clap::{Parser};
|
||||
use cpp_out::{Configuration, Error, FileType};
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::exit_with_error;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(about = "Output a file content or stdin to a c++ header/source file", long_about = None)]
|
||||
struct CommandLine {
|
||||
#[clap(value_parser)]
|
||||
input_file: Option<PathBuf>,
|
||||
|
||||
#[clap(short='n', long="name")]
|
||||
data_name: String,
|
||||
|
||||
#[clap(short='o')]
|
||||
output_file: PathBuf,
|
||||
}
|
||||
|
||||
fn configurate(cmd: &mut CommandLine) -> Result<Configuration, Error> {
|
||||
let file_name = tool_helper::get_file_name_from_path_buf(&cmd.output_file, "output")?;
|
||||
let extension = tool_helper::os_str_to_string(Error::ok_or_new(cmd.output_file.extension(), ||"File extension required for output".to_owned())?, "extension")?.to_ascii_lowercase();
|
||||
let file_type = Error::ok_or_new({
|
||||
match extension.as_ref() {
|
||||
"h" => Some(FileType::CHeader),
|
||||
"hpp" => Some(FileType::CPPHeader),
|
||||
"c" => Some(FileType::CSource),
|
||||
"cpp" => Some(FileType::CPPSource),
|
||||
_ => None
|
||||
}
|
||||
}, ||format!("{} unkown file extension", extension))?;
|
||||
|
||||
Ok(Configuration{file_name, data_name: std::mem::take(&mut cmd.data_name), line_feed: cpp_out::LineFeed::Windows, file_type})
|
||||
}
|
||||
|
||||
fn run_main(mut cmd: CommandLine) -> Result<(), Error> {
|
||||
let cfg = configurate(&mut cmd)?;
|
||||
let input = tool_helper::open_input(&cmd.input_file)?;
|
||||
let output = tool_helper::open_output(&Some(cmd.output_file))?;
|
||||
|
||||
return cpp_out::convert(cfg, input, output);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match CommandLine::try_parse() {
|
||||
Ok(cmd) => {
|
||||
if let Err(error) = run_main(cmd) {
|
||||
exit_with_error(error)
|
||||
}
|
||||
},
|
||||
Err(error) => println!("{}", error)
|
||||
}
|
||||
use clap::{Parser};
|
||||
use cpp_out::{Configuration, Error, FileType};
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::exit_with_error;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(about = "Output a file content or stdin to a c++ header/source file", long_about = None)]
|
||||
struct CommandLine {
|
||||
#[clap(value_parser)]
|
||||
input_file: Option<PathBuf>,
|
||||
|
||||
#[clap(short='n', long="name")]
|
||||
data_name: String,
|
||||
|
||||
#[clap(short='o')]
|
||||
output_file: PathBuf,
|
||||
}
|
||||
|
||||
fn configurate(cmd: &mut CommandLine) -> Result<Configuration, Error> {
|
||||
let file_name = tool_helper::get_file_name_from_path_buf(&cmd.output_file, "output")?;
|
||||
let extension = tool_helper::os_str_to_string(Error::ok_or_new(cmd.output_file.extension(), ||"File extension required for output".to_owned())?, "extension")?.to_ascii_lowercase();
|
||||
let file_type = Error::ok_or_new({
|
||||
match extension.as_ref() {
|
||||
"h" => Some(FileType::CHeader),
|
||||
"hpp" => Some(FileType::CPPHeader),
|
||||
"c" => Some(FileType::CSource),
|
||||
"cpp" => Some(FileType::CPPSource),
|
||||
_ => None
|
||||
}
|
||||
}, ||format!("{} unkown file extension", extension))?;
|
||||
|
||||
Ok(Configuration{file_name, data_name: std::mem::take(&mut cmd.data_name), line_feed: cpp_out::LineFeed::Windows, file_type})
|
||||
}
|
||||
|
||||
fn run_main(mut cmd: CommandLine) -> Result<(), Error> {
|
||||
let cfg = configurate(&mut cmd)?;
|
||||
let input = tool_helper::open_input(&cmd.input_file)?;
|
||||
let output = tool_helper::open_output(&Some(cmd.output_file))?;
|
||||
|
||||
return cpp_out::convert(cfg, input, output);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match CommandLine::try_parse() {
|
||||
Ok(cmd) => {
|
||||
if let Err(error) = run_main(cmd) {
|
||||
exit_with_error(error)
|
||||
}
|
||||
},
|
||||
Err(error) => println!("{}", error)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = mkoverlay
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = mkoverlay
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
||||
@@ -1,51 +1,51 @@
|
||||
use clap::Parser;
|
||||
use mkoverlay::types::OverlayDesc;
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::{Error, exit_with_error, format_if_error, open_input, open_output};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(about = "Creates a linker script and makefile part for the JabyEngine toolchain", long_about = None)]
|
||||
struct CommandLine {
|
||||
#[clap(long="mk-file", help="Output path for the makefile")]
|
||||
mk_file_output: Option<PathBuf>,
|
||||
|
||||
#[clap(long="ld-script", help="Output path for the linker script file")]
|
||||
ld_file_output: Option<PathBuf>,
|
||||
|
||||
#[clap(value_parser, help="Input JSON for creating the files")]
|
||||
input: Option<PathBuf>
|
||||
}
|
||||
|
||||
fn parse_input(input: Option<PathBuf>) -> Result<OverlayDesc, Error> {
|
||||
if let Some(input) = input {
|
||||
let input = format_if_error!(open_input(&Some(input)), "Opening input file failed with: {error_text}")?;
|
||||
format_if_error!(mkoverlay::types::json_reader::read_config(input), "Parsing JSON file failed with: {error_text}")
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(OverlayDesc::new())
|
||||
}
|
||||
}
|
||||
|
||||
fn run_main(cmd_line: CommandLine) -> Result<(), Error> {
|
||||
let input = parse_input(cmd_line.input)?;
|
||||
let mut mk_output = format_if_error!(open_output(&cmd_line.mk_file_output), "Opening file for writing makefile failed with: {error_text}")?;
|
||||
let mut ld_output = format_if_error!(open_output(&cmd_line.ld_file_output), "Opening file for writing linkerfile failed with: {error_text}")?;
|
||||
|
||||
format_if_error!(mkoverlay::creator::makefile::write(&mut mk_output, &input), "Writing makefile failed with: {error_text}")?;
|
||||
format_if_error!(mkoverlay::creator::ldscript::write(&mut ld_output, &input), "Writing ld script failed with: {error_text}")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match CommandLine::try_parse() {
|
||||
Ok(cmd_line) => {
|
||||
match run_main(cmd_line) {
|
||||
Ok(_) => (),
|
||||
Err(error) => exit_with_error(error)
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
println!("{}", error)
|
||||
}
|
||||
}
|
||||
use clap::Parser;
|
||||
use mkoverlay::types::OverlayDesc;
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::{Error, exit_with_error, format_if_error, open_input, open_output};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(about = "Creates a linker script and makefile part for the JabyEngine toolchain", long_about = None)]
|
||||
struct CommandLine {
|
||||
#[clap(long="mk-file", help="Output path for the makefile")]
|
||||
mk_file_output: Option<PathBuf>,
|
||||
|
||||
#[clap(long="ld-script", help="Output path for the linker script file")]
|
||||
ld_file_output: Option<PathBuf>,
|
||||
|
||||
#[clap(value_parser, help="Input JSON for creating the files")]
|
||||
input: Option<PathBuf>
|
||||
}
|
||||
|
||||
fn parse_input(input: Option<PathBuf>) -> Result<OverlayDesc, Error> {
|
||||
if let Some(input) = input {
|
||||
let input = format_if_error!(open_input(&Some(input)), "Opening input file failed with: {error_text}")?;
|
||||
format_if_error!(mkoverlay::types::json_reader::read_config(input), "Parsing JSON file failed with: {error_text}")
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(OverlayDesc::new())
|
||||
}
|
||||
}
|
||||
|
||||
fn run_main(cmd_line: CommandLine) -> Result<(), Error> {
|
||||
let input = parse_input(cmd_line.input)?;
|
||||
let mut mk_output = format_if_error!(open_output(&cmd_line.mk_file_output), "Opening file for writing makefile failed with: {error_text}")?;
|
||||
let mut ld_output = format_if_error!(open_output(&cmd_line.ld_file_output), "Opening file for writing linkerfile failed with: {error_text}")?;
|
||||
|
||||
format_if_error!(mkoverlay::creator::makefile::write(&mut mk_output, &input), "Writing makefile failed with: {error_text}")?;
|
||||
format_if_error!(mkoverlay::creator::ldscript::write(&mut ld_output, &input), "Writing ld script failed with: {error_text}")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match CommandLine::try_parse() {
|
||||
Ok(cmd_line) => {
|
||||
match run_main(cmd_line) {
|
||||
Ok(_) => (),
|
||||
Err(error) => exit_with_error(error)
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
println!("{}", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxcdgen_ex
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxcdgen_ex
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
||||
@@ -1,88 +1,88 @@
|
||||
use super::types::LicenseFile;
|
||||
use super::Error;
|
||||
use std::path::PathBuf;
|
||||
mod xml;
|
||||
|
||||
pub enum LZ4State {
|
||||
None,
|
||||
AlreadyCompressed,
|
||||
Compress,
|
||||
}
|
||||
|
||||
pub struct CDAudioFile {
|
||||
pub file_path: PathBuf,
|
||||
pub align: bool,
|
||||
}
|
||||
|
||||
impl CDAudioFile {
|
||||
pub fn new(file_path: PathBuf, align: bool) -> CDAudioFile {
|
||||
CDAudioFile{file_path, align}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Configuration {
|
||||
pub publisher: Option<String>,
|
||||
pub license_file: LicenseFile,
|
||||
pub backup_license_file: LicenseFile,
|
||||
pub root: Directory,
|
||||
pub cd_audio_files: Vec<CDAudioFile>,
|
||||
pub lead_out_sectors: Option<usize>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
pub fn new() -> Configuration {
|
||||
Configuration{publisher: None, license_file: LicenseFile::None, backup_license_file: LicenseFile::None, root: Directory::new("root", false), cd_audio_files: Vec::new(), lead_out_sectors: None}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommonProperties {
|
||||
pub name: String,
|
||||
pub is_hidden: bool,
|
||||
pub lz4_state: LZ4State,
|
||||
pub padded_size: Option<usize>,
|
||||
}
|
||||
|
||||
pub enum FileKind {
|
||||
Regular,
|
||||
Main(PathBuf),
|
||||
Overlay(PathBuf),
|
||||
XAAudio(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
pub struct File {
|
||||
pub common: CommonProperties,
|
||||
pub path: PathBuf,
|
||||
pub kind: FileKind
|
||||
}
|
||||
pub struct Directory {
|
||||
pub name: String,
|
||||
pub is_hidden: bool,
|
||||
member: Vec<DirMember>,
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
pub fn new(name: &str, is_hidden: bool) -> Directory {
|
||||
Directory{name: String::from(name), is_hidden, member: Vec::new()}
|
||||
}
|
||||
|
||||
pub fn add_file(&mut self, file: File) {
|
||||
self.member.push(DirMember::File(file));
|
||||
}
|
||||
|
||||
pub fn add_dir(&mut self, dir: Directory) {
|
||||
self.member.push(DirMember::Directory(dir));
|
||||
}
|
||||
|
||||
pub fn into_iter(self) -> std::vec::IntoIter<DirMember> {
|
||||
self.member.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DirMember {
|
||||
File(File),
|
||||
Directory(Directory),
|
||||
}
|
||||
|
||||
pub fn parse_xml(xml: String) -> Result<Configuration, Error> {
|
||||
Ok(xml::parse(xml)?)
|
||||
use super::types::LicenseFile;
|
||||
use super::Error;
|
||||
use std::path::PathBuf;
|
||||
mod xml;
|
||||
|
||||
pub enum LZ4State {
|
||||
None,
|
||||
AlreadyCompressed,
|
||||
Compress,
|
||||
}
|
||||
|
||||
pub struct CDAudioFile {
|
||||
pub file_path: PathBuf,
|
||||
pub align: bool,
|
||||
}
|
||||
|
||||
impl CDAudioFile {
|
||||
pub fn new(file_path: PathBuf, align: bool) -> CDAudioFile {
|
||||
CDAudioFile{file_path, align}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Configuration {
|
||||
pub publisher: Option<String>,
|
||||
pub license_file: LicenseFile,
|
||||
pub backup_license_file: LicenseFile,
|
||||
pub root: Directory,
|
||||
pub cd_audio_files: Vec<CDAudioFile>,
|
||||
pub lead_out_sectors: Option<usize>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
pub fn new() -> Configuration {
|
||||
Configuration{publisher: None, license_file: LicenseFile::None, backup_license_file: LicenseFile::None, root: Directory::new("root", false), cd_audio_files: Vec::new(), lead_out_sectors: None}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommonProperties {
|
||||
pub name: String,
|
||||
pub is_hidden: bool,
|
||||
pub lz4_state: LZ4State,
|
||||
pub padded_size: Option<usize>,
|
||||
}
|
||||
|
||||
pub enum FileKind {
|
||||
Regular,
|
||||
Main(PathBuf),
|
||||
Overlay(PathBuf),
|
||||
XAAudio(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
pub struct File {
|
||||
pub common: CommonProperties,
|
||||
pub path: PathBuf,
|
||||
pub kind: FileKind
|
||||
}
|
||||
pub struct Directory {
|
||||
pub name: String,
|
||||
pub is_hidden: bool,
|
||||
member: Vec<DirMember>,
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
pub fn new(name: &str, is_hidden: bool) -> Directory {
|
||||
Directory{name: String::from(name), is_hidden, member: Vec::new()}
|
||||
}
|
||||
|
||||
pub fn add_file(&mut self, file: File) {
|
||||
self.member.push(DirMember::File(file));
|
||||
}
|
||||
|
||||
pub fn add_dir(&mut self, dir: Directory) {
|
||||
self.member.push(DirMember::Directory(dir));
|
||||
}
|
||||
|
||||
pub fn into_iter(self) -> std::vec::IntoIter<DirMember> {
|
||||
self.member.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DirMember {
|
||||
File(File),
|
||||
Directory(Directory),
|
||||
}
|
||||
|
||||
pub fn parse_xml(xml: String) -> Result<Configuration, Error> {
|
||||
Ok(xml::parse(xml)?)
|
||||
}
|
||||
@@ -1,315 +1,315 @@
|
||||
use cdtypes::types::time::Time;
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::{format_if_error, path_with_env_from, print_warning, string_with_env_from};
|
||||
use crate::{config_reader::Directory, types::LicenseFile};
|
||||
|
||||
use super::{CDAudioFile, CommonProperties, Configuration, Error, File, FileKind, LZ4State};
|
||||
|
||||
mod license_tag {
|
||||
pub const NAME: &'static str = "License";
|
||||
pub mod attribute {
|
||||
pub const ALT_LOGO: &'static str = "alt-logo";
|
||||
pub const ALT_TEXT: &'static str = "alt-text";
|
||||
}
|
||||
}
|
||||
|
||||
mod root_tag {
|
||||
pub const NAME: &'static str = "PSXCD";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod filesystem_tag {
|
||||
pub const NAME: &'static str = "Filesystem";
|
||||
pub mod attribute {
|
||||
pub const LEAD_OUT:&'static str = "lead-out";
|
||||
}
|
||||
}
|
||||
|
||||
mod file_and_directory_attribute {
|
||||
pub const NAME: &'static str = "name";
|
||||
pub const HIDDEN: &'static str = "hidden";
|
||||
pub const PADDED_SIZE: &'static str = "padded_size";
|
||||
pub const LBA_SOURCE: &'static str = "lba_source";
|
||||
pub const LZ4_STATE: &'static str = "lz4";
|
||||
}
|
||||
|
||||
mod interleaved_file_tag {
|
||||
pub const NAME: &'static str = "InterleavedFile";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod cdda_tag {
|
||||
pub const NAME: &'static str = "AudioTrack";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod alt_license_tag {
|
||||
pub const NAME: &'static str = "AltLicense";
|
||||
pub mod attribute {
|
||||
pub const TEXT: &'static str = "text";
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(xml: String) -> Result<Configuration, Error> {
|
||||
let mut config = Configuration::new();
|
||||
let parser = format_if_error!(roxmltree::Document::parse(xml.as_str()), "Parsing XML file failed with: {error_text}")?;
|
||||
let children = parser.root().children();
|
||||
|
||||
for node in children {
|
||||
if node.is_element() && node.tag_name().name() == root_tag::NAME {
|
||||
parse_root(node, &mut config)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn parse_root(root: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
let mut parsed_track = false;
|
||||
|
||||
for node in root.children() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"Description" => parse_description(node, config),
|
||||
filesystem_tag::NAME => {
|
||||
if !parsed_track {
|
||||
parsed_track = true;
|
||||
parse_track(node, config)
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("Found more than 1 filesystem. Multi filesystem discs are not supported")))
|
||||
}
|
||||
},
|
||||
cdda_tag::NAME => parse_cd_audio(node, config),
|
||||
_ => Ok(())
|
||||
}?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_description(description: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
fn parse_license_path(license: roxmltree::Node) -> (LicenseFile, LicenseFile) {
|
||||
let required_lic_file = LicenseFile::from_authentic_option(path_from_node(&license, license_tag::NAME).ok());
|
||||
let alt_logo = {
|
||||
if let Some(path) = license.attribute(license_tag::attribute::ALT_LOGO) {
|
||||
Some(path_with_env_from(path))
|
||||
}
|
||||
|
||||
else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let alt_text = license.attribute(license_tag::attribute::ALT_TEXT);
|
||||
(required_lic_file, LicenseFile::from_tmd_option(alt_logo, alt_text))
|
||||
}
|
||||
|
||||
fn parse_altlicense_path(license: roxmltree::Node) -> LicenseFile {
|
||||
LicenseFile::from_tmd_option(path_from_node(&license, alt_license_tag::NAME).ok(), license.attribute(alt_license_tag::attribute::TEXT))
|
||||
}
|
||||
|
||||
for node in description.descendants() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"Publisher" => config.publisher = Some(String::from(node.text().unwrap_or_default())),
|
||||
license_tag::NAME => (config.license_file, config.backup_license_file) = parse_license_path(node),
|
||||
alt_license_tag::NAME => {
|
||||
if matches!(config.license_file, LicenseFile::None) {
|
||||
config.license_file = parse_altlicense_path(node)
|
||||
}
|
||||
|
||||
else {
|
||||
print_warning("Alternate license ignored because a (alternate) license was already provided".to_owned())
|
||||
}
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_track(track: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
fn parse_regular_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
let common = read_common_properties(&file, is_hidden, None)?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
|
||||
Ok(File{common, path, kind: FileKind::Regular})
|
||||
}
|
||||
|
||||
fn parse_main_file(file: roxmltree::Node) -> Result<File, Error> {
|
||||
if let Some(lba_path) = file.attribute(file_and_directory_attribute::LBA_SOURCE) {
|
||||
let common = read_common_properties(&file, false, Some(LZ4State::None))?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
|
||||
Ok(File{common, path, kind: FileKind::Main(PathBuf::from(lba_path))})
|
||||
}
|
||||
|
||||
else {
|
||||
tool_helper::print_warning(format!("The main file should always contain the \"{}\" attribute, even when just empty", file_and_directory_attribute::LBA_SOURCE));
|
||||
parse_regular_file(file, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_overlay_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
// v They will be compressed automatically
|
||||
let common = read_common_properties(&file, is_hidden, Some(LZ4State::AlreadyCompressed))?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
let lba_source = {
|
||||
if let Some(lba_source) = file.attribute(file_and_directory_attribute::LBA_SOURCE) {
|
||||
lba_source
|
||||
}
|
||||
|
||||
else {
|
||||
tool_helper::print_warning(format!("Overlays should always contain the \"{}\" attribute, even when just empty", file_and_directory_attribute::LBA_SOURCE));
|
||||
""
|
||||
}
|
||||
};
|
||||
Ok(File{common, path, kind: FileKind::Overlay(PathBuf::from(lba_source))})
|
||||
}
|
||||
|
||||
fn parse_interleaved(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
// v Never compress interleaved files
|
||||
let common = read_common_properties(&file, is_hidden, Some(LZ4State::None))?;
|
||||
let channel = {
|
||||
let mut channel = Vec::new();
|
||||
for node in file.children() {
|
||||
if node.is_element() && node.tag_name().name() == "Channel" {
|
||||
channel.push(path_from_node(&node, "Channel")?);
|
||||
}
|
||||
}
|
||||
|
||||
channel
|
||||
};
|
||||
|
||||
Ok(File{common, path: PathBuf::new(), kind: FileKind::XAAudio(channel)})
|
||||
}
|
||||
|
||||
fn parse_file_system(cur_node: roxmltree::Node, root: &mut Directory, mut is_hidden: bool) -> Result<(), Error> {
|
||||
for node in cur_node.children() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"File" => root.add_file(parse_regular_file(node, is_hidden)?),
|
||||
"Main" => root.add_file(parse_main_file(node)?),
|
||||
"Overlay" => root.add_file(parse_overlay_file(node, is_hidden)?),
|
||||
interleaved_file_tag::NAME => root.add_file(parse_interleaved(node, is_hidden)?),
|
||||
"Directory" => {
|
||||
is_hidden |= parse_boolean_attribute(&node, file_and_directory_attribute::HIDDEN)?;
|
||||
let mut new_dir = Directory::new(node.attribute(file_and_directory_attribute::NAME).unwrap_or_default(), is_hidden);
|
||||
|
||||
parse_file_system(node, &mut new_dir, is_hidden)?;
|
||||
root.add_dir(new_dir);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_lead_out_sectors(track: roxmltree::Node) -> Result<Option<usize>, Error> {
|
||||
const MIN_OPTION_NAME:&'static str = "minutes";
|
||||
const SECOND_OPTION_NAME:&'static str = "seconds";
|
||||
const SECTOR_OPTION_NAME:&'static str = "sectors";
|
||||
|
||||
fn parse_split<'a>(who: &'a str, str_opt: Option<&'a str>) -> Result<u8, Error> {
|
||||
match str_opt.unwrap_or("0").parse::<u8>() {
|
||||
Ok(num) => Ok(num),
|
||||
Err(error) => Err(Error::from_text(format!("Failed converting \"{}\" option for \"{}\" attribute \"{}\": {}", who, filesystem_tag::NAME, filesystem_tag::attribute::LEAD_OUT, error)))
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
if let Some(length_str) = track.attribute(filesystem_tag::attribute::LEAD_OUT) {
|
||||
let mut pieces = length_str.split(':');
|
||||
let (mins, seconds, sectors) = (
|
||||
parse_split(MIN_OPTION_NAME, pieces.next())?,
|
||||
parse_split(SECOND_OPTION_NAME, pieces.next())?,
|
||||
parse_split(SECTOR_OPTION_NAME, pieces.next())?
|
||||
);
|
||||
|
||||
Ok(Some(Time::from_mss(mins, seconds, sectors).as_sectors()))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
config.lead_out_sectors = get_lead_out_sectors(track)?;
|
||||
parse_file_system(track, &mut config.root, false)
|
||||
}
|
||||
|
||||
fn parse_cd_audio(cdda: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
config.cd_audio_files.push(CDAudioFile::new(path_from_node(&cdda, "CD Audio file")?, parse_boolean_attribute(&cdda, "align")?));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_common_properties(xml: &roxmltree::Node, is_hidden: bool, force_lz4_state: Option<LZ4State>) -> Result<CommonProperties, Error> {
|
||||
let lz4_state = {
|
||||
if let Some(forced_lz4_state) = force_lz4_state {
|
||||
forced_lz4_state
|
||||
}
|
||||
|
||||
else {
|
||||
if let Some(state_str) = xml.attribute(file_and_directory_attribute::LZ4_STATE) {
|
||||
match state_str.to_lowercase().as_str() {
|
||||
"yes" => LZ4State::Compress,
|
||||
"already" => LZ4State::AlreadyCompressed,
|
||||
_ => LZ4State::None,
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
LZ4State::None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(CommonProperties{
|
||||
name: string_with_env_from(xml.attribute(file_and_directory_attribute::NAME).unwrap_or_default()),
|
||||
is_hidden: is_hidden | parse_boolean_attribute(&xml, file_and_directory_attribute::HIDDEN)?,
|
||||
lz4_state,
|
||||
padded_size: read_padded_size(&xml)?
|
||||
})
|
||||
}
|
||||
|
||||
fn read_padded_size(xml: &roxmltree::Node) -> Result<Option<usize>, Error> {
|
||||
if let Some(padded_attr) = xml.attribute(file_and_directory_attribute::PADDED_SIZE) {
|
||||
let padded_size = format_if_error!(padded_attr.parse::<usize>(), "Failed reading \"{}\" as padded size: {error_text}", padded_attr)?;
|
||||
Ok(Some(padded_size))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_boolean_attribute(xml: &roxmltree::Node, attribute_name: &str) -> Result<bool, Error> {
|
||||
if let Some(bool_str) = xml.attribute(attribute_name) {
|
||||
match bool_str {
|
||||
"true" | "1" => Ok(true),
|
||||
"false" | "0" => Ok(false),
|
||||
_ => {
|
||||
return Err(Error::from_text(format!("Attribute \"{}\" expects either true,false or 1,0 as valid attributes - found {}", attribute_name, bool_str)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn path_from_node(node: &roxmltree::Node, name: &str) -> Result<PathBuf, Error> {
|
||||
if let Some(path) = node.text() {
|
||||
Ok(path_with_env_from(path.trim()))
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("No path specified for {}", name)))
|
||||
}
|
||||
use cdtypes::types::time::Time;
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::{format_if_error, path_with_env_from, print_warning, string_with_env_from};
|
||||
use crate::{config_reader::Directory, types::LicenseFile};
|
||||
|
||||
use super::{CDAudioFile, CommonProperties, Configuration, Error, File, FileKind, LZ4State};
|
||||
|
||||
mod license_tag {
|
||||
pub const NAME: &'static str = "License";
|
||||
pub mod attribute {
|
||||
pub const ALT_LOGO: &'static str = "alt-logo";
|
||||
pub const ALT_TEXT: &'static str = "alt-text";
|
||||
}
|
||||
}
|
||||
|
||||
mod root_tag {
|
||||
pub const NAME: &'static str = "PSXCD";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod filesystem_tag {
|
||||
pub const NAME: &'static str = "Filesystem";
|
||||
pub mod attribute {
|
||||
pub const LEAD_OUT:&'static str = "lead-out";
|
||||
}
|
||||
}
|
||||
|
||||
mod file_and_directory_attribute {
|
||||
pub const NAME: &'static str = "name";
|
||||
pub const HIDDEN: &'static str = "hidden";
|
||||
pub const PADDED_SIZE: &'static str = "padded_size";
|
||||
pub const LBA_SOURCE: &'static str = "lba_source";
|
||||
pub const LZ4_STATE: &'static str = "lz4";
|
||||
}
|
||||
|
||||
mod interleaved_file_tag {
|
||||
pub const NAME: &'static str = "InterleavedFile";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod cdda_tag {
|
||||
pub const NAME: &'static str = "AudioTrack";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod alt_license_tag {
|
||||
pub const NAME: &'static str = "AltLicense";
|
||||
pub mod attribute {
|
||||
pub const TEXT: &'static str = "text";
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(xml: String) -> Result<Configuration, Error> {
|
||||
let mut config = Configuration::new();
|
||||
let parser = format_if_error!(roxmltree::Document::parse(xml.as_str()), "Parsing XML file failed with: {error_text}")?;
|
||||
let children = parser.root().children();
|
||||
|
||||
for node in children {
|
||||
if node.is_element() && node.tag_name().name() == root_tag::NAME {
|
||||
parse_root(node, &mut config)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn parse_root(root: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
let mut parsed_track = false;
|
||||
|
||||
for node in root.children() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"Description" => parse_description(node, config),
|
||||
filesystem_tag::NAME => {
|
||||
if !parsed_track {
|
||||
parsed_track = true;
|
||||
parse_track(node, config)
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("Found more than 1 filesystem. Multi filesystem discs are not supported")))
|
||||
}
|
||||
},
|
||||
cdda_tag::NAME => parse_cd_audio(node, config),
|
||||
_ => Ok(())
|
||||
}?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_description(description: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
fn parse_license_path(license: roxmltree::Node) -> (LicenseFile, LicenseFile) {
|
||||
let required_lic_file = LicenseFile::from_authentic_option(path_from_node(&license, license_tag::NAME).ok());
|
||||
let alt_logo = {
|
||||
if let Some(path) = license.attribute(license_tag::attribute::ALT_LOGO) {
|
||||
Some(path_with_env_from(path))
|
||||
}
|
||||
|
||||
else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let alt_text = license.attribute(license_tag::attribute::ALT_TEXT);
|
||||
(required_lic_file, LicenseFile::from_tmd_option(alt_logo, alt_text))
|
||||
}
|
||||
|
||||
fn parse_altlicense_path(license: roxmltree::Node) -> LicenseFile {
|
||||
LicenseFile::from_tmd_option(path_from_node(&license, alt_license_tag::NAME).ok(), license.attribute(alt_license_tag::attribute::TEXT))
|
||||
}
|
||||
|
||||
for node in description.descendants() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"Publisher" => config.publisher = Some(String::from(node.text().unwrap_or_default())),
|
||||
license_tag::NAME => (config.license_file, config.backup_license_file) = parse_license_path(node),
|
||||
alt_license_tag::NAME => {
|
||||
if matches!(config.license_file, LicenseFile::None) {
|
||||
config.license_file = parse_altlicense_path(node)
|
||||
}
|
||||
|
||||
else {
|
||||
print_warning("Alternate license ignored because a (alternate) license was already provided".to_owned())
|
||||
}
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_track(track: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
fn parse_regular_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
let common = read_common_properties(&file, is_hidden, None)?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
|
||||
Ok(File{common, path, kind: FileKind::Regular})
|
||||
}
|
||||
|
||||
fn parse_main_file(file: roxmltree::Node) -> Result<File, Error> {
|
||||
if let Some(lba_path) = file.attribute(file_and_directory_attribute::LBA_SOURCE) {
|
||||
let common = read_common_properties(&file, false, Some(LZ4State::None))?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
|
||||
Ok(File{common, path, kind: FileKind::Main(PathBuf::from(lba_path))})
|
||||
}
|
||||
|
||||
else {
|
||||
tool_helper::print_warning(format!("The main file should always contain the \"{}\" attribute, even when just empty", file_and_directory_attribute::LBA_SOURCE));
|
||||
parse_regular_file(file, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_overlay_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
// v They will be compressed automatically
|
||||
let common = read_common_properties(&file, is_hidden, Some(LZ4State::AlreadyCompressed))?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
let lba_source = {
|
||||
if let Some(lba_source) = file.attribute(file_and_directory_attribute::LBA_SOURCE) {
|
||||
lba_source
|
||||
}
|
||||
|
||||
else {
|
||||
tool_helper::print_warning(format!("Overlays should always contain the \"{}\" attribute, even when just empty", file_and_directory_attribute::LBA_SOURCE));
|
||||
""
|
||||
}
|
||||
};
|
||||
Ok(File{common, path, kind: FileKind::Overlay(PathBuf::from(lba_source))})
|
||||
}
|
||||
|
||||
fn parse_interleaved(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
// v Never compress interleaved files
|
||||
let common = read_common_properties(&file, is_hidden, Some(LZ4State::None))?;
|
||||
let channel = {
|
||||
let mut channel = Vec::new();
|
||||
for node in file.children() {
|
||||
if node.is_element() && node.tag_name().name() == "Channel" {
|
||||
channel.push(path_from_node(&node, "Channel")?);
|
||||
}
|
||||
}
|
||||
|
||||
channel
|
||||
};
|
||||
|
||||
Ok(File{common, path: PathBuf::new(), kind: FileKind::XAAudio(channel)})
|
||||
}
|
||||
|
||||
fn parse_file_system(cur_node: roxmltree::Node, root: &mut Directory, mut is_hidden: bool) -> Result<(), Error> {
|
||||
for node in cur_node.children() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"File" => root.add_file(parse_regular_file(node, is_hidden)?),
|
||||
"Main" => root.add_file(parse_main_file(node)?),
|
||||
"Overlay" => root.add_file(parse_overlay_file(node, is_hidden)?),
|
||||
interleaved_file_tag::NAME => root.add_file(parse_interleaved(node, is_hidden)?),
|
||||
"Directory" => {
|
||||
is_hidden |= parse_boolean_attribute(&node, file_and_directory_attribute::HIDDEN)?;
|
||||
let mut new_dir = Directory::new(node.attribute(file_and_directory_attribute::NAME).unwrap_or_default(), is_hidden);
|
||||
|
||||
parse_file_system(node, &mut new_dir, is_hidden)?;
|
||||
root.add_dir(new_dir);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_lead_out_sectors(track: roxmltree::Node) -> Result<Option<usize>, Error> {
|
||||
const MIN_OPTION_NAME:&'static str = "minutes";
|
||||
const SECOND_OPTION_NAME:&'static str = "seconds";
|
||||
const SECTOR_OPTION_NAME:&'static str = "sectors";
|
||||
|
||||
fn parse_split<'a>(who: &'a str, str_opt: Option<&'a str>) -> Result<u8, Error> {
|
||||
match str_opt.unwrap_or("0").parse::<u8>() {
|
||||
Ok(num) => Ok(num),
|
||||
Err(error) => Err(Error::from_text(format!("Failed converting \"{}\" option for \"{}\" attribute \"{}\": {}", who, filesystem_tag::NAME, filesystem_tag::attribute::LEAD_OUT, error)))
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
if let Some(length_str) = track.attribute(filesystem_tag::attribute::LEAD_OUT) {
|
||||
let mut pieces = length_str.split(':');
|
||||
let (mins, seconds, sectors) = (
|
||||
parse_split(MIN_OPTION_NAME, pieces.next())?,
|
||||
parse_split(SECOND_OPTION_NAME, pieces.next())?,
|
||||
parse_split(SECTOR_OPTION_NAME, pieces.next())?
|
||||
);
|
||||
|
||||
Ok(Some(Time::from_mss(mins, seconds, sectors).as_sectors()))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
config.lead_out_sectors = get_lead_out_sectors(track)?;
|
||||
parse_file_system(track, &mut config.root, false)
|
||||
}
|
||||
|
||||
fn parse_cd_audio(cdda: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
config.cd_audio_files.push(CDAudioFile::new(path_from_node(&cdda, "CD Audio file")?, parse_boolean_attribute(&cdda, "align")?));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_common_properties(xml: &roxmltree::Node, is_hidden: bool, force_lz4_state: Option<LZ4State>) -> Result<CommonProperties, Error> {
|
||||
let lz4_state = {
|
||||
if let Some(forced_lz4_state) = force_lz4_state {
|
||||
forced_lz4_state
|
||||
}
|
||||
|
||||
else {
|
||||
if let Some(state_str) = xml.attribute(file_and_directory_attribute::LZ4_STATE) {
|
||||
match state_str.to_lowercase().as_str() {
|
||||
"yes" => LZ4State::Compress,
|
||||
"already" => LZ4State::AlreadyCompressed,
|
||||
_ => LZ4State::None,
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
LZ4State::None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(CommonProperties{
|
||||
name: string_with_env_from(xml.attribute(file_and_directory_attribute::NAME).unwrap_or_default()),
|
||||
is_hidden: is_hidden | parse_boolean_attribute(&xml, file_and_directory_attribute::HIDDEN)?,
|
||||
lz4_state,
|
||||
padded_size: read_padded_size(&xml)?
|
||||
})
|
||||
}
|
||||
|
||||
fn read_padded_size(xml: &roxmltree::Node) -> Result<Option<usize>, Error> {
|
||||
if let Some(padded_attr) = xml.attribute(file_and_directory_attribute::PADDED_SIZE) {
|
||||
let padded_size = format_if_error!(padded_attr.parse::<usize>(), "Failed reading \"{}\" as padded size: {error_text}", padded_attr)?;
|
||||
Ok(Some(padded_size))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_boolean_attribute(xml: &roxmltree::Node, attribute_name: &str) -> Result<bool, Error> {
|
||||
if let Some(bool_str) = xml.attribute(attribute_name) {
|
||||
match bool_str {
|
||||
"true" | "1" => Ok(true),
|
||||
"false" | "0" => Ok(false),
|
||||
_ => {
|
||||
return Err(Error::from_text(format!("Attribute \"{}\" expects either true,false or 1,0 as valid attributes - found {}", attribute_name, bool_str)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn path_from_node(node: &roxmltree::Node, name: &str) -> Result<PathBuf, Error> {
|
||||
if let Some(path) = node.text() {
|
||||
Ok(path_with_env_from(path.trim()))
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("No path specified for {}", name)))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,62 +1,62 @@
|
||||
use clap::Parser;
|
||||
use psxcdgen_ex::{file_writer::{ImageType, write_image}, config_reader};
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::{Error, exit_with_error, read_file_to_string};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(about = "Creates an ISO image from a description file", long_about = None)]
|
||||
struct CommandLine {
|
||||
#[clap(value_enum, value_parser, help="Specifies the disc image type")]
|
||||
output_type: ImageType,
|
||||
|
||||
#[clap(short='o', help="Output path; Some OUTPUT_TYPE might create additional files with different endings")]
|
||||
output_file: PathBuf,
|
||||
|
||||
#[clap(long="list", id="Path to file", help="If set will list the CD content to stdout; With path will output list to file")]
|
||||
list_content: Option<Option<PathBuf>>,
|
||||
|
||||
#[clap(value_parser, help="Input XML path for creating the image")]
|
||||
input_file: PathBuf,
|
||||
}
|
||||
|
||||
fn run_main(cmd_line: CommandLine) -> Result<(), Error> {
|
||||
const PKG_MINIMUM_CONTENT_SIZE:usize = 512*1024;
|
||||
|
||||
let (mut desc, lba_embedded_files) = psxcdgen_ex::process(config_reader::parse_xml(read_file_to_string(&cmd_line.input_file)?)?)?;
|
||||
let file_map = desc.create_file_map();
|
||||
|
||||
psxcdgen_ex::process_files(file_map, lba_embedded_files)?;
|
||||
let content_size = desc.get_content_size();
|
||||
if content_size < PKG_MINIMUM_CONTENT_SIZE {
|
||||
let missing_size = PKG_MINIMUM_CONTENT_SIZE - content_size;
|
||||
desc.end_dummy_padding = missing_size;
|
||||
tool_helper::print_warning(format!("Content size {}b smaller then {}b.\nCD will be padded with {}b to work as a .pkg", content_size, PKG_MINIMUM_CONTENT_SIZE, missing_size));
|
||||
}
|
||||
|
||||
if desc.lead_out_sectors == 0 {
|
||||
tool_helper::print_warning(format!("Consider adding a 2 second lead-out to your track (Add attribute 'lead-out=\"0:2:0\"' to your Track) for supporting the PS3"));
|
||||
}
|
||||
|
||||
write_image(&desc, cmd_line.output_type, cmd_line.output_file)?;
|
||||
|
||||
if let Some(list_content_option) = cmd_line.list_content {
|
||||
psxcdgen_ex::dump_content(&desc, tool_helper::open_output(&list_content_option)?)
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match CommandLine::try_parse() {
|
||||
Ok(cmd_line) => {
|
||||
if let Err(error) = run_main(cmd_line) {
|
||||
exit_with_error(error)
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
println!("{}", error)
|
||||
}
|
||||
}
|
||||
use clap::Parser;
|
||||
use psxcdgen_ex::{file_writer::{ImageType, write_image}, config_reader};
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::{Error, exit_with_error, read_file_to_string};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(about = "Creates an ISO image from a description file", long_about = None)]
|
||||
struct CommandLine {
|
||||
#[clap(value_enum, value_parser, help="Specifies the disc image type")]
|
||||
output_type: ImageType,
|
||||
|
||||
#[clap(short='o', help="Output path; Some OUTPUT_TYPE might create additional files with different endings")]
|
||||
output_file: PathBuf,
|
||||
|
||||
#[clap(long="list", id="Path to file", help="If set will list the CD content to stdout; With path will output list to file")]
|
||||
list_content: Option<Option<PathBuf>>,
|
||||
|
||||
#[clap(value_parser, help="Input XML path for creating the image")]
|
||||
input_file: PathBuf,
|
||||
}
|
||||
|
||||
fn run_main(cmd_line: CommandLine) -> Result<(), Error> {
|
||||
const PKG_MINIMUM_CONTENT_SIZE:usize = 512*1024;
|
||||
|
||||
let (mut desc, lba_embedded_files) = psxcdgen_ex::process(config_reader::parse_xml(read_file_to_string(&cmd_line.input_file)?)?)?;
|
||||
let file_map = desc.create_file_map();
|
||||
|
||||
psxcdgen_ex::process_files(file_map, lba_embedded_files)?;
|
||||
let content_size = desc.get_content_size();
|
||||
if content_size < PKG_MINIMUM_CONTENT_SIZE {
|
||||
let missing_size = PKG_MINIMUM_CONTENT_SIZE - content_size;
|
||||
desc.end_dummy_padding = missing_size;
|
||||
tool_helper::print_warning(format!("Content size {}b smaller then {}b.\nCD will be padded with {}b to work as a .pkg", content_size, PKG_MINIMUM_CONTENT_SIZE, missing_size));
|
||||
}
|
||||
|
||||
if desc.lead_out_sectors == 0 {
|
||||
tool_helper::print_warning(format!("Consider adding a 2 second lead-out to your track (Add attribute 'lead-out=\"0:2:0\"' to your Track) for supporting the PS3"));
|
||||
}
|
||||
|
||||
write_image(&desc, cmd_line.output_type, cmd_line.output_file)?;
|
||||
|
||||
if let Some(list_content_option) = cmd_line.list_content {
|
||||
psxcdgen_ex::dump_content(&desc, tool_helper::open_output(&list_content_option)?)
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match CommandLine::try_parse() {
|
||||
Ok(cmd_line) => {
|
||||
if let Err(error) = run_main(cmd_line) {
|
||||
exit_with_error(error)
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
println!("{}", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::types::RawData;
|
||||
|
||||
pub fn skip_to_lba_area(content: &mut RawData) -> &mut [u8] {
|
||||
const PSX_HEADER_SIZE:usize = 2048;
|
||||
|
||||
&mut content[PSX_HEADER_SIZE..]
|
||||
use crate::types::RawData;
|
||||
|
||||
pub fn skip_to_lba_area(content: &mut RawData) -> &mut [u8] {
|
||||
const PSX_HEADER_SIZE:usize = 2048;
|
||||
|
||||
&mut content[PSX_HEADER_SIZE..]
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxcdread
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxcdread
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
||||
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxfileconv
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxfileconv
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod xa;
|
||||
pub mod xa;
|
||||
pub mod vag;
|
||||
@@ -1,169 +1,169 @@
|
||||
pub mod types;
|
||||
|
||||
use clap::Args;
|
||||
use std::{io::Write, str::FromStr};
|
||||
use tool_helper::{Error, Input};
|
||||
use types::{LPC, MonoADPCMIterator, VAGADPCM, VAGHeader};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Arguments {
|
||||
#[clap(long, help="Specify the file name to be embedded in the header", default_value = "File name without extensions up to 16 characters")]
|
||||
name: Option<String>,
|
||||
#[clap(short='l', help="Set a loop at the specified time", value_name = "<min>:<sec>.<msec>[-<min>:<sec>.<msec>]", value_parser = clap::value_parser!(Loop))]
|
||||
r#loop: Option<Loop>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Loop {
|
||||
start_time_sec: f64,
|
||||
end_time_sec: Option<f64>,
|
||||
}
|
||||
|
||||
impl Loop {
|
||||
const END_DELIMITER: char = '-';
|
||||
const MIN_DELIMITER: char = ':';
|
||||
|
||||
fn parse(str: Option<&str>) -> Result<Option<f64>, String> {
|
||||
fn print_sec_conversion_error(str: &str, error: &dyn core::fmt::Display) -> String {
|
||||
format!("Converting specified seconds \"{}\" failed with: {}", str, error)
|
||||
}
|
||||
|
||||
if let Some(str) = str {
|
||||
let time = {
|
||||
if let Some(min_delim_idx) = str.find(Self::MIN_DELIMITER) {
|
||||
let (min, sec) = str.split_at(min_delim_idx);
|
||||
let min = min.parse::<u64>().map_err(|e| format!("Converting specified minutes \"{}\" failed with: {}", min, e))?;
|
||||
let sec = sec.trim_start_matches(Self::MIN_DELIMITER).parse::<f64>().map_err(|e| print_sec_conversion_error(sec, &e))?;
|
||||
|
||||
(min*60) as f64 + sec
|
||||
}
|
||||
|
||||
else {
|
||||
str.parse::<f64>().map_err(|e| print_sec_conversion_error(str, &e))?
|
||||
}
|
||||
};
|
||||
Ok(Some(time))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Loop {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(arg: &str) -> Result<Self, Self::Err> {
|
||||
let mut args = arg.split(Self::END_DELIMITER);
|
||||
let start_time_sec = Self::parse(args.next()).map_err(|e| format!("{}", e))?.ok_or_else(|| format!("A start time is required for a loop"))?;
|
||||
let end_time_sec = Self::parse(args.next()).map_err(|e| format!("{}", e))?;
|
||||
|
||||
Ok(Loop{start_time_sec, end_time_sec})
|
||||
}
|
||||
}
|
||||
|
||||
struct LoopInfo {
|
||||
start_sample: usize,
|
||||
end_sample: usize,
|
||||
}
|
||||
|
||||
impl LoopInfo {
|
||||
fn create(r#loop: &Loop, sample_frequency: u32, vagadpcm_count: usize) -> LoopInfo {
|
||||
fn calculate_sample(time_sec: f64, vag_frequency: f64) -> usize {
|
||||
(time_sec*vag_frequency) as usize
|
||||
}
|
||||
|
||||
let sample_frequency = sample_frequency as f64 / VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM as f64;
|
||||
let start_sample = calculate_sample(r#loop.start_time_sec, sample_frequency);
|
||||
let end_sample = {
|
||||
if let Some(end_time_sec) = r#loop.end_time_sec {
|
||||
let end_sample = calculate_sample(end_time_sec, sample_frequency);
|
||||
if end_sample > vagadpcm_count {vagadpcm_count} else {end_sample}
|
||||
}
|
||||
|
||||
else {
|
||||
vagadpcm_count
|
||||
}
|
||||
};
|
||||
|
||||
LoopInfo{start_sample, end_sample}
|
||||
}
|
||||
|
||||
fn is_loop_start_reached(&self, sample: usize) -> bool {
|
||||
self.start_sample == sample
|
||||
}
|
||||
|
||||
fn is_loop_end_reached(&self, sample: usize) -> bool {
|
||||
self.end_sample == sample
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert(args: Arguments, output_file_path: &Option<std::path::PathBuf>, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let mut wav_file = hound::WavReader::new(input)?;
|
||||
let wav_header = wav_file.spec();
|
||||
|
||||
validate(&wav_header)?;
|
||||
|
||||
let vagadpcm_samples = VAGHeader::expected_vagadpcm_samples(wav_file.len()) + 1;
|
||||
let sample_info = args.r#loop.map(|v| LoopInfo::create(&v, wav_header.sample_rate, vagadpcm_samples as usize));
|
||||
let mut lpc = LPC::empty();
|
||||
|
||||
tool_helper::raw::write_raw(output, &VAGHeader::create(vagadpcm_samples, wav_header.sample_rate, &get_name_for_file_header(args.name, output_file_path))?)?;
|
||||
for (sample_id, adpcm_sample) in MonoADPCMIterator::create(wav_file.samples::<i16>()).enumerate() {
|
||||
let (mut vagadpcm, new_lpc) = VAGADPCM::create(adpcm_sample?, lpc);
|
||||
|
||||
if let Some(sample_info) = &sample_info {
|
||||
if sample_info.is_loop_start_reached(sample_id) {
|
||||
vagadpcm = vagadpcm.set_loop_start();
|
||||
}
|
||||
|
||||
if sample_info.is_loop_end_reached(sample_id + 1) {
|
||||
vagadpcm = vagadpcm.set_loop_end_repeat();
|
||||
}
|
||||
}
|
||||
|
||||
tool_helper::raw::write_raw(output, &vagadpcm)?;
|
||||
lpc = new_lpc;
|
||||
}
|
||||
|
||||
if sample_info.is_none() {
|
||||
tool_helper::raw::write_raw(output, &VAGADPCM::end())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate(wav_header: &hound::WavSpec) -> Result<(), Error> {
|
||||
if wav_header.sample_format != hound::SampleFormat::Int {
|
||||
return Err(Error::from_str("VAG: Only integer samples are supported as input."));
|
||||
}
|
||||
|
||||
if wav_header.bits_per_sample != 16 {
|
||||
return Err(Error::from_str("VAG: Only 16bits samples are currently supported as input."));
|
||||
}
|
||||
|
||||
if wav_header.channels != 1 {
|
||||
return Err(Error::from_str("VAG: Only mono samples are currently supported"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_name_for_file_header(name: Option<String>, output_file_path: &Option<std::path::PathBuf>) -> String {
|
||||
if let Some(name) = name {
|
||||
return name;
|
||||
}
|
||||
|
||||
if let Some(output_file_path) = output_file_path {
|
||||
if let Some(file_name) = output_file_path.file_name() {
|
||||
let mut string = file_name.to_string_lossy().to_string();
|
||||
|
||||
if let Some(idx) = string.rfind('.') {
|
||||
string.replace_range(idx.., "");
|
||||
}
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
return "<out>".to_owned();
|
||||
pub mod types;
|
||||
|
||||
use clap::Args;
|
||||
use std::{io::Write, str::FromStr};
|
||||
use tool_helper::{Error, Input};
|
||||
use types::{LPC, MonoADPCMIterator, VAGADPCM, VAGHeader};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Arguments {
|
||||
#[clap(long, help="Specify the file name to be embedded in the header", default_value = "File name without extensions up to 16 characters")]
|
||||
name: Option<String>,
|
||||
#[clap(short='l', help="Set a loop at the specified time", value_name = "<min>:<sec>.<msec>[-<min>:<sec>.<msec>]", value_parser = clap::value_parser!(Loop))]
|
||||
r#loop: Option<Loop>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Loop {
|
||||
start_time_sec: f64,
|
||||
end_time_sec: Option<f64>,
|
||||
}
|
||||
|
||||
impl Loop {
|
||||
const END_DELIMITER: char = '-';
|
||||
const MIN_DELIMITER: char = ':';
|
||||
|
||||
fn parse(str: Option<&str>) -> Result<Option<f64>, String> {
|
||||
fn print_sec_conversion_error(str: &str, error: &dyn core::fmt::Display) -> String {
|
||||
format!("Converting specified seconds \"{}\" failed with: {}", str, error)
|
||||
}
|
||||
|
||||
if let Some(str) = str {
|
||||
let time = {
|
||||
if let Some(min_delim_idx) = str.find(Self::MIN_DELIMITER) {
|
||||
let (min, sec) = str.split_at(min_delim_idx);
|
||||
let min = min.parse::<u64>().map_err(|e| format!("Converting specified minutes \"{}\" failed with: {}", min, e))?;
|
||||
let sec = sec.trim_start_matches(Self::MIN_DELIMITER).parse::<f64>().map_err(|e| print_sec_conversion_error(sec, &e))?;
|
||||
|
||||
(min*60) as f64 + sec
|
||||
}
|
||||
|
||||
else {
|
||||
str.parse::<f64>().map_err(|e| print_sec_conversion_error(str, &e))?
|
||||
}
|
||||
};
|
||||
Ok(Some(time))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Loop {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(arg: &str) -> Result<Self, Self::Err> {
|
||||
let mut args = arg.split(Self::END_DELIMITER);
|
||||
let start_time_sec = Self::parse(args.next()).map_err(|e| format!("{}", e))?.ok_or_else(|| format!("A start time is required for a loop"))?;
|
||||
let end_time_sec = Self::parse(args.next()).map_err(|e| format!("{}", e))?;
|
||||
|
||||
Ok(Loop{start_time_sec, end_time_sec})
|
||||
}
|
||||
}
|
||||
|
||||
struct LoopInfo {
|
||||
start_sample: usize,
|
||||
end_sample: usize,
|
||||
}
|
||||
|
||||
impl LoopInfo {
|
||||
fn create(r#loop: &Loop, sample_frequency: u32, vagadpcm_count: usize) -> LoopInfo {
|
||||
fn calculate_sample(time_sec: f64, vag_frequency: f64) -> usize {
|
||||
(time_sec*vag_frequency) as usize
|
||||
}
|
||||
|
||||
let sample_frequency = sample_frequency as f64 / VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM as f64;
|
||||
let start_sample = calculate_sample(r#loop.start_time_sec, sample_frequency);
|
||||
let end_sample = {
|
||||
if let Some(end_time_sec) = r#loop.end_time_sec {
|
||||
let end_sample = calculate_sample(end_time_sec, sample_frequency);
|
||||
if end_sample > vagadpcm_count {vagadpcm_count} else {end_sample}
|
||||
}
|
||||
|
||||
else {
|
||||
vagadpcm_count
|
||||
}
|
||||
};
|
||||
|
||||
LoopInfo{start_sample, end_sample}
|
||||
}
|
||||
|
||||
fn is_loop_start_reached(&self, sample: usize) -> bool {
|
||||
self.start_sample == sample
|
||||
}
|
||||
|
||||
fn is_loop_end_reached(&self, sample: usize) -> bool {
|
||||
self.end_sample == sample
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert(args: Arguments, output_file_path: &Option<std::path::PathBuf>, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let mut wav_file = hound::WavReader::new(input)?;
|
||||
let wav_header = wav_file.spec();
|
||||
|
||||
validate(&wav_header)?;
|
||||
|
||||
let vagadpcm_samples = VAGHeader::expected_vagadpcm_samples(wav_file.len()) + 1;
|
||||
let sample_info = args.r#loop.map(|v| LoopInfo::create(&v, wav_header.sample_rate, vagadpcm_samples as usize));
|
||||
let mut lpc = LPC::empty();
|
||||
|
||||
tool_helper::raw::write_raw(output, &VAGHeader::create(vagadpcm_samples, wav_header.sample_rate, &get_name_for_file_header(args.name, output_file_path))?)?;
|
||||
for (sample_id, adpcm_sample) in MonoADPCMIterator::create(wav_file.samples::<i16>()).enumerate() {
|
||||
let (mut vagadpcm, new_lpc) = VAGADPCM::create(adpcm_sample?, lpc);
|
||||
|
||||
if let Some(sample_info) = &sample_info {
|
||||
if sample_info.is_loop_start_reached(sample_id) {
|
||||
vagadpcm = vagadpcm.set_loop_start();
|
||||
}
|
||||
|
||||
if sample_info.is_loop_end_reached(sample_id + 1) {
|
||||
vagadpcm = vagadpcm.set_loop_end_repeat();
|
||||
}
|
||||
}
|
||||
|
||||
tool_helper::raw::write_raw(output, &vagadpcm)?;
|
||||
lpc = new_lpc;
|
||||
}
|
||||
|
||||
if sample_info.is_none() {
|
||||
tool_helper::raw::write_raw(output, &VAGADPCM::end())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate(wav_header: &hound::WavSpec) -> Result<(), Error> {
|
||||
if wav_header.sample_format != hound::SampleFormat::Int {
|
||||
return Err(Error::from_str("VAG: Only integer samples are supported as input."));
|
||||
}
|
||||
|
||||
if wav_header.bits_per_sample != 16 {
|
||||
return Err(Error::from_str("VAG: Only 16bits samples are currently supported as input."));
|
||||
}
|
||||
|
||||
if wav_header.channels != 1 {
|
||||
return Err(Error::from_str("VAG: Only mono samples are currently supported"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_name_for_file_header(name: Option<String>, output_file_path: &Option<std::path::PathBuf>) -> String {
|
||||
if let Some(name) = name {
|
||||
return name;
|
||||
}
|
||||
|
||||
if let Some(output_file_path) = output_file_path {
|
||||
if let Some(file_name) = output_file_path.file_name() {
|
||||
let mut string = file_name.to_string_lossy().to_string();
|
||||
|
||||
if let Some(idx) = string.rfind('.') {
|
||||
string.replace_range(idx.., "");
|
||||
}
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
return "<out>".to_owned();
|
||||
}
|
||||
@@ -1,226 +1,226 @@
|
||||
use bitflags::bitflags;
|
||||
use tool_helper::{raw::RawConversion, Error};
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Clone)]
|
||||
pub struct VAGHeader {
|
||||
_id: [u8; 4],
|
||||
version: u32,
|
||||
_reserved: u32,
|
||||
data_size: u32,
|
||||
sampling_frequency: u32,
|
||||
_reserved2: [u8; 12],
|
||||
_name: [u8; 16]
|
||||
}
|
||||
|
||||
impl VAGHeader {
|
||||
const SIZE: usize = std::mem::size_of::<VAGHeader>();
|
||||
const ID: [u8; 4] = ['V' as u8, 'A' as u8, 'G' as u8, 'p' as u8];
|
||||
const VERSION: u32 = 0x02;
|
||||
const RESERVED: u32 = 0;
|
||||
const RESERVED2: [u8; 12] = [0; 12];
|
||||
|
||||
pub fn expected_vagadpcm_samples(adpcm_samples: u32) -> u32 {
|
||||
((adpcm_samples as usize + (VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM - 1))/VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM) as u32
|
||||
}
|
||||
|
||||
pub fn create(vagadpcm_samples: u32, sampling_frequency: u32, name: &str) -> Result<VAGHeader, Error> {
|
||||
let data_size = vagadpcm_samples*VAGADPCM::SIZE as u32;
|
||||
let name = {
|
||||
if !name.is_ascii() {
|
||||
return Err(Error::from_text(format!("File name {} is not ascii", name)));
|
||||
}
|
||||
|
||||
let name_length = if name.len() > 16 {16} else {name.len()};
|
||||
let mut new_name = [0u8; 16];
|
||||
|
||||
new_name[..name_length].copy_from_slice(&name.as_bytes()[..name_length]);
|
||||
new_name
|
||||
};
|
||||
|
||||
Ok(VAGHeader{
|
||||
_id: VAGHeader::ID,
|
||||
version: VAGHeader::VERSION,
|
||||
_reserved: VAGHeader::RESERVED,
|
||||
data_size: data_size,
|
||||
sampling_frequency: sampling_frequency,
|
||||
_reserved2: VAGHeader::RESERVED2,
|
||||
_name: name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RawConversion<{VAGHeader::SIZE}> for VAGHeader {
|
||||
fn convert_to_raw(&self) -> [u8; VAGHeader::SIZE] {
|
||||
unsafe {
|
||||
let mut vag_header = self.clone();
|
||||
|
||||
vag_header.version = self.version.to_be();
|
||||
vag_header.data_size = self.data_size.to_be();
|
||||
vag_header.sampling_frequency = self.sampling_frequency.to_be();
|
||||
|
||||
let data: [u8; VAGHeader::SIZE] = std::mem::transmute(vag_header);
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct VAGFlagBits : u8 {
|
||||
const LoopEnd = (1 << 0);
|
||||
const Repeat = (1 << 1);
|
||||
const LoopStart = (1 << 2);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VAGADPCM {
|
||||
data: [u32; 4]
|
||||
}
|
||||
|
||||
impl VAGADPCM {
|
||||
const SIZE: usize = std::mem::size_of::<VAGADPCM>();
|
||||
pub(super) const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;
|
||||
|
||||
pub fn empty() -> VAGADPCM {
|
||||
VAGADPCM{data: [0; 4]}
|
||||
}
|
||||
|
||||
fn create_for_filter_shift(filter: u32, shift: u32) -> VAGADPCM {
|
||||
VAGADPCM{data: [(12 - shift) | filter << 4, 0, 0, 0]}
|
||||
}
|
||||
|
||||
pub fn end() -> VAGADPCM {
|
||||
VAGADPCM::empty().set_loop_self()
|
||||
}
|
||||
|
||||
pub fn create(samples: ADPCMSampleForVag, lpc_tap: LPC) -> (VAGADPCM, LPC) {
|
||||
fn cap_value(value: i32, min: i32, max: i32) -> i32 {
|
||||
if value < min {
|
||||
min
|
||||
}
|
||||
|
||||
else if value > max {
|
||||
max
|
||||
}
|
||||
|
||||
else {
|
||||
value
|
||||
}
|
||||
}
|
||||
let mut best_frame = VAGADPCM::empty();
|
||||
let mut best_tap = LPC::empty();
|
||||
let mut best_error = std::u64::MAX as u64;
|
||||
|
||||
for (filter_id, filter) in LPC::FILTERS.iter().enumerate() {
|
||||
for shift in 0..=12 {
|
||||
let mut this_frame = VAGADPCM::create_for_filter_shift(filter_id as u32, shift);
|
||||
let this_tap = lpc_tap.clone();
|
||||
let mut this_error = 0;
|
||||
|
||||
for (sample_id, sample) in samples.iter().enumerate() {
|
||||
let x = *sample as i32;
|
||||
let p = (this_tap.first*filter.first + this_tap.second*filter.second + 32) >> 6;
|
||||
let r = x - p;
|
||||
let q = cap_value((r + (((1 << shift) - (r < 0) as i32) >> 1)) >> shift, -8, 7);
|
||||
let y = cap_value(p + (q << shift), std::i16::MIN as i32, std::i16::MAX as i32);
|
||||
let e = y - x;
|
||||
|
||||
this_frame.data[(sample_id+4)/8] |= ((q&0xF) << (((sample_id + 4)%8)*4)) as u32;
|
||||
this_error += (e*e) as u64;
|
||||
}
|
||||
|
||||
if this_error < best_error {
|
||||
best_tap = this_tap;
|
||||
best_error = this_error;
|
||||
best_frame = this_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
(best_frame, best_tap)
|
||||
}
|
||||
|
||||
fn set_vag_flags(&mut self, flags: VAGFlagBits) {
|
||||
let mut first_sample = self.data[0].to_ne_bytes();
|
||||
|
||||
first_sample[1] = flags.bits();
|
||||
self.data[0] = u32::from_ne_bytes(first_sample);
|
||||
}
|
||||
|
||||
pub fn set_loop_start(mut self) -> Self {
|
||||
self.set_vag_flags(VAGFlagBits::LoopStart);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_loop_end_repeat(mut self) -> Self {
|
||||
self.set_vag_flags(VAGFlagBits::LoopEnd | VAGFlagBits::Repeat);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_loop_self(mut self) -> Self {
|
||||
self.set_vag_flags(VAGFlagBits::LoopStart | VAGFlagBits::LoopEnd);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RawConversion<{VAGADPCM::SIZE}> for VAGADPCM {
|
||||
fn convert_to_raw(&self) -> [u8; VAGADPCM::SIZE] {
|
||||
unsafe {
|
||||
let data: [u8; VAGADPCM::SIZE] = std::mem::transmute_copy(&self as &VAGADPCM);
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LPC {
|
||||
first: i32,
|
||||
second: i32
|
||||
}
|
||||
|
||||
impl LPC {
|
||||
const FILTERS: [LPC; 5] = [
|
||||
LPC{first: 0, second: 0},
|
||||
LPC{first: 60, second: 0},
|
||||
LPC{first: 115, second: -52},
|
||||
LPC{first: 98, second: -55},
|
||||
LPC{first: 122, second: -60}
|
||||
];
|
||||
|
||||
pub fn empty() -> LPC {
|
||||
LPC{first: 0, second: 0}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ADPCMSampleForVag = [i16; VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM];
|
||||
|
||||
pub struct MonoADPCMIterator<I: std::iter::Iterator<Item=Result<i16, hound::Error>>> {
|
||||
iter: I
|
||||
}
|
||||
|
||||
impl<I:std::iter::Iterator<Item=Result<i16, hound::Error>>> MonoADPCMIterator<I>{
|
||||
pub fn create(iter: I) -> MonoADPCMIterator<I> {
|
||||
MonoADPCMIterator{iter}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I:std::iter::Iterator<Item=Result<i16, hound::Error>>> std::iter::Iterator for MonoADPCMIterator<I> {
|
||||
type Item = Result<[i16; VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM], Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
const STREAM_GONE_ERROR: &'static str = "Reading ADPCM sample failed";
|
||||
|
||||
if let Some(next_sample) = self.iter.next() {
|
||||
let Ok(next_sample) = next_sample else {return Some(Err(Error::from_str(STREAM_GONE_ERROR)));};
|
||||
|
||||
let mut sample = [0;VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM];
|
||||
sample[0] = next_sample;
|
||||
|
||||
for idx in 1..VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM {
|
||||
let Ok(next_sample) = self.iter.next().unwrap_or(Ok(0)) else {return Some(Err(Error::from_str(STREAM_GONE_ERROR)));};
|
||||
sample[idx] = next_sample;
|
||||
}
|
||||
return Some(Ok(sample));
|
||||
}
|
||||
None
|
||||
}
|
||||
use bitflags::bitflags;
|
||||
use tool_helper::{raw::RawConversion, Error};
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Clone)]
|
||||
pub struct VAGHeader {
|
||||
_id: [u8; 4],
|
||||
version: u32,
|
||||
_reserved: u32,
|
||||
data_size: u32,
|
||||
sampling_frequency: u32,
|
||||
_reserved2: [u8; 12],
|
||||
_name: [u8; 16]
|
||||
}
|
||||
|
||||
impl VAGHeader {
|
||||
const SIZE: usize = std::mem::size_of::<VAGHeader>();
|
||||
const ID: [u8; 4] = ['V' as u8, 'A' as u8, 'G' as u8, 'p' as u8];
|
||||
const VERSION: u32 = 0x02;
|
||||
const RESERVED: u32 = 0;
|
||||
const RESERVED2: [u8; 12] = [0; 12];
|
||||
|
||||
pub fn expected_vagadpcm_samples(adpcm_samples: u32) -> u32 {
|
||||
((adpcm_samples as usize + (VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM - 1))/VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM) as u32
|
||||
}
|
||||
|
||||
pub fn create(vagadpcm_samples: u32, sampling_frequency: u32, name: &str) -> Result<VAGHeader, Error> {
|
||||
let data_size = vagadpcm_samples*VAGADPCM::SIZE as u32;
|
||||
let name = {
|
||||
if !name.is_ascii() {
|
||||
return Err(Error::from_text(format!("File name {} is not ascii", name)));
|
||||
}
|
||||
|
||||
let name_length = if name.len() > 16 {16} else {name.len()};
|
||||
let mut new_name = [0u8; 16];
|
||||
|
||||
new_name[..name_length].copy_from_slice(&name.as_bytes()[..name_length]);
|
||||
new_name
|
||||
};
|
||||
|
||||
Ok(VAGHeader{
|
||||
_id: VAGHeader::ID,
|
||||
version: VAGHeader::VERSION,
|
||||
_reserved: VAGHeader::RESERVED,
|
||||
data_size: data_size,
|
||||
sampling_frequency: sampling_frequency,
|
||||
_reserved2: VAGHeader::RESERVED2,
|
||||
_name: name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RawConversion<{VAGHeader::SIZE}> for VAGHeader {
|
||||
fn convert_to_raw(&self) -> [u8; VAGHeader::SIZE] {
|
||||
unsafe {
|
||||
let mut vag_header = self.clone();
|
||||
|
||||
vag_header.version = self.version.to_be();
|
||||
vag_header.data_size = self.data_size.to_be();
|
||||
vag_header.sampling_frequency = self.sampling_frequency.to_be();
|
||||
|
||||
let data: [u8; VAGHeader::SIZE] = std::mem::transmute(vag_header);
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct VAGFlagBits : u8 {
|
||||
const LoopEnd = (1 << 0);
|
||||
const Repeat = (1 << 1);
|
||||
const LoopStart = (1 << 2);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VAGADPCM {
|
||||
data: [u32; 4]
|
||||
}
|
||||
|
||||
impl VAGADPCM {
|
||||
const SIZE: usize = std::mem::size_of::<VAGADPCM>();
|
||||
pub(super) const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;
|
||||
|
||||
pub fn empty() -> VAGADPCM {
|
||||
VAGADPCM{data: [0; 4]}
|
||||
}
|
||||
|
||||
fn create_for_filter_shift(filter: u32, shift: u32) -> VAGADPCM {
|
||||
VAGADPCM{data: [(12 - shift) | filter << 4, 0, 0, 0]}
|
||||
}
|
||||
|
||||
pub fn end() -> VAGADPCM {
|
||||
VAGADPCM::empty().set_loop_self()
|
||||
}
|
||||
|
||||
pub fn create(samples: ADPCMSampleForVag, lpc_tap: LPC) -> (VAGADPCM, LPC) {
|
||||
fn cap_value(value: i32, min: i32, max: i32) -> i32 {
|
||||
if value < min {
|
||||
min
|
||||
}
|
||||
|
||||
else if value > max {
|
||||
max
|
||||
}
|
||||
|
||||
else {
|
||||
value
|
||||
}
|
||||
}
|
||||
let mut best_frame = VAGADPCM::empty();
|
||||
let mut best_tap = LPC::empty();
|
||||
let mut best_error = std::u64::MAX as u64;
|
||||
|
||||
for (filter_id, filter) in LPC::FILTERS.iter().enumerate() {
|
||||
for shift in 0..=12 {
|
||||
let mut this_frame = VAGADPCM::create_for_filter_shift(filter_id as u32, shift);
|
||||
let this_tap = lpc_tap.clone();
|
||||
let mut this_error = 0;
|
||||
|
||||
for (sample_id, sample) in samples.iter().enumerate() {
|
||||
let x = *sample as i32;
|
||||
let p = (this_tap.first*filter.first + this_tap.second*filter.second + 32) >> 6;
|
||||
let r = x - p;
|
||||
let q = cap_value((r + (((1 << shift) - (r < 0) as i32) >> 1)) >> shift, -8, 7);
|
||||
let y = cap_value(p + (q << shift), std::i16::MIN as i32, std::i16::MAX as i32);
|
||||
let e = y - x;
|
||||
|
||||
this_frame.data[(sample_id+4)/8] |= ((q&0xF) << (((sample_id + 4)%8)*4)) as u32;
|
||||
this_error += (e*e) as u64;
|
||||
}
|
||||
|
||||
if this_error < best_error {
|
||||
best_tap = this_tap;
|
||||
best_error = this_error;
|
||||
best_frame = this_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
(best_frame, best_tap)
|
||||
}
|
||||
|
||||
fn set_vag_flags(&mut self, flags: VAGFlagBits) {
|
||||
let mut first_sample = self.data[0].to_ne_bytes();
|
||||
|
||||
first_sample[1] = flags.bits();
|
||||
self.data[0] = u32::from_ne_bytes(first_sample);
|
||||
}
|
||||
|
||||
pub fn set_loop_start(mut self) -> Self {
|
||||
self.set_vag_flags(VAGFlagBits::LoopStart);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_loop_end_repeat(mut self) -> Self {
|
||||
self.set_vag_flags(VAGFlagBits::LoopEnd | VAGFlagBits::Repeat);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_loop_self(mut self) -> Self {
|
||||
self.set_vag_flags(VAGFlagBits::LoopStart | VAGFlagBits::LoopEnd);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RawConversion<{VAGADPCM::SIZE}> for VAGADPCM {
|
||||
fn convert_to_raw(&self) -> [u8; VAGADPCM::SIZE] {
|
||||
unsafe {
|
||||
let data: [u8; VAGADPCM::SIZE] = std::mem::transmute_copy(&self as &VAGADPCM);
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LPC {
|
||||
first: i32,
|
||||
second: i32
|
||||
}
|
||||
|
||||
impl LPC {
|
||||
const FILTERS: [LPC; 5] = [
|
||||
LPC{first: 0, second: 0},
|
||||
LPC{first: 60, second: 0},
|
||||
LPC{first: 115, second: -52},
|
||||
LPC{first: 98, second: -55},
|
||||
LPC{first: 122, second: -60}
|
||||
];
|
||||
|
||||
pub fn empty() -> LPC {
|
||||
LPC{first: 0, second: 0}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ADPCMSampleForVag = [i16; VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM];
|
||||
|
||||
pub struct MonoADPCMIterator<I: std::iter::Iterator<Item=Result<i16, hound::Error>>> {
|
||||
iter: I
|
||||
}
|
||||
|
||||
impl<I:std::iter::Iterator<Item=Result<i16, hound::Error>>> MonoADPCMIterator<I>{
|
||||
pub fn create(iter: I) -> MonoADPCMIterator<I> {
|
||||
MonoADPCMIterator{iter}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I:std::iter::Iterator<Item=Result<i16, hound::Error>>> std::iter::Iterator for MonoADPCMIterator<I> {
|
||||
type Item = Result<[i16; VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM], Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
const STREAM_GONE_ERROR: &'static str = "Reading ADPCM sample failed";
|
||||
|
||||
if let Some(next_sample) = self.iter.next() {
|
||||
let Ok(next_sample) = next_sample else {return Some(Err(Error::from_str(STREAM_GONE_ERROR)));};
|
||||
|
||||
let mut sample = [0;VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM];
|
||||
sample[0] = next_sample;
|
||||
|
||||
for idx in 1..VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM {
|
||||
let Ok(next_sample) = self.iter.next().unwrap_or(Ok(0)) else {return Some(Err(Error::from_str(STREAM_GONE_ERROR)));};
|
||||
sample[idx] = next_sample;
|
||||
}
|
||||
return Some(Ok(sample));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,39 @@
|
||||
mod raw_audio;
|
||||
mod xa_audio;
|
||||
|
||||
use clap::{Args, ValueEnum};
|
||||
use std::io::Write;
|
||||
use tool_helper::{Error, Input};
|
||||
use xa_audio::{LOW_FREQUENCY, HIGH_FREQUENCY};
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub struct Arguments {
|
||||
#[clap(short='f', long="frequency", value_enum, value_parser, default_value_t=Frequency::High)]
|
||||
frequency: Frequency,
|
||||
#[clap(short='b', long="bitdepth", value_enum, value_parser, default_value_t=SampleDepth::Normal)]
|
||||
sample_depth: SampleDepth,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, ValueEnum)]
|
||||
pub enum Frequency {
|
||||
High,
|
||||
Low,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, ValueEnum)]
|
||||
pub enum SampleDepth {
|
||||
Normal,
|
||||
High
|
||||
}
|
||||
|
||||
pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let prepared_xa_audio = raw_audio::load_as_i16_audio(input, frequency_to_value(args.frequency))?;
|
||||
xa_audio::encode(prepared_xa_audio, output, &args)
|
||||
}
|
||||
|
||||
fn frequency_to_value(requested_freq: Frequency) -> u32 {
|
||||
match requested_freq {
|
||||
Frequency::High => HIGH_FREQUENCY,
|
||||
Frequency::Low => LOW_FREQUENCY,
|
||||
}
|
||||
mod raw_audio;
|
||||
mod xa_audio;
|
||||
|
||||
use clap::{Args, ValueEnum};
|
||||
use std::io::Write;
|
||||
use tool_helper::{Error, Input};
|
||||
use xa_audio::{LOW_FREQUENCY, HIGH_FREQUENCY};
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub struct Arguments {
|
||||
#[clap(short='f', long="frequency", value_enum, value_parser, default_value_t=Frequency::High)]
|
||||
frequency: Frequency,
|
||||
#[clap(short='b', long="bitdepth", value_enum, value_parser, default_value_t=SampleDepth::Normal)]
|
||||
sample_depth: SampleDepth,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, ValueEnum)]
|
||||
pub enum Frequency {
|
||||
High,
|
||||
Low,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, ValueEnum)]
|
||||
pub enum SampleDepth {
|
||||
Normal,
|
||||
High
|
||||
}
|
||||
|
||||
pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let prepared_xa_audio = raw_audio::load_as_i16_audio(input, frequency_to_value(args.frequency))?;
|
||||
xa_audio::encode(prepared_xa_audio, output, &args)
|
||||
}
|
||||
|
||||
fn frequency_to_value(requested_freq: Frequency) -> u32 {
|
||||
match requested_freq {
|
||||
Frequency::High => HIGH_FREQUENCY,
|
||||
Frequency::Low => LOW_FREQUENCY,
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,35 @@
|
||||
use super::Error;
|
||||
use symphonia::core::errors::Error as SymError;
|
||||
use rubato::{ResampleError, ResamplerConstructionError};
|
||||
|
||||
fn generic_map_error(action: &str, error_str: String) -> Error {
|
||||
Error::from_text(format!("symphonia error: {} during {}", error_str, action))
|
||||
}
|
||||
|
||||
pub fn probe(error: SymError) -> Error {
|
||||
generic_map_error("probing of input", error.to_string())
|
||||
}
|
||||
|
||||
pub fn decoder(error: SymError) -> Error {
|
||||
generic_map_error("finding codec", error.to_string())
|
||||
}
|
||||
|
||||
pub fn next_packet(error: SymError) -> Error {
|
||||
generic_map_error("getting next raw packet", error.to_string())
|
||||
}
|
||||
|
||||
pub fn decode(error: SymError) -> Error {
|
||||
generic_map_error("decoding of raw packet", error.to_string())
|
||||
}
|
||||
|
||||
pub fn resampler_construction(error: ResamplerConstructionError) -> Error {
|
||||
generic_map_error("creating resampler", error.to_string())
|
||||
}
|
||||
|
||||
pub fn resample(error: ResampleError) -> Error {
|
||||
generic_map_error("resampling", error.to_string())
|
||||
}
|
||||
|
||||
pub fn find_track() -> Error {
|
||||
Error::from_str("symphonia error: No audio track located")
|
||||
use super::Error;
|
||||
use symphonia::core::errors::Error as SymError;
|
||||
use rubato::{ResampleError, ResamplerConstructionError};
|
||||
|
||||
fn generic_map_error(action: &str, error_str: String) -> Error {
|
||||
Error::from_text(format!("symphonia error: {} during {}", error_str, action))
|
||||
}
|
||||
|
||||
pub fn probe(error: SymError) -> Error {
|
||||
generic_map_error("probing of input", error.to_string())
|
||||
}
|
||||
|
||||
pub fn decoder(error: SymError) -> Error {
|
||||
generic_map_error("finding codec", error.to_string())
|
||||
}
|
||||
|
||||
pub fn next_packet(error: SymError) -> Error {
|
||||
generic_map_error("getting next raw packet", error.to_string())
|
||||
}
|
||||
|
||||
pub fn decode(error: SymError) -> Error {
|
||||
generic_map_error("decoding of raw packet", error.to_string())
|
||||
}
|
||||
|
||||
pub fn resampler_construction(error: ResamplerConstructionError) -> Error {
|
||||
generic_map_error("creating resampler", error.to_string())
|
||||
}
|
||||
|
||||
pub fn resample(error: ResampleError) -> Error {
|
||||
generic_map_error("resampling", error.to_string())
|
||||
}
|
||||
|
||||
pub fn find_track() -> Error {
|
||||
Error::from_str("symphonia error: No audio track located")
|
||||
}
|
||||
@@ -1,238 +1,238 @@
|
||||
mod error;
|
||||
|
||||
use rubato::{FftFixedInOut, Resampler};
|
||||
use symphonia::core::{
|
||||
audio::{AudioBuffer, Layout, SampleBuffer, Signal, SignalSpec},
|
||||
codecs::{Decoder, DecoderOptions, CODEC_TYPE_NULL},
|
||||
errors::Error as SymError,
|
||||
formats::{FormatOptions, FormatReader},
|
||||
io::MediaSourceStream,
|
||||
meta::MetadataOptions,
|
||||
probe::Hint
|
||||
};
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Orality {
|
||||
Stereo,
|
||||
Mono,
|
||||
}
|
||||
|
||||
pub struct CDAudioSamples {
|
||||
samples: Vec::<i16>,
|
||||
orality: Orality,
|
||||
}
|
||||
|
||||
impl CDAudioSamples {
|
||||
pub fn new(samples: Vec<i16>, channels: usize) -> Result<CDAudioSamples, Error> {
|
||||
let orality = match channels {
|
||||
0 => return Err(Error::from_str("Input file has no audio channels")),
|
||||
1 => Orality::Mono,
|
||||
2 => Orality::Stereo,
|
||||
_ => return Err(Error::from_str("Only Mono and Stereo input are supported")),
|
||||
};
|
||||
Ok(CDAudioSamples{samples, orality})
|
||||
}
|
||||
|
||||
pub fn samples(&self) -> &Vec::<i16> {
|
||||
&self.samples
|
||||
}
|
||||
|
||||
pub fn orality(&self) -> Orality {
|
||||
self.orality.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct InternalAudioSamples {
|
||||
planar_samples: Vec<Vec<f32>>,
|
||||
frequency: u32,
|
||||
}
|
||||
|
||||
impl InternalAudioSamples {
|
||||
pub fn new(planar_samples: Vec<Vec<f32>>, frequency: u32) -> Result<InternalAudioSamples, Error> {
|
||||
if planar_samples.len() < 1 || planar_samples.len() > 2{
|
||||
Err(Error::from_str("Audio samples need to be either mono or stereo"))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(InternalAudioSamples{planar_samples, frequency})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_audio_buffer(self) -> AudioBuffer<f32> {
|
||||
let duration = self.sample_len() as u64;
|
||||
let mut new_audio_buffer = AudioBuffer::new(duration, SignalSpec::new_with_layout(self.frequency, if self.planar_samples.len() == 1 {Layout::Mono} else {Layout::Stereo}));
|
||||
|
||||
new_audio_buffer.render_silence(None);
|
||||
for (channel_idx, channel) in self.planar_samples.into_iter().enumerate() {
|
||||
let dst_channel = new_audio_buffer.chan_mut(channel_idx);
|
||||
for (sample_idx, sample) in channel.into_iter().enumerate() {
|
||||
dst_channel[sample_idx] = sample;
|
||||
}
|
||||
}
|
||||
new_audio_buffer
|
||||
}
|
||||
|
||||
pub fn sample_len(&self) -> usize {
|
||||
self.planar_samples[0].len()
|
||||
}
|
||||
|
||||
pub fn channels(&self) -> usize {
|
||||
self.planar_samples.len()
|
||||
}
|
||||
|
||||
pub fn planar_slices(&self) -> Vec<&[f32]> {
|
||||
let mut planar_slices = Vec::new();
|
||||
|
||||
for channel in &self.planar_samples {
|
||||
planar_slices.push(channel.as_slice());
|
||||
}
|
||||
planar_slices
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_as_i16_audio(input: Input, target_frequency: u32) -> Result<CDAudioSamples, Error> {
|
||||
let raw_audio = load_raw_audio(input)?;
|
||||
let raw_audio = resample(raw_audio, target_frequency)?;
|
||||
|
||||
down_sample_interleave(raw_audio)
|
||||
}
|
||||
|
||||
fn load_raw_audio(input: Input) -> Result<InternalAudioSamples, Error> {
|
||||
let media_stream = MediaSourceStream::new(Box::new(load_to_ram(input)?), Default::default());
|
||||
let format = symphonia::default::get_probe().format(&Hint::new(), media_stream, &FormatOptions::default(), &MetadataOptions::default()).map_err(error::probe)?.format;
|
||||
let track = format.tracks().iter().find(|t| t.codec_params.codec != CODEC_TYPE_NULL).ok_or_else(error::find_track)?;
|
||||
|
||||
// Create a decoder for the track.
|
||||
let decoder = symphonia::default::get_codecs().make(&track.codec_params, &DecoderOptions::default()).map_err(error::decoder)?;
|
||||
let track_id = track.id;
|
||||
|
||||
decode(format, decoder, track_id)
|
||||
}
|
||||
|
||||
fn decode(mut format: Box<dyn FormatReader>, mut decoder: Box<dyn Decoder>, track_id: u32) -> Result<InternalAudioSamples, Error> {
|
||||
let mut samples = Vec::new();
|
||||
let mut channel_count = 0;
|
||||
let mut frequency = 0;
|
||||
let mut read_buffer = None;
|
||||
|
||||
loop {
|
||||
// Get the next packet from the media format.
|
||||
let packet = match format.next_packet() {
|
||||
Ok(packet) => packet,
|
||||
Err(err) => {
|
||||
if let SymError::IoError(io_err) = &err {
|
||||
if io_err.kind() == std::io::ErrorKind::UnexpectedEof {
|
||||
return InternalAudioSamples::new(samples, frequency);
|
||||
}
|
||||
}
|
||||
return Err(error::next_packet(err));
|
||||
}
|
||||
};
|
||||
|
||||
// Consume any new metadata that has been read since the last packet.
|
||||
format.metadata().skip_to_latest();
|
||||
|
||||
// If the packet does not belong to the selected track, skip over it.
|
||||
if packet.track_id() != track_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decode the packet into audio samples.
|
||||
let packet = decoder.decode(&packet).map_err(error::decode)?;
|
||||
if read_buffer.is_none() {
|
||||
let duration = packet.capacity() as u64;
|
||||
let specs = packet.spec();
|
||||
|
||||
channel_count = specs.channels.count();
|
||||
frequency = specs.rate;
|
||||
read_buffer = Some(SampleBuffer::<f32>::new(duration, packet.spec().clone()));
|
||||
|
||||
for _ in 0..channel_count {
|
||||
samples.push(Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(read_buffer) = &mut read_buffer {
|
||||
read_buffer.copy_planar_ref(packet);
|
||||
let cur_samples = read_buffer.samples();
|
||||
let mut cur_samples = cur_samples.chunks(cur_samples.len()/channel_count);
|
||||
|
||||
for dst_sample in &mut samples {
|
||||
dst_sample.extend(cur_samples.next().ok_or_else(|| Error::from_str("Not enough channels in input as expected"))?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resample(input: InternalAudioSamples, target_frequency: u32) -> Result<InternalAudioSamples, Error> {
|
||||
const HIGH_QUALITY_CHUNKS:usize = (1024*10)*100;
|
||||
fn process_partial(input_option: Option<&[&[f32]]>, resampler: &mut FftFixedInOut<f32>, planar_output: &mut Vec<Vec<f32>>) -> Result<(), Error> {
|
||||
let new_samples = resampler.process_partial(input_option, None).map_err(error::resample)?;
|
||||
for (channel, channel_samples) in new_samples.into_iter().enumerate() {
|
||||
planar_output[channel].extend(channel_samples.iter());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let chunk_size = HIGH_QUALITY_CHUNKS;
|
||||
let mut planar_input = input.planar_slices();
|
||||
let mut resampler = FftFixedInOut::<f32>::new(input.frequency as usize, target_frequency as usize, chunk_size, input.channels()).map_err(error::resampler_construction)?;
|
||||
let delay = resampler.output_delay();
|
||||
let mut sample_len = input.sample_len();
|
||||
let new_sample_len = (sample_len as f64*(target_frequency as f64/input.frequency as f64)) as usize;
|
||||
let mut planar_output = {
|
||||
let mut planar_output = Vec::new();
|
||||
|
||||
for _ in 0..planar_input.len() {
|
||||
planar_output.push(Vec::<f32>::new());
|
||||
}
|
||||
planar_output
|
||||
};
|
||||
|
||||
loop {
|
||||
let next_input_frames = resampler.input_frames_next();
|
||||
if next_input_frames > sample_len {
|
||||
if sample_len > 0 {
|
||||
// Still frames left
|
||||
process_partial(Some(&planar_input), &mut resampler, &mut planar_output)?;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let new_samples = resampler.process(&planar_input, None).map_err(error::resample)?;
|
||||
for (channel, slice) in planar_input.iter_mut().enumerate() {
|
||||
*slice = &slice[next_input_frames..];
|
||||
planar_output[channel].extend(new_samples[channel].iter());
|
||||
}
|
||||
sample_len -= next_input_frames;
|
||||
}
|
||||
|
||||
if planar_output[0].len() < delay + new_sample_len {
|
||||
// Flush
|
||||
process_partial(None, &mut resampler, &mut planar_output)?;
|
||||
}
|
||||
|
||||
for channel in &mut planar_output {
|
||||
let start = delay;
|
||||
let end = start + new_sample_len;
|
||||
*channel = channel[start..end].into();
|
||||
}
|
||||
InternalAudioSamples::new(planar_output, target_frequency)
|
||||
}
|
||||
|
||||
fn down_sample_interleave(input: InternalAudioSamples) -> Result<CDAudioSamples, Error> {
|
||||
let channels = input.channels();
|
||||
let audio_buffer = input.into_audio_buffer();
|
||||
let mut sample_buffer = SampleBuffer::<i16>::new(audio_buffer.capacity() as u64, audio_buffer.spec().clone());
|
||||
|
||||
sample_buffer.copy_interleaved_typed::<f32>(&audio_buffer);
|
||||
CDAudioSamples::new(sample_buffer.samples().to_vec(), channels)
|
||||
}
|
||||
|
||||
fn load_to_ram(mut input: Input) -> Result<std::io::Cursor<Vec<u8>>, Error> {
|
||||
let mut buffer = Vec::default();
|
||||
|
||||
input.read_to_end(&mut buffer)?;
|
||||
Ok(std::io::Cursor::new(buffer))
|
||||
mod error;
|
||||
|
||||
use rubato::{FftFixedInOut, Resampler};
|
||||
use symphonia::core::{
|
||||
audio::{AudioBuffer, Layout, SampleBuffer, Signal, SignalSpec},
|
||||
codecs::{Decoder, DecoderOptions, CODEC_TYPE_NULL},
|
||||
errors::Error as SymError,
|
||||
formats::{FormatOptions, FormatReader},
|
||||
io::MediaSourceStream,
|
||||
meta::MetadataOptions,
|
||||
probe::Hint
|
||||
};
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Orality {
|
||||
Stereo,
|
||||
Mono,
|
||||
}
|
||||
|
||||
pub struct CDAudioSamples {
|
||||
samples: Vec::<i16>,
|
||||
orality: Orality,
|
||||
}
|
||||
|
||||
impl CDAudioSamples {
|
||||
pub fn new(samples: Vec<i16>, channels: usize) -> Result<CDAudioSamples, Error> {
|
||||
let orality = match channels {
|
||||
0 => return Err(Error::from_str("Input file has no audio channels")),
|
||||
1 => Orality::Mono,
|
||||
2 => Orality::Stereo,
|
||||
_ => return Err(Error::from_str("Only Mono and Stereo input are supported")),
|
||||
};
|
||||
Ok(CDAudioSamples{samples, orality})
|
||||
}
|
||||
|
||||
pub fn samples(&self) -> &Vec::<i16> {
|
||||
&self.samples
|
||||
}
|
||||
|
||||
pub fn orality(&self) -> Orality {
|
||||
self.orality.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct InternalAudioSamples {
|
||||
planar_samples: Vec<Vec<f32>>,
|
||||
frequency: u32,
|
||||
}
|
||||
|
||||
impl InternalAudioSamples {
|
||||
pub fn new(planar_samples: Vec<Vec<f32>>, frequency: u32) -> Result<InternalAudioSamples, Error> {
|
||||
if planar_samples.len() < 1 || planar_samples.len() > 2{
|
||||
Err(Error::from_str("Audio samples need to be either mono or stereo"))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(InternalAudioSamples{planar_samples, frequency})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_audio_buffer(self) -> AudioBuffer<f32> {
|
||||
let duration = self.sample_len() as u64;
|
||||
let mut new_audio_buffer = AudioBuffer::new(duration, SignalSpec::new_with_layout(self.frequency, if self.planar_samples.len() == 1 {Layout::Mono} else {Layout::Stereo}));
|
||||
|
||||
new_audio_buffer.render_silence(None);
|
||||
for (channel_idx, channel) in self.planar_samples.into_iter().enumerate() {
|
||||
let dst_channel = new_audio_buffer.chan_mut(channel_idx);
|
||||
for (sample_idx, sample) in channel.into_iter().enumerate() {
|
||||
dst_channel[sample_idx] = sample;
|
||||
}
|
||||
}
|
||||
new_audio_buffer
|
||||
}
|
||||
|
||||
pub fn sample_len(&self) -> usize {
|
||||
self.planar_samples[0].len()
|
||||
}
|
||||
|
||||
pub fn channels(&self) -> usize {
|
||||
self.planar_samples.len()
|
||||
}
|
||||
|
||||
pub fn planar_slices(&self) -> Vec<&[f32]> {
|
||||
let mut planar_slices = Vec::new();
|
||||
|
||||
for channel in &self.planar_samples {
|
||||
planar_slices.push(channel.as_slice());
|
||||
}
|
||||
planar_slices
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_as_i16_audio(input: Input, target_frequency: u32) -> Result<CDAudioSamples, Error> {
|
||||
let raw_audio = load_raw_audio(input)?;
|
||||
let raw_audio = resample(raw_audio, target_frequency)?;
|
||||
|
||||
down_sample_interleave(raw_audio)
|
||||
}
|
||||
|
||||
fn load_raw_audio(input: Input) -> Result<InternalAudioSamples, Error> {
|
||||
let media_stream = MediaSourceStream::new(Box::new(load_to_ram(input)?), Default::default());
|
||||
let format = symphonia::default::get_probe().format(&Hint::new(), media_stream, &FormatOptions::default(), &MetadataOptions::default()).map_err(error::probe)?.format;
|
||||
let track = format.tracks().iter().find(|t| t.codec_params.codec != CODEC_TYPE_NULL).ok_or_else(error::find_track)?;
|
||||
|
||||
// Create a decoder for the track.
|
||||
let decoder = symphonia::default::get_codecs().make(&track.codec_params, &DecoderOptions::default()).map_err(error::decoder)?;
|
||||
let track_id = track.id;
|
||||
|
||||
decode(format, decoder, track_id)
|
||||
}
|
||||
|
||||
fn decode(mut format: Box<dyn FormatReader>, mut decoder: Box<dyn Decoder>, track_id: u32) -> Result<InternalAudioSamples, Error> {
|
||||
let mut samples = Vec::new();
|
||||
let mut channel_count = 0;
|
||||
let mut frequency = 0;
|
||||
let mut read_buffer = None;
|
||||
|
||||
loop {
|
||||
// Get the next packet from the media format.
|
||||
let packet = match format.next_packet() {
|
||||
Ok(packet) => packet,
|
||||
Err(err) => {
|
||||
if let SymError::IoError(io_err) = &err {
|
||||
if io_err.kind() == std::io::ErrorKind::UnexpectedEof {
|
||||
return InternalAudioSamples::new(samples, frequency);
|
||||
}
|
||||
}
|
||||
return Err(error::next_packet(err));
|
||||
}
|
||||
};
|
||||
|
||||
// Consume any new metadata that has been read since the last packet.
|
||||
format.metadata().skip_to_latest();
|
||||
|
||||
// If the packet does not belong to the selected track, skip over it.
|
||||
if packet.track_id() != track_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decode the packet into audio samples.
|
||||
let packet = decoder.decode(&packet).map_err(error::decode)?;
|
||||
if read_buffer.is_none() {
|
||||
let duration = packet.capacity() as u64;
|
||||
let specs = packet.spec();
|
||||
|
||||
channel_count = specs.channels.count();
|
||||
frequency = specs.rate;
|
||||
read_buffer = Some(SampleBuffer::<f32>::new(duration, packet.spec().clone()));
|
||||
|
||||
for _ in 0..channel_count {
|
||||
samples.push(Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(read_buffer) = &mut read_buffer {
|
||||
read_buffer.copy_planar_ref(packet);
|
||||
let cur_samples = read_buffer.samples();
|
||||
let mut cur_samples = cur_samples.chunks(cur_samples.len()/channel_count);
|
||||
|
||||
for dst_sample in &mut samples {
|
||||
dst_sample.extend(cur_samples.next().ok_or_else(|| Error::from_str("Not enough channels in input as expected"))?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resample(input: InternalAudioSamples, target_frequency: u32) -> Result<InternalAudioSamples, Error> {
|
||||
const HIGH_QUALITY_CHUNKS:usize = (1024*10)*100;
|
||||
fn process_partial(input_option: Option<&[&[f32]]>, resampler: &mut FftFixedInOut<f32>, planar_output: &mut Vec<Vec<f32>>) -> Result<(), Error> {
|
||||
let new_samples = resampler.process_partial(input_option, None).map_err(error::resample)?;
|
||||
for (channel, channel_samples) in new_samples.into_iter().enumerate() {
|
||||
planar_output[channel].extend(channel_samples.iter());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let chunk_size = HIGH_QUALITY_CHUNKS;
|
||||
let mut planar_input = input.planar_slices();
|
||||
let mut resampler = FftFixedInOut::<f32>::new(input.frequency as usize, target_frequency as usize, chunk_size, input.channels()).map_err(error::resampler_construction)?;
|
||||
let delay = resampler.output_delay();
|
||||
let mut sample_len = input.sample_len();
|
||||
let new_sample_len = (sample_len as f64*(target_frequency as f64/input.frequency as f64)) as usize;
|
||||
let mut planar_output = {
|
||||
let mut planar_output = Vec::new();
|
||||
|
||||
for _ in 0..planar_input.len() {
|
||||
planar_output.push(Vec::<f32>::new());
|
||||
}
|
||||
planar_output
|
||||
};
|
||||
|
||||
loop {
|
||||
let next_input_frames = resampler.input_frames_next();
|
||||
if next_input_frames > sample_len {
|
||||
if sample_len > 0 {
|
||||
// Still frames left
|
||||
process_partial(Some(&planar_input), &mut resampler, &mut planar_output)?;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let new_samples = resampler.process(&planar_input, None).map_err(error::resample)?;
|
||||
for (channel, slice) in planar_input.iter_mut().enumerate() {
|
||||
*slice = &slice[next_input_frames..];
|
||||
planar_output[channel].extend(new_samples[channel].iter());
|
||||
}
|
||||
sample_len -= next_input_frames;
|
||||
}
|
||||
|
||||
if planar_output[0].len() < delay + new_sample_len {
|
||||
// Flush
|
||||
process_partial(None, &mut resampler, &mut planar_output)?;
|
||||
}
|
||||
|
||||
for channel in &mut planar_output {
|
||||
let start = delay;
|
||||
let end = start + new_sample_len;
|
||||
*channel = channel[start..end].into();
|
||||
}
|
||||
InternalAudioSamples::new(planar_output, target_frequency)
|
||||
}
|
||||
|
||||
fn down_sample_interleave(input: InternalAudioSamples) -> Result<CDAudioSamples, Error> {
|
||||
let channels = input.channels();
|
||||
let audio_buffer = input.into_audio_buffer();
|
||||
let mut sample_buffer = SampleBuffer::<i16>::new(audio_buffer.capacity() as u64, audio_buffer.spec().clone());
|
||||
|
||||
sample_buffer.copy_interleaved_typed::<f32>(&audio_buffer);
|
||||
CDAudioSamples::new(sample_buffer.samples().to_vec(), channels)
|
||||
}
|
||||
|
||||
fn load_to_ram(mut input: Input) -> Result<std::io::Cursor<Vec<u8>>, Error> {
|
||||
let mut buffer = Vec::default();
|
||||
|
||||
input.read_to_end(&mut buffer)?;
|
||||
Ok(std::io::Cursor::new(buffer))
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
mod xapcm;
|
||||
|
||||
use super::Arguments;
|
||||
use super::raw_audio::CDAudioSamples;
|
||||
use cdtypes::types::sector::{Mode2Form2, SECTOR_SIZE};
|
||||
use std::io::Write;
|
||||
use tool_helper::Error;
|
||||
|
||||
pub const HIGH_FREQUENCY:u32 = 37_800;
|
||||
pub const LOW_FREQUENCY:u32 = 18_900;
|
||||
|
||||
pub fn encode(input: CDAudioSamples, output: &mut dyn Write, arguments: &Arguments) -> Result<(), Error> {
|
||||
let mut encoder = xapcm::Encoder::new(&input, arguments.frequency, arguments.sample_depth);
|
||||
|
||||
while let Some(xa_sector) = encoder.encode_next_xa_sector()? {
|
||||
let xa_sector = unsafe {std::mem::transmute::<Mode2Form2, [u8; SECTOR_SIZE]>(xa_sector)};
|
||||
output.write(&xa_sector)?;
|
||||
}
|
||||
Ok(())
|
||||
mod xapcm;
|
||||
|
||||
use super::Arguments;
|
||||
use super::raw_audio::CDAudioSamples;
|
||||
use cdtypes::types::sector::{Mode2Form2, SECTOR_SIZE};
|
||||
use std::io::Write;
|
||||
use tool_helper::Error;
|
||||
|
||||
pub const HIGH_FREQUENCY:u32 = 37_800;
|
||||
pub const LOW_FREQUENCY:u32 = 18_900;
|
||||
|
||||
pub fn encode(input: CDAudioSamples, output: &mut dyn Write, arguments: &Arguments) -> Result<(), Error> {
|
||||
let mut encoder = xapcm::Encoder::new(&input, arguments.frequency, arguments.sample_depth);
|
||||
|
||||
while let Some(xa_sector) = encoder.encode_next_xa_sector()? {
|
||||
let xa_sector = unsafe {std::mem::transmute::<Mode2Form2, [u8; SECTOR_SIZE]>(xa_sector)};
|
||||
output.write(&xa_sector)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,313 +1,313 @@
|
||||
use crate::audio::xa::{raw_audio::{CDAudioSamples, Orality}, Frequency, SampleDepth};
|
||||
use cdtypes::types::sector::{Mode2Form2, XAADPCMBitsPerSample, XAADPCMSampleRate, XAADPCMSound};
|
||||
use tool_helper::Error;
|
||||
|
||||
pub struct Encoder<'a> {
|
||||
left: ChannelState,
|
||||
right: ChannelState,
|
||||
source: &'a[i16],
|
||||
frequency: Frequency,
|
||||
sample_depth: SampleDepth,
|
||||
orality: Orality,
|
||||
samples_per_block: i32,
|
||||
sample_limit: i32
|
||||
}
|
||||
|
||||
impl<'a> Encoder<'a> {
|
||||
const BLOCKS_PER_SECTOR:usize = 18;
|
||||
const XA_ADPCM_FILTER_COUNT: i32 = 4;
|
||||
const FILTER_K1: [i16; 5] = [0, 60, 115, 98, 122];
|
||||
const FILTER_K2: [i16; 5] = [0, 0, -52, -55, -60];
|
||||
|
||||
pub fn new(cd_sample: &CDAudioSamples, frequency: Frequency, sample_depth: SampleDepth) -> Encoder {
|
||||
let orality = cd_sample.orality();
|
||||
let (samples_per_block, sample_limit) = Self::samples_per_block_and_limit(&cd_sample.samples(), sample_depth, orality);
|
||||
|
||||
Encoder{left: ChannelState::default(), right: ChannelState::default(), source: &cd_sample.samples(), frequency, sample_depth, orality, samples_per_block, sample_limit}
|
||||
}
|
||||
|
||||
pub fn encode_next_xa_sector(&mut self) -> Result<Option<Mode2Form2>, Error> {
|
||||
if self.source.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut sector = self.create_new_sector();
|
||||
let mut dst = &mut sector.data[0..];
|
||||
|
||||
for _ in 0..Self::BLOCKS_PER_SECTOR {
|
||||
if self.source.len() < self.samples_per_block as usize {
|
||||
self.source = &self.source[self.source.len()..];
|
||||
break;
|
||||
}
|
||||
|
||||
self.encode_xa(&self.source[0..], self.sample_limit, dst)?;
|
||||
|
||||
self.sample_limit -= self.samples_per_block;
|
||||
self.source = &self.source[self.samples_per_block as usize..];
|
||||
dst = &mut dst[0x80..];
|
||||
}
|
||||
|
||||
sector.finalize();
|
||||
Ok(Some(sector))
|
||||
}
|
||||
|
||||
fn create_new_sector(&self) -> Mode2Form2 {
|
||||
let mut sector = Mode2Form2::new();
|
||||
let sub_mode = &mut sector.sub_header.sub_mode;
|
||||
let coding_info = &mut sector.sub_header.coding_info;
|
||||
|
||||
sub_mode.set_real_time();
|
||||
|
||||
coding_info.set_sound_type(match self.orality {
|
||||
Orality::Mono => XAADPCMSound::Mono,
|
||||
Orality::Stereo => XAADPCMSound::Stereo
|
||||
});
|
||||
coding_info.set_sample_rate(match self.frequency {
|
||||
Frequency::Low => XAADPCMSampleRate::Freq18900Hz,
|
||||
Frequency::High => XAADPCMSampleRate::Freq37800Hz,
|
||||
});
|
||||
coding_info.set_bits_per_sample(match self.sample_depth {
|
||||
SampleDepth::Normal => XAADPCMBitsPerSample::Normal,
|
||||
SampleDepth::High => XAADPCMBitsPerSample::High,
|
||||
});
|
||||
|
||||
sector
|
||||
}
|
||||
|
||||
fn encode_xa(&mut self, samples: &[i16], sample_limit: i32, data: &mut [u8]) -> Result<(), Error> {
|
||||
const SHIFT_RANGE_4BPS: i32 = 12;
|
||||
const SHIFT_RANGE_8BPS: i32 = 8;
|
||||
|
||||
let channels = [&mut self.left, &mut self.right];
|
||||
match self.sample_depth {
|
||||
SampleDepth::Normal => {
|
||||
let (modulo, offset) = if self.orality == Orality::Stereo {(2, &STEREO_4BIT)} else {(1, &MONO_4BIT)};
|
||||
let (first_offset, second_offset) = offset;
|
||||
|
||||
for (offset_idx, offset_set) in [first_offset, second_offset].iter().enumerate() {
|
||||
for (idx, offset) in offset_set.iter().enumerate() {
|
||||
let byte = Self::encode(channels[idx%modulo], &samples[offset.sample..], sample_limit + offset.sample_limit, offset.pitch, &mut data[offset.data..], offset.data_shift, offset.data_pitch, Self::XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS)?;
|
||||
data[idx + (offset_idx*8)] = byte;
|
||||
data[idx + 4 + (offset_idx*8)] = byte;
|
||||
}
|
||||
}
|
||||
},
|
||||
SampleDepth::High => {
|
||||
let (modulo, offset_set) = if self.orality == Orality::Stereo {(2, &STEREO_8BIT)} else {(1, &MONO_8BIT)};
|
||||
for (idx, offset) in offset_set.iter().enumerate() {
|
||||
let byte = Self::encode(channels[idx%modulo], &samples[offset.sample..], sample_limit + offset.sample_limit, offset.pitch, &mut data[offset.data..], offset.data_shift, offset.data_pitch, Self::XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS)?;
|
||||
data[idx] = byte;
|
||||
data[idx + 4] = byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode(channel_state: &mut ChannelState, samples: &[i16], sample_limit: i32, pitch: i32, data: &mut [u8], data_shift: i32, data_pitch: i32, filter_count: i32, shift_range: i32) -> Result<u8, Error> {
|
||||
let mut best_mse = 1i64 << 50i64;
|
||||
let mut best_filer = 0;
|
||||
let mut best_sample_shift = 0;
|
||||
|
||||
for filter in 0..filter_count {
|
||||
let true_min_shift = Self::find_min_shift(channel_state, samples, sample_limit, pitch, filter, shift_range)?;
|
||||
|
||||
// Testing has shown that the optimal shift can be off the true minimum shift
|
||||
// by 1 in *either* direction.
|
||||
// This is NOT the case when dither is used.
|
||||
let min_shift = if true_min_shift - 1 < 0 {0} else {true_min_shift - 1};
|
||||
let max_shift = if true_min_shift + 1 > shift_range {shift_range} else {true_min_shift + 1};
|
||||
|
||||
for sample_shift in min_shift..=max_shift {
|
||||
let mut proposed = channel_state.clone();
|
||||
Self::attempt_encode(&mut proposed, samples, sample_limit, pitch, data, data_shift, data_pitch, filter, sample_shift, shift_range)?;
|
||||
if best_mse > proposed.mse {
|
||||
best_mse = proposed.mse;
|
||||
best_filer = filter;
|
||||
best_sample_shift = sample_shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::attempt_encode(channel_state, samples, sample_limit, pitch, data, data_shift, data_pitch, best_filer, best_sample_shift, shift_range)
|
||||
}
|
||||
|
||||
fn attempt_encode(out_channel_state: &mut ChannelState, samples: &[i16], sample_limit: i32, pitch: i32, data: &mut [u8], data_shift: i32, data_pitch: i32, filter: i32, sample_shift: i32, shift_range: i32) -> Result<u8, Error> {
|
||||
let sample_mask = (0xFFFF >> shift_range) as u8;
|
||||
let nondata_mask = (!(sample_mask << data_shift)) as u8;
|
||||
|
||||
let min_shift = sample_shift;
|
||||
let k1 = Self::FILTER_K1[filter as usize] as i32;
|
||||
let k2 = Self::FILTER_K2[filter as usize] as i32;
|
||||
|
||||
let hdr = ((min_shift & 0x0F) | ((filter as i32) << 4)) as u8;
|
||||
|
||||
out_channel_state.mse = 0;
|
||||
|
||||
for i in 0..28 {
|
||||
let sample = (if i >= sample_limit {0} else {samples[(i*pitch) as usize] as i32}) + out_channel_state.qerr;
|
||||
let previous_value = (k1*out_channel_state.prev1 + k2*out_channel_state.prev2 + (1 << 5)) >> 6;
|
||||
|
||||
let mut sample_enc = sample - previous_value;
|
||||
sample_enc <<= min_shift;
|
||||
sample_enc += 1 << (shift_range - 1);
|
||||
sample_enc >>= shift_range;
|
||||
|
||||
if sample_enc < (std::i16::MIN as i32 >> shift_range) {sample_enc = std::i16::MIN as i32 >> shift_range}
|
||||
if sample_enc > (std::i16::MAX as i32 >> shift_range) {sample_enc = std::i16::MAX as i32 >> shift_range}
|
||||
sample_enc &= sample_mask as i32;
|
||||
|
||||
let mut sample_dec = (((sample_enc & sample_mask as i32) << shift_range) as i16) as i32;
|
||||
sample_dec >>= min_shift;
|
||||
sample_dec += previous_value;
|
||||
if sample_dec > std::i16::MAX as i32 {sample_dec = std::i16::MAX as i32}
|
||||
if sample_dec < std::i16::MIN as i32 {sample_dec = std::i16::MIN as i32}
|
||||
|
||||
let sample_error = sample_dec - sample;
|
||||
if sample_error >= (1 << 30) || sample_error <= -(1 << 30) {
|
||||
return Err(Error::from_text(format!("Sample error exceeds 30bit: {}", sample_error)));
|
||||
}
|
||||
|
||||
data[(i*data_pitch) as usize] = ((data[(i*data_pitch) as usize] & nondata_mask) as i32 | (sample_enc << data_shift)) as u8;
|
||||
|
||||
out_channel_state.mse += (sample_error as u64*sample_error as u64) as i64;
|
||||
out_channel_state.prev2 = out_channel_state.prev1;
|
||||
out_channel_state.prev1 = sample_dec;
|
||||
}
|
||||
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn find_min_shift(channel_state: &ChannelState, samples: &[i16], sample_limit: i32, pitch: i32, filter: i32, shift_range: i32) -> Result<i32, Error> {
|
||||
/*
|
||||
Assumption made:
|
||||
There is value in shifting right one step further to allow the nibbles to clip.
|
||||
However, given a possible shift value, there is no value in shifting one step less.
|
||||
|
||||
Having said that, this is not a completely accurate model of the encoder,
|
||||
so maybe we will need to shift one step less.
|
||||
*/
|
||||
|
||||
let mut prev1 = channel_state.prev1;
|
||||
let mut prev2 = channel_state.prev2;
|
||||
let k1 = Self::FILTER_K1[filter as usize] as i32;
|
||||
let k2 = Self::FILTER_K2[filter as usize] as i32;
|
||||
let mut right_shift = 0;
|
||||
let mut s_min = 0;
|
||||
let mut s_max = 0;
|
||||
|
||||
for i in 0..28 {
|
||||
let raw_sample = if i >= sample_limit {0} else {samples[(i*pitch) as usize]} as i32;
|
||||
let prev_values = (k1*prev1 + k2*prev2 + (1 << 5)) >> 6;
|
||||
let sample = raw_sample - prev_values;
|
||||
|
||||
if sample < s_min {
|
||||
s_min = sample;
|
||||
}
|
||||
|
||||
if sample > s_max {
|
||||
s_max = sample;
|
||||
}
|
||||
|
||||
prev2 = prev1;
|
||||
prev1 = raw_sample;
|
||||
}
|
||||
|
||||
while right_shift < shift_range && (s_max >> right_shift) > (std::i16::MAX as i32 >> shift_range) {
|
||||
right_shift += 1;
|
||||
}
|
||||
|
||||
while right_shift < shift_range && (s_min >> right_shift) < (std::i16::MIN as i32 >> shift_range) {
|
||||
right_shift += 1;
|
||||
}
|
||||
|
||||
let min_shift = shift_range - right_shift;
|
||||
if 0 <= min_shift && min_shift <= shift_range {
|
||||
Ok(min_shift)
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("0 <= {} && {} <= {} was not satisfied with min_shift: {}", min_shift, min_shift, shift_range, min_shift)))
|
||||
}
|
||||
}
|
||||
|
||||
fn samples_per_block_and_limit(input: &[i16], sample_depth: SampleDepth, orality: Orality) -> (i32, i32) {
|
||||
let samples_per_block = match sample_depth {
|
||||
SampleDepth::Normal => 224,
|
||||
SampleDepth::High => 112,
|
||||
};
|
||||
|
||||
let sample_limit = match orality {
|
||||
Orality::Stereo => input.len()*2,
|
||||
Orality::Mono => input.len(),
|
||||
};
|
||||
|
||||
(samples_per_block, sample_limit as i32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ChannelState {
|
||||
qerr: i32, // quanitisation error
|
||||
mse: i64, // mean square error
|
||||
prev1: i32,
|
||||
prev2: i32
|
||||
}
|
||||
|
||||
impl std::default::Default for ChannelState {
|
||||
fn default() -> Self {
|
||||
ChannelState{qerr: 0, mse: 0, prev1: 0, prev2: 0}
|
||||
}
|
||||
}
|
||||
|
||||
struct EncodingOffsets {
|
||||
sample: usize,
|
||||
sample_limit: i32,
|
||||
pitch: i32,
|
||||
data: usize,
|
||||
data_shift: i32,
|
||||
data_pitch: i32,
|
||||
}
|
||||
|
||||
const STEREO_4BIT: ([EncodingOffsets; 4], [EncodingOffsets; 4]) = (
|
||||
[
|
||||
EncodingOffsets{sample: 0, sample_limit: 0, pitch: 2, data: 0x10, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 1, sample_limit: 0, pitch: 2, data: 0x10, data_shift: 4, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56, sample_limit: -28, pitch: 2, data: 0x11, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56 + 1, sample_limit: -28, pitch: 2, data: 0x11, data_shift: 4, data_pitch: 4},
|
||||
],
|
||||
[
|
||||
EncodingOffsets{sample: 56*2, sample_limit: -28*2, pitch: 2, data: 0x12, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56*2 + 1, sample_limit: -28*2, pitch: 2, data: 0x12, data_shift: 4, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56*3, sample_limit: -28*3, pitch: 2, data: 0x13, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56*3 + 1, sample_limit: -28*3, pitch: 2, data: 0x13, data_shift: 4, data_pitch: 4}
|
||||
]
|
||||
);
|
||||
const MONO_4BIT: ([EncodingOffsets; 4], [EncodingOffsets; 4]) = (
|
||||
[
|
||||
EncodingOffsets{sample: 0, sample_limit: 0, pitch: 1, data: 0x10, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28, sample_limit: -28, pitch: 1, data: 0x10, data_shift: 4, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*2, sample_limit: -28*2, pitch: 1, data: 0x11, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*3, sample_limit: -28*3, pitch: 1, data: 0x11, data_shift: 4, data_pitch: 4},
|
||||
],
|
||||
[
|
||||
EncodingOffsets{sample: 28*4, sample_limit: -28*4, pitch: 1, data: 0x12, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*5, sample_limit: -28*5, pitch: 1, data: 0x12, data_shift: 4, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*6, sample_limit: -28*6, pitch: 1, data: 0x13, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*7, sample_limit: -28*7, pitch: 1, data: 0x13, data_shift: 4, data_pitch: 4}
|
||||
]
|
||||
);
|
||||
|
||||
const STEREO_8BIT: [EncodingOffsets;4] = [
|
||||
EncodingOffsets{sample: 0, sample_limit: 0, pitch: 2, data: 0x10, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 1, sample_limit: 0, pitch: 2, data: 0x11, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56, sample_limit: -28, pitch: 2, data: 0x12, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56 + 1, sample_limit: -28, pitch: 2, data: 0x13, data_shift: 0, data_pitch: 4},
|
||||
];
|
||||
const MONO_8BIT: [EncodingOffsets;4] = [
|
||||
EncodingOffsets{sample: 0, sample_limit: 0, pitch: 1, data: 0x10, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28, sample_limit: -28, pitch: 1, data: 0x11, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*2, sample_limit: -28*2, pitch: 1, data: 0x12, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*3, sample_limit: -28*3, pitch: 1, data: 0x13, data_shift: 0, data_pitch: 4},
|
||||
use crate::audio::xa::{raw_audio::{CDAudioSamples, Orality}, Frequency, SampleDepth};
|
||||
use cdtypes::types::sector::{Mode2Form2, XAADPCMBitsPerSample, XAADPCMSampleRate, XAADPCMSound};
|
||||
use tool_helper::Error;
|
||||
|
||||
pub struct Encoder<'a> {
|
||||
left: ChannelState,
|
||||
right: ChannelState,
|
||||
source: &'a[i16],
|
||||
frequency: Frequency,
|
||||
sample_depth: SampleDepth,
|
||||
orality: Orality,
|
||||
samples_per_block: i32,
|
||||
sample_limit: i32
|
||||
}
|
||||
|
||||
impl<'a> Encoder<'a> {
|
||||
const BLOCKS_PER_SECTOR:usize = 18;
|
||||
const XA_ADPCM_FILTER_COUNT: i32 = 4;
|
||||
const FILTER_K1: [i16; 5] = [0, 60, 115, 98, 122];
|
||||
const FILTER_K2: [i16; 5] = [0, 0, -52, -55, -60];
|
||||
|
||||
pub fn new(cd_sample: &CDAudioSamples, frequency: Frequency, sample_depth: SampleDepth) -> Encoder {
|
||||
let orality = cd_sample.orality();
|
||||
let (samples_per_block, sample_limit) = Self::samples_per_block_and_limit(&cd_sample.samples(), sample_depth, orality);
|
||||
|
||||
Encoder{left: ChannelState::default(), right: ChannelState::default(), source: &cd_sample.samples(), frequency, sample_depth, orality, samples_per_block, sample_limit}
|
||||
}
|
||||
|
||||
pub fn encode_next_xa_sector(&mut self) -> Result<Option<Mode2Form2>, Error> {
|
||||
if self.source.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut sector = self.create_new_sector();
|
||||
let mut dst = &mut sector.data[0..];
|
||||
|
||||
for _ in 0..Self::BLOCKS_PER_SECTOR {
|
||||
if self.source.len() < self.samples_per_block as usize {
|
||||
self.source = &self.source[self.source.len()..];
|
||||
break;
|
||||
}
|
||||
|
||||
self.encode_xa(&self.source[0..], self.sample_limit, dst)?;
|
||||
|
||||
self.sample_limit -= self.samples_per_block;
|
||||
self.source = &self.source[self.samples_per_block as usize..];
|
||||
dst = &mut dst[0x80..];
|
||||
}
|
||||
|
||||
sector.finalize();
|
||||
Ok(Some(sector))
|
||||
}
|
||||
|
||||
fn create_new_sector(&self) -> Mode2Form2 {
|
||||
let mut sector = Mode2Form2::new();
|
||||
let sub_mode = &mut sector.sub_header.sub_mode;
|
||||
let coding_info = &mut sector.sub_header.coding_info;
|
||||
|
||||
sub_mode.set_real_time();
|
||||
|
||||
coding_info.set_sound_type(match self.orality {
|
||||
Orality::Mono => XAADPCMSound::Mono,
|
||||
Orality::Stereo => XAADPCMSound::Stereo
|
||||
});
|
||||
coding_info.set_sample_rate(match self.frequency {
|
||||
Frequency::Low => XAADPCMSampleRate::Freq18900Hz,
|
||||
Frequency::High => XAADPCMSampleRate::Freq37800Hz,
|
||||
});
|
||||
coding_info.set_bits_per_sample(match self.sample_depth {
|
||||
SampleDepth::Normal => XAADPCMBitsPerSample::Normal,
|
||||
SampleDepth::High => XAADPCMBitsPerSample::High,
|
||||
});
|
||||
|
||||
sector
|
||||
}
|
||||
|
||||
fn encode_xa(&mut self, samples: &[i16], sample_limit: i32, data: &mut [u8]) -> Result<(), Error> {
|
||||
const SHIFT_RANGE_4BPS: i32 = 12;
|
||||
const SHIFT_RANGE_8BPS: i32 = 8;
|
||||
|
||||
let channels = [&mut self.left, &mut self.right];
|
||||
match self.sample_depth {
|
||||
SampleDepth::Normal => {
|
||||
let (modulo, offset) = if self.orality == Orality::Stereo {(2, &STEREO_4BIT)} else {(1, &MONO_4BIT)};
|
||||
let (first_offset, second_offset) = offset;
|
||||
|
||||
for (offset_idx, offset_set) in [first_offset, second_offset].iter().enumerate() {
|
||||
for (idx, offset) in offset_set.iter().enumerate() {
|
||||
let byte = Self::encode(channels[idx%modulo], &samples[offset.sample..], sample_limit + offset.sample_limit, offset.pitch, &mut data[offset.data..], offset.data_shift, offset.data_pitch, Self::XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS)?;
|
||||
data[idx + (offset_idx*8)] = byte;
|
||||
data[idx + 4 + (offset_idx*8)] = byte;
|
||||
}
|
||||
}
|
||||
},
|
||||
SampleDepth::High => {
|
||||
let (modulo, offset_set) = if self.orality == Orality::Stereo {(2, &STEREO_8BIT)} else {(1, &MONO_8BIT)};
|
||||
for (idx, offset) in offset_set.iter().enumerate() {
|
||||
let byte = Self::encode(channels[idx%modulo], &samples[offset.sample..], sample_limit + offset.sample_limit, offset.pitch, &mut data[offset.data..], offset.data_shift, offset.data_pitch, Self::XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS)?;
|
||||
data[idx] = byte;
|
||||
data[idx + 4] = byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode(channel_state: &mut ChannelState, samples: &[i16], sample_limit: i32, pitch: i32, data: &mut [u8], data_shift: i32, data_pitch: i32, filter_count: i32, shift_range: i32) -> Result<u8, Error> {
|
||||
let mut best_mse = 1i64 << 50i64;
|
||||
let mut best_filer = 0;
|
||||
let mut best_sample_shift = 0;
|
||||
|
||||
for filter in 0..filter_count {
|
||||
let true_min_shift = Self::find_min_shift(channel_state, samples, sample_limit, pitch, filter, shift_range)?;
|
||||
|
||||
// Testing has shown that the optimal shift can be off the true minimum shift
|
||||
// by 1 in *either* direction.
|
||||
// This is NOT the case when dither is used.
|
||||
let min_shift = if true_min_shift - 1 < 0 {0} else {true_min_shift - 1};
|
||||
let max_shift = if true_min_shift + 1 > shift_range {shift_range} else {true_min_shift + 1};
|
||||
|
||||
for sample_shift in min_shift..=max_shift {
|
||||
let mut proposed = channel_state.clone();
|
||||
Self::attempt_encode(&mut proposed, samples, sample_limit, pitch, data, data_shift, data_pitch, filter, sample_shift, shift_range)?;
|
||||
if best_mse > proposed.mse {
|
||||
best_mse = proposed.mse;
|
||||
best_filer = filter;
|
||||
best_sample_shift = sample_shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::attempt_encode(channel_state, samples, sample_limit, pitch, data, data_shift, data_pitch, best_filer, best_sample_shift, shift_range)
|
||||
}
|
||||
|
||||
fn attempt_encode(out_channel_state: &mut ChannelState, samples: &[i16], sample_limit: i32, pitch: i32, data: &mut [u8], data_shift: i32, data_pitch: i32, filter: i32, sample_shift: i32, shift_range: i32) -> Result<u8, Error> {
|
||||
let sample_mask = (0xFFFF >> shift_range) as u8;
|
||||
let nondata_mask = (!(sample_mask << data_shift)) as u8;
|
||||
|
||||
let min_shift = sample_shift;
|
||||
let k1 = Self::FILTER_K1[filter as usize] as i32;
|
||||
let k2 = Self::FILTER_K2[filter as usize] as i32;
|
||||
|
||||
let hdr = ((min_shift & 0x0F) | ((filter as i32) << 4)) as u8;
|
||||
|
||||
out_channel_state.mse = 0;
|
||||
|
||||
for i in 0..28 {
|
||||
let sample = (if i >= sample_limit {0} else {samples[(i*pitch) as usize] as i32}) + out_channel_state.qerr;
|
||||
let previous_value = (k1*out_channel_state.prev1 + k2*out_channel_state.prev2 + (1 << 5)) >> 6;
|
||||
|
||||
let mut sample_enc = sample - previous_value;
|
||||
sample_enc <<= min_shift;
|
||||
sample_enc += 1 << (shift_range - 1);
|
||||
sample_enc >>= shift_range;
|
||||
|
||||
if sample_enc < (std::i16::MIN as i32 >> shift_range) {sample_enc = std::i16::MIN as i32 >> shift_range}
|
||||
if sample_enc > (std::i16::MAX as i32 >> shift_range) {sample_enc = std::i16::MAX as i32 >> shift_range}
|
||||
sample_enc &= sample_mask as i32;
|
||||
|
||||
let mut sample_dec = (((sample_enc & sample_mask as i32) << shift_range) as i16) as i32;
|
||||
sample_dec >>= min_shift;
|
||||
sample_dec += previous_value;
|
||||
if sample_dec > std::i16::MAX as i32 {sample_dec = std::i16::MAX as i32}
|
||||
if sample_dec < std::i16::MIN as i32 {sample_dec = std::i16::MIN as i32}
|
||||
|
||||
let sample_error = sample_dec - sample;
|
||||
if sample_error >= (1 << 30) || sample_error <= -(1 << 30) {
|
||||
return Err(Error::from_text(format!("Sample error exceeds 30bit: {}", sample_error)));
|
||||
}
|
||||
|
||||
data[(i*data_pitch) as usize] = ((data[(i*data_pitch) as usize] & nondata_mask) as i32 | (sample_enc << data_shift)) as u8;
|
||||
|
||||
out_channel_state.mse += (sample_error as u64*sample_error as u64) as i64;
|
||||
out_channel_state.prev2 = out_channel_state.prev1;
|
||||
out_channel_state.prev1 = sample_dec;
|
||||
}
|
||||
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn find_min_shift(channel_state: &ChannelState, samples: &[i16], sample_limit: i32, pitch: i32, filter: i32, shift_range: i32) -> Result<i32, Error> {
|
||||
/*
|
||||
Assumption made:
|
||||
There is value in shifting right one step further to allow the nibbles to clip.
|
||||
However, given a possible shift value, there is no value in shifting one step less.
|
||||
|
||||
Having said that, this is not a completely accurate model of the encoder,
|
||||
so maybe we will need to shift one step less.
|
||||
*/
|
||||
|
||||
let mut prev1 = channel_state.prev1;
|
||||
let mut prev2 = channel_state.prev2;
|
||||
let k1 = Self::FILTER_K1[filter as usize] as i32;
|
||||
let k2 = Self::FILTER_K2[filter as usize] as i32;
|
||||
let mut right_shift = 0;
|
||||
let mut s_min = 0;
|
||||
let mut s_max = 0;
|
||||
|
||||
for i in 0..28 {
|
||||
let raw_sample = if i >= sample_limit {0} else {samples[(i*pitch) as usize]} as i32;
|
||||
let prev_values = (k1*prev1 + k2*prev2 + (1 << 5)) >> 6;
|
||||
let sample = raw_sample - prev_values;
|
||||
|
||||
if sample < s_min {
|
||||
s_min = sample;
|
||||
}
|
||||
|
||||
if sample > s_max {
|
||||
s_max = sample;
|
||||
}
|
||||
|
||||
prev2 = prev1;
|
||||
prev1 = raw_sample;
|
||||
}
|
||||
|
||||
while right_shift < shift_range && (s_max >> right_shift) > (std::i16::MAX as i32 >> shift_range) {
|
||||
right_shift += 1;
|
||||
}
|
||||
|
||||
while right_shift < shift_range && (s_min >> right_shift) < (std::i16::MIN as i32 >> shift_range) {
|
||||
right_shift += 1;
|
||||
}
|
||||
|
||||
let min_shift = shift_range - right_shift;
|
||||
if 0 <= min_shift && min_shift <= shift_range {
|
||||
Ok(min_shift)
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("0 <= {} && {} <= {} was not satisfied with min_shift: {}", min_shift, min_shift, shift_range, min_shift)))
|
||||
}
|
||||
}
|
||||
|
||||
fn samples_per_block_and_limit(input: &[i16], sample_depth: SampleDepth, orality: Orality) -> (i32, i32) {
|
||||
let samples_per_block = match sample_depth {
|
||||
SampleDepth::Normal => 224,
|
||||
SampleDepth::High => 112,
|
||||
};
|
||||
|
||||
let sample_limit = match orality {
|
||||
Orality::Stereo => input.len()*2,
|
||||
Orality::Mono => input.len(),
|
||||
};
|
||||
|
||||
(samples_per_block, sample_limit as i32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ChannelState {
|
||||
qerr: i32, // quanitisation error
|
||||
mse: i64, // mean square error
|
||||
prev1: i32,
|
||||
prev2: i32
|
||||
}
|
||||
|
||||
impl std::default::Default for ChannelState {
|
||||
fn default() -> Self {
|
||||
ChannelState{qerr: 0, mse: 0, prev1: 0, prev2: 0}
|
||||
}
|
||||
}
|
||||
|
||||
struct EncodingOffsets {
|
||||
sample: usize,
|
||||
sample_limit: i32,
|
||||
pitch: i32,
|
||||
data: usize,
|
||||
data_shift: i32,
|
||||
data_pitch: i32,
|
||||
}
|
||||
|
||||
const STEREO_4BIT: ([EncodingOffsets; 4], [EncodingOffsets; 4]) = (
|
||||
[
|
||||
EncodingOffsets{sample: 0, sample_limit: 0, pitch: 2, data: 0x10, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 1, sample_limit: 0, pitch: 2, data: 0x10, data_shift: 4, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56, sample_limit: -28, pitch: 2, data: 0x11, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56 + 1, sample_limit: -28, pitch: 2, data: 0x11, data_shift: 4, data_pitch: 4},
|
||||
],
|
||||
[
|
||||
EncodingOffsets{sample: 56*2, sample_limit: -28*2, pitch: 2, data: 0x12, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56*2 + 1, sample_limit: -28*2, pitch: 2, data: 0x12, data_shift: 4, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56*3, sample_limit: -28*3, pitch: 2, data: 0x13, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56*3 + 1, sample_limit: -28*3, pitch: 2, data: 0x13, data_shift: 4, data_pitch: 4}
|
||||
]
|
||||
);
|
||||
const MONO_4BIT: ([EncodingOffsets; 4], [EncodingOffsets; 4]) = (
|
||||
[
|
||||
EncodingOffsets{sample: 0, sample_limit: 0, pitch: 1, data: 0x10, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28, sample_limit: -28, pitch: 1, data: 0x10, data_shift: 4, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*2, sample_limit: -28*2, pitch: 1, data: 0x11, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*3, sample_limit: -28*3, pitch: 1, data: 0x11, data_shift: 4, data_pitch: 4},
|
||||
],
|
||||
[
|
||||
EncodingOffsets{sample: 28*4, sample_limit: -28*4, pitch: 1, data: 0x12, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*5, sample_limit: -28*5, pitch: 1, data: 0x12, data_shift: 4, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*6, sample_limit: -28*6, pitch: 1, data: 0x13, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*7, sample_limit: -28*7, pitch: 1, data: 0x13, data_shift: 4, data_pitch: 4}
|
||||
]
|
||||
);
|
||||
|
||||
const STEREO_8BIT: [EncodingOffsets;4] = [
|
||||
EncodingOffsets{sample: 0, sample_limit: 0, pitch: 2, data: 0x10, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 1, sample_limit: 0, pitch: 2, data: 0x11, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56, sample_limit: -28, pitch: 2, data: 0x12, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 56 + 1, sample_limit: -28, pitch: 2, data: 0x13, data_shift: 0, data_pitch: 4},
|
||||
];
|
||||
const MONO_8BIT: [EncodingOffsets;4] = [
|
||||
EncodingOffsets{sample: 0, sample_limit: 0, pitch: 1, data: 0x10, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28, sample_limit: -28, pitch: 1, data: 0x11, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*2, sample_limit: -28*2, pitch: 1, data: 0x12, data_shift: 0, data_pitch: 4},
|
||||
EncodingOffsets{sample: 28*3, sample_limit: -28*3, pitch: 1, data: 0x13, data_shift: 0, data_pitch: 4},
|
||||
];
|
||||
@@ -1,69 +1,69 @@
|
||||
use clap::{Args, ValueEnum};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Arguments {
|
||||
#[clap(value_enum, value_parser)]
|
||||
pub color_depth: ColorType,
|
||||
|
||||
#[clap(value_enum, value_parser, default_value_t=ClutAlignment::None)]
|
||||
pub clut_align: ClutAlignment,
|
||||
|
||||
#[clap(long="semi-trans", default_value_t=false)]
|
||||
pub semi_transparent: bool,
|
||||
|
||||
#[clap(long="color-trans", default_value_t=false)]
|
||||
pub transparent_palette: bool
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Point {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub const POINT_VALUE_NAME:&'static str = "{x,y}";
|
||||
}
|
||||
|
||||
impl std::default::Default for Point {
|
||||
fn default() -> Self {
|
||||
Point{x: 0, y: 0}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Point {
|
||||
fn to_string(&self) -> std::string::String {
|
||||
"{0,0}".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Point {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let values:Vec<&str> = s.split(&['{', ',', '}']).filter_map(|value| if value.is_empty() {None} else {Some(value)}).collect();
|
||||
|
||||
if values.len() != 2 {
|
||||
return Err(format!("Two values expected for Point but found {}", values.len()));
|
||||
}
|
||||
|
||||
let x = values[0].parse().map_err(|e| format!("Failed converting 'x' for Point: {e}"))?;
|
||||
let y = values[1].parse().map_err(|e| format!("Failed converting 'y' for Point: {e}"))?;
|
||||
Ok(Point{x, y})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
pub enum ColorType{
|
||||
Clut4,
|
||||
Clut8,
|
||||
Full16,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
pub enum ClutAlignment {
|
||||
None,
|
||||
Linear,
|
||||
Block
|
||||
use clap::{Args, ValueEnum};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Arguments {
|
||||
#[clap(value_enum, value_parser)]
|
||||
pub color_depth: ColorType,
|
||||
|
||||
#[clap(value_enum, value_parser, default_value_t=ClutAlignment::None)]
|
||||
pub clut_align: ClutAlignment,
|
||||
|
||||
#[clap(long="semi-trans", default_value_t=false)]
|
||||
pub semi_transparent: bool,
|
||||
|
||||
#[clap(long="color-trans", default_value_t=false)]
|
||||
pub transparent_palette: bool
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Point {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub const POINT_VALUE_NAME:&'static str = "{x,y}";
|
||||
}
|
||||
|
||||
impl std::default::Default for Point {
|
||||
fn default() -> Self {
|
||||
Point{x: 0, y: 0}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Point {
|
||||
fn to_string(&self) -> std::string::String {
|
||||
"{0,0}".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Point {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let values:Vec<&str> = s.split(&['{', ',', '}']).filter_map(|value| if value.is_empty() {None} else {Some(value)}).collect();
|
||||
|
||||
if values.len() != 2 {
|
||||
return Err(format!("Two values expected for Point but found {}", values.len()));
|
||||
}
|
||||
|
||||
let x = values[0].parse().map_err(|e| format!("Failed converting 'x' for Point: {e}"))?;
|
||||
let y = values[1].parse().map_err(|e| format!("Failed converting 'y' for Point: {e}"))?;
|
||||
Ok(Point{x, y})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
pub enum ColorType{
|
||||
Clut4,
|
||||
Clut8,
|
||||
Full16,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
pub enum ClutAlignment {
|
||||
None,
|
||||
Linear,
|
||||
Block
|
||||
}
|
||||
@@ -1,150 +1,150 @@
|
||||
pub mod args;
|
||||
pub mod color_clut;
|
||||
pub mod color_full16;
|
||||
pub mod reduced_tim;
|
||||
pub mod tim;
|
||||
pub mod types;
|
||||
|
||||
use args::{ColorType, ClutAlignment, Point};
|
||||
use color_clut::{IndexedImage, OutputType};
|
||||
use color_full16::{RgbaImage, RgbImage};
|
||||
use types::{Color as PSXColor, HeaderEncoder, PSXImageConverter, Rect};
|
||||
use image::{DynamicImage, io::Reader as ImageReader};
|
||||
use std::io::{Cursor, Write};
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
fn modify_palette(mut image: IndexedImage, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> IndexedImage {
|
||||
if semi_transparent {
|
||||
for color in image.palette.iter_mut() {
|
||||
*color = PSXColor::semi_transparent(color.get_red(), color.get_green(), color.get_blue());
|
||||
}
|
||||
}
|
||||
|
||||
if transparent_palette {
|
||||
if clut_align == ClutAlignment::Block {
|
||||
for color in image.palette.iter_mut().step_by(16) {
|
||||
*color = PSXColor::transparent();
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
if let Some(first_color) = image.palette.get_mut(0) {
|
||||
*first_color = PSXColor::transparent();
|
||||
}
|
||||
}
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
fn encode<T: PSXImageConverter>(header_conv: &mut dyn HeaderEncoder, image: T, tex_pos: Point, clut_pos: Point, color_depth: ColorType, clut_align: ClutAlignment, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let (width, height) = {
|
||||
fn return_error(clut_type: u32, div: u32, width: u16, height: u16) -> Result<(u16, u16), Error> {
|
||||
return Err(Error::from_callback(|| {format!("CLUT {} images require a width divideable by {} (found width: {}/{}={}, height: {})", clut_type, div, width, div, (width as f32/div as f32), height)}));
|
||||
}
|
||||
|
||||
let width = image.width();
|
||||
let height = image.height();
|
||||
match color_depth {
|
||||
ColorType::Clut4 => {
|
||||
if width & 3 == 0 {
|
||||
Ok((width/4, height))
|
||||
}
|
||||
|
||||
else {
|
||||
return_error(4, 4, width, height)
|
||||
}
|
||||
},
|
||||
ColorType::Clut8 => {
|
||||
if width & 1 == 0 {
|
||||
Ok((width/2, height))
|
||||
}
|
||||
|
||||
else {
|
||||
return_error(8, 2, width, height)
|
||||
}
|
||||
},
|
||||
ColorType::Full16 => {
|
||||
Ok((width, height))
|
||||
}
|
||||
}
|
||||
}?;
|
||||
let palette = image.get_palette();
|
||||
let (pal_width, pal_height) = {
|
||||
if let Some(palette) = &palette {
|
||||
let pal_length_adjusted = {
|
||||
let pal_length = palette.len();
|
||||
if pal_length <= 16 {
|
||||
16u16
|
||||
}
|
||||
|
||||
else {
|
||||
256u16
|
||||
}
|
||||
};
|
||||
match clut_align {
|
||||
ClutAlignment::None |
|
||||
ClutAlignment::Linear => (pal_length_adjusted, 1u16),
|
||||
ClutAlignment::Block => (16u16, pal_length_adjusted/16u16),
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
(0u16, 0u16)
|
||||
}
|
||||
};
|
||||
|
||||
header_conv.encode_settings(color_depth, Rect::new(tex_pos.x, tex_pos.y, width, height), Rect::new(clut_pos.x, clut_pos.y, pal_width, pal_height))?;
|
||||
|
||||
header_conv.write_header(output)?;
|
||||
header_conv.write_clut_header(output)?;
|
||||
if let Some(palette) = palette {
|
||||
let mut color_count = pal_width*pal_height;
|
||||
for color in palette {
|
||||
tool_helper::raw::write_raw(output, color)?;
|
||||
color_count -= 1;
|
||||
}
|
||||
|
||||
while color_count > 0 {
|
||||
tool_helper::raw::write_raw(output, &PSXColor::black())?;
|
||||
color_count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
header_conv.write_pixel_header(output)?;
|
||||
for color in image {
|
||||
tool_helper::raw::write_raw(output, &color)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_full16(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write, tex_pos: Point) -> Result<(), Error> {
|
||||
match ImageReader::new(Cursor::new(tool_helper::input_to_vec(input)?)).with_guessed_format()?.decode() {
|
||||
Ok(image) => {
|
||||
match image {
|
||||
DynamicImage::ImageRgb8(image) => encode(header_conv, RgbImage::new(image), tex_pos, Point::default(), ColorType::Full16, ClutAlignment::None, output),
|
||||
DynamicImage::ImageRgba8(image) => encode(header_conv, RgbaImage::new(image), tex_pos, Point::default(), ColorType::Full16, ClutAlignment::None, output),
|
||||
|
||||
_ => Err(Error::from_str("Only RGB and RGBA images are supported for 16bit encoding"))
|
||||
}
|
||||
},
|
||||
Err(error) => Err(Error::from_error(error))
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_palette_based(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write, tex_pos: Point, clut_pos: Point, color_type: ColorType, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> Result<(), Error> {
|
||||
match png::Decoder::new(input).read_info() {
|
||||
Ok(reader) => {
|
||||
let output_type = {
|
||||
match color_type {
|
||||
ColorType::Clut4 => OutputType::FourBit,
|
||||
ColorType::Clut8 => OutputType::EightBit,
|
||||
_ => return Err(Error::from_str("ColorType not supported"))
|
||||
}
|
||||
};
|
||||
|
||||
encode(header_conv, modify_palette(IndexedImage::new(reader, output_type)?, clut_align, semi_transparent, transparent_palette), tex_pos, clut_pos, color_type, clut_align, output)
|
||||
},
|
||||
Err(error) => Err(Error::from_error(error))
|
||||
}
|
||||
pub mod args;
|
||||
pub mod color_clut;
|
||||
pub mod color_full16;
|
||||
pub mod reduced_tim;
|
||||
pub mod tim;
|
||||
pub mod types;
|
||||
|
||||
use args::{ColorType, ClutAlignment, Point};
|
||||
use color_clut::{IndexedImage, OutputType};
|
||||
use color_full16::{RgbaImage, RgbImage};
|
||||
use types::{Color as PSXColor, HeaderEncoder, PSXImageConverter, Rect};
|
||||
use image::{DynamicImage, io::Reader as ImageReader};
|
||||
use std::io::{Cursor, Write};
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
fn modify_palette(mut image: IndexedImage, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> IndexedImage {
|
||||
if semi_transparent {
|
||||
for color in image.palette.iter_mut() {
|
||||
*color = PSXColor::semi_transparent(color.get_red(), color.get_green(), color.get_blue());
|
||||
}
|
||||
}
|
||||
|
||||
if transparent_palette {
|
||||
if clut_align == ClutAlignment::Block {
|
||||
for color in image.palette.iter_mut().step_by(16) {
|
||||
*color = PSXColor::transparent();
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
if let Some(first_color) = image.palette.get_mut(0) {
|
||||
*first_color = PSXColor::transparent();
|
||||
}
|
||||
}
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
fn encode<T: PSXImageConverter>(header_conv: &mut dyn HeaderEncoder, image: T, tex_pos: Point, clut_pos: Point, color_depth: ColorType, clut_align: ClutAlignment, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let (width, height) = {
|
||||
fn return_error(clut_type: u32, div: u32, width: u16, height: u16) -> Result<(u16, u16), Error> {
|
||||
return Err(Error::from_callback(|| {format!("CLUT {} images require a width divideable by {} (found width: {}/{}={}, height: {})", clut_type, div, width, div, (width as f32/div as f32), height)}));
|
||||
}
|
||||
|
||||
let width = image.width();
|
||||
let height = image.height();
|
||||
match color_depth {
|
||||
ColorType::Clut4 => {
|
||||
if width & 3 == 0 {
|
||||
Ok((width/4, height))
|
||||
}
|
||||
|
||||
else {
|
||||
return_error(4, 4, width, height)
|
||||
}
|
||||
},
|
||||
ColorType::Clut8 => {
|
||||
if width & 1 == 0 {
|
||||
Ok((width/2, height))
|
||||
}
|
||||
|
||||
else {
|
||||
return_error(8, 2, width, height)
|
||||
}
|
||||
},
|
||||
ColorType::Full16 => {
|
||||
Ok((width, height))
|
||||
}
|
||||
}
|
||||
}?;
|
||||
let palette = image.get_palette();
|
||||
let (pal_width, pal_height) = {
|
||||
if let Some(palette) = &palette {
|
||||
let pal_length_adjusted = {
|
||||
let pal_length = palette.len();
|
||||
if pal_length <= 16 {
|
||||
16u16
|
||||
}
|
||||
|
||||
else {
|
||||
256u16
|
||||
}
|
||||
};
|
||||
match clut_align {
|
||||
ClutAlignment::None |
|
||||
ClutAlignment::Linear => (pal_length_adjusted, 1u16),
|
||||
ClutAlignment::Block => (16u16, pal_length_adjusted/16u16),
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
(0u16, 0u16)
|
||||
}
|
||||
};
|
||||
|
||||
header_conv.encode_settings(color_depth, Rect::new(tex_pos.x, tex_pos.y, width, height), Rect::new(clut_pos.x, clut_pos.y, pal_width, pal_height))?;
|
||||
|
||||
header_conv.write_header(output)?;
|
||||
header_conv.write_clut_header(output)?;
|
||||
if let Some(palette) = palette {
|
||||
let mut color_count = pal_width*pal_height;
|
||||
for color in palette {
|
||||
tool_helper::raw::write_raw(output, color)?;
|
||||
color_count -= 1;
|
||||
}
|
||||
|
||||
while color_count > 0 {
|
||||
tool_helper::raw::write_raw(output, &PSXColor::black())?;
|
||||
color_count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
header_conv.write_pixel_header(output)?;
|
||||
for color in image {
|
||||
tool_helper::raw::write_raw(output, &color)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_full16(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write, tex_pos: Point) -> Result<(), Error> {
|
||||
match ImageReader::new(Cursor::new(tool_helper::input_to_vec(input)?)).with_guessed_format()?.decode() {
|
||||
Ok(image) => {
|
||||
match image {
|
||||
DynamicImage::ImageRgb8(image) => encode(header_conv, RgbImage::new(image), tex_pos, Point::default(), ColorType::Full16, ClutAlignment::None, output),
|
||||
DynamicImage::ImageRgba8(image) => encode(header_conv, RgbaImage::new(image), tex_pos, Point::default(), ColorType::Full16, ClutAlignment::None, output),
|
||||
|
||||
_ => Err(Error::from_str("Only RGB and RGBA images are supported for 16bit encoding"))
|
||||
}
|
||||
},
|
||||
Err(error) => Err(Error::from_error(error))
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_palette_based(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write, tex_pos: Point, clut_pos: Point, color_type: ColorType, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> Result<(), Error> {
|
||||
match png::Decoder::new(input).read_info() {
|
||||
Ok(reader) => {
|
||||
let output_type = {
|
||||
match color_type {
|
||||
ColorType::Clut4 => OutputType::FourBit,
|
||||
ColorType::Clut8 => OutputType::EightBit,
|
||||
_ => return Err(Error::from_str("ColorType not supported"))
|
||||
}
|
||||
};
|
||||
|
||||
encode(header_conv, modify_palette(IndexedImage::new(reader, output_type)?, clut_align, semi_transparent, transparent_palette), tex_pos, clut_pos, color_type, clut_align, output)
|
||||
},
|
||||
Err(error) => Err(Error::from_error(error))
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
pub mod types;
|
||||
|
||||
use super::args::{ColorType, Point};
|
||||
use std::io::Write;
|
||||
use types::Header;
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
pub type Arguments = super::args::Arguments;
|
||||
|
||||
pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let mut header_conv = Header::default();
|
||||
match args.color_depth {
|
||||
ColorType::Full16 => super::convert_full16(&mut header_conv, input, output, Point::default()),
|
||||
_ => super::convert_palette_based(&mut header_conv, input, output, Point::default(), Point::default(), args.color_depth, args.clut_align, args.semi_transparent, args.transparent_palette),
|
||||
}
|
||||
pub mod types;
|
||||
|
||||
use super::args::{ColorType, Point};
|
||||
use std::io::Write;
|
||||
use types::Header;
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
pub type Arguments = super::args::Arguments;
|
||||
|
||||
pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let mut header_conv = Header::default();
|
||||
match args.color_depth {
|
||||
ColorType::Full16 => super::convert_full16(&mut header_conv, input, output, Point::default()),
|
||||
_ => super::convert_palette_based(&mut header_conv, input, output, Point::default(), Point::default(), args.color_depth, args.clut_align, args.semi_transparent, args.transparent_palette),
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,63 @@
|
||||
use super::super::{args::ColorType, types::{HeaderEncoder, set_member_value, Rect}};
|
||||
use std::io::Write;
|
||||
use tool_helper::{bits::BitRange, raw::RawConversion, Error};
|
||||
|
||||
#[repr(packed(1))]
|
||||
pub struct Header {
|
||||
value: u32
|
||||
}
|
||||
|
||||
impl Header {
|
||||
const TEX_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(0, 8);
|
||||
const TEX_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(9, 16);
|
||||
const CLUT_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(17, 22);
|
||||
const CLUT_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(23, 31);
|
||||
}
|
||||
|
||||
impl Default for Header {
|
||||
fn default() -> Self {
|
||||
Header{value: 0}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeaderEncoder for Header {
|
||||
fn encode_settings(&mut self, _color_type: ColorType, tex_rect: Rect, clut_rect: Rect) -> Result<(), Error> {
|
||||
let clut_width = clut_rect.width;
|
||||
let clut_height = clut_rect.height;
|
||||
let tex_width = tex_rect.width;
|
||||
let tex_height = tex_rect.height;
|
||||
|
||||
if tex_width & 1 == 1 || tex_height & 1 == 1 {
|
||||
Err(Error::from_text(format!("Image size (width: {}, height: {}) needs to be even", tex_width, tex_height)))
|
||||
}
|
||||
|
||||
else {
|
||||
let value = set_member_value!(set_member_value!(set_member_value!(set_member_value!(0,
|
||||
tex_width, 1, u32),
|
||||
tex_height, 1, u32),
|
||||
clut_width, 4, u32),
|
||||
clut_height, 0, u32);
|
||||
|
||||
self.value = value;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
|
||||
Ok(output.write(&self.convert_to_raw())?)
|
||||
}
|
||||
|
||||
fn write_clut_header(&self, _output: &mut dyn Write) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn write_pixel_header(&self, _output: &mut dyn Write) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl RawConversion<4> for Header {
|
||||
fn convert_to_raw(&self) -> [u8; 4] {
|
||||
self.value.to_le_bytes()
|
||||
}
|
||||
use super::super::{args::ColorType, types::{HeaderEncoder, set_member_value, Rect}};
|
||||
use std::io::Write;
|
||||
use tool_helper::{bits::BitRange, raw::RawConversion, Error};
|
||||
|
||||
#[repr(packed(1))]
|
||||
pub struct Header {
|
||||
value: u32
|
||||
}
|
||||
|
||||
impl Header {
|
||||
const TEX_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(0, 8);
|
||||
const TEX_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(9, 16);
|
||||
const CLUT_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(17, 22);
|
||||
const CLUT_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(23, 31);
|
||||
}
|
||||
|
||||
impl Default for Header {
|
||||
fn default() -> Self {
|
||||
Header{value: 0}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeaderEncoder for Header {
|
||||
fn encode_settings(&mut self, _color_type: ColorType, tex_rect: Rect, clut_rect: Rect) -> Result<(), Error> {
|
||||
let clut_width = clut_rect.width;
|
||||
let clut_height = clut_rect.height;
|
||||
let tex_width = tex_rect.width;
|
||||
let tex_height = tex_rect.height;
|
||||
|
||||
if tex_width & 1 == 1 || tex_height & 1 == 1 {
|
||||
Err(Error::from_text(format!("Image size (width: {}, height: {}) needs to be even", tex_width, tex_height)))
|
||||
}
|
||||
|
||||
else {
|
||||
let value = set_member_value!(set_member_value!(set_member_value!(set_member_value!(0,
|
||||
tex_width, 1, u32),
|
||||
tex_height, 1, u32),
|
||||
clut_width, 4, u32),
|
||||
clut_height, 0, u32);
|
||||
|
||||
self.value = value;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
|
||||
Ok(output.write(&self.convert_to_raw())?)
|
||||
}
|
||||
|
||||
fn write_clut_header(&self, _output: &mut dyn Write) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn write_pixel_header(&self, _output: &mut dyn Write) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl RawConversion<4> for Header {
|
||||
fn convert_to_raw(&self) -> [u8; 4] {
|
||||
self.value.to_le_bytes()
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,29 @@
|
||||
pub mod types;
|
||||
|
||||
use super::args::{ColorType, Point};
|
||||
use clap::Args;
|
||||
use std::io::Write;
|
||||
use types::Header;
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Arguments {
|
||||
#[clap(flatten)]
|
||||
global: super::args::Arguments,
|
||||
|
||||
#[clap(long, value_parser, default_value_t, value_name = Point::POINT_VALUE_NAME)]
|
||||
clut_pos: Point,
|
||||
|
||||
#[clap(long, value_parser, default_value_t, value_name = Point::POINT_VALUE_NAME)]
|
||||
tex_pos: Point,
|
||||
}
|
||||
|
||||
pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let global_args = args.global;
|
||||
let mut header_conv = Header::default();
|
||||
|
||||
match global_args.color_depth {
|
||||
ColorType::Full16 => super::convert_full16(&mut header_conv, input, output, args.tex_pos),
|
||||
_ => super::convert_palette_based(&mut header_conv, input, output, args.tex_pos, args.clut_pos, global_args.color_depth, global_args.clut_align, global_args.semi_transparent, global_args.transparent_palette),
|
||||
}
|
||||
pub mod types;
|
||||
|
||||
use super::args::{ColorType, Point};
|
||||
use clap::Args;
|
||||
use std::io::Write;
|
||||
use types::Header;
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Arguments {
|
||||
#[clap(flatten)]
|
||||
global: super::args::Arguments,
|
||||
|
||||
#[clap(long, value_parser, default_value_t, value_name = Point::POINT_VALUE_NAME)]
|
||||
clut_pos: Point,
|
||||
|
||||
#[clap(long, value_parser, default_value_t, value_name = Point::POINT_VALUE_NAME)]
|
||||
tex_pos: Point,
|
||||
}
|
||||
|
||||
pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
let global_args = args.global;
|
||||
let mut header_conv = Header::default();
|
||||
|
||||
match global_args.color_depth {
|
||||
ColorType::Full16 => super::convert_full16(&mut header_conv, input, output, args.tex_pos),
|
||||
_ => super::convert_palette_based(&mut header_conv, input, output, args.tex_pos, args.clut_pos, global_args.color_depth, global_args.clut_align, global_args.semi_transparent, global_args.transparent_palette),
|
||||
}
|
||||
}
|
||||
@@ -1,100 +1,100 @@
|
||||
use super::super::{args::ColorType, types::{HeaderEncoder, Rect}};
|
||||
use std::io::Write;
|
||||
use tool_helper::{bits::{Bit, BitRange}, raw::RawConversion, Error};
|
||||
|
||||
|
||||
pub struct Header {
|
||||
flag: u32,
|
||||
clut_block: DataBlock,
|
||||
pixel_block: DataBlock,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
const ID_VALUE: u32 = BitRange::from_to(0, 7).as_value(0x10) as u32;
|
||||
const ID_VERSION_VALUE:u32 = BitRange::from_to(8, 15).as_value(0x0) as u32;
|
||||
|
||||
const FLAG_PMODE_BIT_RANGE: BitRange = BitRange::from_to(0, 2);
|
||||
const FLAG_CF_BIT: Bit = Bit::at(3);
|
||||
|
||||
const ID:u32 = Self::ID_VALUE | Self::ID_VERSION_VALUE;
|
||||
}
|
||||
|
||||
impl Default for Header {
|
||||
fn default() -> Self {
|
||||
Header{flag: 0, clut_block: DataBlock::default(), pixel_block: DataBlock::default()}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeaderEncoder for Header {
|
||||
fn encode_settings(&mut self, color_type: ColorType, tex_rect: Rect, clut_rect: Rect) -> Result<(), Error> {
|
||||
self.flag = match color_type {
|
||||
ColorType::Clut4 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x0) | Self::FLAG_CF_BIT.as_value(true)) as u32,
|
||||
ColorType::Clut8 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x1) | Self::FLAG_CF_BIT.as_value(true)) as u32,
|
||||
ColorType::Full16 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x2) | Self::FLAG_CF_BIT.as_value(false)) as u32,
|
||||
};
|
||||
|
||||
self.clut_block = DataBlock::new(clut_rect);
|
||||
self.pixel_block = DataBlock::new(tex_rect);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
|
||||
let bytes = output.write(&Header::ID.to_le_bytes())?;
|
||||
Ok(bytes + output.write(&self.flag.to_le_bytes())?)
|
||||
}
|
||||
|
||||
fn write_clut_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
|
||||
if self.clut_block.w > 0 {
|
||||
Ok(output.write(&self.clut_block.convert_to_raw())?)
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pixel_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
|
||||
Ok(output.write(&self.pixel_block.convert_to_raw())?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataBlock {
|
||||
bytes: u32,
|
||||
x: u16,
|
||||
y: u16,
|
||||
w: u16,
|
||||
h: u16,
|
||||
}
|
||||
|
||||
impl DataBlock {
|
||||
const RAW_HEADER_SIZE: usize = (4*std::mem::size_of::<u16>()) + std::mem::size_of::<u32>();
|
||||
|
||||
pub fn new(rect: Rect) -> DataBlock {
|
||||
let x = rect.x;
|
||||
let y = rect.y;
|
||||
let w = rect.width;
|
||||
let h = rect.height;
|
||||
|
||||
let bytes = ((w as usize*h as usize*std::mem::size_of::<u16>()) + Self::RAW_HEADER_SIZE) as u32;
|
||||
DataBlock{bytes, x, y, w, h}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for DataBlock {
|
||||
fn default() -> Self {
|
||||
DataBlock{bytes: 0, x: 0, y: 0, w: 0, h: 0}
|
||||
}
|
||||
}
|
||||
|
||||
impl RawConversion<{Self::RAW_HEADER_SIZE}> for DataBlock {
|
||||
fn convert_to_raw(&self) -> [u8; Self::RAW_HEADER_SIZE] {
|
||||
let mut raw = [0u8; Self::RAW_HEADER_SIZE];
|
||||
|
||||
raw[ 0..4].copy_from_slice(&self.bytes.to_le_bytes());
|
||||
raw[ 4..6].copy_from_slice(&self.x.to_le_bytes());
|
||||
raw[ 6..8].copy_from_slice(&self.y.to_le_bytes());
|
||||
raw[ 8..10].copy_from_slice(&self.w.to_le_bytes());
|
||||
raw[10..12].copy_from_slice(&self.h.to_le_bytes());
|
||||
raw
|
||||
}
|
||||
use super::super::{args::ColorType, types::{HeaderEncoder, Rect}};
|
||||
use std::io::Write;
|
||||
use tool_helper::{bits::{Bit, BitRange}, raw::RawConversion, Error};
|
||||
|
||||
|
||||
pub struct Header {
|
||||
flag: u32,
|
||||
clut_block: DataBlock,
|
||||
pixel_block: DataBlock,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
const ID_VALUE: u32 = BitRange::from_to(0, 7).as_value(0x10) as u32;
|
||||
const ID_VERSION_VALUE:u32 = BitRange::from_to(8, 15).as_value(0x0) as u32;
|
||||
|
||||
const FLAG_PMODE_BIT_RANGE: BitRange = BitRange::from_to(0, 2);
|
||||
const FLAG_CF_BIT: Bit = Bit::at(3);
|
||||
|
||||
const ID:u32 = Self::ID_VALUE | Self::ID_VERSION_VALUE;
|
||||
}
|
||||
|
||||
impl Default for Header {
|
||||
fn default() -> Self {
|
||||
Header{flag: 0, clut_block: DataBlock::default(), pixel_block: DataBlock::default()}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeaderEncoder for Header {
|
||||
fn encode_settings(&mut self, color_type: ColorType, tex_rect: Rect, clut_rect: Rect) -> Result<(), Error> {
|
||||
self.flag = match color_type {
|
||||
ColorType::Clut4 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x0) | Self::FLAG_CF_BIT.as_value(true)) as u32,
|
||||
ColorType::Clut8 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x1) | Self::FLAG_CF_BIT.as_value(true)) as u32,
|
||||
ColorType::Full16 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x2) | Self::FLAG_CF_BIT.as_value(false)) as u32,
|
||||
};
|
||||
|
||||
self.clut_block = DataBlock::new(clut_rect);
|
||||
self.pixel_block = DataBlock::new(tex_rect);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
|
||||
let bytes = output.write(&Header::ID.to_le_bytes())?;
|
||||
Ok(bytes + output.write(&self.flag.to_le_bytes())?)
|
||||
}
|
||||
|
||||
fn write_clut_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
|
||||
if self.clut_block.w > 0 {
|
||||
Ok(output.write(&self.clut_block.convert_to_raw())?)
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pixel_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
|
||||
Ok(output.write(&self.pixel_block.convert_to_raw())?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataBlock {
|
||||
bytes: u32,
|
||||
x: u16,
|
||||
y: u16,
|
||||
w: u16,
|
||||
h: u16,
|
||||
}
|
||||
|
||||
impl DataBlock {
|
||||
const RAW_HEADER_SIZE: usize = (4*std::mem::size_of::<u16>()) + std::mem::size_of::<u32>();
|
||||
|
||||
pub fn new(rect: Rect) -> DataBlock {
|
||||
let x = rect.x;
|
||||
let y = rect.y;
|
||||
let w = rect.width;
|
||||
let h = rect.height;
|
||||
|
||||
let bytes = ((w as usize*h as usize*std::mem::size_of::<u16>()) + Self::RAW_HEADER_SIZE) as u32;
|
||||
DataBlock{bytes, x, y, w, h}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for DataBlock {
|
||||
fn default() -> Self {
|
||||
DataBlock{bytes: 0, x: 0, y: 0, w: 0, h: 0}
|
||||
}
|
||||
}
|
||||
|
||||
impl RawConversion<{Self::RAW_HEADER_SIZE}> for DataBlock {
|
||||
fn convert_to_raw(&self) -> [u8; Self::RAW_HEADER_SIZE] {
|
||||
let mut raw = [0u8; Self::RAW_HEADER_SIZE];
|
||||
|
||||
raw[ 0..4].copy_from_slice(&self.bytes.to_le_bytes());
|
||||
raw[ 4..6].copy_from_slice(&self.x.to_le_bytes());
|
||||
raw[ 6..8].copy_from_slice(&self.y.to_le_bytes());
|
||||
raw[ 8..10].copy_from_slice(&self.w.to_le_bytes());
|
||||
raw[10..12].copy_from_slice(&self.h.to_le_bytes());
|
||||
raw
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
pub mod audio;
|
||||
pub mod images;
|
||||
pub mod audio;
|
||||
pub mod images;
|
||||
pub mod nothing;
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::io::Write;
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
pub fn copy(input: &mut Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
std::io::copy(input, output)?;
|
||||
Ok(())
|
||||
use std::io::Write;
|
||||
use tool_helper::{Error, Input};
|
||||
|
||||
pub fn copy(input: &mut Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||
std::io::copy(input, output)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxreadmap
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxreadmap
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
||||
@@ -1,117 +1,117 @@
|
||||
use clap::Parser;
|
||||
use crossterm::{event::{self, Event as CEvent, KeyboardEnhancementFlags, KeyEventKind, PushKeyboardEnhancementFlags}, execute, terminal};
|
||||
use psxreadmap::{ConsoleUI, Event, EventReceiver, Terminal, load_memory_map, UIState};
|
||||
use std::{io::{self, Stdout}, path::PathBuf, sync::mpsc, thread, time::{Duration, Instant}};
|
||||
use tool_helper::{Error, exit_with_error, open_output};
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
|
||||
pub const RUN_DUMP_TOOL_IN_WSL:bool = cfg!(windows);
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(about = "Opens and scans an ELF file to print extended memory information", long_about = None)]
|
||||
struct CommandLine {
|
||||
#[clap(value_parser, help="Input ELF file for scannning")]
|
||||
input: PathBuf,
|
||||
#[clap(long="wsl", help="Run \"objdump\" in WSL", default_value_t=RUN_DUMP_TOOL_IN_WSL)]
|
||||
use_wsl: bool,
|
||||
#[clap(long="raw", default_value_t=false)]
|
||||
raw_dump: bool,
|
||||
#[clap(short='o', help="Output a memory map file with running the tool")]
|
||||
output: Option<PathBuf>
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
match CommandLine::try_parse() {
|
||||
Ok(cmd_line) => {
|
||||
match run_main(cmd_line) {
|
||||
Ok(_) => (),
|
||||
Err(error) => exit_with_error(error)
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
println!("{})", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_main(cmd: CommandLine) -> Result<(), Error> {
|
||||
if cmd.raw_dump {
|
||||
psxreadmap::get_tool_output(cmd.use_wsl, cmd.input, open_output(&cmd.output)?)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let memory_map = load_memory_map(cmd.use_wsl, cmd.input)?; dump_memory_map(cmd.output, &memory_map)?;
|
||||
let rx = start_event_loop();
|
||||
let terminal = setup_console()?;
|
||||
let mut console_ui = ConsoleUI::new(rx, terminal);
|
||||
|
||||
console_ui.update_data(memory_map)?;
|
||||
loop {
|
||||
match console_ui.update()? {
|
||||
UIState::Alive => (),
|
||||
UIState::Render => {console_ui.render()?;}
|
||||
UIState::Terminated => break
|
||||
}
|
||||
}
|
||||
|
||||
console_ui.close()
|
||||
}
|
||||
|
||||
fn dump_memory_map(output: Option<PathBuf>, memory_map: &readmap::types::MemoryMap) -> Result<(), Error> {
|
||||
if let Some(output) = output {
|
||||
let output = tool_helper::open_output(&Some(output))?;
|
||||
|
||||
readmap::dump::write(output, &memory_map)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_event_loop() -> EventReceiver {
|
||||
// Set up a mpsc (multiproducer, single consumer) channel to communicate between the input handler and the rendering loop.
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let tick_rate = Duration::from_millis(200);
|
||||
thread::spawn(move || {
|
||||
let mut last_tick = Instant::now();
|
||||
|
||||
tx.send(Event::ForceRender).expect("Send ForceRender command!");
|
||||
loop {
|
||||
let timeout = tick_rate.checked_sub(last_tick.elapsed()).unwrap_or_else(|| Duration::from_secs(0));
|
||||
if event::poll(timeout).expect("Event poll working") {
|
||||
if let CEvent::Key(key) = event::read().expect("Can read key input") {
|
||||
if key.kind == KeyEventKind::Release {
|
||||
tx.send(Event::Input(key)).expect("Can send KeyInput");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
if let Ok(_) = tx.send(Event::Tick) {
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
fn setup_console() -> Result<Terminal, Error> {
|
||||
fn open_stdout() -> Result<Stdout, Error> {
|
||||
let mut stdout = io::stdout();
|
||||
if cfg!(unix) {
|
||||
execute!(stdout, PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES))?;
|
||||
}
|
||||
Ok(stdout)
|
||||
}
|
||||
|
||||
terminal::enable_raw_mode()?;
|
||||
|
||||
// Setup Crossterm for the Terminal
|
||||
let stdout = open_stdout()?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
terminal.clear()?;
|
||||
Ok(terminal)
|
||||
use clap::Parser;
|
||||
use crossterm::{event::{self, Event as CEvent, KeyboardEnhancementFlags, KeyEventKind, PushKeyboardEnhancementFlags}, execute, terminal};
|
||||
use psxreadmap::{ConsoleUI, Event, EventReceiver, Terminal, load_memory_map, UIState};
|
||||
use std::{io::{self, Stdout}, path::PathBuf, sync::mpsc, thread, time::{Duration, Instant}};
|
||||
use tool_helper::{Error, exit_with_error, open_output};
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
|
||||
pub const RUN_DUMP_TOOL_IN_WSL:bool = cfg!(windows);
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(about = "Opens and scans an ELF file to print extended memory information", long_about = None)]
|
||||
struct CommandLine {
|
||||
#[clap(value_parser, help="Input ELF file for scannning")]
|
||||
input: PathBuf,
|
||||
#[clap(long="wsl", help="Run \"objdump\" in WSL", default_value_t=RUN_DUMP_TOOL_IN_WSL)]
|
||||
use_wsl: bool,
|
||||
#[clap(long="raw", default_value_t=false)]
|
||||
raw_dump: bool,
|
||||
#[clap(short='o', help="Output a memory map file with running the tool")]
|
||||
output: Option<PathBuf>
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
match CommandLine::try_parse() {
|
||||
Ok(cmd_line) => {
|
||||
match run_main(cmd_line) {
|
||||
Ok(_) => (),
|
||||
Err(error) => exit_with_error(error)
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
println!("{})", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_main(cmd: CommandLine) -> Result<(), Error> {
|
||||
if cmd.raw_dump {
|
||||
psxreadmap::get_tool_output(cmd.use_wsl, cmd.input, open_output(&cmd.output)?)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let memory_map = load_memory_map(cmd.use_wsl, cmd.input)?; dump_memory_map(cmd.output, &memory_map)?;
|
||||
let rx = start_event_loop();
|
||||
let terminal = setup_console()?;
|
||||
let mut console_ui = ConsoleUI::new(rx, terminal);
|
||||
|
||||
console_ui.update_data(memory_map)?;
|
||||
loop {
|
||||
match console_ui.update()? {
|
||||
UIState::Alive => (),
|
||||
UIState::Render => {console_ui.render()?;}
|
||||
UIState::Terminated => break
|
||||
}
|
||||
}
|
||||
|
||||
console_ui.close()
|
||||
}
|
||||
|
||||
fn dump_memory_map(output: Option<PathBuf>, memory_map: &readmap::types::MemoryMap) -> Result<(), Error> {
|
||||
if let Some(output) = output {
|
||||
let output = tool_helper::open_output(&Some(output))?;
|
||||
|
||||
readmap::dump::write(output, &memory_map)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_event_loop() -> EventReceiver {
|
||||
// Set up a mpsc (multiproducer, single consumer) channel to communicate between the input handler and the rendering loop.
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let tick_rate = Duration::from_millis(200);
|
||||
thread::spawn(move || {
|
||||
let mut last_tick = Instant::now();
|
||||
|
||||
tx.send(Event::ForceRender).expect("Send ForceRender command!");
|
||||
loop {
|
||||
let timeout = tick_rate.checked_sub(last_tick.elapsed()).unwrap_or_else(|| Duration::from_secs(0));
|
||||
if event::poll(timeout).expect("Event poll working") {
|
||||
if let CEvent::Key(key) = event::read().expect("Can read key input") {
|
||||
if key.kind == KeyEventKind::Release {
|
||||
tx.send(Event::Input(key)).expect("Can send KeyInput");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
if let Ok(_) = tx.send(Event::Tick) {
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
fn setup_console() -> Result<Terminal, Error> {
|
||||
fn open_stdout() -> Result<Stdout, Error> {
|
||||
let mut stdout = io::stdout();
|
||||
if cfg!(unix) {
|
||||
execute!(stdout, PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES))?;
|
||||
}
|
||||
Ok(stdout)
|
||||
}
|
||||
|
||||
terminal::enable_raw_mode()?;
|
||||
|
||||
// Setup Crossterm for the Terminal
|
||||
let stdout = open_stdout()?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
terminal.clear()?;
|
||||
Ok(terminal)
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = tool_helper
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = tool_helper
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
||||
@@ -1,267 +1,267 @@
|
||||
use colored::*;
|
||||
use envmnt::{ExpandOptions, ExpansionType};
|
||||
use std::{boxed::Box, fs::OpenOptions, io::{BufRead, BufReader, BufWriter, Read, Write}, path::PathBuf, str::FromStr};
|
||||
|
||||
pub mod bits;
|
||||
pub mod compress;
|
||||
pub mod raw;
|
||||
pub mod vec_helper;
|
||||
|
||||
pub type BufferedInputFile = BufReader<std::fs::File>;
|
||||
pub type Output = Box<dyn Write>;
|
||||
pub type Input = Box<dyn BufRead>;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! format_if_error {
|
||||
($result:expr, $format_text:literal) => {
|
||||
tool_helper::callback_if_any_error($result, |error_text| {
|
||||
format!($format_text, error_text=error_text)
|
||||
})
|
||||
};
|
||||
($result:expr, $format_text:literal, $($arg:expr)*) => {
|
||||
tool_helper::callback_if_any_error($result, |error_text| {
|
||||
format!($format_text, $($arg),*, error_text=error_text)
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! format_if_error_drop_cause {
|
||||
($result:expr, $format_text:literal) => {
|
||||
tool_helper::callback_if_any_error($result, |error_text| {
|
||||
format!($format_text)
|
||||
})
|
||||
};
|
||||
($result:expr, $format_text:literal, $($arg:expr)*) => {
|
||||
tool_helper::callback_if_any_error($result, |error_text| {
|
||||
format!($format_text, $($arg),*)
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_ENV_EXPAND_OPTIONS : ExpandOptions = ExpandOptions{expansion_type: Some(ExpansionType::All), default_to_empty: false};
|
||||
|
||||
pub struct Error {
|
||||
pub exit_code: i32,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
const DEFAULT_EXITCODE:i32 = -1;
|
||||
|
||||
pub fn from_str(str: &str) -> Error {
|
||||
Self::from_text(str.to_owned())
|
||||
}
|
||||
|
||||
pub fn from_text(text: String) -> Error {
|
||||
Error{exit_code: Self::DEFAULT_EXITCODE, text}
|
||||
}
|
||||
|
||||
pub fn from_error<T>(error: T) -> Error where T: std::fmt::Display {
|
||||
Error::from_text(error.to_string())
|
||||
}
|
||||
|
||||
pub fn from_core_error<T>(error: T) -> Error where T: core::fmt::Display {
|
||||
Error::from_text(error.to_string())
|
||||
}
|
||||
|
||||
pub fn from_callback<F>(callback: F) -> Error where F: Fn() -> String {
|
||||
Error::from_text(callback())
|
||||
}
|
||||
|
||||
pub fn not_implemented(function: &str) -> Error {
|
||||
Error::from_text(format!("{} not implemented yet", function))
|
||||
}
|
||||
|
||||
pub fn try_or_new<T, S>(result: std::result::Result<T, S>) -> Result<T, Error> where S: std::fmt::Display {
|
||||
match result {
|
||||
Ok(value) => Ok(value),
|
||||
Err(error) => Err(Error{exit_code: Self::DEFAULT_EXITCODE, text: error.to_string()}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ok_or_new<T, F>(option: Option<T>, error_text: F) -> Result<T, Error> where F: Fn () -> String{
|
||||
Ok(option.ok_or(Error::from_callback(error_text))?)
|
||||
}
|
||||
|
||||
pub fn print_generic_to_std_err<T: std::fmt::Display>(object: &T) {
|
||||
eprintln!("{}", format!("ERROR: {}", object).red());
|
||||
}
|
||||
|
||||
pub fn print_to_std_err(&self) {
|
||||
Self::print_generic_to_std_err(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.text)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<std::io::Error> for Error {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
Error{exit_code: -1, text: error.to_string()}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<cdtypes::Error> for Error {
|
||||
fn from(error: cdtypes::Error) -> Self {
|
||||
Error::from_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<std::convert::Infallible> for Error {
|
||||
fn from(error: std::convert::Infallible) -> Self {
|
||||
Error::from_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<std::sync::mpsc::RecvError> for Error {
|
||||
fn from(error: std::sync::mpsc::RecvError) -> Self {
|
||||
Error::from_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<hound::Error> for Error {
|
||||
fn from(error: hound::Error) -> Self {
|
||||
Error::from_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit_with_error(error: Error) {
|
||||
error.print_to_std_err();
|
||||
std::process::exit(error.exit_code);
|
||||
}
|
||||
|
||||
pub fn print_warning(str: String) {
|
||||
eprintln!("{}", str.yellow());
|
||||
}
|
||||
|
||||
pub fn prefix_if_error<T>(prefix: &str, result: Result<T, Error>) -> Result<T, Error> {
|
||||
match result {
|
||||
Ok(value) => Ok(value),
|
||||
Err(mut error) => {
|
||||
let mut new_text = String::from(prefix);
|
||||
|
||||
new_text.push_str(error.text.as_str());
|
||||
error.text = new_text;
|
||||
Err(error)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn callback_if_error<F: Fn(String) -> String, T>(result: Result<T, Error>, callback: F) -> Result<T, Error> {
|
||||
match result {
|
||||
Ok(value) => Ok(value),
|
||||
Err(mut error) => {
|
||||
error.text = callback(error.text);
|
||||
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn callback_if_any_error<F: Fn(String) -> String, T, E: std::string::ToString>(result: Result<T, E>, callback: F) -> Result<T, Error> {
|
||||
match result {
|
||||
Ok(value) => Ok(value),
|
||||
Err(error) => {
|
||||
Err(Error::from_text(callback(error.to_string())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string_with_env_from(str: &str) -> String {
|
||||
String::from(envmnt::expand(str, Some(DEFAULT_ENV_EXPAND_OPTIONS)))
|
||||
}
|
||||
|
||||
pub fn path_with_env_from(path: &str) -> PathBuf {
|
||||
PathBuf::from(envmnt::expand(path, Some(DEFAULT_ENV_EXPAND_OPTIONS)))
|
||||
}
|
||||
|
||||
pub fn open_output_file(output_path: &PathBuf) -> Result<BufWriter<std::fs::File>, Error> {
|
||||
Ok(std::io::BufWriter::new(std::fs::File::create(win_or_wsl_path(output_path)?)?))
|
||||
}
|
||||
|
||||
pub fn open_output(output_file: &Option<PathBuf>) -> Result<Output, Error> {
|
||||
match output_file {
|
||||
Some(output_path) => Ok(Box::new(open_output_file(&output_path)?)),
|
||||
None => Ok(Box::new(BufWriter::new(std::io::stdout()))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_input(input_file: &Option<PathBuf>) -> Result<Input, Error> {
|
||||
match input_file {
|
||||
Some(input_path) => Ok(Box::new(open_input_file_buffered(&input_path)?)),
|
||||
None => Ok(Box::new(BufReader::new(std::io::stdin()))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_input_file_buffered(input_path: &PathBuf) -> Result<BufferedInputFile, Error> {
|
||||
Ok(BufReader::new(std::fs::File::open(win_or_wsl_path(input_path)?)?))
|
||||
}
|
||||
|
||||
pub fn os_str_to_string(input: &std::ffi::OsStr, name: &str) -> Result<String, Error> {
|
||||
Ok(Error::ok_or_new(input.to_str(), ||format!("Converting {} to UTF-8 failed", name))?.to_owned())
|
||||
}
|
||||
|
||||
pub fn get_file_name_from_path_buf(input: &PathBuf, name: &str) -> Result<String, Error> {
|
||||
os_str_to_string(Error::ok_or_new(input.file_name(), ||format!("No {} file name found", name))?, name)
|
||||
}
|
||||
|
||||
pub fn input_to_vec(input: Input) -> Result<Vec<u8>, Error> {
|
||||
let mut data = Vec::new();
|
||||
|
||||
for byte in input.bytes() {
|
||||
data.push(byte?);
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn read_file(file_path: &PathBuf) -> Result<Vec<u8>, Error> {
|
||||
if let Some(ext) = file_path.extension() {
|
||||
if ext == "subst" {
|
||||
// File needs substitution!
|
||||
let file_content = read_file_to_string(file_path)?;
|
||||
return Ok(string_with_env_from(&file_content).into_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
match std::fs::read(win_or_wsl_path(file_path)?) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(error) => create_file_read_error(file_path, error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_file_to_string(file_path: &PathBuf) -> Result<String, Error> {
|
||||
match std::fs::read_to_string(win_or_wsl_path(file_path)?) {
|
||||
Ok(string) => Ok(string),
|
||||
Err(error) => create_file_read_error(file_path, error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_file(file_path: &PathBuf, data: Vec<u8>) -> Result<(), Error> {
|
||||
let mut file = OpenOptions::new().read(true).write(true).truncate(true).create(true).open(file_path)?;
|
||||
|
||||
file.write_all(data.as_slice())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_file_read_error<T>(file_path: &PathBuf, error: std::io::Error) -> Result<T, Error> {
|
||||
Err(Error::from_text(format!("Failed reading file {} with error: \"{}\"", file_path.display(), error)))
|
||||
}
|
||||
|
||||
fn win_or_wsl_path(path: &PathBuf) -> Result<PathBuf, Error> {
|
||||
let path = path.clone();
|
||||
if path.exists() {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
else {
|
||||
match path.into_os_string().into_string() {
|
||||
Ok(path) => Ok(PathBuf::from_str(wslpath::force_convert(path).as_str())?),
|
||||
Err(error) => Err(Error::from_text(format!("Failed converting {:?} to string for win/wsl mapping", error)))
|
||||
}
|
||||
}
|
||||
use colored::*;
|
||||
use envmnt::{ExpandOptions, ExpansionType};
|
||||
use std::{boxed::Box, fs::OpenOptions, io::{BufRead, BufReader, BufWriter, Read, Write}, path::PathBuf, str::FromStr};
|
||||
|
||||
pub mod bits;
|
||||
pub mod compress;
|
||||
pub mod raw;
|
||||
pub mod vec_helper;
|
||||
|
||||
pub type BufferedInputFile = BufReader<std::fs::File>;
|
||||
pub type Output = Box<dyn Write>;
|
||||
pub type Input = Box<dyn BufRead>;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! format_if_error {
|
||||
($result:expr, $format_text:literal) => {
|
||||
tool_helper::callback_if_any_error($result, |error_text| {
|
||||
format!($format_text, error_text=error_text)
|
||||
})
|
||||
};
|
||||
($result:expr, $format_text:literal, $($arg:expr)*) => {
|
||||
tool_helper::callback_if_any_error($result, |error_text| {
|
||||
format!($format_text, $($arg),*, error_text=error_text)
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! format_if_error_drop_cause {
|
||||
($result:expr, $format_text:literal) => {
|
||||
tool_helper::callback_if_any_error($result, |error_text| {
|
||||
format!($format_text)
|
||||
})
|
||||
};
|
||||
($result:expr, $format_text:literal, $($arg:expr)*) => {
|
||||
tool_helper::callback_if_any_error($result, |error_text| {
|
||||
format!($format_text, $($arg),*)
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_ENV_EXPAND_OPTIONS : ExpandOptions = ExpandOptions{expansion_type: Some(ExpansionType::All), default_to_empty: false};
|
||||
|
||||
pub struct Error {
|
||||
pub exit_code: i32,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
const DEFAULT_EXITCODE:i32 = -1;
|
||||
|
||||
pub fn from_str(str: &str) -> Error {
|
||||
Self::from_text(str.to_owned())
|
||||
}
|
||||
|
||||
pub fn from_text(text: String) -> Error {
|
||||
Error{exit_code: Self::DEFAULT_EXITCODE, text}
|
||||
}
|
||||
|
||||
pub fn from_error<T>(error: T) -> Error where T: std::fmt::Display {
|
||||
Error::from_text(error.to_string())
|
||||
}
|
||||
|
||||
pub fn from_core_error<T>(error: T) -> Error where T: core::fmt::Display {
|
||||
Error::from_text(error.to_string())
|
||||
}
|
||||
|
||||
pub fn from_callback<F>(callback: F) -> Error where F: Fn() -> String {
|
||||
Error::from_text(callback())
|
||||
}
|
||||
|
||||
pub fn not_implemented(function: &str) -> Error {
|
||||
Error::from_text(format!("{} not implemented yet", function))
|
||||
}
|
||||
|
||||
pub fn try_or_new<T, S>(result: std::result::Result<T, S>) -> Result<T, Error> where S: std::fmt::Display {
|
||||
match result {
|
||||
Ok(value) => Ok(value),
|
||||
Err(error) => Err(Error{exit_code: Self::DEFAULT_EXITCODE, text: error.to_string()}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ok_or_new<T, F>(option: Option<T>, error_text: F) -> Result<T, Error> where F: Fn () -> String{
|
||||
Ok(option.ok_or(Error::from_callback(error_text))?)
|
||||
}
|
||||
|
||||
pub fn print_generic_to_std_err<T: std::fmt::Display>(object: &T) {
|
||||
eprintln!("{}", format!("ERROR: {}", object).red());
|
||||
}
|
||||
|
||||
pub fn print_to_std_err(&self) {
|
||||
Self::print_generic_to_std_err(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.text)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<std::io::Error> for Error {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
Error{exit_code: -1, text: error.to_string()}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<cdtypes::Error> for Error {
|
||||
fn from(error: cdtypes::Error) -> Self {
|
||||
Error::from_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<std::convert::Infallible> for Error {
|
||||
fn from(error: std::convert::Infallible) -> Self {
|
||||
Error::from_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<std::sync::mpsc::RecvError> for Error {
|
||||
fn from(error: std::sync::mpsc::RecvError) -> Self {
|
||||
Error::from_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<hound::Error> for Error {
|
||||
fn from(error: hound::Error) -> Self {
|
||||
Error::from_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit_with_error(error: Error) {
|
||||
error.print_to_std_err();
|
||||
std::process::exit(error.exit_code);
|
||||
}
|
||||
|
||||
pub fn print_warning(str: String) {
|
||||
eprintln!("{}", str.yellow());
|
||||
}
|
||||
|
||||
pub fn prefix_if_error<T>(prefix: &str, result: Result<T, Error>) -> Result<T, Error> {
|
||||
match result {
|
||||
Ok(value) => Ok(value),
|
||||
Err(mut error) => {
|
||||
let mut new_text = String::from(prefix);
|
||||
|
||||
new_text.push_str(error.text.as_str());
|
||||
error.text = new_text;
|
||||
Err(error)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn callback_if_error<F: Fn(String) -> String, T>(result: Result<T, Error>, callback: F) -> Result<T, Error> {
|
||||
match result {
|
||||
Ok(value) => Ok(value),
|
||||
Err(mut error) => {
|
||||
error.text = callback(error.text);
|
||||
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn callback_if_any_error<F: Fn(String) -> String, T, E: std::string::ToString>(result: Result<T, E>, callback: F) -> Result<T, Error> {
|
||||
match result {
|
||||
Ok(value) => Ok(value),
|
||||
Err(error) => {
|
||||
Err(Error::from_text(callback(error.to_string())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string_with_env_from(str: &str) -> String {
|
||||
String::from(envmnt::expand(str, Some(DEFAULT_ENV_EXPAND_OPTIONS)))
|
||||
}
|
||||
|
||||
pub fn path_with_env_from(path: &str) -> PathBuf {
|
||||
PathBuf::from(envmnt::expand(path, Some(DEFAULT_ENV_EXPAND_OPTIONS)))
|
||||
}
|
||||
|
||||
pub fn open_output_file(output_path: &PathBuf) -> Result<BufWriter<std::fs::File>, Error> {
|
||||
Ok(std::io::BufWriter::new(std::fs::File::create(win_or_wsl_path(output_path)?)?))
|
||||
}
|
||||
|
||||
pub fn open_output(output_file: &Option<PathBuf>) -> Result<Output, Error> {
|
||||
match output_file {
|
||||
Some(output_path) => Ok(Box::new(open_output_file(&output_path)?)),
|
||||
None => Ok(Box::new(BufWriter::new(std::io::stdout()))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_input(input_file: &Option<PathBuf>) -> Result<Input, Error> {
|
||||
match input_file {
|
||||
Some(input_path) => Ok(Box::new(open_input_file_buffered(&input_path)?)),
|
||||
None => Ok(Box::new(BufReader::new(std::io::stdin()))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_input_file_buffered(input_path: &PathBuf) -> Result<BufferedInputFile, Error> {
|
||||
Ok(BufReader::new(std::fs::File::open(win_or_wsl_path(input_path)?)?))
|
||||
}
|
||||
|
||||
pub fn os_str_to_string(input: &std::ffi::OsStr, name: &str) -> Result<String, Error> {
|
||||
Ok(Error::ok_or_new(input.to_str(), ||format!("Converting {} to UTF-8 failed", name))?.to_owned())
|
||||
}
|
||||
|
||||
pub fn get_file_name_from_path_buf(input: &PathBuf, name: &str) -> Result<String, Error> {
|
||||
os_str_to_string(Error::ok_or_new(input.file_name(), ||format!("No {} file name found", name))?, name)
|
||||
}
|
||||
|
||||
pub fn input_to_vec(input: Input) -> Result<Vec<u8>, Error> {
|
||||
let mut data = Vec::new();
|
||||
|
||||
for byte in input.bytes() {
|
||||
data.push(byte?);
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn read_file(file_path: &PathBuf) -> Result<Vec<u8>, Error> {
|
||||
if let Some(ext) = file_path.extension() {
|
||||
if ext == "subst" {
|
||||
// File needs substitution!
|
||||
let file_content = read_file_to_string(file_path)?;
|
||||
return Ok(string_with_env_from(&file_content).into_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
match std::fs::read(win_or_wsl_path(file_path)?) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(error) => create_file_read_error(file_path, error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_file_to_string(file_path: &PathBuf) -> Result<String, Error> {
|
||||
match std::fs::read_to_string(win_or_wsl_path(file_path)?) {
|
||||
Ok(string) => Ok(string),
|
||||
Err(error) => create_file_read_error(file_path, error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_file(file_path: &PathBuf, data: Vec<u8>) -> Result<(), Error> {
|
||||
let mut file = OpenOptions::new().read(true).write(true).truncate(true).create(true).open(file_path)?;
|
||||
|
||||
file.write_all(data.as_slice())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_file_read_error<T>(file_path: &PathBuf, error: std::io::Error) -> Result<T, Error> {
|
||||
Err(Error::from_text(format!("Failed reading file {} with error: \"{}\"", file_path.display(), error)))
|
||||
}
|
||||
|
||||
fn win_or_wsl_path(path: &PathBuf) -> Result<PathBuf, Error> {
|
||||
let path = path.clone();
|
||||
if path.exists() {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
else {
|
||||
match path.into_os_string().into_string() {
|
||||
Ok(path) => Ok(PathBuf::from_str(wslpath::force_convert(path).as_str())?),
|
||||
Err(error) => Err(Error::from_text(format!("Failed converting {:?} to string for win/wsl mapping", error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::Error;
|
||||
use std::str;
|
||||
|
||||
pub fn to_string(output: Vec<u8>) -> Result<String, Error> {
|
||||
match str::from_utf8(&output) {
|
||||
Ok(str) => Ok(str.to_owned()),
|
||||
Err(_) => Err(Error::from_str("Invalid UTF8 sequence"))
|
||||
}
|
||||
use super::Error;
|
||||
use std::str;
|
||||
|
||||
pub fn to_string(output: Vec<u8>) -> Result<String, Error> {
|
||||
match str::from_utf8(&output) {
|
||||
Ok(str) => Ok(str.to_owned()),
|
||||
Err(_) => Err(Error::from_str("Invalid UTF8 sequence"))
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = wslpath
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = wslpath
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
||||
Reference in New Issue
Block a user