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