Fix inconsistent EOL

This commit is contained in:
Jaby
2025-01-08 22:27:37 +01:00
parent 0f48b3aedb
commit 8ef6e3a9df
184 changed files with 13686 additions and 13685 deletions

View File

@@ -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"
}
}
}

View File

@@ -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))

View File

@@ -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

View File

@@ -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());
}
};
};
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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;
}
};
}

View File

@@ -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));
}
};
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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();*/
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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};
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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)

View File

@@ -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>

View File

@@ -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"
}
]
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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)
}
}
}

View File

@@ -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)

View File

@@ -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)?)
}

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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..]
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,2 +1,2 @@
pub mod xa;
pub mod xa;
pub mod vag;

View File

@@ -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();
}

View File

@@ -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
}
}

View File

@@ -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,
}
}

View File

@@ -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")
}

View File

@@ -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))
}

View File

@@ -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(())
}

View File

@@ -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},
];

View File

@@ -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
}

View File

@@ -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))
}
}

View File

@@ -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),
}
}

View File

@@ -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()
}
}

View File

@@ -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),
}
}

View File

@@ -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
}
}

View File

@@ -1,3 +1,3 @@
pub mod audio;
pub mod images;
pub mod audio;
pub mod images;
pub mod nothing;

View File

@@ -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(())
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)))
}
}
}

View File

@@ -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"))
}
}

View File

@@ -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)