diff --git a/.gitignore b/.gitignore index a812ba9c..7010baff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,31 @@ .DS_Store -sdk/toolchain/ -!sdk/toolchain/download.sh -!sdk/toolchain/Makefile -!sdk/toolchain/patches/ -!sdk/toolchain/unpack.sh +.codelite +*.o +*.a +*.elf +*.op +*.os +*.o3 +*.build +*.log +*.lo +*.la +*.cache +*.status +*.sym +*.d +*.opk +*.klf +*.iso +*.gdi +*.cdi +.libs +build/ +release/ +*DS_CORE.BIN +romdisk.img +exports.c +exports_stubs.c +exports_gcc.c +exports_gcc_stubs.c +DS-*.png diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e480076c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "lib/fatfs"] + path = lib/fatfs + url = https://github.com/DC-SWAT/FatFs.git + branch = master diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100755 index 00000000..c853a34c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "DreamShell debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/DS-DBG.elf", + "cwd": ".", + "MIMode": "gdb", + "miDebuggerPath": "/opt/toolchains/dc/sh-elf/bin/sh-elf-gdb", + "miDebuggerServerAddress": "localhost:2159", + "debugServerPath": "/usr/local/dc/kos-tool/build/kos-tool", + "debugServerArgs": "-g -m ${workspaceFolder}/build -t 192.168.1.130 -x ${workspaceFolder}/DS.elf", + "filterStdout": false, + "logging": { + "exceptions": true, + "moduleLoad": true, + "programOutput": true, + "engineLogging": false, + "trace": true, + "traceResponse": true + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..2af3ff9f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "C_Cpp.default.includePath": [ + "/usr/local/dc/kos/kos/include/", + "/usr/local/dc/kos/kos/kernel/arch/dreamcast/include/", + "/usr/local/dc/kos/kos/addons/include/", + "/usr/local/dc/kos/kos-ports/include/", + "/opt/toolchains/dc/sh-elf/sh-elf/include/", + "/usr/local/dc/kos/kos/ds/include/", + "/usr/local/dc/kos/kos/ds/firmware/isoldr/loader/include" + ], + "C_Cpp.default.compilerPath": "kos-cc" +} diff --git a/CLA.md b/CLA.md new file mode 100644 index 00000000..5dc75774 --- /dev/null +++ b/CLA.md @@ -0,0 +1,78 @@ +# DreamShell Contributor License Agreement (CLA) + +This Contributor License Agreement ("Agreement") is entered into between: + +- **You** (the individual or legal entity submitting a Contribution), and +- **Project Owner**: Ruslan Rostovtsev (SWAT) ("Project Owner"). + +If You are submitting on behalf of an employer or another legal entity, You represent that You +have authority to bind that entity, and "You" refers to that entity. + +## 1. Definitions + +"Contribution" means any work of authorship, including any modifications, additions, or +derivative works, that You intentionally submit to the Project Owner for inclusion in DreamShell +and its components (including ISO Loader), whether submitted by email, pull request, patch, or +any other form of written communication. + +## 2. Copyright License Grant + +You grant to the Project Owner a perpetual, worldwide, non-exclusive, royalty-free, +irrevocable copyright license to reproduce, prepare derivative works of, publicly display, +publicly perform, sublicense, and distribute Your Contribution and such derivative works. + +This grant includes the right for the Project Owner to **relicense** Your Contribution, and +works that incorporate it, under any terms (including commercial license terms), at the Project +Owner's discretion. + +## 3. Patent License Grant + +You grant to the Project Owner a perpetual, worldwide, non-exclusive, royalty-free, +irrevocable patent license to make, have made, use, offer to sell, sell, import, and otherwise +transfer the Contribution, where such license applies only to those patent claims licensable by +You that are necessarily infringed by Your Contribution alone or by combination of Your +Contribution with the project to which it was submitted. + +## 4. Your Representations + +You represent that: + +- You have the legal right to grant the licenses in this Agreement. +- Your Contribution is an original work of authorship by You, or You have sufficient rights to + submit it under this Agreement. +- To the best of Your knowledge, Your Contribution does not knowingly include third-party code + under terms incompatible with submission under this Agreement. + +## 5. Disclaimer + +Except as stated in this Agreement, Your Contribution is provided "AS IS", without warranties +or conditions of any kind. + +## 6. Submission of Contributions + +Unless You explicitly state otherwise in writing at the time You submit a Contribution, any +Contribution intentionally submitted for inclusion is subject to this Agreement. + +## 7. Miscellaneous + +This Agreement does not grant You rights to use the Project Owner's trademarks. + +This Agreement is separate from, and does not modify, the applicable license terms for the +project (for example, `LICENSE` and `NOTICE`). This Agreement governs the rights You grant to +the Project Owner for Your Contributions. + +--- + +## Signature (optional, for record keeping) + +Name: + +Email: + +GitHub / handle: + +Date: + +Signature: + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..6b5faf2d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# Contributing to DreamShell + +## License and notices + +Before contributing, please review: + +- `LICENSE` (PolyForm Noncommercial License 1.0.0) +- `NOTICE` (Required Notice lines) + +## Contributor License Agreement (CLA) + +To keep commercial licensing possible by a separate agreement with the Project Owner, all +contributions to DreamShell and its components (including ISO Loader) require acceptance of: + +- `CLA.md` + +By submitting a pull request, patch, or any other Contribution for inclusion, You agree that +Your Contribution is provided under the terms of `CLA.md`. + +## Third-party code + +Do not submit third-party code unless you have the right to do so and its license is compatible +with inclusion. If you include third-party code, you must include the license text and clearly +identify the origin. diff --git a/Core.project b/Core.project deleted file mode 100644 index 32f066fb..00000000 --- a/Core.project +++ /dev/null @@ -1,545 +0,0 @@ - - - - - - - - - - - _OLD\ - release\ - resources\ - sdk\ - emu\ - cd\ - - - - - - ../ds - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - None - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - None - - - - - - - - - - - - - - - - - - - - - diff --git a/DreamShell.workspace b/DreamShell.workspace deleted file mode 100644 index e12ee3db..00000000 --- a/DreamShell.workspace +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1a71cb64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,131 @@ +# PolyForm Noncommercial License 1.0.0 + + + +## Acceptance + +In order to get any license under these terms, you must agree +to them as both strict obligations and conditions to all +your licenses. + +## Copyright License + +The licensor grants you a copyright license for the +software to do everything you might do with the software +that would otherwise infringe the licensor's copyright +in it for any permitted purpose. However, you may +only distribute the software according to [Distribution +License](#distribution-license) and make changes or new works +based on the software according to [Changes and New Works +License](#changes-and-new-works-license). + +## Distribution License + +The licensor grants you an additional copyright license +to distribute copies of the software. Your license +to distribute covers distributing the software with +changes and new works permitted by [Changes and New Works +License](#changes-and-new-works-license). + +## Notices + +You must ensure that anyone who gets a copy of any part of +the software from you also gets a copy of these terms or the +URL for them above, as well as copies of any plain-text lines +beginning with `Required Notice:` that the licensor provided +with the software. For example: + +> Required Notice: Copyright Yoyodyne, Inc. (http://example.com) + +## Changes and New Works License + +The licensor grants you an additional copyright license to +make changes and new works based on the software for any +permitted purpose. + +## Patent License + +The licensor grants you a patent license for the software that +covers patent claims the licensor can license, or becomes able +to license, that you would infringe by using the software. + +## Noncommercial Purposes + +Any noncommercial purpose is a permitted purpose. + +## Personal Uses + +Personal use for research, experiment, and testing for +the benefit of public knowledge, personal study, private +entertainment, hobby projects, amateur pursuits, or religious +observance, without any anticipated commercial application, +is use for a permitted purpose. + +## Noncommercial Organizations + +Use by any charitable organization, educational institution, +public research organization, public safety or health +organization, environmental protection organization, +or government institution is use for a permitted purpose +regardless of the source of funding or obligations resulting +from the funding. + +## Fair Use + +You may have "fair use" rights for the software under the +law. These terms do not limit them. + +## No Other Rights + +These terms do not allow you to sublicense or transfer any of +your licenses to anyone else, or prevent the licensor from +granting licenses to anyone else. These terms do not imply +any other licenses. + +## Patent Defense + +If you make any written claim that the software infringes or +contributes to infringement of any patent, your patent license +for the software granted under these terms ends immediately. If +your company makes such a claim, your patent license ends +immediately for work on behalf of your company. + +## Violations + +The first time you are notified in writing that you have +violated any of these terms, or done anything with the software +not covered by your licenses, your licenses can nonetheless +continue if you come into full compliance with these terms, +and take practical steps to correct past violations, within +32 days of receiving notice. Otherwise, all your licenses +end immediately. + +## No Liability + +***As far as the law allows, the software comes as is, without +any warranty or condition, and the licensor will not be liable +to you for any damages arising out of these terms or the use +or nature of the software, under any kind of legal claim.*** + +## Definitions + +The **licensor** is the individual or entity offering these +terms, and the **software** is the software the licensor makes +available under these terms. + +**You** refers to the individual or entity agreeing to these +terms. + +**Your company** is any legal entity, sole proprietorship, +or other kind of organization that you work for, plus all +organizations that have control over, are under the control of, +or are under common control with that organization. **Control** +means ownership of substantially all the assets of an entity, +or the power to direct its management and policies by vote, +contract, or otherwise. Control can be direct or indirect. + +**Your licenses** are all the licenses granted to you for the +software under these terms. + +**Use** means anything you do with the software requiring one +of your licenses. diff --git a/Loadable.project b/Loadable.project deleted file mode 100644 index 9d41cad1..00000000 --- a/Loadable.project +++ /dev/null @@ -1,832 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Makefile b/Makefile index cf2f54aa..9c3f82c6 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ # # DreamShell Makefile -# Copyright (C) 2004-2016 SWAT +# Copyright (C) 2004-2026 SWAT # http://www.dc-swat.ru # -# This makefile can build CDI image (type "make cdi"), +# This Makefile can build CDI image (type "make cdi"), # launch DS on DC through dc-tool-ip (type "make run") # or launch DS on emulators nullDC and lxdream # (type "make nulldc" or "make lxdream"). @@ -11,22 +11,53 @@ # TARGET = DS +VER_MAJOR = 4 +VER_MINOR = 0 +VER_MICRO = 4 +# Build types: 0x0N - Alpha, 0x1N - Beta, 0x2N - RC, 0x3N - Release +VER_BUILD = 0x30 + +BUILD_TYPE_BASE = $(if $(filter 0x3%,$(VER_BUILD)),Release,$(if $(filter 0x2%,$(VER_BUILD)),RC,$(if $(filter 0x1%,$(VER_BUILD)),Beta,$(if $(filter 0x0%,$(VER_BUILD)),Alpha,Release)))) +BUILD_NUM = $(lastword $(subst 0x2,,$(subst 0x1,,$(subst 0x0,,$(subst 0x3,,$(VER_BUILD)))))) +BUILD_TYPE_NAME = $(if $(filter Release,$(BUILD_TYPE_BASE)),$(BUILD_TYPE_BASE),$(BUILD_TYPE_BASE)$(BUILD_NUM)) +TARGET_NAME = DreamShell_v$(VER_MAJOR).$(VER_MINOR).$(VER_MICRO)_$(BUILD_TYPE_NAME) TARGET_BIN = $(TARGET)_CORE.BIN TARGET_BIN_CD = 1$(TARGET_BIN) -TRAGET_VERSION = -DVER_MAJOR=4 -DVER_MINOR=0 -DVER_MICRO=0 -DVER_BUILD=0x24 #RC 4 + +TRAGET_VERSION = -DVER_MAJOR=$(VER_MAJOR) \ + -DVER_MINOR=$(VER_MINOR) \ + -DVER_MICRO=$(VER_MICRO) \ + -DVER_BUILD=$(VER_BUILD) +# TARGET_DEBUG = 1 # or 2 for GDB +# TARGET_EMU = 1 +# TARGET_PROF = 1 all: rm-elf $(TARGET) +KOS_ROMDISK_DIR = romdisk +SFX_FILES = click click2 screenshot move chpage slide error success coin +SFX_TARGETS = $(addprefix $(DS_BUILD)/sfx/,$(addsuffix .wav,$(SFX_FILES))) + include sdk/Makefile.cfg -INC_DIR = ./include -SRC_DIR = ./src -LIB_DIR = ./lib -RES_DIR = ./resources +INC_DIR = $(DS_BASE)/include +SRC_DIR = $(DS_BASE)/src +LIB_DIR = $(DS_BASE)/lib KOS_LDFLAGS += -L$(LIB_DIR) -KOS_CFLAGS += -I$(INC_DIR) -I$(INC_DIR)/SDL -I$(INC_DIR)/fatfs -I$(INC_DIR)/ntfs \ - -DHAVE_SDLIMAGE $(TRAGET_VERSION) #-DEMU=1 +KOS_CFLAGS += -I$(INC_DIR) -I$(INC_DIR)/img -I$(INC_DIR)/SDL \ + -I$(LIB_DIR)/fatfs/include \ + -I$(INC_DIR)/tsunami \ + -DHAVE_SDLIMAGE $(TRAGET_VERSION) + +KOS_CPPFLAGS += -Wno-template-id-cdtor + +ifdef TARGET_DEBUG +KOS_CFLAGS += -g -DDS_DEBUG=$(TARGET_DEBUG) +endif +ifdef TARGET_EMU +KOS_CFLAGS += -DDS_EMU=1 +endif SDL_VER = 1.2.13 SDL_GFX_VER = 2.0.25 @@ -34,21 +65,26 @@ SDL_IMAGE_VER = 1.2.12 SDL_TTF_VER = 2.0.11 SDL_RTF_VER = 0.1.1 LUA_VER = 5.1.4-2 +TSUNAMI_VER = 2.1.0 +PARALLAX_VER = 2.0.0 +FREETYPE_VER = 2.4.4 -EXTRA_LIBS = -lcfg -lmxml -lparallax +EXTRA_LIBS = -lcfg -lmxml -lfatfs SDL_LIBS = -lSDL_$(SDL_VER) \ -lSDL_image_$(SDL_IMAGE_VER) \ -lSDL_ttf_$(SDL_TTF_VER) \ -lSDL_rtf_$(SDL_RTF_VER) \ -lSDL_gfx_$(SDL_GFX_VER) \ - -lfreetype - + -lfreetype_$(FREETYPE_VER) + IMAGE_LIBS = -lkmg -ljpeg -lpng -lz LUA_LIBS = -llua_$(LUA_VER) -KLIBS = -lkosext2fs -lkosutils -lstdc++ -lm +KLIBS = -lpthread -lkosutils -lstdc++ -lm +GRAPHICS_LIBS = -ltsunami_$(TSUNAMI_VER) \ + -lparallax_$(PARALLAX_VER) -CORE_LIBS = $(EXTRA_LIBS) $(SDL_LIBS) $(IMAGE_LIBS) $(LUA_LIBS) $(KLIBS) +CORE_LIBS = $(EXTRA_LIBS) $(SDL_LIBS) $(GRAPHICS_LIBS) $(IMAGE_LIBS) $(LUA_LIBS) $(KLIBS) SDL_GUI = $(LIB_DIR)/SDL_gui SDL_CONSOLE = $(LIB_DIR)/SDL_Console/src @@ -63,34 +99,13 @@ GUI_OBJS = $(SDL_GUI)/SDL_gui.o $(SDL_GUI)/Exception.o $(SDL_GUI)/Object.o \ $(SDL_GUI)/ToggleButton.o $(SDL_GUI)/ProgressBar.o $(SDL_GUI)/ScrollBar.o \ $(SDL_GUI)/AbstractTable.o $(SDL_GUI)/ListBox.o $(SDL_GUI)/VBoxLayout.o \ $(SDL_GUI)/RealScreen.o $(SDL_GUI)/ScrollPanel.o $(SDL_GUI)/RTF.o \ - $(SDL_GUI)/Window.o $(SDL_GUI)/FileManager.o + $(SDL_GUI)/Window.o $(SDL_GUI)/FileManager.o $(SDL_GUI)/Dialog.o CONSOLE_OBJ = $(SDL_CONSOLE)/SDL_console.o $(SDL_CONSOLE)/DT_drawtext.o \ $(SDL_CONSOLE)/internal.o -DRIVERS_OBJ = $(SRC_DIR)/drivers/spi.o $(SRC_DIR)/drivers/sd.o \ - $(SRC_DIR)/drivers/enc28j60.o $(SRC_DIR)/drivers/asic.o \ - $(SRC_DIR)/drivers/rtc.o - -FATFS_DIR = $(SRC_DIR)/fs/fat -FATFS = $(FATFS_DIR)/utils.o $(FATFS_DIR)/option/ccsbcs.o \ - $(FATFS_DIR)/option/syscall.o $(FATFS_DIR)/ff.o \ - $(FATFS_DIR)/dc.o - -NTFS_DIR = $(SRC_DIR)/fs/ntfs -NTFS = $(NTFS_DIR)/attrib.o $(NTFS_DIR)/attrlist.o \ - $(NTFS_DIR)/bitmap.o $(NTFS_DIR)/bootsect.o \ - $(NTFS_DIR)/collate.o $(NTFS_DIR)/compat.o \ - $(NTFS_DIR)/compress.o $(NTFS_DIR)/crypto.o \ - $(NTFS_DIR)/debug.o $(NTFS_DIR)/device.o \ - $(NTFS_DIR)/device_io.o $(NTFS_DIR)/dir.o \ - $(NTFS_DIR)/index.o $(NTFS_DIR)/inode.o \ - $(NTFS_DIR)/lcnalloc.o $(NTFS_DIR)/misc.o \ - $(NTFS_DIR)/mft.o $(NTFS_DIR)/mst.o \ - $(NTFS_DIR)/runlist.o $(NTFS_DIR)/security.o \ - $(NTFS_DIR)/unistr.o $(NTFS_DIR)/version.o \ - $(NTFS_DIR)/volume.o #TODO $(NTFS_DIR)/dc_io.o - +DRIVERS_OBJ = + UTILS_DIR = $(SRC_DIR)/utils UTILS_OBJ = $(SRC_DIR)/utils.o $(UTILS_DIR)/gmtime.o $(UTILS_DIR)/strftime.o \ $(UTILS_DIR)/debug_console.o $(UTILS_DIR)/memcpy.op \ @@ -100,12 +115,25 @@ OBJS = $(SRC_DIR)/main.o $(SRC_DIR)/video.o $(SRC_DIR)/console.o \ $(SRC_DIR)/gui/gui.o $(SRC_DIR)/commands.o \ $(SRC_DIR)/module.o $(SRC_DIR)/events.o $(SRC_DIR)/fs/fs.o \ $(SRC_DIR)/lua/lua.o $(SRC_DIR)/lua/lua_ds.o $(SRC_DIR)/lua/packlib.o \ - $(SRC_DIR)/app/app.o $(SRC_DIR)/app/load.o $(SRC_DIR)/list.o \ - $(SRC_DIR)/img/pvr.o $(SRC_DIR)/cmd_elf.o $(SRC_DIR)/vmu/vmu.o \ + $(SRC_DIR)/app/app.o $(SRC_DIR)/app/load.o $(SRC_DIR)/app/tsunami.o \ + $(SRC_DIR)/img/SegaPVRImage.o $(SRC_DIR)/img/stb_image.o \ + $(SRC_DIR)/list.o $(SRC_DIR)/img/load.o $(SRC_DIR)/img/utils.o \ + $(SRC_DIR)/img/decode.o $(SRC_DIR)/img/convert.o $(SRC_DIR)/img/copy.o \ + $(SRC_DIR)/cmd_elf.o $(SRC_DIR)/vmu/vmu.o \ $(SRC_DIR)/irq/exceptions.o $(SRC_DIR)/irq/setjmp.o \ - $(SRC_DIR)/settings.o $(DRIVERS_OBJ) $(GUI_OBJS) $(CONSOLE_OBJ) \ - $(UTILS_OBJ) $(FATFS) $(SRC_DIR)/exports.o $(SRC_DIR)/exports_gcc.o \ - romdisk.o #$(NTFS) + $(SRC_DIR)/settings.o $(SRC_DIR)/sfx.o \ + $(SRC_DIR)/naomi/bsram.o $(SRC_DIR)/naomi/coins.o \ + $(DRIVERS_OBJ) $(GUI_OBJS) $(CONSOLE_OBJ) \ + $(UTILS_OBJ) $(SRC_DIR)/exports.o $(SRC_DIR)/exports_gcc.o \ + romdisk.o + +ifdef TARGET_PROF + KOS_CFLAGS += -DDS_PROF=$(TARGET_PROF) -pg -fno-omit-frame-pointer + KOS_LDFLAGS += -pg + GPROF_LIBS = -Wl,--whole-archive -lgprof -Wl,--no-whole-archive +else + GPROF_LIBS = +endif %.op: %.S kos-cc $(CFLAGS) -c $< -o $@ @@ -121,25 +149,135 @@ $(SRC_DIR)/exports_gcc.c: exports_gcc.txt $(KOS_BASE)/utils/genexports/genexports.sh exports_gcc.txt $(SRC_DIR)/exports_gcc.c gcc_symtab $(KOS_BASE)/utils/genexports/genexportstubs.sh exports_gcc.txt $(SRC_DIR)/exports_gcc_stubs.c $(KOS_MAKE) -f Makefile.gcc_stubs - -romdisk.img: romdisk/logo.kmg.gz - $(KOS_GENROMFS) -f romdisk.img -d romdisk -v - -romdisk.o: romdisk.img - $(KOS_BASE)/utils/bin2o/bin2o romdisk.img romdisk romdisk.o -logo: romdisk/logo.kmg.gz -romdisk/logo.kmg.gz: $(RES_DIR)/logo_sq.png - $(KOS_BASE)/utils/kmgenc/kmgenc -v $(RES_DIR)/logo_sq.png - mv $(RES_DIR)/logo_sq.kmg logo.kmg +logo: $(KOS_ROMDISK_DIR)/logo.kmg.gz +$(KOS_ROMDISK_DIR)/logo.kmg.gz: $(DS_RES)/logo_sq.png + $(KOS_BASE)/utils/kmgenc/kmgenc -v $(DS_RES)/logo_sq.png + mv $(DS_RES)/logo_sq.kmg logo.kmg gzip -9 logo.kmg - mv logo.kmg.gz romdisk/logo.kmg.gz - -$(TARGET): $(TARGET_BIN) + mv logo.kmg.gz $(KOS_ROMDISK_DIR)/logo.kmg.gz + +sfx: $(KOS_ROMDISK_DIR)/startup.raw.gz $(SFX_TARGETS) +$(KOS_ROMDISK_DIR)/startup.raw.gz: $(DS_RES)/sfx/startup.wav + ffmpeg -i $(DS_RES)/sfx/startup.wav -af "apad=pad_dur=2" $(DS_RES)/sfx/startup_pad.wav + ffmpeg -i $(DS_RES)/sfx/startup_pad.wav -acodec adpcm_yamaha -fs 327680 -f s16le $(KOS_ROMDISK_DIR)/startup.raw + rm -f $(DS_RES)/sfx/startup_pad.wav + gzip -9 $(KOS_ROMDISK_DIR)/startup.raw + +$(DS_BUILD)/sfx/%.wav: $(DS_RES)/sfx/%.wav + @mkdir -p $(DS_BUILD)/sfx + @$(KOS_BASE)/utils/wav2adpcm/wav2adpcm -t $< $@ 2>/dev/null + +make-build: $(DS_BUILD)/lua/startup.lua + +$(DS_BUILD)/lua/startup.lua: $(DS_RES)/lua/startup.lua $(SFX_TARGETS) + @echo Creating build directory... + @mkdir -p $(DS_BUILD) + @mkdir -p $(DS_BUILD)/apps + @mkdir -p $(DS_BUILD)/cmds + @mkdir -p $(DS_BUILD)/modules + @mkdir -p $(DS_BUILD)/screenshot + @mkdir -p $(DS_BUILD)/vmu + @cp -R $(DS_RES)/doc $(DS_BUILD) + @cp LICENSE NOTICE $(DS_BUILD)/doc + @cp -R $(DS_RES)/firmware $(DS_BUILD) + @rm -rf $(DS_BUILD)/firmware/bios/ds/patches + @mkdir -p $(DS_BUILD)/firmware/eeprom + @cp -R $(DS_RES)/fonts $(DS_BUILD) + @cp -R $(DS_RES)/gui $(DS_BUILD) + @cp -R $(DS_RES)/lua $(DS_BUILD) + +libs: $(LIB_DIR)/libSDL_$(SDL_VER).a + +$(LIB_DIR)/libSDL_$(SDL_VER).a: + cd $(LIB_DIR) && make + +build: $(TARGET) + @echo Building modules, commands, applications and firmwares... + cd $(DS_BASE)/modules && make + cd $(DS_BASE)/commands && make + cd $(DS_BASE)/applications && make + cd $(DS_BASE)/firmware/isoldr && make -j8 && make install + cd $(DS_BASE)/firmware/bootloader && make && make install + cd $(DS_BASE)/firmware/hollysh && make && make install + @echo + @echo "\033[42m DreamShell build completed \033[0m" + +clean-all: clean + @-rm -rf $(DS_BUILD)/.* 2> /dev/null + @-rm -rf $(DS_BASE)/release + @-rm -rf $(DS_BASE)/build + @-rm -rf $(DS_SDK)/lib/*.a + cd $(DS_BASE)/lib && make clean + cd $(DS_BASE)/modules && make clean + cd $(DS_BASE)/commands && make clean + cd $(DS_BASE)/applications && make clean + cd $(DS_BASE)/firmware/isoldr && make clean + cd $(DS_BASE)/firmware/bootloader && make clean + cd $(DS_BASE)/firmware/hollysh && make clean + +release: build cdi + @echo Creating a full release... + @-rm -rf $(DS_BUILD)/.* 2> /dev/null || true + @-rm -rf $(DS_BUILD)/firmware/bios/ds/patches + @-rm -rf $(DS_BASE)/release || true + @mkdir -p $(DS_BASE)/release/$(TARGET) + @cp -R $(DS_BUILD)/* $(DS_BASE)/release/$(TARGET) + @cp $(TARGET_BIN) $(DS_BASE)/release/$(TARGET) + @mv $(TARGET).cdi $(DS_BASE)/release/$(TARGET_NAME).cdi + @make TARGET_DEBUG=1 + @cp $(TARGET_BIN) $(DS_BASE)/release/$(TARGET)/DEBUG_$(TARGET_BIN) + @make TARGET_EMU=1 + @cp $(TARGET_BIN) $(DS_BASE)/release/$(TARGET)/EMU_$(TARGET_BIN) + @make + @cd $(DS_BASE)/firmware/bootloader && make && make release + @mv $(DS_BASE)/firmware/bootloader/*.cdi $(DS_BASE)/release + @echo Compressing... + @cd $(DS_BASE)/release && zip -q -r $(TARGET_NAME).zip * 2> /dev/null + @echo + @echo "\033[42m DreamShell release created in $(DS_BASE)/release \033[0m" + +update: + @echo Fetching DreamShell from GitHub... + @git fetch && git checkout origin/master + @git submodule update --init --recursive + @echo Fetching KallistiOS from GitHub... + @cd $(KOS_BASE) && git fetch && git checkout `cat $(DS_BASE)/sdk/doc/KallistiOS.txt` + @echo Fetching kos-ports from GitHub... + @cd $(KOS_BASE)/../kos-ports && git fetch && git checkout origin/master + +update-build: + @echo Fetching DreamShell from GitHub... + @git fetch && git checkout origin/master + @git submodule update --init --recursive + @echo Fetching and build KallistiOS from GitHub... + @cd $(KOS_BASE) && git fetch && git checkout `cat $(DS_BASE)/sdk/doc/KallistiOS.txt` && make clean && make -j8 + @echo Fetching and build kos-ports from GitHub... + @cd "$(KOS_BASE)/../kos-ports" && git fetch && git checkout origin/master && \ + . "$(KOS_BASE)/environ.sh" && ./utils/build-all.sh || true + @echo Building DreamShell... + @make clean-all && make build + +build-ports: + @echo Fetching and rebuilding kos-ports from GitHub... + @cd "$(KOS_BASE)/../kos-ports" && git fetch && git checkout origin/master + @echo Uninstalling previous kos-ports... + @cd "$(KOS_BASE)/../kos-ports" && . "$(KOS_BASE)/environ.sh" && ./utils/uninstall-all.sh || true + @echo Building new kos-ports... + @cd "$(KOS_BASE)/../kos-ports" && . "$(KOS_BASE)/environ.sh" && ./utils/build-all.sh + +toolchain: + @cp $(DS_SDK)/toolchain/environ.sh $(KOS_BASE)/environ.sh + @cp $(DS_SDK)/toolchain/patches/*.diff $(KOS_BASE)/utils/kos-chain/patches + @cp $(DS_SDK)/toolchain/Makefile.cfg $(KOS_BASE)/utils/kos-chain/Makefile.cfg + @source $(KOS_BASE)/environ.sh + @cd $(KOS_BASE)/utils/kos-chain && make + +$(TARGET): libs $(TARGET_BIN) make-build $(TARGET).elf: $(OBJS) $(KOS_CC) $(KOS_CFLAGS) $(KOS_LDFLAGS) -o $(TARGET).elf \ - $(OBJS) $(OBJEXTRA) $(CORE_LIBS) $(KOS_LIBS) + $(OBJS) $(OBJEXTRA) $(CORE_LIBS) $(GPROF_LIBS) $(KOS_LIBS) $(TARGET_BIN): $(TARGET).elf @echo Copy elf for debug... @@ -151,10 +289,30 @@ $(TARGET_BIN): $(TARGET).elf $(TARGET_BIN_CD): $(TARGET_BIN) @echo Scramble binary file... - @./sdk/bin/scramble $(TARGET_BIN) $(TARGET_BIN_CD) + @$(KOS_BASE)/utils/scramble/scramble $(TARGET_BIN) $(TARGET_BIN_CD) cdi: $(TARGET).cdi +$(TARGET).cdi: $(TARGET_BIN_CD) make-build + @echo Updating IP.BIN with current version and date... + @utils/update_ip_bin.sh $(DS_RES)/IP.BIN /tmp/IP_$(TARGET).BIN $(VER_MAJOR) $(VER_MINOR) $(VER_MICRO) $(VER_BUILD) >/dev/null + @echo Creating ISO... + @-rm -f $(DS_BUILD)/$(TARGET_BIN) + @-rm -f $(DS_BUILD)/$(TARGET_BIN_CD) + @cp $(TARGET_BIN_CD) $(DS_BUILD)/$(TARGET_BIN_CD) + @-rm -rf $(DS_BUILD)/.* 2> /dev/null + @$(DS_SDK)/bin/mkisofs -V DreamShell -C 0,11702 -G /tmp/IP_$(TARGET).BIN -joliet -rock -l -x .DS_Store -o $(TARGET).iso $(DS_BUILD) + @echo Convert ISO to CDI... + @-rm -f $(TARGET).cdi + @$(DS_SDK)/bin/cdi4dc $(TARGET).iso $(TARGET).cdi >/dev/null + @-rm -f $(TARGET).iso + @-rm -f $(DS_BUILD)/$(TARGET_BIN_CD) + @-rm -f /tmp/IP_$(TARGET).BIN + +# If you have problems with mkisofs try data/data image: +# $(DS_SDK)/bin/mkisofs -V DreamShell -G $(DS_RES)/IP.BIN -joliet -rock -l -x .DS_Store -o $(TARGET).iso $(DS_BUILD) +# @$(DS_SDK)/bin/cdi4dc $(TARGET).iso $(TARGET).cdi -d >/dev/null + nulldc: $(TARGET).cdi @echo Running DreamShell... @./emu/nullDC.exe -serial "debug.log" @@ -162,46 +320,47 @@ nulldc: $(TARGET).cdi nulldcl: $(TARGET).cdi @echo Running DreamShell with log... @run ./emu/nullDC.exe -serial "debug.log" > emu.log - + run: $(TARGET).elf - $(DS_SDK)/bin/dc-tool -c $(DS_BUILD) -t $(DC_IP) -x $(TARGET).elf - -debug: $(TARGET).elf - $(DS_SDK)/bin/dc-tool -g -c $(DS_BUILD) -t $(DC_IP) -x $(TARGET).elf - + $(KOS_TOOL) -m $(DS_BUILD) -t $(DC_LAN_IP) -x $(TARGET).elf + +run-ns: $(TARGET).elf + $(KOS_TOOL) -t $(DC_LAN_IP) -x $(TARGET).elf + +run-serial: $(TARGET).elf + $(KOS_TOOL) -m $(DS_BUILD) -t $(DC_SERIAL_PORT) -b $(DC_SERIAL_BAUD) -x $(TARGET).elf + +debug: $(TARGET)-DBG.elf + $(KOS_TOOL) -g -m $(DS_BUILD) -t $(DC_LAN_IP) -x $(TARGET).elf + +debug-serial: $(TARGET)-DBG.elf + $(KOS_TOOL) -g -m $(DS_BUILD) -t $(DC_SERIAL_PORT) -b $(DC_SERIAL_BAUD) -x $(TARGET).elf + gdb: $(TARGET)-DBG.elf - $(KOS_CC_BASE)/bin/$(KOS_CC_PREFIX)-gdb $(TARGET)-DBG.elf --eval-command "target remote localhost:2159" + kos-gdb $(TARGET)-DBG.elf lxdream: $(TARGET).cdi - lxdream -p $(TARGET).cdi - -lxelf: $(TARGET).elf + lxdream -d -p $(TARGET).cdi + +lxdelf: $(TARGET).elf lxdream -u -p -e $(TARGET).elf -lxgdb: +lxdgdb: $(TARGET).cdi lxdream -g 2000 -n $(TARGET).cdi & sleep 2 $(KOS_CC_BASE)/bin/$(KOS_CC_PREFIX)-gdb $(TARGET)-DBG.elf --eval-command "target remote localhost:2000" -dropbox: $(TARGET).elf - @$(KOS_STRIP) $(TARGET).elf - rm -f ~/Dropbox/Dev/DS/DS.elf - cp $(TARGET).elf ~/Dropbox/Dev/DS/DS.elf +flycast: $(TARGET).cdi + Flycast -config config:Debug.SerialConsoleEnabled=yes ./$(TARGET).cdi -$(TARGET).cdi: $(TARGET_BIN_CD) $(DS_BUILD)/lua/startup.lua - @echo Creating ISO... - @-rm -f $(DS_BUILD)/$(TARGET_BIN) - @-rm -f $(DS_BUILD)/$(TARGET_BIN_CD) - @cp $(TARGET_BIN_CD) $(DS_BUILD)/$(TARGET_BIN_CD) - @genisoimage -V DreamShell -G $(RES_DIR)/IP.BIN -joliet -rock -l -x .svn -o $(TARGET).iso $(DS_BUILD) - @echo Convert ISO to CDI... - @-rm -f $(TARGET).cdi - @$(DS_SDK)/bin/cdi4dc $(TARGET).iso $(TARGET).cdi -d > conv_log.txt - @-rm -f conv_log.txt - @-rm -f $(TARGET).iso +gprof: + @sh-elf-gprof $(TARGET)-DBG.elf $(DS_BUILD)/gmon.out > gprof.out + @cat gprof.out | gprof2dot | dot -Tpng -o $(TARGET)-gprof.png + @-rm -f gprof.out + @echo "\033[42m Profiling data saved to $(TARGET)-gprof.png \033[0m" -#@$(DS_SDK)/bin/mkisofs -V DreamShell -C 0,11702 -G $(RES_DIR)/IP.BIN -joliet -rock -l -x .svn -o $(TARGET).iso $(DS_BUILD) -#@$(DS_SDK)/bin/cdi4dc $(TARGET).iso $(TARGET).cdi > conv_log.txt +core: $(TARGET_BIN) + cp $(DS_BASE)/$(TARGET_BIN) $(DS_BUILD) TARGET_CLEAN_BIN = 1$(TARGET)_CORE.BIN $(TARGET)_CORE.BIN $(TARGET).elf $(TARGET).cdi @@ -209,4 +368,7 @@ clean: -rm -f $(TARGET_CLEAN_BIN) $(OBJS) $(SRC_DIR)/exports.c $(SRC_DIR)/exports_gcc.c rm-elf: - -rm -f $(TARGET_CLEAN_BIN) + -rm -f $(TARGET_CLEAN_BIN) + -rm -f $(SRC_DIR)/main.o + -rm -f romdisk.* + -rm -rf $(KOS_ROMDISK_DIR)/.* 2> /dev/null || true diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..907e1825 --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +Required Notice: Copyright (C) 2004-present Ruslan Rostovtsev (SWAT) (http://www.dc-swat.ru) +Required Notice: Commercial licensing: contact Ruslan Rostovtsev (SWAT) via http://www.dc-swat.ru diff --git a/README.md b/README.md index 9c629214..2c71a54c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,141 @@ DreamShell ========== -Dreamshell is the operating system for the game console Sega Dreamcast. -This repository contain all source code of this project and it is licensed under KallistiOS. +The Dreamshell is the operating system for the Sega Dreamcast based on the KallistiOS kernel. +It has a dynamic loadable modular system and interface for creating applications with XML UI and both C/C++ and Lua script on. +You can see examples in ready-made applications and modules, drivers for various devices, formats and interfaces. Examples for audio and video decoding, compression, packaging, binding, network, emulation, scripts and more. From hardcore low-level assembler to high-level applications. +There are also large subproject is the ISO Loader, which contains emulation of BIOS system calls, CDDA playback and VMU, also it can hooking interrupts for various SDKs and more. + + +## Build + +### Setup environment +##### Packages +```console +sudo apt update +sudo apt install -y gawk patch bzip2 tar make cmake pkg-config +sudo apt install -y gettext wget bison flex sed meson ninja-build +sudo apt install -y build-essential diffutils curl python3 rake +sudo apt install -y genisoimage squashfs-tools texinfo git +sudo apt install -y libgmp-dev libmpfr-dev libmpc-dev libelf-dev libisofs-dev +sudo apt install -y libpng-dev libjpeg-dev liblzo2-dev liblua5.2-dev +cd /tmp +git clone https://github.com/LuaDist/tolua.git +cd /tmp/tolua && mkdir build && cd ./build +cmake ../ && make && sudo make install +``` +##### Code +```console +sudo mkdir -p /usr/local/dc/kos +sudo chown -R $(id -u):$(id -g) /usr/local/dc +cd /usr/local/dc/kos +git clone https://github.com/KallistiOS/kos-ports.git +git clone https://github.com/KallistiOS/KallistiOS.git kos +cd /usr/local/dc/kos/kos +git clone https://github.com/DC-SWAT/DreamShell.git ds +git checkout `cat ds/sdk/doc/KallistiOS.txt` +cp ds/sdk/toolchain/environ.sh environ.sh +cp ds/sdk/toolchain/patches/*.diff utils/kos-chain/patches +cd /usr/local/dc/kos/kos/ds +git submodule update --init --recursive +``` +##### Toolchain +```console +sudo mkdir -p /opt/toolchains/dc +sudo chown -R $(id -u):$(id -g) /opt/toolchains/dc +cd /usr/local/dc/kos/kos +cp ./ds/sdk/toolchain/Makefile.cfg ./utils/kos-chain/Makefile.cfg +cd utils/kos-chain && make +``` +##### SDK +```console +cd /usr/local/dc/kos/kos +source ./environ.sh +make && cd ../kos-ports && ./utils/build-all.sh +cd ${KOS_BASE}/ds/sdk/bin/src && make && make install +cd ${KOS_BASE}/ds +ln -nsf `which tolua` sdk/bin/tolua +ln -nsf `which mkisofs` sdk/bin/mkisofs +ln -nsf `which mksquashfs` sdk/bin/mksquashfs +``` + +### Use environment +##### for each new terminal type: +```console +cd /usr/local/dc/kos/kos/ds && source ../environ.sh +``` + +### Build code +##### Full build +```console +make build +``` +##### Full clean +```console +make clean-all +``` +##### Make release package +```console +make release +``` +##### Update all code from GitHub +```console +make update +``` +##### Update all code from GitHub and re-build +```console +make update-build +``` +##### Update and re-build kos-ports +```console +make build-ports +``` +##### Re-build toochain (if updated) +```console +make toolchain +``` +##### Core and libraries only +```console +make +``` +##### Libraries only +```console +cd ${KOS_BASE}/ds/lib && make +``` +##### Modules, applications and commands only +```console +cd ${KOS_BASE}/ds/modules && make +cd ${KOS_BASE}/ds/commands && make +cd ${KOS_BASE}/ds/applications && make +``` +##### Firmwares only +```console +cd ${KOS_BASE}/ds/firmware/bootloader && make && make release +cd ${KOS_BASE}/ds/firmware/isoldr && make -j8 && make install +cd ${KOS_BASE}/ds/firmware/hollysh && make && make install +``` + +### Running +- dc-tool-ip: `make run` +- dc-tool-serial: `make run-serial` +- lxdream emulator: `make lxdream` +- nulldc emulator: `make nulldc` +- flycast emulator: `make flycast` +- make cdi image: `make cdi` + +## Links +- Website: http://www.dc-swat.ru/ +- Forum: http://www.dc-swat.ru/forum/ +- Donate: http://www.dc-swat.ru/page/donate/ + +## Licensing + +DreamShell core and DreamShell-specific components (including ISO Loader) are licensed under the PolyForm Noncommercial License 1.0.0 in `LICENSE`. + +Required notices are in `NOTICE`. + +This distribution also includes third-party components under their own licenses (for example, `lib/`, `modules/`, and parts of `sdk/`). For those, the license stated in the corresponding file or directory applies. + +## Contributing + +Contributions require acceptance of `CONTRIBUTING.md` and `CLA.md`. diff --git a/Resources.project b/Resources.project deleted file mode 100644 index f2b7a356..00000000 --- a/Resources.project +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/applications/.gitignore b/applications/.gitignore new file mode 100644 index 00000000..75f49684 --- /dev/null +++ b/applications/.gitignore @@ -0,0 +1 @@ +tolua_*.c \ No newline at end of file diff --git a/applications/Makefile b/applications/Makefile index f6d2b2ab..05890b9f 100644 --- a/applications/Makefile +++ b/applications/Makefile @@ -1,18 +1,18 @@ # # DreamShell applications Makefile -# Copyright (C) 2009-2016 SWAT +# Copyright (C) 2009-2026 SWAT # http://www.dc-swat.ru # _SUBDIRS = main filemanager region_changer iso_loader \ bios_flasher gd_ripper speedtest vmu_manager \ - settings - + settings gdplay dreameye network games_menu \ + mie_jvs arcade launch_app all: $(patsubst %, _dir_%, $(_SUBDIRS)) $(patsubst %, _dir_%, $(_SUBDIRS)): - $(MAKE) -C $(patsubst _dir_%, %, $@) + $(MAKE) -C $(patsubst _dir_%, %, $@) all install clean: $(patsubst %, _clean_dir_%, $(_SUBDIRS)) diff --git a/applications/arcade/Makefile b/applications/arcade/Makefile new file mode 100644 index 00000000..28fab842 --- /dev/null +++ b/applications/arcade/Makefile @@ -0,0 +1,31 @@ +# +# Arcade App for DreamShell +# Copyright (C) 2026 SWAT +# + +SUBDIRS = modules + +include ../../sdk/Makefile.cfg + +APP_NAME = arcade +APP_DIR = $(DS_BUILD)/apps/$(APP_NAME) +DEPS = modules/app_$(APP_NAME).klf + +KOS_CFLAGS += -I./ + +all: install + +$(DEPS): $(wildcard modules/*.c) + cd modules && make + +clean: + cd modules && make clean && cd ../ + +install: app.xml config.lua $(DEPS) + -mkdir -p $(APP_DIR) + -mkdir -p $(APP_DIR)/modules + -mkdir -p $(APP_DIR)/images + cp modules/app_$(APP_NAME).klf $(APP_DIR)/modules/app_$(APP_NAME).klf + cp app.xml $(APP_DIR)/app.xml + cp config.lua $(APP_DIR)/config.lua + cp images/*.png $(APP_DIR)/images diff --git a/applications/arcade/app.xml b/applications/arcade/app.xml new file mode 100644 index 00000000..ecf5e7f2 --- /dev/null +++ b/applications/arcade/app.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/arcade/config.lua b/applications/arcade/config.lua new file mode 100644 index 00000000..9e8edb0d --- /dev/null +++ b/applications/arcade/config.lua @@ -0,0 +1,92 @@ +-- Arcade application configuration. +-- Must return a table. Missing fields fall back to built-in defaults. +-- +-- This is regular Lua: declare local variables before return, +-- compute values, combine them. +-- +-- Video position and outer frame size are defined by the video_border +-- rectangle in app.xml. video_border_size here is the inner inset used +-- to compute the actual trailer playback area. +-- +-- Background fog layers are defined in app.xml as circle elements named +-- fog_circle_* (up to 8 layers). + +return { + -- Directories to scan for games (in order) + games_paths = { + "/ide/games", + "/sd/games", + "/cd/games", + }, + + -- Scan mode: "fallback" - next path only if the page is not full; + -- "all" - scan every path and merge results + scan_mode = "fallback", + + -- Number of game covers shown on one page + games_per_page = 8, + + -- Default cover image (relative to app directory or absolute path) + default_cover = "images/cover.png", + + -- Cover file extensions inside a game folder (cover.png, cover.jpg, ...) + cover_extensions = { "png", "jpg" }, + + -- Trailer filename inside a game folder + trailer_filename = "trailer.avi", + + -- Trailer and CDDA volume: 0-255, -1 = use DreamShell audio settings + media_volume = -1, + + -- Inner inset from video_border rect in app.xml; defines trailer area + video_border_size = 2, + + -- fade_to_video progress (0.0-1.0) when video border becomes visible + video_border_show_progress = 0.7, + + -- Fog circle Z-axis drift range + fog_z_range = 14, + + -- Game label font sizes + label_size = 8, + focus_label_size = 10, + + -- Idle delay before auto-selecting the next game, ms + idle_time_ms = 4000, + + -- Focus animation duration, ms + focus_time_ms = 1000, + + -- Delay before starting trailer/audio after focus, ms + pre_video_time_ms = 1500, + + -- Wait time without trailer/audio before unfocusing, ms + no_trailer_wait_ms = 5000, + + -- Max trailer/audio playback time in auto mode, ms + video_play_time_ms = 20000, + + -- Unfocus animation duration, ms + unfocus_time_ms = 1500, + + -- Cover fade-out duration before trailer starts, ms + fade_to_video_ms = 1000, + + -- Cover fade-in duration after trailer/audio ends, ms + fade_to_cover_ms = 500, + + -- Trailer fade-in duration on start, ms + video_fade_in_ms = 500, + + -- Media fade-out duration when switching selection, ms + media_fade_out_ms = 300, + + -- Delay before checking whether CDDA track has finished, ms + audio_start_grace_ms = 1000, + + -- Pause when switching pages before loading new covers, ms + page_switch_wait_ms = 800, + + -- UI overlay fade duration (reserved), ms + fade_overlay_ms = 500, +} diff --git a/applications/arcade/images/bg.png b/applications/arcade/images/bg.png new file mode 100644 index 00000000..f69ca65c Binary files /dev/null and b/applications/arcade/images/bg.png differ diff --git a/applications/arcade/images/cover.png b/applications/arcade/images/cover.png new file mode 100644 index 00000000..75981a05 Binary files /dev/null and b/applications/arcade/images/cover.png differ diff --git a/applications/arcade/images/icon.png b/applications/arcade/images/icon.png new file mode 100644 index 00000000..a4fddb87 Binary files /dev/null and b/applications/arcade/images/icon.png differ diff --git a/applications/arcade/images/icon.svg b/applications/arcade/images/icon.svg new file mode 100644 index 00000000..7fff1b1c --- /dev/null +++ b/applications/arcade/images/icon.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/arcade/modules/Makefile b/applications/arcade/modules/Makefile new file mode 100644 index 00000000..63d4faaf --- /dev/null +++ b/applications/arcade/modules/Makefile @@ -0,0 +1,25 @@ +# +# Arcade app module for DreamShell +# Copyright (C) 2026 SWAT +# + +APP_NAME = arcade +TARGET_NAME = app_$(APP_NAME) +OBJS = module.o utils.o games.o media.o config.o +DBG_LIBS = -lds -lisofs -lisoldr -lwave -lffmpeg +EXPORTS_FILE = exports.txt + +VER_MAJOR = 1 +VER_MINOR = 0 +VER_MICRO = 0 + +all: rm-elf + +include ../../../sdk/Makefile.loadable + +rm-elf: + -rm -f $(TARGET) + +install: $(TARGET) + -rm $(DS_BUILD)/apps/$(APP_NAME)/modules/$(TARGET) + cp $(TARGET) $(DS_BUILD)/apps/$(APP_NAME)/modules/$(TARGET) diff --git a/applications/arcade/modules/app_internal.h b/applications/arcade/modules/app_internal.h new file mode 100644 index 00000000..c5912635 --- /dev/null +++ b/applications/arcade/modules/app_internal.h @@ -0,0 +1,251 @@ +/* DreamShell + + app_internal.h - Arcade app internal header + Copyright (C) 2026 SWAT + +*/ + +#ifndef __ARCADE_APP_INTERNAL_H +#define __ARCADE_APP_INTERNAL_H + +#include +#include +#include + +#define GAMES_ALLOC_CHUNK 32 +#define GAMES_PER_PAGE 8 +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 + +#define IDLE_TIME_MS 4000 +#define FOCUS_TIME_MS 1000 +#define PRE_VIDEO_TIME_MS 1500 +#define NO_TRAILER_WAIT_MS 5000 +#define FADE_TO_VIDEO_MS 1000.0f +#define VIDEO_PLAY_TIME_MS 20000 +#define FADE_TO_COVER_MS 500.0f +#define UNFOCUS_TIME_MS 1500 +#define VIDEO_FADE_IN_MS 500 +#define MEDIA_FADE_OUT_MS 300 +#define AUDIO_START_GRACE_MS 1000 +#define PAGE_SWITCH_WAIT_MS 800 +#define FADE_OVERLAY_MS 500.0f +#define DEFAULT_MEDIA_VOLUME 240 +#define VIDEO_BORDER_SHOW_PROGRESS 0.7f +#define CDDA_PROBE_TRACK 5 +#define CDDA_RANDOM_TRACK_COUNT 20 +#define CDDA_RANDOM_TRACK_OFFSET 4 +#define CDDA_MIN_TRACK_SIZE (5 << 10) + +#define VIDEO_X 192 +#define VIDEO_Y 42 +#define VIDEO_W 256 +#define VIDEO_H 256 +#define VIDEO_BORDER_SIZE 2 + +#define COVER_WIDTH 256 +#define COVER_HEIGHT 256 + +#define LABEL_SIZE 8 +#define FOCUS_LABEL_SIZE 10 + +#define FOCUS_LABEL_R 1.0f +#define FOCUS_LABEL_G 0.816f +#define FOCUS_LABEL_B 0.0f + +#define FOG_MAX 8 +#define FOG_Z_RANGE 14.0f + +#define ARCADE_MAX_GAME_PATHS 8 +#define ARCADE_MAX_COVER_EXTS 8 + +#define ARCADE_SCAN_FALLBACK 0 +#define ARCADE_SCAN_ALL 1 + +typedef struct { + char games_paths[ARCADE_MAX_GAME_PATHS][NAME_MAX]; + int games_paths_count; + int scan_mode; + int games_per_page; + char default_cover[NAME_MAX]; + char cover_exts[ARCADE_MAX_COVER_EXTS][8]; + int cover_exts_count; + char trailer_filename[NAME_MAX]; + int media_volume; + int idle_time_ms; + int focus_time_ms; + int pre_video_time_ms; + int no_trailer_wait_ms; + int video_play_time_ms; + int unfocus_time_ms; + int fade_to_video_ms; + int fade_to_cover_ms; + int video_fade_in_ms; + int media_fade_out_ms; + int audio_start_grace_ms; + int page_switch_wait_ms; + int fade_overlay_ms; + float video_border_show_progress; + int video_border_size; + float fog_z_range; + int label_size; + int focus_label_size; +} ArcadeConfig_t; + +enum { + APP_DEVICE_CD = 0, + APP_DEVICE_SD = 1, + APP_DEVICE_IDE, + APP_DEVICE_PC, + APP_DEVICE_COUNT +}; + +typedef struct { + char path[NAME_MAX]; + char game_file[NAME_MAX]; + char name[64]; + char cover_path[NAME_MAX]; + char trailer_path[NAME_MAX]; + + Banner *visual; + Texture *texture; + LogXYZMover *mover; + LogScaleMover *scale_mover; + LogXYZMover *label_mover; + Animation *anim; + Label *label; + + float x, y, z; + bool has_trailer; + bool has_cover; + bool assets_checked; + uint8 md5[16]; + uint64_t last_unfocus_time; +} GameEntry; + +typedef enum { + STATE_IDLE, + STATE_FOCUSING, + STATE_WAIT_PRE_VIDEO, + STATE_FADE_TO_VIDEO, + STATE_PLAYING_VIDEO, + STATE_PLAYING_AUDIO, + STATE_FADE_TO_COVER, + STATE_UNFOCUSING, + STATE_PAGE_SWITCH_OUT, + STATE_PAGE_SWITCH_IN +} ArcadeState; + +typedef struct AppArcadeContext { + App_t *app; + char app_path[NAME_MAX]; + ArcadeConfig_t config; + + GameEntry *games; + int games_capacity; + int game_count; + int current_page; + int total_pages; + int page_switch_dir; + int page_load_idx; + + int focused_game_idx; + int last_picked_idx; + ArcadeState state; + uint64_t last_action_time; + + Event_t *input_event; + Event_t *video_event; + + Rectangle *video_border; + int video_x; + int video_y; + int video_w; + int video_h; + Wave *wave; + float wave_z; + + Circle *fog_circles[FOG_MAX]; + LogXYZMover *fog_movers[FOG_MAX]; + float fog_depths[FOG_MAX]; + int fog_count; + + Texture *def_cover_texture; + bool exiting; + bool manual_focus; + + Label *page_label; + Label *coin_label; + Font *font; + AlphaFader *page_fader; + AlphaFader *coin_fader; + int coins; + volatile uint8_t coins_dirty; + + struct { + char path[NAME_MAX]; + ffplay_params_t params; + kthread_t *thread; + mutex_t mutex; + bool active; + int64_t duration; + } trailer; + + int64_t cdda_duration; + + kthread_t *loader_thread; + bool loader_active; + bool abort_loading; + int target_page; + bool l_trig_held; + bool r_trig_held; + + bool media_fading_out; + uint64_t media_fade_start; + int fading_game_idx; + + bool analog_left_held; + bool analog_right_held; + bool analog_up_held; + bool analog_down_held; + + bool launching; +} AppArcadeContext_t; + +extern AppArcadeContext_t self; + +void ArcadeConfigSetDefaults(ArcadeConfig_t *cfg); +int LoadArcadeConfig(const char *app_path, ArcadeConfig_t *cfg); +void ArcadeConfigResolvePath(const char *app_path, const char *path, char *result, size_t size); +void UpdateVideoLayout(void); + +int getDeviceType(const char *dir); +void getFirstPathComponent(const char *path, char *result); +void makeGameRelativePath(char *dst, size_t dst_size, const char *base_path, const char *game_path, const char *filename); +int IsGameExtension(const char *filename); +int IsNaomiRom(const char *path); +void GetMD5(const char *path, uint8 *md5); + +void ScanGames(void); +void FreeGamesList(void); +void UnloadGameVisual(int index); +void PreloadDefaultCover(void); +void LoadGameVisual(int index, int fly_in_dir); +void EnsurePageVisualsLoaded(void); +void RunGame(int index, int test_mode); +void PlayTrailer(int index); +void StopTrailer(void); +void StopMedia(void); +int GetMediaVolume(void); +size_t GetCDDATrackFilename(int num, const char *fpath, const char *filename, char *result); +void PlayCDDATrack(const char *file, int loop); +void StopCDDATrack(void); +void PauseCDDATrack(void); +void ResumeCDDATrack(void); +int IsCDDATrackPlaying(void); +void SetCDDAVolume(int vol); + +float GetRandomCloudZ(void); +void SetupCloudAnimation(GameEntry *g, float z_depth, float target_x, float target_y, float factor); + +#endif diff --git a/applications/arcade/modules/app_module.h b/applications/arcade/modules/app_module.h new file mode 100644 index 00000000..74d12d00 --- /dev/null +++ b/applications/arcade/modules/app_module.h @@ -0,0 +1,13 @@ +/* DreamShell + + app_module.h - Arcade app module header + Copyright (C) 2026 + +*/ + +#include + +void ArcadeApp_Init(App_t *app); +void ArcadeApp_Shutdown(App_t *app); +void ArcadeApp_Open(App_t *app); +void ArcadeApp_Close(App_t *app); diff --git a/applications/arcade/modules/config.c b/applications/arcade/modules/config.c new file mode 100644 index 00000000..eee1728c --- /dev/null +++ b/applications/arcade/modules/config.c @@ -0,0 +1,153 @@ +/* DreamShell + + config.c - Arcade app config + Copyright (C) 2026 SWAT + +*/ + +#include +#include +#include "app_internal.h" + +void ArcadeConfigSetDefaults(ArcadeConfig_t *cfg) { + memset(cfg, 0, sizeof(*cfg)); + + cfg->games_paths_count = 3; + strncpy(cfg->games_paths[0], "/ide/games", NAME_MAX - 1); + strncpy(cfg->games_paths[1], "/sd/games", NAME_MAX - 1); + strncpy(cfg->games_paths[2], "/cd/games", NAME_MAX - 1); + + cfg->scan_mode = ARCADE_SCAN_FALLBACK; + cfg->games_per_page = GAMES_PER_PAGE; + strncpy(cfg->default_cover, "images/cover.png", NAME_MAX - 1); + + cfg->cover_exts_count = 2; + strncpy(cfg->cover_exts[0], "png", sizeof(cfg->cover_exts[0]) - 1); + strncpy(cfg->cover_exts[1], "jpg", sizeof(cfg->cover_exts[1]) - 1); + + strncpy(cfg->trailer_filename, "trailer.avi", NAME_MAX - 1); + cfg->media_volume = -1; + + cfg->idle_time_ms = IDLE_TIME_MS; + cfg->focus_time_ms = FOCUS_TIME_MS; + cfg->pre_video_time_ms = PRE_VIDEO_TIME_MS; + cfg->no_trailer_wait_ms = NO_TRAILER_WAIT_MS; + cfg->video_play_time_ms = VIDEO_PLAY_TIME_MS; + cfg->unfocus_time_ms = UNFOCUS_TIME_MS; + cfg->fade_to_video_ms = (int)FADE_TO_VIDEO_MS; + cfg->fade_to_cover_ms = (int)FADE_TO_COVER_MS; + cfg->video_fade_in_ms = VIDEO_FADE_IN_MS; + cfg->media_fade_out_ms = MEDIA_FADE_OUT_MS; + cfg->audio_start_grace_ms = AUDIO_START_GRACE_MS; + cfg->page_switch_wait_ms = PAGE_SWITCH_WAIT_MS; + cfg->fade_overlay_ms = (int)FADE_OVERLAY_MS; + cfg->video_border_show_progress = VIDEO_BORDER_SHOW_PROGRESS; + cfg->video_border_size = VIDEO_BORDER_SIZE; + cfg->fog_z_range = FOG_Z_RANGE; + cfg->label_size = LABEL_SIZE; + cfg->focus_label_size = FOCUS_LABEL_SIZE; +} + +void ArcadeConfigResolvePath(const char *app_path, const char *path, char *result, size_t size) { + if (!path || !path[0]) { + result[0] = '\0'; + return; + } + + if (path[0] == '/') { + strncpy(result, path, size - 1); + result[size - 1] = '\0'; + } + else { + snprintf(result, size, "%s/%s", app_path, path); + } +} + +static void ReadCoverExtensions(const LuaConfig_t *lcfg, ArcadeConfig_t *cfg) { + int n = LuaConfigGetStringArray(lcfg, "cover_extensions", + (char *)cfg->cover_exts, sizeof(cfg->cover_exts[0]), ARCADE_MAX_COVER_EXTS); + if (n > 0) { + cfg->cover_exts_count = n; + } +} + +static void ReadScanMode(const LuaConfig_t *lcfg, ArcadeConfig_t *cfg) { + char mode[32]; + + mode[0] = '\0'; + if (LuaConfigGetString(lcfg, "scan_mode", mode, sizeof(mode)) && !strcasecmp(mode, "all")) { + cfg->scan_mode = ARCADE_SCAN_ALL; + } + else { + cfg->scan_mode = ARCADE_SCAN_FALLBACK; + } +} + +int LoadArcadeConfig(const char *app_path, ArcadeConfig_t *cfg) { + char config_path[NAME_MAX]; + + ArcadeConfigSetDefaults(cfg); + snprintf(config_path, sizeof(config_path), "%s/config.lua", app_path); + + LuaConfig_t lcfg; + + if (LuaOpenConfigFile(config_path, &lcfg) != 0) { + ds_printf("Arcade: Failed to load %s, using default config\n", config_path); + return -1; + } + + cfg->games_paths_count = LuaConfigGetStringArray(&lcfg, "games_paths", + (char *)cfg->games_paths, NAME_MAX, ARCADE_MAX_GAME_PATHS); + ReadScanMode(&lcfg, cfg); + ReadCoverExtensions(&lcfg, cfg); + + cfg->games_per_page = LuaConfigGetInt(&lcfg, "games_per_page", cfg->games_per_page); + cfg->media_volume = LuaConfigGetInt(&lcfg, "media_volume", cfg->media_volume); + + cfg->idle_time_ms = LuaConfigGetInt(&lcfg, "idle_time_ms", cfg->idle_time_ms); + cfg->focus_time_ms = LuaConfigGetInt(&lcfg, "focus_time_ms", cfg->focus_time_ms); + cfg->pre_video_time_ms = LuaConfigGetInt(&lcfg, "pre_video_time_ms", cfg->pre_video_time_ms); + cfg->no_trailer_wait_ms = LuaConfigGetInt(&lcfg, "no_trailer_wait_ms", cfg->no_trailer_wait_ms); + cfg->video_play_time_ms = LuaConfigGetInt(&lcfg, "video_play_time_ms", cfg->video_play_time_ms); + cfg->unfocus_time_ms = LuaConfigGetInt(&lcfg, "unfocus_time_ms", cfg->unfocus_time_ms); + cfg->fade_to_video_ms = LuaConfigGetInt(&lcfg, "fade_to_video_ms", cfg->fade_to_video_ms); + cfg->fade_to_cover_ms = LuaConfigGetInt(&lcfg, "fade_to_cover_ms", cfg->fade_to_cover_ms); + cfg->video_fade_in_ms = LuaConfigGetInt(&lcfg, "video_fade_in_ms", cfg->video_fade_in_ms); + cfg->media_fade_out_ms = LuaConfigGetInt(&lcfg, "media_fade_out_ms", cfg->media_fade_out_ms); + cfg->audio_start_grace_ms = LuaConfigGetInt(&lcfg, "audio_start_grace_ms", cfg->audio_start_grace_ms); + cfg->page_switch_wait_ms = LuaConfigGetInt(&lcfg, "page_switch_wait_ms", cfg->page_switch_wait_ms); + cfg->fade_overlay_ms = LuaConfigGetInt(&lcfg, "fade_overlay_ms", cfg->fade_overlay_ms); + cfg->video_border_show_progress = LuaConfigGetFloat(&lcfg, "video_border_show_progress", cfg->video_border_show_progress); + cfg->video_border_size = LuaConfigGetInt(&lcfg, "video_border_size", cfg->video_border_size); + cfg->fog_z_range = LuaConfigGetFloat(&lcfg, "fog_z_range", cfg->fog_z_range); + cfg->label_size = LuaConfigGetInt(&lcfg, "label_size", cfg->label_size); + cfg->focus_label_size = LuaConfigGetInt(&lcfg, "focus_label_size", cfg->focus_label_size); + + LuaConfigGetString(&lcfg, "default_cover", cfg->default_cover, sizeof(cfg->default_cover)); + LuaConfigGetString(&lcfg, "trailer_filename", cfg->trailer_filename, sizeof(cfg->trailer_filename)); + + if (cfg->games_per_page < 1) { + cfg->games_per_page = GAMES_PER_PAGE; + } + if (cfg->video_border_size < 0) { + cfg->video_border_size = VIDEO_BORDER_SIZE; + } + if (cfg->fog_z_range <= 0.0f) { + cfg->fog_z_range = FOG_Z_RANGE; + } + if (cfg->label_size < 1) { + cfg->label_size = LABEL_SIZE; + } + if (cfg->focus_label_size < 1) { + cfg->focus_label_size = FOCUS_LABEL_SIZE; + } + if (cfg->games_paths_count <= 0) { + cfg->games_paths_count = 2; + strncpy(cfg->games_paths[0], "/ide/games", NAME_MAX - 1); + strncpy(cfg->games_paths[1], "/sd/games", NAME_MAX - 1); + // strncpy(cfg->games_paths[2], "/cd/games", NAME_MAX - 1); + } + + LuaCloseConfigFile(&lcfg); + return 0; +} diff --git a/applications/arcade/modules/exports.txt b/applications/arcade/modules/exports.txt new file mode 100644 index 00000000..d51594ff --- /dev/null +++ b/applications/arcade/modules/exports.txt @@ -0,0 +1,8 @@ +include kos/exports.h +include app_module.h + +# Arcade exports +ArcadeApp_Init +ArcadeApp_Shutdown +ArcadeApp_Open +ArcadeApp_Close diff --git a/applications/arcade/modules/games.c b/applications/arcade/modules/games.c new file mode 100644 index 00000000..9178e961 --- /dev/null +++ b/applications/arcade/modules/games.c @@ -0,0 +1,445 @@ +/* DreamShell + + games.c - Arcade app games + Copyright (C) 2026 SWAT + +*/ + +#include +#include +#include +#include + +#include "app_internal.h" + +static void FormatGameLabelText(const GameEntry *g, char *buf, size_t size) { + int i; + + for (i = 0; g->name[i] && i < (int)size - 1; i++) { + buf[i] = (g->name[i] == '_') ? ' ' : g->name[i]; + } + buf[i] = '\0'; +} + +static int EnsureGamesCapacity(int min_capacity) { + if (min_capacity <= self.games_capacity) { + return 1; + } + + int new_capacity = self.games_capacity; + + if (new_capacity == 0) { + new_capacity = GAMES_ALLOC_CHUNK; + } + + while (new_capacity < min_capacity) { + new_capacity += GAMES_ALLOC_CHUNK; + } + + GameEntry *new_games = (GameEntry *)realloc(self.games, new_capacity * sizeof(GameEntry)); + + if (!new_games) { + ds_printf("Arcade: Failed to allocate memory for %d games\n", new_capacity); + return 0; + } + + if (new_capacity > self.games_capacity) { + memset(new_games + self.games_capacity, 0, + (new_capacity - self.games_capacity) * sizeof(GameEntry)); + } + + self.games = new_games; + self.games_capacity = new_capacity; + return 1; +} + +static int AddGameEntry(const char *game_dir, const char *dir_name, const char *game_file) { + if (!EnsureGamesCapacity(self.game_count + 1)) { + ds_printf("Arcade: Out of memory, stopping scan at %d games\n", self.game_count); + return 0; + } + + GameEntry *g = &self.games[self.game_count]; + snprintf(g->path, sizeof(g->path), "%s", game_dir); + snprintf(g->game_file, sizeof(g->game_file), "%s", game_file); + snprintf(g->name, sizeof(g->name), "%s", dir_name); + + g->has_cover = false; + g->has_trailer = false; + g->assets_checked = false; + + self.game_count++; + return 1; +} + +void FreeGamesList(void) { + free(self.games); + self.games = NULL; + self.games_capacity = 0; + self.game_count = 0; +} + +static int GetGameFileFromDir(const char *dir, char *result_name, size_t result_size) { + file_t d = fs_open(dir, O_DIR | O_RDONLY); + if (d == FILEHND_INVALID) return 0; + + const dirent_t *de; + int found = 0; + + while ((de = fs_readdir(d))) { + if (IsGameExtension(de->name)) { + if (result_name) snprintf(result_name, result_size, "%s", de->name); + found = 1; + break; + } + } + fs_close(d); + return found; +} + +static void ProcessGameEntry(int index) { + GameEntry *g = &self.games[index]; + + if (g->assets_checked) return; + + g->cover_path[0] = '\0'; + g->trailer_path[0] = '\0'; + g->has_cover = false; + g->has_trailer = false; + + for (int i = 0; i < self.config.cover_exts_count; i++) { + snprintf(g->cover_path, sizeof(g->cover_path), "%s/cover.%s", g->path, self.config.cover_exts[i]); + if (FileExists(g->cover_path)) { + g->has_cover = true; + break; + } + } + if (!g->has_cover) { + g->cover_path[0] = '\0'; + } + + snprintf(g->trailer_path, sizeof(g->trailer_path), "%s/%s", g->path, self.config.trailer_filename); + g->has_trailer = FileExists(g->trailer_path); + if (!g->has_trailer) { + g->trailer_path[0] = '\0'; + } + g->assets_checked = true; +} + +static bool ScanGamesInDir(const char *dir) { + file_t d = fs_open(dir, O_DIR | O_RDONLY); + if (d == FILEHND_INVALID) return false; + + const dirent_t *de; + while ((de = fs_readdir(d))) { + if (de->name[0] == '.') continue; + if (de->attr & O_DIR) { + + char dir_name[NAME_MAX]; + strncpy(dir_name, de->name, sizeof(dir_name) - 1); + dir_name[sizeof(dir_name) - 1] = '\0'; + + char game_dir[NAME_MAX]; + snprintf(game_dir, sizeof(game_dir), "%s/%s", dir, dir_name); + + char game_file[NAME_MAX]; + if (GetGameFileFromDir(game_dir, game_file, sizeof(game_file))) { + if (!AddGameEntry(game_dir, dir_name, game_file)) { + fs_close(d); + return false; + } + } + else { + if (!ScanGamesInDir(game_dir)) { + fs_close(d); + return false; + } + } + } + } + fs_close(d); + return true; +} + +static void ScanGamesFallback(void) { + int path_count = self.config.games_paths_count; + + if (path_count <= 0) { + return; + } + + ScanGamesInDir(self.config.games_paths[0]); + + for (int i = 1; i < path_count; i++) { + if (self.game_count >= self.config.games_per_page) { + break; + } + if (i >= 2 && self.game_count > 0) { + break; + } + ScanGamesInDir(self.config.games_paths[i]); + } +} + +void ScanGames() { + self.game_count = 0; + + if (self.config.scan_mode == ARCADE_SCAN_ALL) { + for (int i = 0; i < self.config.games_paths_count; i++) { + if (!ScanGamesInDir(self.config.games_paths[i])) { + break; + } + } + } + else { + ScanGamesFallback(); + } + + ds_printf("Arcade: Found %d games\n", self.game_count); + + self.total_pages = (self.game_count + self.config.games_per_page - 1) / self.config.games_per_page; + if (self.total_pages == 0) self.total_pages = 1; +} + +void UnloadGameVisual(int index) { + if (index < 0 || index >= self.game_count) return; + GameEntry *g = &self.games[index]; + + if (g->visual) { + TSU_AppSubRemoveBanner(self.app->tsunami, g->visual); + TSU_BannerDestroy(&g->visual); + g->visual = NULL; + } + + if (g->label) { + TSU_AppSubRemoveLabel(self.app->tsunami, g->label); + TSU_LabelDestroy(&g->label); + g->label = NULL; + } + + if (g->texture != self.def_cover_texture) { + TSU_TextureDestroy(&g->texture); + } + g->texture = NULL; + + if (g->mover) { + TSU_LogXYZMoverDestroy(&g->mover); + g->mover = NULL; + } + if (g->scale_mover) { + TSU_LogScaleMoverDestroy(&g->scale_mover); + g->scale_mover = NULL; + } + if (g->label_mover) { + TSU_LogXYZMoverDestroy(&g->label_mover); + g->label_mover = NULL; + } +} + +void PreloadDefaultCover(void) { + if (self.def_cover_texture) { + return; + } + + char def_cover_path[NAME_MAX]; + ArcadeConfigResolvePath(self.app_path, self.config.default_cover, + def_cover_path, sizeof(def_cover_path)); + + self.def_cover_texture = TSU_TextureCreateEmpty(); + if (!TSU_TextureLoadFromFile(self.def_cover_texture, def_cover_path, true, false, 0)) { + TSU_TextureDestroy(&self.def_cover_texture); + self.def_cover_texture = NULL; + } +} + +void EnsurePageVisualsLoaded(void) { + if (self.game_count <= 0) { + return; + } + + int start = self.current_page * self.config.games_per_page; + int count = (self.game_count < self.config.games_per_page) ? self.game_count : self.config.games_per_page; + + for (int i = 0; i < count; i++) { + int idx = (start + i) % self.game_count; + if (!self.games[idx].visual) { + LoadGameVisual(idx, self.page_switch_dir); + } + } +} + +void LoadGameVisual(int index, int fly_in_dir) { + if (index < 0 || index >= self.game_count) return; + + if (self.games[index].visual) { + UnloadGameVisual(index); + } + + GameEntry *g = &self.games[index]; + ProcessGameEntry(index); + + bool is_transparent = false; + + if (g->has_cover) { + g->texture = TSU_TextureCreateEmpty(); + if (!TSU_TextureLoadFromFile(g->texture, g->cover_path, true, false, 0)) { + TSU_TextureDestroy(&g->texture); + g->texture = NULL; + g->has_cover = false; + } + } + else if (g->game_file[0]) { + char full_game_path[NAME_MAX]; + snprintf(full_game_path, sizeof(full_game_path), "%s/%s", g->path, g->game_file); + + int fn_len = strlen(g->game_file); + bool is_naomi = (fn_len > 4 && !strcasecmp(g->game_file + fn_len - 4, ".dni")); + + if (!is_naomi && fs_iso_mount("/isocover", full_game_path) == 0) { + const char *cover_path = "/isocover/0GDTEX.PVR"; + + if (FileExists(cover_path)) { + g->texture = TSU_TextureCreateEmpty(); + if (TSU_TextureLoadFromFile(g->texture, cover_path, true, false, 0)) { + is_transparent = true; + } + else { + TSU_TextureDestroy(&g->texture); + g->texture = NULL; + } + } + fs_iso_unmount("/isocover"); + } + } + + if (!g->texture) { + PreloadDefaultCover(); + g->texture = self.def_cover_texture; + is_transparent = true; + } + + if (!g->texture) return; + + g->visual = TSU_BannerCreate(is_transparent ? PVR_LIST_TR_POLY : PVR_LIST_OP_POLY, g->texture); + if (!g->visual) { + if (g->texture != self.def_cover_texture) { + TSU_TextureDestroy(&g->texture); + } + g->texture = NULL; + return; + } + + TSU_BannerSetSize(g->visual, COVER_WIDTH, COVER_HEIGHT); + + float z_depth = GetRandomCloudZ(); + float scale_val = 0.25f + (z_depth / 100.0f) * 0.35f; + + g->z = z_depth; + + Vector scale = {scale_val, scale_val, 1.0f, 0.0f}; + TSU_DrawableSetScale((Drawable *)g->visual, &scale); + + int obj_size = (int)(COVER_WIDTH * scale_val); + float target_y = (rand() % (SCREEN_HEIGHT - obj_size)) + obj_size * 0.75f; + + float start_x, start_y; + + if (fly_in_dir == 2 || fly_in_dir == -2) { + start_x = (rand() % SCREEN_WIDTH) - 50.0f; + + if (fly_in_dir > 0) start_y = SCREEN_HEIGHT + 100.0f; + else start_y = -COVER_HEIGHT - 100.0f; + } + else { + start_y = target_y; + + if (fly_in_dir != 0) { + if (fly_in_dir > 0) start_x = SCREEN_WIDTH + 100.0f; + else start_x = -COVER_WIDTH - 100.0f; + } else { + start_x = (rand() % SCREEN_WIDTH) - 50.0f; + } + } + + g->x = start_x; + g->y = start_y; + + Vector pos = {g->x + obj_size / 2.0f, g->y, g->z, 1.0f}; + TSU_DrawableTranslate((Drawable *)g->visual, &pos); + + if (self.font) { + char label_text[sizeof(g->name)]; + + FormatGameLabelText(g, label_text, sizeof(label_text)); + + g->label = TSU_LabelCreate(self.font, label_text, self.config.label_size, false, false, false); + static Color white = {1.0f, 1.0f, 1.0f, 1.0f}; + TSU_LabelSetTint(g->label, &white); + + float w, h; + TSU_LabelGetSize(g->label, &w, &h); + + float off_x = (obj_size - w) / 2.0f; + float off_y = (float)obj_size / 2.0f + h + 5.0f; + + Vector l_abs_pos = {g->x + off_x, g->y + off_y, g->z + 0.5f, 1.0f}; + TSU_DrawableSetTranslate((Drawable *)g->label, &l_abs_pos); + } +} + +static int FindGameFile(GameEntry *g, char *full_path, size_t size) { + if (!g->game_file[0]) return 0; + + snprintf(full_path, size, "%s/%s", g->path, g->game_file); + + int len = strlen(g->game_file); + if (len <= 4) return 0; + + const char *ext = g->game_file + len - 4; + + if (!strcasecmp(ext, ".dni")) { + if (IsNaomiRom(full_path)) { + naomi_cart_header_t hdr; + file_t f = fs_open(full_path, O_RDONLY); + if (f != FILEHND_INVALID) { + fs_read(f, &hdr, sizeof(hdr)); + fs_close(f); + kos_md5((uint8 *)&hdr, sizeof(hdr), g->md5); + return 1; + } + } + } + else { + GetMD5(full_path, g->md5); + return 1; + } + return 0; +} + +void RunGame(int index, int test_mode) { + GameEntry *g = &self.games[index]; + char game_path[NAME_MAX]; + + if (!FindGameFile(g, game_path, sizeof(game_path))) { + ds_printf("Arcade: Failed to find game file for %s\n", g->path); + return; + } + + isoldr_info_t *isoldr = isoldr_get_info(game_path, test_mode); + + if (!isoldr) { + ds_printf("Arcade: Failed to get isoldr info for %s\n", game_path); + return; + } + + char *preset_file = isoldr_find_preset(game_path, g->md5, 0); + uintptr_t exec_addr = isoldr_apply_preset(isoldr, preset_file); + + if (exec_addr == (uintptr_t)-1) { + free(isoldr); + return; + } + + isoldr_exec(isoldr, exec_addr); + free(isoldr); +} diff --git a/applications/arcade/modules/media.c b/applications/arcade/modules/media.c new file mode 100644 index 00000000..6a585bdd --- /dev/null +++ b/applications/arcade/modules/media.c @@ -0,0 +1,150 @@ +/* DreamShell + + media.c - Arcade app media + Copyright (C) 2026 SWAT + +*/ + +#include