Working on 2.20. Or maybe it'll be 3.0.

This commit is contained in:
Scott Duensing 2026-09-02 16:54:23 -05:00
parent f62bd1ed30
commit f6d63b6eb0
37 changed files with 11583 additions and 11123 deletions

3
.gitattributes vendored
View file

@ -25,6 +25,9 @@
*.exe filter=lfs diff=lfs merge=lfs -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.dat filter=lfs diff=lfs merge=lfs -text
# Singe game and menu descriptors are Lua text, not binary.
games.dat !filter !diff !merge text
menu.dat !filter !diff !merge text
*.ttf filter=lfs diff=lfs merge=lfs -text
#

11
.gitignore vendored
View file

@ -1,9 +1,18 @@
*~
**/.builddir/
**/.git-hidden/
**/.github/
thirdparty/**/.github/
cmake-build-debug/
*.log
*.user
temp/
*#
build/
CMakeCache.txt
CMakeFiles/
*.a
*.o
docs/Manual.html
docs/Manual.pdf
docs/.asciidoctor/
.claude/settings.local.json

124
CHANGELOG
View file

@ -1,3 +1,121 @@
SINGE 2.20
==========
Unreleased
API Changes
-----------
- The sprite handle is now the FIRST argument of spriteDraw(), spriteLoop(),
spriteQuality(), spriteRotate(), spriteRotateAndScale(), spriteScale(), and
spriteSetFrame(), matching the video*() functions. Move the last argument
of each call to the front. Games that cannot be edited can set
SINGE_LEGACY_SPRITE_ARGS = true before loading the framework, or add
LEGACY_SPRITE_ARGS = true to their games.dat entry. See "Migrating from
Singe 2.10" in the manual.
- mouseSetEnabled(bool) and singeSetPauseKeyEnabled(bool) added. The old
mouseEnable/mouseDisable and singeEnablePauseKey/singeDisablePauseKey are
now aliases defined in Framework.singe.
- Handles, counts, and frame numbers are returned as Lua integers.
videoIsPlaying() returns a boolean. mouseSetMode() returns nothing.
- onControllerMoved() receives the controller index (0 to 3), not SDL's
instance ID. onMouseMoved() in MANY_MOUSE mode receives real relative
motion in its third and fourth arguments.
- Runtime errors inside callbacks now end the game with a traceback, the way
argument errors always have.
- Held keys no longer auto-repeat in MODE_NORMAL.
- The engine parks the disc on frame 1 before the script runs; the framework
no longer seeks when it is loaded, so threaded games keep their own
startup positioning.
- The pause key now pauses the whole game. The engine freezes the script
(no callbacks, no singeMain), holds sound completions, releases and later
re-presses held inputs, and draws a PAUSED indicator. It acts on the key
press, and SWITCH_PAUSE reaches the script only when the key is disabled
with singeSetPauseKeyEnabled(false). singeSetPauseFlag() still pauses
media without freezing the script. Gamepad and mouse buttons mapped to
pause, quit, screenshot, or grab now work in MODE_FULL too; keyboard
mappings stay raw there.
- Constants that used to be duplicated in Framework.singe (SWITCH_*,
FONT_QUALITY_*, MODE_*, MOUSE_*, OVERLAY_*, RENDER_*, SOUND_ERROR_*) are
now defined by the engine. New: DISC_STOPPED/DISC_PLAYING/DISC_PAUSED
for discGetState(), SINGE_VERSION_MAJOR/MINOR/STRING, and the SINGE_*
input code layout that Framework.singe builds GAMEPAD_N and MOUSE_N from.
Fixes
-----
- controllerGetButton() never worked; it rejected every framework button
code.
- The five argument form of spriteDraw() always failed.
- spriteSetFrame() on a still image crashed.
- Unloading the selected font left later fontPrint() calls using freed
memory.
- overlayCircle() drew around the origin instead of the requested center.
- overlaySetResolution() broke mouse coordinates and vldpGetPixel() because
the overlay scale was computed with integer division.
- mouseGetPosition() always returned 0,0 and controllerGetAxis() read the
wrong controller because the axis cache was indexed two different ways.
- Analog stick release events fired with a stale input code. Controllers
that were unplugged and replugged shifted their codes.
- The -o/--audio option was lost when the configuration was copied.
- --option=value forms of numeric options, and options after the script
name, were not accepted.
- Running "singe game.singe" from the game's own directory wrote its data
to the filesystem root.
- Framefile playback never advanced past the first segment, and switching
segments dropped the selected audio track.
- Video timing accumulated main loop latency, drifting behind the audio.
Audio is now the master clock: the picture follows the samples the sound
device has actually consumed, so the two cannot drift apart and the
mixer's buffering latency is accounted for. Silent videos follow the wall
clock. The first frame after a seek was shown one update late.
- Video without an audio track crashed on the first update.
- The video audio stream was shared with the mixer thread without a lock.
- onSoundCompleted was called from the audio thread. It is now delivered
from the game loop.
- Screenshots read the frame buffer through the window surface, which SDL
forbids alongside a renderer, and could corrupt memory on shutdown.
- .patch archives crashed the installer. Archives without an explicit top
level directory entry were rejected. Extraction now refuses paths that
escape the installation directory.
- The Sinden gun arguments could overflow the configuration structure.
- Windows builds now carry the icon and version resource, with the version
number encoded correctly.
- The build no longer depends on lyx, upx, or xxd; embedded resources, the
version header, and the manual are generated by CMake. The manual is
embedded again and extracted as Singe/Manual.pdf.
SINGE 2.10
==========
@ -8,8 +126,8 @@ New Features
------------
- spriteDraw() now has two more forms. In addition to being able to draw
regular sprites and streteched sprites it can now draw both using the
sprite's center as the anchor instead of the upper right. This is highly
regular sprites and stretched sprites it can now draw both using the
sprite's center as the anchor instead of the upper left. This is highly
useful when dealing with rotated sprites.
- Animated sprites! Both animated GIF and WEBP images are supported.
@ -38,7 +156,7 @@ New Features
directly into the script context. Now there is a proper Lua module search
handler. Scripts can properly require() modules from the following:
- Lua Standard Library
- Lua Auxillary Library
- Lua Auxiliary Library
- LuaFileSystem
- LuaSocket
- LuaSec

View file

@ -20,8 +20,13 @@
cmake_minimum_required(VERSION 3.22)
set(CMAKE_CXX_STANDARD 17)
project(singe2)
project(singe2 VERSION 2.20 LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
# The copyright end year tracks the build date so nothing has to be hand edited.
string(TIMESTAMP SINGE_COPYRIGHT_END_YEAR "%Y")
if(NOT DEFINED KANGAROO_OS)
@ -31,201 +36,283 @@ if(NOT DEFINED KANGAROO_ARCH)
set(KANGAROO_ARCH x86_64)
endif()
# Where build-all.sh installed the third party libraries for this platform.
set(BUILD_DIR ${CMAKE_SOURCE_DIR}/.builddir/${KANGAROO_OS}/${KANGAROO_ARCH})
set(GENERATED_DIR ${CMAKE_BINARY_DIR}/generated)
file(MAKE_DIRECTORY ${GENERATED_DIR})
# ===== Host tools needed to generate embedded resources =====
find_program(IMAGEMAGICK NAMES magick convert)
if(NOT IMAGEMAGICK)
message(FATAL_ERROR "ImageMagick (magick or convert) is required to render the embedded images.")
endif()
find_program(FFMPEG_TOOL NAMES ffmpeg)
if(NOT FFMPEG_TOOL)
message(FATAL_ERROR "ffmpeg is required to build the menu background video.")
endif()
find_program(LUA_TOOL NAMES lua5.4 lua)
if(NOT LUA_TOOL)
message(FATAL_ERROR "A Lua interpreter is required to generate the LuaSec option table.")
endif()
find_program(ASCIIDOCTOR_PDF NAMES asciidoctor-pdf)
if(NOT ASCIIDOCTOR_PDF)
message(FATAL_ERROR "asciidoctor-pdf is required to build the embedded manual (gem install asciidoctor-pdf rouge).")
endif()
find_program(ASCIIDOCTOR NAMES asciidoctor)
# ===== Generated sources =====
# Version header and Windows resources come from the project() line above.
configure_file(src/version.h.in ${GENERATED_DIR}/version.h @ONLY)
configure_file(src/singe.rc.in ${GENERATED_DIR}/singe.rc @ONLY)
# LuaSec needs its headers reachable as <luasocket/...>.
file(GLOB LUASOCKET_HEADERS thirdparty/luasocket/src/*.h)
file(COPY ${LUASOCKET_HEADERS} DESTINATION ${GENERATED_DIR}/include/luasocket)
# LuaSec option table generated from the OpenSSL headers that were built.
add_custom_command(
OUTPUT ${GENERATED_DIR}/luasec_options.c
COMMAND ${CMAKE_COMMAND} -DOUTPUT=${GENERATED_DIR}/luasec_options.c -DCOMMAND=${LUA_TOOL} "-DARGS=${CMAKE_SOURCE_DIR}/thirdparty/luasec/src/options.lua;-g;${BUILD_DIR}/include/openssl/ssl.h" -P ${CMAKE_SOURCE_DIR}/cmake/runToFile.cmake
DEPENDS thirdparty/luasec/src/options.lua ${BUILD_DIR}/include/openssl/ssl.h cmake/runToFile.cmake
COMMENT "Generating LuaSec options"
VERBATIM
)
# Embeds a file as a C array. The symbol name is derived from the file name, as xxd does.
set(EMBEDDED_HEADERS)
function(singeEmbed input output prefix)
get_filename_component(name ${input} NAME)
string(MAKE_C_IDENTIFIER "${prefix}${name}" symbol)
get_filename_component(guardName ${output} NAME)
string(MAKE_C_IDENTIFIER "${guardName}" guard)
string(TOUPPER "${guard}" guard)
add_custom_command(
OUTPUT ${output}
COMMAND ${CMAKE_COMMAND} -DINPUT=${input} -DOUTPUT=${output} -DGUARD=${guard} -DSYMBOL=${symbol} -DCOPYRIGHT_END_YEAR=${SINGE_COPYRIGHT_END_YEAR} -P ${CMAKE_SOURCE_DIR}/cmake/embed.cmake
DEPENDS ${input} cmake/embed.cmake
COMMENT "Embedding ${name}"
VERBATIM
)
set(EMBEDDED_HEADERS ${EMBEDDED_HEADERS} ${output} PARENT_SCOPE)
endfunction()
# Renders a layered image to a flattened PNG and embeds it.
function(singeEmbedImage name)
add_custom_command(
OUTPUT ${GENERATED_DIR}/${name}.png
COMMAND ${IMAGEMAGICK} ${CMAKE_SOURCE_DIR}/assets/${name}.xcf -background "rgba(0,0,0,0)" -flatten ${GENERATED_DIR}/${name}.png
DEPENDS assets/${name}.xcf
COMMENT "Rendering ${name}.png"
VERBATIM
)
singeEmbed(${GENERATED_DIR}/${name}.png ${GENERATED_DIR}/${name}.h "")
set(EMBEDDED_HEADERS ${EMBEDDED_HEADERS} PARENT_SCOPE)
endfunction()
# Embeds a Lua module from the third party tree.
function(singeEmbedLua path prefix)
get_filename_component(name ${path} NAME_WE)
singeEmbed(${CMAKE_SOURCE_DIR}/${path} ${GENERATED_DIR}/${prefix}${name}_lua.h "${prefix}")
set(EMBEDDED_HEADERS ${EMBEDDED_HEADERS} PARENT_SCOPE)
endfunction()
# Images
singeEmbedImage(font)
singeEmbedImage(icon)
singeEmbedImage(kangarooPunchLogo)
singeEmbedImage(singeLogo)
singeEmbedImage(laserDisc)
singeEmbedImage(magnifyingGlass)
singeEmbedImage(indexing)
# Windows icon for the resource file.
add_custom_command(
OUTPUT ${GENERATED_DIR}/icon.ico
COMMAND ${IMAGEMAGICK} ${GENERATED_DIR}/icon.png -define icon:auto-resize=64,48,32,16 ${GENERATED_DIR}/icon.ico
DEPENDS ${GENERATED_DIR}/icon.png
COMMENT "Rendering icon.ico"
VERBATIM
)
# Support files extracted at first run.
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Framework.singe ${GENERATED_DIR}/Framework_singe.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/controls.cfg ${GENERATED_DIR}/controls_cfg.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Menu.singe ${GENERATED_DIR}/Menu_singe.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/FreeSansBold.ttf ${GENERATED_DIR}/FreeSansBold_ttf.h "")
# Menu background video: two clips cropped to 4:3, scaled to 720x480, and joined.
file(WRITE ${GENERATED_DIR}/menuBackground.txt "file ${GENERATED_DIR}/menuBackground1.mkv\nfile ${GENERATED_DIR}/menuBackground2.mkv\n")
add_custom_command(
OUTPUT ${GENERATED_DIR}/menuBackground.mkv
COMMAND ${FFMPEG_TOOL} -y -loglevel error -i "${CMAKE_SOURCE_DIR}/assets/Singe Engine Intro.mpg" -filter:v "crop=ih/3*4:ih,scale=720:480" -c:v libx264 -c:a aac -f matroska ${GENERATED_DIR}/menuBackground1.mkv
COMMAND ${FFMPEG_TOOL} -y -loglevel error -i ${CMAKE_SOURCE_DIR}/assets/180503_01_PurpleGrid.mp4 -filter:v "crop=ih/3*4:ih,scale=720:480" -c:v libx264 -c:a aac -f matroska ${GENERATED_DIR}/menuBackground2.mkv
COMMAND ${FFMPEG_TOOL} -y -loglevel error -f concat -safe 0 -i ${GENERATED_DIR}/menuBackground.txt -c copy ${GENERATED_DIR}/menuBackground.mkv
DEPENDS "assets/Singe Engine Intro.mpg" assets/180503_01_PurpleGrid.mp4
COMMENT "Building menuBackground.mkv"
VERBATIM
)
singeEmbed(${GENERATED_DIR}/menuBackground.mkv ${GENERATED_DIR}/menuBackground_mkv.h "")
# Manual, rendered from the AsciiDoc source and shipped inside the binary.
add_custom_command(
OUTPUT ${GENERATED_DIR}/Manual.pdf
COMMAND ${ASCIIDOCTOR_PDF} -a revnumber=${PROJECT_VERSION} ${CMAKE_SOURCE_DIR}/docs/Manual.adoc -o ${GENERATED_DIR}/Manual.pdf
DEPENDS docs/Manual.adoc
COMMENT "Rendering Manual.pdf"
VERBATIM
)
singeEmbed(${GENERATED_DIR}/Manual.pdf ${GENERATED_DIR}/Manual_pdf.h "")
# Lua libraries
singeEmbedLua(thirdparty/luasocket/src/ftp.lua "")
singeEmbedLua(thirdparty/luasocket/src/headers.lua "")
singeEmbedLua(thirdparty/luasocket/src/http.lua "")
singeEmbedLua(thirdparty/luasocket/src/ltn12.lua "")
singeEmbedLua(thirdparty/luasocket/src/mbox.lua "")
singeEmbedLua(thirdparty/luasocket/src/mime.lua "")
singeEmbedLua(thirdparty/luasocket/src/smtp.lua "")
singeEmbedLua(thirdparty/luasocket/src/socket.lua "")
singeEmbedLua(thirdparty/luasocket/src/tp.lua "")
singeEmbedLua(thirdparty/luasocket/src/url.lua "")
singeEmbedLua(thirdparty/luasec/src/https.lua "")
singeEmbedLua(thirdparty/luasec/src/ssl.lua "")
singeEmbedLua(thirdparty/librs232/bindings/lua/rs232.lua "")
singeEmbedLua(thirdparty/copas/src/copas.lua "")
singeEmbedLua(thirdparty/copas/src/copas/ftp.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/http.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/smtp.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/lock.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/queue.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/semaphore.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/timer.lua "copas_")
singeEmbedLua(thirdparty/binaryheap.lua/src/binaryheap.lua "")
singeEmbedLua(thirdparty/timerwheel.lua/src/timerwheel/timerwheel.lua "")
singeEmbedLua(thirdparty/json.lua/json.lua "")
# Optional HTML manual for browsing: cmake --build . --target docs
if(ASCIIDOCTOR)
add_custom_target(docs
COMMAND ${ASCIIDOCTOR} -a revnumber=${PROJECT_VERSION} ${CMAKE_SOURCE_DIR}/docs/Manual.adoc -o ${CMAKE_BINARY_DIR}/Manual.html
DEPENDS ${GENERATED_DIR}/Manual.pdf
COMMENT "Rendering Manual.html"
VERBATIM
)
endif()
# ===== Sources =====
set(SINGE_SOURCE
src/common.h
src/embedded.h
src/frameFile.c
src/frameFile.h
src/main.c
src/main.h
src/singe.c
src/singe.h
src/util.h
src/embedded.h
src/frameFile.h
src/main.c
src/common.h
src/videoPlayer.c
src/stddclmr.h
src/frameFile.c
src/videoPlayer.h
src/util.c
src/util.h
src/videoPlayer.c
src/videoPlayer.h
)
set(ARG_PARSER_SOURCE
thirdparty/arg_parser/carg_parser.c
thirdparty/arg_parser/carg_parser.h
)
set(JBIG_SOURCE
thirdparty/jbigkit/libjbig/jbig.c
thirdparty/jbigkit/libjbig/jbig.h
thirdparty/jbigkit/libjbig/jbig_ar.c
thirdparty/jbigkit/libjbig/jbig_ar.h
)
set(LUA_SOURCE
thirdparty/lua/src/lmem.h
thirdparty/lua/src/ltm.h
thirdparty/lua/src/lutf8lib.c
thirdparty/lua/src/ldo.h
thirdparty/lua/src/ldump.c
thirdparty/lua/src/lstring.h
thirdparty/lua/src/lparser.h
thirdparty/lua/src/lcorolib.c
thirdparty/lua/src/loslib.c
thirdparty/lua/src/lparser.c
thirdparty/lua/src/ltablib.c
thirdparty/lua/src/ltable.c
thirdparty/lua/src/ljumptab.h
thirdparty/lua/src/luaconf.h
thirdparty/lua/src/lstate.c
thirdparty/lua/src/lobject.h
thirdparty/lua/src/lstate.h
thirdparty/lua/src/ldo.c
thirdparty/lua/src/lfunc.c
thirdparty/lua/src/lmathlib.c
thirdparty/lua/src/lua.h
thirdparty/lua/src/lauxlib.h
thirdparty/lua/src/ltm.c
thirdparty/lua/src/lstrlib.c
thirdparty/lua/src/lapi.h
thirdparty/lua/src/lopcodes.c
thirdparty/lua/src/lvm.h
thirdparty/lua/src/linit.c
thirdparty/lua/src/lobject.c
thirdparty/lua/src/ldebug.c
thirdparty/lua/src/lctype.c
thirdparty/lua/src/lgc.c
thirdparty/lua/src/lzio.h
thirdparty/lua/src/lgc.h
thirdparty/lua/src/lctype.h
thirdparty/lua/src/lopcodes.h
thirdparty/lua/src/llimits.h
thirdparty/lua/src/lprefix.h
thirdparty/lua/src/llex.h
thirdparty/lua/src/lundump.c
thirdparty/lua/src/lbaselib.c
thirdparty/lua/src/loadlib.c
thirdparty/lua/src/ldblib.c
thirdparty/lua/src/ldebug.h
thirdparty/lua/src/lundump.h
thirdparty/lua/src/lopnames.h
thirdparty/lua/src/ltable.h
thirdparty/lua/src/lmem.c
thirdparty/lua/src/lcode.h
thirdparty/lua/src/lua.hpp
thirdparty/lua/src/lauxlib.c
thirdparty/lua/src/liolib.c
thirdparty/lua/src/lapi.c
thirdparty/lua/src/lcode.c
thirdparty/lua/src/llex.c
thirdparty/lua/src/lstring.c
thirdparty/lua/src/lvm.c
thirdparty/lua/src/lualib.h
thirdparty/lua/src/lfunc.h
thirdparty/lua/src/lzio.c
)
file(GLOB LUA_SOURCE thirdparty/lua/src/*.c thirdparty/lua/src/*.h)
list(FILTER LUA_SOURCE EXCLUDE REGEX "/(lua|luac|onelua)\\.c$")
set(LUA_FILESYSTEM_SOURCE
thirdparty/luafilesystem/src/lfs.c
thirdparty/luafilesystem/src/lfs.h
)
set(LUA_SOCKET_SOURCE
thirdparty/luasocket/src/luasocket.h
thirdparty/luasocket/src/luasocket.c
thirdparty/luasocket/src/timeout.h
thirdparty/luasocket/src/timeout.c
thirdparty/luasocket/src/buffer.h
thirdparty/luasocket/src/buffer.c
thirdparty/luasocket/src/io.h
thirdparty/luasocket/src/io.c
thirdparty/luasocket/src/auxiliar.h
thirdparty/luasocket/src/auxiliar.c
thirdparty/luasocket/src/compat.h
thirdparty/luasocket/src/auxiliar.h
thirdparty/luasocket/src/buffer.c
thirdparty/luasocket/src/buffer.h
thirdparty/luasocket/src/compat.c
thirdparty/luasocket/src/options.h
thirdparty/luasocket/src/options.c
thirdparty/luasocket/src/inet.h
thirdparty/luasocket/src/inet.c
thirdparty/luasocket/src/except.h
thirdparty/luasocket/src/compat.h
thirdparty/luasocket/src/except.c
thirdparty/luasocket/src/select.h
thirdparty/luasocket/src/select.c
thirdparty/luasocket/src/tcp.h
thirdparty/luasocket/src/tcp.c
thirdparty/luasocket/src/udp.h
thirdparty/luasocket/src/udp.c
thirdparty/luasocket/src/mime.h
thirdparty/luasocket/src/except.h
thirdparty/luasocket/src/inet.c
thirdparty/luasocket/src/inet.h
thirdparty/luasocket/src/io.c
thirdparty/luasocket/src/io.h
thirdparty/luasocket/src/luasocket.c
thirdparty/luasocket/src/luasocket.h
thirdparty/luasocket/src/mime.c
thirdparty/luasocket/src/mime.h
thirdparty/luasocket/src/options.c
thirdparty/luasocket/src/options.h
thirdparty/luasocket/src/select.c
thirdparty/luasocket/src/select.h
thirdparty/luasocket/src/socket.h
thirdparty/luasocket/src/pierror.h
thirdparty/luasocket/src/tcp.c
thirdparty/luasocket/src/tcp.h
thirdparty/luasocket/src/timeout.c
thirdparty/luasocket/src/timeout.h
thirdparty/luasocket/src/udp.c
thirdparty/luasocket/src/udp.h
)
if(WIN32)
set(LUA_SOCKET_SOURCE
${LUA_SOCKET_SOURCE}
thirdparty/luasocket/src/wsocket.h
list(APPEND LUA_SOCKET_SOURCE
thirdparty/luasocket/src/wsocket.c
thirdparty/luasocket/src/wsocket.h
)
ELSE()
set(LUA_SOCKET_SOURCE
${LUA_SOCKET_SOURCE}
thirdparty/luasocket/src/usocket.h
thirdparty/luasocket/src/usocket.c
thirdparty/luasocket/src/unix.h
else()
list(APPEND LUA_SOCKET_SOURCE
thirdparty/luasocket/src/serial.c
thirdparty/luasocket/src/unix.c
thirdparty/luasocket/src/unixstream.h
thirdparty/luasocket/src/unixstream.c
thirdparty/luasocket/src/unix.h
thirdparty/luasocket/src/unixdgram.c
thirdparty/luasocket/src/unixdgram.h
thirdparty/luasocket/src/serial.c
thirdparty/luasocket/src/unixstream.c
thirdparty/luasocket/src/unixstream.h
thirdparty/luasocket/src/usocket.c
thirdparty/luasocket/src/usocket.h
)
ENDIF()
endif()
set(LUASEC_SOURCE
thirdparty/luasec/src/compat.h
thirdparty/luasec/src/config.c
${BUILD_DIR}/generated/luasec_options.c
thirdparty/luasec/src/context.c
thirdparty/luasec/src/context.h
thirdparty/luasec/src/ec.c
thirdparty/luasec/src/ec.h
thirdparty/luasec/src/options.h
thirdparty/luasec/src/ssl.c
thirdparty/luasec/src/ssl.h
thirdparty/luasec/src/x509.c
thirdparty/luasec/src/x509.h
${GENERATED_DIR}/luasec_options.c
thirdparty/luasec/src/compat.h
thirdparty/luasec/src/config.c
thirdparty/luasec/src/context.c
thirdparty/luasec/src/context.h
thirdparty/luasec/src/ec.c
thirdparty/luasec/src/ec.h
thirdparty/luasec/src/options.h
thirdparty/luasec/src/ssl.c
thirdparty/luasec/src/ssl.h
thirdparty/luasec/src/x509.c
thirdparty/luasec/src/x509.h
)
set(LUA_RS232_SOURCE
thirdparty/librs232/bindings/lua/luars232.c
thirdparty/librs232/src/rs232.c
)
if(WIN32)
set(LUA_RS232_SOURCE
${LUA_RS232_SOURCE}
thirdparty/librs232/src/rs232_windows.c
)
ELSE()
set(LUA_RS232_SOURCE
${LUA_RS232_SOURCE}
thirdparty/librs232/src/rs232_posix.c
)
ENDIF()
list(APPEND LUA_RS232_SOURCE thirdparty/librs232/src/rs232_windows.c)
else()
list(APPEND LUA_RS232_SOURCE thirdparty/librs232/src/rs232_posix.c)
endif()
set(UTHASH_SOURCE
thirdparty/uthash/src/uthash.h
thirdparty/uthash/src/utlist.h
)
set(MANYMOUSE_SOURCE
thirdparty/manymouse/linux_evdev.c
thirdparty/manymouse/macosx_hidmanager.c
@ -236,17 +323,19 @@ set(MANYMOUSE_SOURCE
thirdparty/manymouse/x11_xinput2.c
)
set(SDL2_GFX_SOURCE
thirdparty/SDL2_gfx/SDL2_rotozoom.h
thirdparty/SDL2_gfx/SDL2_rotozoom.c
)
# ===== Target =====
add_executable(${CMAKE_PROJECT_NAME}
${SINGE_SOURCE}
${EMBEDDED_HEADERS}
${GENERATED_DIR}/version.h
${ARG_PARSER_SOURCE}
${JBIG_SOURCE}
${LUA_SOURCE}
${LUA_FILESYSTEM_SOURCE}
${LUA_SOCKET_SOURCE}
@ -256,36 +345,40 @@ add_executable(${CMAKE_PROJECT_NAME}
${MANYMOUSE_SOURCE}
${SDL2_GFX_SOURCE}
)
# Perform pre-build operations.
#add_custom_target(BUILD_PREREQS
# COMMAND ${BUILD_DIR}-deps.sh "${CMAKE_SOURCE_DIR}"
# BYPRODUCTS
# ${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libarchive.a
#)
#add_dependencies(${CMAKE_PROJECT_NAME} BUILD_PREREQS)
if(WIN32)
# Note: _WIN32_WINNT=0x0600 sets the minimum compatible version of
# Windows to Vista. The function inet_pton() does not exist before then.
set(DEFINE_LIST
-Dmain=SDL_main
-D_WIN32_WINNT=0x0600
)
else()
set(DEFINE_LIST)
enable_language(RC)
target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${GENERATED_DIR}/singe.rc ${GENERATED_DIR}/icon.ico)
endif()
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC
${DEFINE_LIST}
-DRS232_STATIC
-DFFMS_STATIC
# Output name matches the release artifact: Singe-v2.20-Linux-x86_64
string(SUBSTRING ${KANGAROO_OS} 0 1 osInitial)
string(SUBSTRING ${KANGAROO_OS} 1 -1 osRest)
string(TOUPPER ${osInitial} osInitial)
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME "Singe-v${PROJECT_VERSION}-${osInitial}${osRest}-${KANGAROO_ARCH}")
# Warnings apply to our code only; the vendored libraries are not ours to fix.
set_source_files_properties(${SINGE_SOURCE} PROPERTIES COMPILE_OPTIONS "-Wall;-Wextra")
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
RS232_STATIC
FFMS_STATIC
)
if(WIN32)
# _WIN32_WINNT=0x0600 sets the minimum compatible version of Windows to Vista.
# The function inet_pton() does not exist before then.
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
main=SDL_main
_WIN32_WINNT=0x0600
)
endif()
target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
-Wno-deprecated-declarations
)
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
${CMAKE_BINARY_DIR}
${GENERATED_DIR}/include
${BUILD_DIR}
${BUILD_DIR}/include
${BUILD_DIR}/include/SDL2
@ -294,30 +387,21 @@ target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
thirdparty/librs232/include
)
target_link_directories(${CMAKE_PROJECT_NAME} PUBLIC
target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
${BUILD_DIR}/lib
)
if(${KANGAROO_OS} STREQUAL "linux")
set(LIB_LIST
-lX11
-lvdpau
${BUILD_DIR}/lib/libjpeg.a
${BUILD_DIR}/lib/libpng.a
-lstdc++
)
endif()
if(${KANGAROO_OS} STREQUAL "pi")
set(LIB_LIST
${BUILD_DIR}/lib/libjpeg.a
${BUILD_DIR}/lib/libpng.a
-lstdc++
)
endif()
if(${KANGAROO_OS} STREQUAL "macos")
set(LIB_LIST
# ===== Libraries =====
# Platform specific system libraries.
if(KANGAROO_OS STREQUAL "linux")
# ffmpeg's vdpau hardware context is always compiled in on X11 hosts.
set(SYSTEM_LIBS -lX11 -lvdpau)
elseif(KANGAROO_OS STREQUAL "pi")
set(SYSTEM_LIBS)
elseif(KANGAROO_OS STREQUAL "macos")
set(SYSTEM_LIBS
-Wl,-framework,CoreVideo
-Wl,-framework,Cocoa
-Wl,-framework,IOKit
@ -335,9 +419,11 @@ if(${KANGAROO_OS} STREQUAL "macos")
-lc++
${OSXCROSS_TARGET_DIR}/darwin/libclang_rt.osx.a
)
endif()
if(${KANGAROO_OS} STREQUAL "windows")
set(LIB_LIST
if(NOT DEFINED OSXCROSS_TARGET_DIR)
message(FATAL_ERROR "OSXCROSS_TARGET_DIR must be set for macOS builds.")
endif()
elseif(KANGAROO_OS STREQUAL "windows")
set(SYSTEM_LIBS
-mwindows
-static
-lmingw32
@ -359,29 +445,22 @@ if(${KANGAROO_OS} STREQUAL "windows")
-lbcrypt
-lssp
-lcrypt32
${BUILD_DIR}/lib/libjpeg.a
${BUILD_DIR}/lib/libpng.a
-lstdc++
)
else()
message(FATAL_ERROR "Unknown KANGAROO_OS: ${KANGAROO_OS}")
endif()
set(LIB_LIST
${LIB_LIST}
# Static third party libraries built by build-all.sh.
set(STATIC_LIBS
${BUILD_DIR}/lib/libarchive.a
${BUILD_DIR}/lib/libavcodec.a
${BUILD_DIR}/lib/libavdevice.a
${BUILD_DIR}/lib/libavfilter.a
${BUILD_DIR}/lib/libavformat.a
# ${BUILD_DIR}/lib/libavif.a
${BUILD_DIR}/lib/libavutil.a
# ${BUILD_DIR}/lib/libbrotlicommon-static.a
# ${BUILD_DIR}/lib/libbrotlidec-static.a
${BUILD_DIR}/lib/libbz2_static.a
# ${BUILD_DIR}/lib/libdav1d.a
${BUILD_DIR}/lib/libffms2.a
${BUILD_DIR}/lib/libfreetype.a
# ${BUILD_DIR}/lib/libharfbuzz.a
# ${BUILD_DIR}/lib/libhwy.a
# ${BUILD_DIR}/lib/libjxl_dec.a
${BUILD_DIR}/lib/liblzma.a
${BUILD_DIR}/lib/libopus.a
${BUILD_DIR}/lib/libopusfile.a
@ -391,11 +470,9 @@ set(LIB_LIST
${BUILD_DIR}/lib/libSDL2_image.a
${BUILD_DIR}/lib/libSDL2main.a
${BUILD_DIR}/lib/libSDL2_mixer.a
${BUILD_DIR}/lib/libSDL2_test.a
${BUILD_DIR}/lib/libSDL2_ttf.a
${BUILD_DIR}/lib/libswresample.a
${BUILD_DIR}/lib/libswscale.a
# ${BUILD_DIR}/lib/libtiff.a
${BUILD_DIR}/lib/libwavpack.a
${BUILD_DIR}/lib/libwebp.a
${BUILD_DIR}/lib/libwebpdemux.a
@ -405,15 +482,21 @@ set(LIB_LIST
${BUILD_DIR}/lib/libcrypto.a
${BUILD_DIR}/lib/libssl.a
)
target_link_libraries(${CMAKE_PROJECT_NAME}
# -Wl,--start-group
${LIB_LIST}
${LIB_LIST}
${LIB_LIST}
# -Wl,--end-group
-pthread
-lm
)
if(NOT KANGAROO_OS STREQUAL "macos")
list(APPEND STATIC_LIBS
${BUILD_DIR}/lib/libjpeg.a
${BUILD_DIR}/lib/libpng.a
-lstdc++
)
endif()
# System libraries follow the static ones so their symbols resolve for everything above.
# Apple's linker has no --start-group, so the list is repeated there instead.
if(KANGAROO_OS STREQUAL "macos")
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${STATIC_LIBS} ${STATIC_LIBS} ${STATIC_LIBS} ${SYSTEM_LIBS} -pthread -lm)
else()
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE -Wl,--start-group ${STATIC_LIBS} -Wl,--end-group ${SYSTEM_LIBS} -pthread -lm)
endif()
install(TARGETS ${CMAKE_PROJECT_NAME})

34
INSTALL
View file

@ -1,7 +1,8 @@
SINGE 2.10
SINGE 2.20
==========
(For the latest version of this document, visit https://kangaroopunch.com!)
(For the latest version of this document, visit https://kangaroopunch.com!
The full manual is extracted to Singe/Manual.pdf on first run.)
Welcome to Singe! The Somewhat Interactive Nostalgic Game Engine!
@ -38,30 +39,7 @@ the new files.
COMMAND LINE
============
___ ___ _ _ ___ ___
/ __|_ _| \| |/ __| __| Somewhat Interactive Nostalgic Game Engine v2.10
\__ \| || .` | (_ | _| Copyright (c) 2006-2024 Scott C. Duensing
|___/___|_|\_|\___|___| https://KangarooPunch.com https://SingeEngine.com
Usage: Singe-v2.10-Windows-x86_64.exe [OPTIONS] scriptName{.singe}
-a, --aspect=N:D force aspect ratio
-c, --showcalculated show calculated framefile values for debugging
-d, --datadir=PATHNAME alternate location for written files
-e, --volume_nonvldp=PERCENT specify sound effects volume in percent
-f, --fullscreen run in full screen mode
-h, --help this display
-k, --nologos kill the splash screens
-l, --volume_vldp=PERCENT specify laserdisc volume in percent
-m, --nomouse disable mouse
-n, --nocrosshair request game not display gun crosshairs
-o, --audio=TRACK select default track for audio output
-p, --program trace Singe execution to screen and file
-s, --nosound, --mutesound mutes all sound
-t, --trace trace script execution to screen and file
-u, --stretch use ugly stretched video
-v, --framefile=FILENAME use an alternate video file
-w, --fullscreen_window run in windowed full screen mode
-x, --xresolution=VALUE specify horizontal resolution
-y, --yresolution=VALUE specify vertical resolution
-z, --noconsole zero console output
Run the binary with -h (or --help) for the option summary. Every option is
described in the "Command Line Options" chapter of Singe/Manual.pdf, which
is extracted on first run.

View file

@ -9,16 +9,23 @@ bzip2 bzip2-1.0.6 https://sourceware.org/bzip2
copas MIT https://lunarmodules.github.io/copas
ffmpeg LGPL-2.1 https://ffmpeg.org
ffms2 GPL-3.0-only https://github.com/FFMS/ffms2
jbigkit GPL-2.0 https://www.cl.cam.ac.uk/~mgk25/jbigkit
freetype FTL https://freetype.org
json.lua MIT https://github.com/rxi/json.lua
libarchive BSD-2-Clause https://libarchive.org
libjpeg-turbo IJG https://libjpeg-turbo.org
libogg BSD-3-Clause https://xiph.org/ogg
libpng libpng-2.0 http://www.libpng.org
librs232 MIT https://github.com/srdgame/librs232
libwebp BSD-3-Clause https://developers.google.com/speed/webp
libxmp MIT https://github.com/libxmp/libxmp
lua MIT https://www.lua.org
luafilesystem MIT https://lunarmodules.github.io/luafilesystem
luasec MIT https://github.com/lunarmodules/luasec
luasocket MIT https://lunarmodules.github.io/luasocket
manymouse Zlib https://icculus.org/manymouse
openssl Apache-2.0 https://www.openssl.org
opus BSD-3-Clause https://opus-codec.org
opusfile BSD-3-Clause https://opus-codec.org
SDL2 Zlib https://www.libsdl.org
SDL2_gfx LGPL-2.1 https://github.com/ferzkopp/SDL_gfx
SDL2_image Zlib https://www.libsdl.org
@ -27,7 +34,14 @@ SDL2_ttf Zlib https://www.libsdl.org
timerwheel.lua MIT https://tieske.github.io/timerwheel.lua
uthash BSD-1-Clause https://troydhanson.github.io/uthash
vlc GPL-2.0 https://www.videolan.org/vlc
wavpack BSD-3-Clause https://www.wavpack.com
xz Public-Domain https://github.com/tukaani-project/xz
zlib Zlib https://zlib.net
zstd BSD-3-Clause https://facebook.github.io/zstd
Fonts
-----
FreeSansBold GPL-3.0 with font exception https://www.gnu.org/software/freefont
BreatheFire (license not recorded - used only by the manual's example)

View file

@ -25,24 +25,26 @@
-- Singe 2.xx Features -------------------------------------------------------
SINGE_FRAMEWORK_VERSION = 2.10
-- SINGE_FRAMEWORK_VERSION, SWITCH_*, FONT_QUALITY_*, MODE_*, MOUSE_*, OVERLAY_*,
-- RENDER_*, DISC_*, SOUND_ERROR_* and the SINGE_* input code layout are all
-- pushed by the engine before this file runs, so they are defined in exactly
-- one place: singe.c.
if singeGetScriptPath ~= nil then
DIR = singeGetScriptPath():match("(.*[/\\])") or "./"
end
function utilDeepCopy(orig)
local orig_type = type(orig)
function utilDeepCopy(original)
local copy
if orig_type == 'table' then
if type(original) == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[utilDeepCopy(orig_key)] = utilDeepCopy(orig_value)
for key, value in next, original, nil do
copy[utilDeepCopy(key)] = utilDeepCopy(value)
end
setmetatable(copy, utilDeepCopy(getmetatable(orig)))
setmetatable(copy, utilDeepCopy(getmetatable(original)))
else -- number, string, boolean, etc
copy = orig
copy = original
end
return copy
end
@ -65,16 +67,26 @@ end
function utilGetTableSize(t)
local count = 0
for _, __ in pairs(t) do
count = count + 1
end
return count
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
-- Returns a copy of a { NAME = { name = NAME, value = N } } table with every value shifted.
function utilOffsetTable(source, offset)
local copy = utilDeepCopy(source)
for _, entry in pairs(copy) do
entry.value = entry.value + offset
end
return copy
end
function utilTrim(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
@ -323,9 +335,6 @@ SCANCODE = {
AUDIOFASTFORWARD = { name = "AUDIOFASTFORWARD", value = 286 }
}
SCANCODE_MIN = 4 -- Lowest value
SCANCODE_MAX = 286 -- Highest value, not the number of items in the table.
MODIFIER = {
NONE = { name = "NONE", value = 0x0000 },
LSHIFT = { name = "LSHIFT", value = 0x0001 },
@ -347,63 +356,41 @@ MODIFIER = {
GUI = { name = "GUI", value = 0x0400 + 0x0800 }
}
GAMEPAD_0 = {
AXIS_LEFT_X = { name = "AXIS_LEFT_X", value = 500 },
AXIS_LEFT_X_L = { name = "AXIS_LEFT_X_L", value = 501 },
AXIS_LEFT_X_R = { name = "AXIS_LEFT_X_R", value = 502 },
AXIS_LEFT_Y = { name = "AXIS_LEFT_Y", value = 503 },
AXIS_LEFT_Y_U = { name = "AXIS_LEFT_Y_U", value = 504 },
AXIS_LEFT_Y_D = { name = "AXIS_LEFT_Y_D", value = 505 },
AXIS_RIGHT_X = { name = "AXIS_RIGHT_X", value = 506 },
AXIS_RIGHT_X_L = { name = "AXIS_RIGHT_X_L", value = 507 },
AXIS_RIGHT_X_R = { name = "AXIS_RIGHT_X_R", value = 508 },
AXIS_RIGHT_Y = { name = "AXIS_RIGHT_Y", value = 509 },
AXIS_RIGHT_Y_U = { name = "AXIS_RIGHT_Y_U", value = 510 },
AXIS_RIGHT_Y_D = { name = "AXIS_RIGHT_Y_D", value = 511 },
AXIS_LEFT_TRIGGER = { name = "AXIS_LEFT_TRIGGER", value = 512 },
AXIS_LEFT_TRIGGER_N = { name = "AXIS_LEFT_TRIGGER_N", value = 513 },
AXIS_LEFT_TRIGGER_P = { name = "AXIS_LEFT_TRIGGER_P", value = 514 },
AXIS_RIGHT_TRIGGER = { name = "AXIS_RIGHT_TRIGGER", value = 515 },
AXIS_RIGHT_TRIGGER_N = { name = "AXIS_RIGHT_TRIGGER_N", value = 516 },
AXIS_RIGHT_TRIGGER_P = { name = "AXIS_RIGHT_TRIGGER_P", value = 517 },
BUTTON_A = { name = "BUTTON_A", value = 518 },
BUTTON_B = { name = "BUTTON_B", value = 519 },
BUTTON_X = { name = "BUTTON_X", value = 520 },
BUTTON_Y = { name = "BUTTON_Y", value = 521 },
BUTTON_BACK = { name = "BUTTON_BACK", value = 522 },
BUTTON_GUIDE = { name = "BUTTON_GUIDE", value = 523 },
BUTTON_START = { name = "BUTTON_START", value = 524 },
BUTTON_LEFT_STICK = { name = "BUTTON_LEFT_STICK", value = 525 },
BUTTON_RIGHT_STICK = { name = "BUTTON_RIGHT_STICK", value = 526 },
BUTTON_LEFT_BUMPER = { name = "BUTTON_LEFT_BUMPER", value = 527 },
BUTTON_RIGHT_BUMPER = { name = "BUTTON_RIGHT_BUMPER", value = 528 },
DPAD_UP = { name = "DPAD_UP", value = 529 },
DPAD_DOWN = { name = "DPAD_DOWN", value = 530 },
DPAD_LEFT = { name = "DPAD_LEFT", value = 531 },
DPAD_RIGHT = { name = "DPAD_RIGHT", value = 532 }
}
GAMEPAD_0_MIN = 500 -- Lowest value
GAMEPAD_0_MAX = 532 -- Highest value, not the number of items in the table.
GAMEPAD_1 = utilDeepCopy(GAMEPAD_0)
GAMEPAD_2 = utilDeepCopy(GAMEPAD_0)
GAMEPAD_3 = utilDeepCopy(GAMEPAD_0)
for key, value in pairs(GAMEPAD_0) do
GAMEPAD_1[key].value = GAMEPAD_1[key].value + 100
GAMEPAD_2[key].value = GAMEPAD_2[key].value + 200
GAMEPAD_3[key].value = GAMEPAD_3[key].value + 300
-- Controller codes are built from the engine's layout: SINGE_GAMEPAD_BASE plus
-- SINGE_GAMEPAD_STRIDE per controller; each axis takes SINGE_AXIS_STRIDE codes
-- (axis, negative direction, positive direction) and buttons follow at
-- SINGE_GAMEPAD_BUTTON_OFFSET in SDL's button order.
local function gamepadAxisCodes(axis, name, negative, positive)
local base = SINGE_GAMEPAD_BASE + axis * SINGE_AXIS_STRIDE
return {
{ name, base }, { name .. "_" .. negative, base + 1 }, { name .. "_" .. positive, base + 2 }
}
end
GAMEPAD_1_MIN = GAMEPAD_0_MAX + 100
GAMEPAD_1_MAX = GAMEPAD_0_MAX + 100
local gamepadCodes = {}
for _, entry in ipairs(gamepadAxisCodes(0, "AXIS_LEFT_X", "L", "R")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(1, "AXIS_LEFT_Y", "U", "D")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(2, "AXIS_RIGHT_X", "L", "R")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(3, "AXIS_RIGHT_Y", "U", "D")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(4, "AXIS_LEFT_TRIGGER", "N", "P")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(5, "AXIS_RIGHT_TRIGGER", "N", "P")) do gamepadCodes[#gamepadCodes + 1] = entry end
GAMEPAD_2_MIN = GAMEPAD_0_MAX + 200
GAMEPAD_2_MAX = GAMEPAD_0_MAX + 200
local gamepadButtons = {
"BUTTON_A", "BUTTON_B", "BUTTON_X", "BUTTON_Y", "BUTTON_BACK", "BUTTON_GUIDE", "BUTTON_START",
"BUTTON_LEFT_STICK", "BUTTON_RIGHT_STICK", "BUTTON_LEFT_BUMPER", "BUTTON_RIGHT_BUMPER",
"DPAD_UP", "DPAD_DOWN", "DPAD_LEFT", "DPAD_RIGHT"
}
for index, name in ipairs(gamepadButtons) do
gamepadCodes[#gamepadCodes + 1] = { name, SINGE_GAMEPAD_BASE + SINGE_GAMEPAD_BUTTON_OFFSET + index - 1 }
end
GAMEPAD_3_MIN = GAMEPAD_0_MAX + 300
GAMEPAD_3_MAX = GAMEPAD_0_MAX + 300
GAMEPAD_0 = {}
for _, entry in ipairs(gamepadCodes) do
GAMEPAD_0[entry[1]] = { name = entry[1], value = entry[2] }
end
GAMEPAD_1 = utilOffsetTable(GAMEPAD_0, SINGE_GAMEPAD_STRIDE)
GAMEPAD_2 = utilOffsetTable(GAMEPAD_0, SINGE_GAMEPAD_STRIDE * 2)
GAMEPAD_3 = utilOffsetTable(GAMEPAD_0, SINGE_GAMEPAD_STRIDE * 3)
GAMEPAD_AXIS_LEFT_X = 0
GAMEPAD_AXIS_LEFT_Y = 1
@ -412,47 +399,57 @@ GAMEPAD_AXIS_RIGHT_Y = 3
GAMEPAD_AXIS_LEFT_TRIGGER = 4
GAMEPAD_AXIS_RIGHT_TRIGGER = 5
MOUSE_0 = {
BUTTON_LEFT = { name = "BUTTON_LEFT", value = 1000 },
BUTTON_RIGHT = { name = "BUTTON_RIGHT", value = 1001 },
BUTTON_MIDDLE = { name = "BUTTON_MIDDLE", value = 1002 },
BUTTON_X1 = { name = "BUTTON_X1", value = 1003 },
BUTTON_X2 = { name = "BUTTON_X2", value = 1004 },
WHEEL_UP = { name = "WHEEL_UP", value = 1005 },
WHEEL_DOWN = { name = "WHEEL_DOWN", value = 1006 }
}
-- Mouse codes: SINGE_MOUSE_BASE plus SINGE_MOUSE_STRIDE per mouse; buttons then wheel.
local mouseNames = { "BUTTON_LEFT", "BUTTON_RIGHT", "BUTTON_MIDDLE", "BUTTON_X1", "BUTTON_X2", "WHEEL_UP", "WHEEL_DOWN" }
MOUSE_0 = {}
for index, name in ipairs(mouseNames) do
MOUSE_0[name] = { name = name, value = SINGE_MOUSE_BASE + index - 1 }
end
MOUSE_1 = utilOffsetTable(MOUSE_0, SINGE_MOUSE_STRIDE)
MOUSE_2 = utilOffsetTable(MOUSE_0, SINGE_MOUSE_STRIDE * 2)
MOUSE_3 = utilOffsetTable(MOUSE_0, SINGE_MOUSE_STRIDE * 3)
MOUSE_0_MIN = 1000
MOUSE_0_MAX = 1006
MOUSE_1 = utilDeepCopy(MOUSE_0)
MOUSE_2 = utilDeepCopy(MOUSE_0)
MOUSE_3 = utilDeepCopy(MOUSE_0)
for key, value in pairs(MOUSE_0) do
MOUSE_1[key].value = MOUSE_1[key].value + 100
MOUSE_2[key].value = MOUSE_2[key].value + 200
MOUSE_3[key].value = MOUSE_3[key].value + 300
-- Older names for engine functions that gained a setter.
if mouseSetEnabled ~= nil then
mouseEnable = function() mouseSetEnabled(true) end
mouseDisable = function() mouseSetEnabled(false) end
end
if singeSetPauseKeyEnabled ~= nil then
singeEnablePauseKey = function() singeSetPauseKeyEnabled(true) end
singeDisablePauseKey = function() singeSetPauseKeyEnabled(false) end
end
MOUSE_1_MIN = MOUSE_0_MAX + 100
MOUSE_1_MAX = MOUSE_0_MAX + 100
MOUSE_2_MIN = MOUSE_0_MAX + 200
MOUSE_2_MAX = MOUSE_0_MAX + 200
MOUSE_3_MIN = MOUSE_0_MAX + 300
MOUSE_3_MAX = MOUSE_0_MAX + 300
SWITCH_BUTTON4 = 21
SWITCH_TILT = 22
SWITCH_GRAB = 23
MOUSE_SINGLE = 100
MOUSE_MANY = 200
RENDER_SMOOTH = 1
RENDER_PIXELATED = 0
-- Singe 2.20 moved the sprite handle to the first argument. Games written for
-- 2.10 can set SINGE_LEGACY_SPRITE_ARGS = true (or LEGACY_SPRITE_ARGS = true in
-- games.dat) to keep calling the old way.
if SINGE_LEGACY_SPRITE_ARGS and spriteDraw ~= nil then
local newSpriteDraw = spriteDraw
spriteDraw = function(...)
local args = { ... }
local id = table.remove(args)
return newSpriteDraw(id, table.unpack(args))
end
local newSpriteLoop = spriteLoop
spriteLoop = function(loop, id) return newSpriteLoop(id, loop) end
local newSpriteQuality = spriteQuality
spriteQuality = function(smooth, id) return newSpriteQuality(id, smooth) end
local newSpriteRotate = spriteRotate
spriteRotate = function(angle, id) return newSpriteRotate(id, angle) end
local newSpriteRotateAndScale = spriteRotateAndScale
spriteRotateAndScale = function(...)
local args = { ... }
local id = table.remove(args)
return newSpriteRotateAndScale(id, table.unpack(args))
end
local newSpriteScale = spriteScale
spriteScale = function(...)
local args = { ... }
local id = table.remove(args)
return newSpriteScale(id, table.unpack(args))
end
local newSpriteSetFrame = spriteSetFrame
spriteSetFrame = function(frame, id) return newSpriteSetFrame(id, frame) end
end
if videoGetLanguageDescription ~= nil then
discGetLanguageDescription = videoGetLanguageDescription
@ -462,52 +459,6 @@ end
-- Singe 1.xx Features -------------------------------------------------------
if discSetFPS ~= nil then
discSetFPS(29.97)
discSearch(1)
end
SWITCH_UP = 0
SWITCH_LEFT = 1
SWITCH_DOWN = 2
SWITCH_RIGHT = 3
SWITCH_START1 = 4
SWITCH_START2 = 5
SWITCH_BUTTON1 = 6
SWITCH_BUTTON2 = 7
SWITCH_BUTTON3 = 8
SWITCH_COIN1 = 9
SWITCH_COIN2 = 10
SWITCH_SKILL1 = 11
SWITCH_SKILL2 = 12
SWITCH_SKILL3 = 13
SWITCH_SERVICE = 14
SWITCH_TEST = 15
SWITCH_RESET = 16
SWITCH_SCREENSHOT = 17
SWITCH_QUIT = 18
SWITCH_PAUSE = 19
SWITCH_CONSOLE = 20
SWITCH_BUTTON4 = 21 -- Added in Singe 2.00
SWITCH_TILT = 22 -- Added in Singe 2.00
FONT_QUALITY_SOLID = 1
FONT_QUALITY_SHADED = 2
FONT_QUALITY_BLENDED = 3
SOUND_ERROR_INVALID = -1
SOUND_REMOVE_HANDLE = -1
SOUND_ERROR_FULL = -2
OVERLAY_NOT_UPDATED = 0
OVERLAY_UPDATED = 1
SINGLE_MOUSE = 100
MANY_MOUSE = 200
MODE_NORMAL = 0
MODE_FULL = 1
-- Make old random number calls still work
random = {}
random.new = math.random
@ -524,12 +475,18 @@ end
if singeMain ~= nil then
local singeThread = coroutine.create(singeMain)
onOverlayUpdate = function()
coroutine.resume(SINGE_SELF)
return(OVERLAY_UPDATED)
local ok, err = coroutine.resume(singeThread)
if not ok then
error(err, 0)
end
if coroutine.status(singeThread) == "dead" then
singeQuit()
end
return OVERLAY_UPDATED
end
singeYield = coroutine.yield
SINGE_SELF = coroutine.create(singeMain)
end

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,7 @@
dofile("Singe/Framework.singe")
lfs = require("lfs")
local lfs = require("lfs")
function cleanTitle(a)
@ -133,12 +133,12 @@ function onOverlayUpdate()
-- Cabinet image
x = CABINET_X + (CABINET_W - spriteGetWidth(SPRITE_CABINET)) * 0.5
y = CABINET_Y + (CABINET_H - spriteGetHeight(SPRITE_CABINET)) * 0.5
spriteDraw(x, y, SPRITE_CABINET)
spriteDraw(SPRITE_CABINET, x, y)
-- Marquee Image
x = MARQUEE_X + (MARQUEE_W - spriteGetWidth(SPRITE_MARQUEE)) * 0.5
y = MARQUEE_Y + (MARQUEE_H - spriteGetHeight(SPRITE_MARQUEE)) * 0.5
spriteDraw(x, y, SPRITE_MARQUEE)
spriteDraw(SPRITE_MARQUEE, x, y)
-- Attract Mode Video
videoDraw(VIDEO_ATTRACT, VIDEO_X, VIDEO_Y, VIDEO_X + VIDEO_W, VIDEO_Y + VIDEO_H)
@ -163,7 +163,7 @@ function onOverlayUpdate()
if (t >= TEXT_LINE_TOP) then
if (c < TEXT_LINE_LIMIT) then
if (handle >= 0) then
spriteDraw(TEXT_X, y, handle)
spriteDraw(handle, TEXT_X, y)
end
y = y + TEXT_LINE_HEIGHT + 1
c = c + 1
@ -186,6 +186,9 @@ end
function onShutdown()
unloadGameAssets()
saveConfig(not SHUTDOWN_FROM_PUSH)
if freeSans18 then
fontUnload(freeSans18)
end
end
@ -193,18 +196,32 @@ function saveConfig(showIntro)
if GAME_COUNT > 0 then
-- Save what game we're currently viewing
local cfg = io.open(CONFIG_FILE, "w")
if cfg then
cfg:write("GAME_SELECTED = " .. GAME_SELECTED .. "\n")
cfg:write("SHOW_INTRO = " .. tostring(showIntro) .. "\n")
cfg:close()
cfg:close()
else
debugPrint("Unable to write " .. CONFIG_FILE)
end
end
end
function unloadGameAssets()
spriteUnload(SPRITE_CABINET)
spriteUnload(SPRITE_MARQUEE)
videoUnload(VIDEO_ATTRACT)
for _, handle in ipairs(TEXT_SPRITE_LIST) do
-- Nothing is loaded when there are no games.
if SPRITE_CABINET then
spriteUnload(SPRITE_CABINET)
SPRITE_CABINET = nil
end
if SPRITE_MARQUEE then
spriteUnload(SPRITE_MARQUEE)
SPRITE_MARQUEE = nil
end
if VIDEO_ATTRACT then
videoUnload(VIDEO_ATTRACT)
VIDEO_ATTRACT = nil
end
for _, handle in ipairs(TEXT_SPRITE_LIST or {}) do
if handle >= 0 then
spriteUnload(handle)
end
@ -301,7 +318,7 @@ if GAME_COUNT == 0 then
debugPrint("No games found! Exiting.")
singeQuit()
else
overlaySetResolution(vldpGetWidth(), vldpGetHeight())
overlaySetResolution(discGetWidth(), discGetHeight())
freeSans18 = fontLoad("Singe/FreeSansBold.ttf", 18)
fontQuality(FONT_QUALITY_BLENDED)
@ -363,6 +380,8 @@ else
local confattr = lfs.attributes(CONFIG_FILE)
if confattr then
dofile(CONFIG_FILE)
-- A damaged menu.dat must not take the menu down with it.
GAME_SELECTED = tonumber(GAME_SELECTED) or 1
if GAME_SELECTED > GAME_COUNT then
GAME_SELECTED = GAME_COUNT
end

View file

@ -69,10 +69,10 @@ function onOverlayUpdate()
overlayPrint(1, 3, dldGameList.result)
end
colorForeground(255, 0, 0)
colorForeground(255, 0, 0, 255)
overlayBox(0, 0, overlayGetWidth() - 1, overlayGetHeight() - 1)
spriteDraw((overlayGetWidth() - spriteGetWidth(sprServiceMenu)) / 2, 25, sprServiceMenu)
spriteDraw(sprServiceMenu, (overlayGetWidth() - spriteGetWidth(sprServiceMenu)) / 2, 25)
return(OVERLAY_UPDATED)
@ -92,7 +92,7 @@ copas.running = true
dldGameList = get("https://kangaroopunch.com/api/singeSoftware")
overlaySetResolution(vldpGetWidth(), vldpGetHeight())
overlaySetResolution(discGetWidth(), discGetHeight())
DISC_SINGE_LOGO = 140

View file

@ -26,27 +26,29 @@
DEAD_ZONE = 15000
-- One table per switch, in the order the engine numbers them (SWITCH_UP = 0 ...).
-- Each entry is a { name, value } pair from the SCANCODE, GAMEPAD_N, or MOUSE_N tables.
INPUT_UP = { SCANCODE.UP, SCANCODE.KP_8, GAMEPAD_0.AXIS_LEFT_Y_U, GAMEPAD_0.AXIS_RIGHT_Y_U, GAMEPAD_0.DPAD_UP }
INPUT_LEFT = { SCANCODE.LEFT, SCANCODE.KP_4, GAMEPAD_0.AXIS_LEFT_X_L, GAMEPAD_0.AXIS_RIGHT_X_L, GAMEPAD_0.DPAD_LEFT }
INPUT_DOWN = { SCANCODE.DOWN, SCANCODE.KP_2, GAMEPAD_0.AXIS_LEFT_Y_D, GAMEPAD_0.AXIS_RIGHT_Y_D, GAMEPAD_0.DPAD_DOWN }
INPUT_RIGHT = { SCANCODE.RIGHT, SCANCODE.KP_6, GAMEPAD_0.AXIS_LEFT_X_R, GAMEPAD_0.AXIS_RIGHT_X_R, GAMEPAD_0.DPAD_RIGHT }
INPUT_1P_COIN = { SCANCODE.MAIN_5, SCANCODE.C, GAMEPAD_0.BUTTON_LEFT_BUMPER }
INPUT_2P_COIN = { SCANCODE.MAIN_6 }
INPUT_1P_START = { SCANCODE.MAIN_1, GAMEPAD_0.BUTTON_RIGHT_BUMPER }
INPUT_2P_START = { SCANCODE.MAIN_2 }
INPUT_ACTION_1 = { SCANCODE.SPACE, SCANCODE.LCTRL, GAMEPAD_0.BUTTON_A, MOUSE_0.BUTTON_RIGHT }
INPUT_ACTION_2 = { SCANCODE.LALT, GAMEPAD_0.BUTTON_B, MOUSE_0.BUTTON_MIDDLE }
INPUT_ACTION_3 = { SCANCODE.LSHIFT, GAMEPAD_0.BUTTON_X, MOUSE_0.BUTTON_LEFT }
INPUT_ACTION_4 = { SCANCODE.RSHIFT, GAMEPAD_0.BUTTON_Y, MOUSE_0.BUTTON_X1 }
INPUT_1P_COIN = { SCANCODE.MAIN_5, SCANCODE.C, GAMEPAD_0.BUTTON_LEFT_BUMPER }
INPUT_2P_COIN = { SCANCODE.MAIN_6 }
INPUT_SKILL_EASY = { SCANCODE.KP_DIVIDE }
INPUT_SKILL_MEDIUM = { SCANCODE.KP_MULTIPLY }
INPUT_SKILL_HARD = { SCANCODE.KP_MINUS }
INPUT_SERVICE = { SCANCODE.MAIN_9 }
INPUT_TEST_MODE = { SCANCODE.F2 }
INPUT_RESET_CPU = { SCANCODE.F3 }
INPUT_SCREENSHOT = { SCANCODE.F12, SCANCODE.F11, GAMEPAD_0.BUTTON_BACK }
INPUT_SCREENSHOT = { SCANCODE.F12, SCANCODE.F11, GAMEPAD_0.BUTTON_BACK } -- F12 is taken by some desktops and debuggers, so F11 works too.
INPUT_QUIT = { SCANCODE.ESCAPE, SCANCODE.Q }
INPUT_PAUSE = { SCANCODE.P, GAMEPAD_0.BUTTON_START }
INPUT_CONSOLE = { SCANCODE.GRAVE }
INPUT_ACTION_4 = { SCANCODE.RSHIFT, GAMEPAD_0.BUTTON_Y, MOUSE_0.BUTTON_X1 }
INPUT_TILT = { SCANCODE.T }
INPUT_GRAB = { SCANCODE.G }

View file

@ -1,4 +1,4 @@
#!/bin/bash -x
#!/bin/bash
#
# Singe 2
@ -40,9 +40,9 @@ function buildAll() {
G_GENERATED=${G_TARGET}/generated
COMMON="-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${G_TARGET} -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}"
export LDFLAGS="-L${G_TARGET}/lib ${LDFLAGS}"
export CFLAGS="-I${G_TARGET}/include ${CFLAGS}"
export CXXFLAGS="-I${G_TARGET}/include ${CXXFLAGS}"
export LDFLAGS="-L${G_TARGET}/lib ${LDFLAGS:-}"
export CFLAGS="-I${G_TARGET}/include ${CFLAGS:-}"
export CXXFLAGS="-I${G_TARGET}/include ${CXXFLAGS:-}"
export LD_LIBRARY_PATH="${G_TARGET}/lib"
export PKG_CONFIG_LIBDIR="${G_TARGET}/lib/pkgconfig"
@ -55,7 +55,6 @@ function buildAll() {
sudo chroot ${SYSROOT} apt-get -y install libasound-dev libxi-dev libvdpau-dev
fi
if [[ 1 == 1 ]]; then
pushd thirdparty/bzip2
clearAndEnterBuild
cmake ${COMMON} \
@ -92,7 +91,6 @@ if [[ 1 == 1 ]]; then
../build/cmake
make install
popd
fi
pushd thirdparty/SDL2
clearAndEnterBuild
@ -129,7 +127,7 @@ fi
-DWEBP_BUILD_WEBPMUX=off \
-DWEBP_BUILD_EXTRAS=off \
..
# 'make install' failes on zlib, which we don't want anyway.
# 'make install' fails on zlib, which we don't want anyway.
make
cp -f ../include/SDL_image.h "${G_TARGET}/include/SDL2/."
cp -f libSDL2_image.a "${G_TARGET}/lib/."
@ -244,15 +242,10 @@ fi
no-tests \
zlib \
enable-zstd
make install
make build_sw
make install_sw
popd
lua thirdparty/luasec/src/options.lua -g "${G_TARGET}/include/openssl/ssl.h" > "${G_TARGET}/generated/luasec_options.c"
mkdir -p "${G_TARGET}/include/luasocket"
cp -f \
thirdparty/luasocket/src/*.h \
"${G_TARGET}/include/luasocket/."
if [[ "${OS}" == "pi" ]]; then
# Hack to make ffmpeg compile.
mkdir -p "${G_TARGET}/include/sys"
@ -287,14 +280,8 @@ fi
popd
pushd thirdparty/ffms2
#libtoolize --force
#aclocal
#autoheader
#automake --force-missing --add-missing
#autoupdate
#autoconf
#./autogen.sh
#make distclean || true
# The configure script is not checked in; generate it once.
[[ -x configure ]] || NOCONFIGURE=1 ./autogen.sh
clearAndEnterBuild
../configure \
--prefix=${G_TARGET} \
@ -307,97 +294,14 @@ fi
make install-libLTLIBRARIES # This weird target prevents building the command line tools.
popd
# === Known FFMPEG Types ===
createExtensionHeader ffmpeg > ${G_GENERATED}/extensions.h
# === Overlay Font ===
createEmbeddedImage font
# === Window Icon ===
createEmbeddedImage icon
# === Kangaroo Punch Logo ===
createEmbeddedImage kangarooPunchLogo
# === Singe Logo ===
createEmbeddedImage singeLogo
# === Laser Disc ===
createEmbeddedImage laserDisc
# === Magnifying Glass ===
createEmbeddedImage magnifyingGlass
# === "Indexing" Text ===
createEmbeddedImage indexing
# === Singe Menu Font ===
createEmbeddedBinary assets/FreeSansBold.ttf ${G_GENERATED}/FreeSansBold_ttf.h FREESANSBOLD_TTF_H
# === Singe Menu Background Video ===
ffmpeg -i "assets/Singe Engine Intro.mpg" -filter:v 'crop=ih/3*4:ih' -vf scale=720:480 -c:v libx264 -c:a aac -f matroska ${G_TARGET}/temp1.mkv
ffmpeg -i assets/180503_01_PurpleGrid.mp4 -filter:v 'crop=ih/3*4:ih' -vf scale=720:480 -c:v libx264 -c:a aac -f matroska ${G_TARGET}/temp2.mkv
ffmpeg -f concat -safe 0 -i <(echo -e "file ${G_TARGET}/temp1.mkv\nfile ${G_TARGET}/temp2.mkv\n") -c copy ${G_TARGET}/menuBackground.mkv
createEmbeddedBinary ${G_TARGET}/menuBackground.mkv ${G_GENERATED}/menuBackground_mkv.h MENUBACKGROUND_MKV_H
rm ${G_TARGET}/temp1.mkv ${G_TARGET}/temp2.mkv ${G_TARGET}/menuBackground.mkv
# === LuaSocket ===
createEmbeddedBinary thirdparty/luasocket/src/ftp.lua ${G_GENERATED}/ftp_lua.h FTP_LUA_H
createEmbeddedBinary thirdparty/luasocket/src/headers.lua ${G_GENERATED}/headers_lua.h HEADERS_LUA_H
createEmbeddedBinary thirdparty/luasocket/src/http.lua ${G_GENERATED}/http_lua.h HTTP_LUA_H
createEmbeddedBinary thirdparty/luasocket/src/ltn12.lua ${G_GENERATED}/ltn12_lua.h LTN12_LUA_H
createEmbeddedBinary thirdparty/luasocket/src/mbox.lua ${G_GENERATED}/mbox_lua.h MBOX_LUA_H
createEmbeddedBinary thirdparty/luasocket/src/mime.lua ${G_GENERATED}/mime_lua.h MIME_LUA_H
createEmbeddedBinary thirdparty/luasocket/src/smtp.lua ${G_GENERATED}/smtp_lua.h SMTP_LUA_H
createEmbeddedBinary thirdparty/luasocket/src/socket.lua ${G_GENERATED}/socket_lua.h SOCKET_LUA_H
createEmbeddedBinary thirdparty/luasocket/src/tp.lua ${G_GENERATED}/tp_lua.h TP_LUA_H
createEmbeddedBinary thirdparty/luasocket/src/url.lua ${G_GENERATED}/url_lua.h URL_LUA_H
# === LuaSec ===
createEmbeddedBinary thirdparty/luasec/src/https.lua ${G_GENERATED}/https_lua.h HTTPS_LUA_H
createEmbeddedBinary thirdparty/luasec/src/ssl.lua ${G_GENERATED}/ssl_lua.h SSL_LUA_H
# === LuaRS232 ===
createEmbeddedBinary thirdparty/librs232/bindings/lua/rs232.lua ${G_GENERATED}/rs232_lua.h RS232_LUA_H
# === Copas ===
createEmbeddedBinary thirdparty/copas/src/copas.lua ${G_GENERATED}/copas_lua.h COPAS_LUA_H
createEmbeddedBinary thirdparty/copas/src/copas/ftp.lua ${G_GENERATED}/copas_ftp_lua.h COPAS_FTP_LUA_H copas
createEmbeddedBinary thirdparty/copas/src/copas/http.lua ${G_GENERATED}/copas_http_lua.h COPAS_HTTP_LUA_H copas
createEmbeddedBinary thirdparty/copas/src/copas/smtp.lua ${G_GENERATED}/copas_smtp_lua.h COPAS_SMTP_LUA_H copas
createEmbeddedBinary thirdparty/copas/src/copas/lock.lua ${G_GENERATED}/copas_lock_lua.h COPAS_LOCK_LUA_H copas
createEmbeddedBinary thirdparty/copas/src/copas/queue.lua ${G_GENERATED}/copas_queue_lua.h COPAS_QUEUE_LUA_H copas
createEmbeddedBinary thirdparty/copas/src/copas/semaphore.lua ${G_GENERATED}/copas_semaphore_lua.h COPAS_SEMAPHORE_LUA_H copas
createEmbeddedBinary thirdparty/copas/src/copas/timer.lua ${G_GENERATED}/copas_timer_lua.h COPAS_TIMER_LUA_H copas
# === binaryheap.lua ===
createEmbeddedBinary thirdparty/binaryheap.lua/src/binaryheap.lua ${G_GENERATED}/binaryheap_lua.h BINARYHEAP_LUA_H
# === timerwheel.lua ===
createEmbeddedBinary thirdparty/timerwheel.lua/src/timerwheel/timerwheel.lua ${G_GENERATED}/timerwheel_lua.h TIMERWHEEL_LUA_H
# === json.lua ===
createEmbeddedBinary thirdparty/json.lua/json.lua ${G_GENERATED}/json_lua.h JSON_LUA_H
# === Singe Framework ===
createEmbeddedBinary assets/Framework.singe ${G_GENERATED}/Framework_singe.h FRAMEWORK_SINGE_H
# === Default Config ===
createEmbeddedBinary assets/controls.cfg ${G_GENERATED}/controls_cfg.h CONTROLS_CFG_H
# === Singe Menu App ===
createEmbeddedBinary assets/Menu.singe ${G_GENERATED}/Menu_singe.h MENU_SINGE_H
# === Singe Manual ===
#lyx -batch -f all -E pdf ${G_GENERATED}/Manual.pdf assets/Manual.lyx
#createEmbeddedBinary ${G_GENERATED}/Manual.pdf ${G_GENERATED}/Manual_pdf.h MANUAL_H
# Embedded resources, the version header, and the manual are generated by CMake.
pushd ${G_TARGET}
clearAndEnterBuild
cmake ${COMMON} ${G_BUILDROOT}
cmake ${COMMON} -DKANGAROO_OS=${OS} -DKANGAROO_ARCH=${ARCH} ${G_BUILDROOT}
make
#upx -9 --force singe2${SUFFIX}
mv -f singe2${SUFFIX} ${G_BUILDROOT}/${G_BUILDDIR}/Singe-v2.10-${OS^}-${ARCH}${SUFFIX}
# CMake names the binary Singe-v<version>-<Os>-<arch>.
mv -f Singe-v*${SUFFIX} ${G_BUILDROOT}/${G_BUILDDIR}/.
popd
}
@ -409,134 +313,16 @@ function clearAndEnterBuild() {
}
function createEmbeddedBinary() {
local BINFILE=$1
local SOURCEFILE=$2 # This is assumed to be an absolute path
local BLOCKER=$3
local PREFIX=$4
local FILENAME=$(basename ${BINFILE})
local DIRNAME=$(dirname ${BINFILE})
outputLicense > ${SOURCEFILE}
outputHeader ${BLOCKER} >> ${SOURCEFILE}
printf "\n#ifdef EMBED_HERE\n\n" >> ${SOURCEFILE}
pushd ${DIRNAME}
xxd -i ${FILENAME} >> ${SOURCEFILE}
popd
if [[ ! -z ${PREFIX} ]]; then
PREFIX=${PREFIX}_
sed -i "s/unsigned char /unsigned char ${PREFIX}/" ${SOURCEFILE}
sed -i "s/unsigned int /unsigned int ${PREFIX}/" ${SOURCEFILE}
fi
printf "\n#else // EMBED_HERE\n\n" >> ${SOURCEFILE}
printf "extern unsigned char ${PREFIX}${FILENAME/\./_}[];\n" >> ${SOURCEFILE}
printf "extern unsigned int ${PREFIX}${FILENAME/\./_}_len;\n" >> ${SOURCEFILE}
printf "\n#endif // EMBED_HERE\n\n" >> ${SOURCEFILE}
outputFooter ${BLOCKER} >> ${SOURCEFILE}
}
function createEmbeddedImage() {
local BASENAME=$1
pushd assets
convert -flatten -background rgba\(0,0,0,0\) ${BASENAME}.xcf ${G_GENERATED}/${BASENAME}.png
createEmbeddedBinary ${G_GENERATED}/${BASENAME}.png ${G_GENERATED}/${BASENAME}.h ${BASENAME^^}_H
rm ${G_GENERATED}/${BASENAME}.png
popd
}
function createExtensionHeader() {
local FFMPEG=$1
local a=
local c=0
outputLicense
outputHeader FFMPEG_EXTENSIONS_H
printf "static char *ffmpegExtensions[] = {\n"
printf "\t"
getExtensions "${FFMPEG}" | sort | uniq -u | while read a; do
printf "\"${a}\", "
c=$((c + 1))
if [[ ${c} -ge 10 ]]; then
printf "\n\t"
c=0
fi
done
printf "0\n};"
outputFooter FFMPEG_EXTENSIONS_H
}
function getExtensions() {
local FFMPEG=$1
local a=
local b=
local c=
local d=
local e=
local f=
local g=
"${FFMPEG}" -demuxers 2> /dev/null | while read a b c; do
if [[ "${a}x" == "Dx" ]]; then
"${FFMPEG}" -h demuxer=${b} 2> /dev/null | grep "Common extensions" | while read d e f; do
g=${f/./}
echo -e "${g//,/\\n}"
done
fi
done
}
function outputFooter() {
local BLOCKER=$1
printf "\n#pragma GCC diagnostic pop\n\n\n#endif // ${BLOCKER}\n"
}
function outputHeader() {
local BLOCKER=$1
printf "\n\n#ifndef ${BLOCKER}\n#define ${BLOCKER}\n\n\n"
printf "// ===== THIS FILE IS AUTOMATICALLY GENERATED - DO NOT EDIT =====\n\n\n"
printf "#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wunused-variable\"\n"
}
function outputLicense() {
cat <<- LICENSE
/*
*
* Singe 2
* Copyright (C) 2006-2024 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*/
LICENSE
}
# -e = stop script on errors
# -u = stop script on undefined variable
# -o pipefail = stop pipeline if any step fails
set -e
set -euo pipefail
export MAKEFLAGS="${MAKEFLAGS:--j$(nproc)}"
if [[ ! -x ../toolchains/toolchains.sh ]]; then
echo "error: ../toolchains/toolchains.sh not found (see INSTALL)" >&2
exit 1
fi
mkdir -p ${G_BUILDDIR}
@ -544,21 +330,28 @@ mkdir -p ${G_BUILDDIR}
# These are required for the build.
sudo apt-get install -y \
xxd \
imagemagick \
lua5.4 \
lyx \
ffmpeg \
asciidoctor \
ruby-asciidoctor-pdf \
autoconf \
automake \
libtool \
libasound-dev \
libxi-dev \
libvdpau-dev \
upx-ucl
libvdpau-dev
buildAll linux x86_64 2>&1 | tee ${G_BUILDDIR}/linux-x86_64.log
buildAll macos aarch64 2>&1 | tee ${G_BUILDDIR}/macos-aarch64.log
buildAll pi aarch64 2>&1 | tee ${G_BUILDDIR}/pi-aarch64.log
buildAll windows x86_64 2>&1 | tee ${G_BUILDDIR}/windows-x86_64.log
# Usage: build-all.sh [os arch] (default: every supported platform)
if [[ $# -eq 2 ]]; then
buildAll "$1" "$2" 2>&1 | tee ${G_BUILDDIR}/$1-$2.log
else
buildAll linux x86_64 2>&1 | tee ${G_BUILDDIR}/linux-x86_64.log
buildAll macos aarch64 2>&1 | tee ${G_BUILDDIR}/macos-aarch64.log
buildAll pi aarch64 2>&1 | tee ${G_BUILDDIR}/pi-aarch64.log
buildAll windows x86_64 2>&1 | tee ${G_BUILDDIR}/windows-x86_64.log
fi
# === UNSUPPORTED ===

26
build-docs.sh Executable file
View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Build the Singe manual from docs/Manual.adoc.
# Produces docs/Manual.html and docs/Manual.pdf. The version comes from CMakeLists.txt.
set -euo pipefail
here=$(cd "$(dirname "$0")" && pwd)
src=$here/docs/Manual.adoc
if [[ ! -f $src ]]; then
echo "error: $src not found" >&2
exit 1
fi
for tool in asciidoctor asciidoctor-pdf; do
if ! command -v "$tool" > /dev/null; then
echo "error: $tool not found (gem install asciidoctor asciidoctor-pdf rouge)" >&2
exit 1
fi
done
version=$(sed -n 's/^project(singe2 VERSION \([0-9.]*\).*/\1/p' "$here/CMakeLists.txt")
asciidoctor -a revnumber="$version" "$src" -o "$here/docs/Manual.html"
asciidoctor-pdf -a revnumber="$version" "$src" -o "$here/docs/Manual.pdf"
echo "built:"
ls -la "$here/docs/Manual.html" "$here/docs/Manual.pdf"

61
cmake/embed.cmake Normal file
View file

@ -0,0 +1,61 @@
# Embeds a file as a C array. Invoked in script mode by CMakeLists.txt:
# cmake -DINPUT=<file> -DOUTPUT=<header> -DGUARD=<macro> -DSYMBOL=<name> -P embed.cmake
# The header defines the array when EMBED_HERE is defined and declares it extern otherwise.
file(READ "${INPUT}" content HEX)
string(LENGTH "${content}" hexLength)
math(EXPR byteLength "${hexLength} / 2")
# 0x00, 0x01, ... twelve per line.
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " bytes "${content}")
string(REGEX REPLACE "((0x[0-9a-f][0-9a-f], ){12})" "\\1\n\t" bytes "${bytes}")
string(REGEX REPLACE ", $" "" bytes "${bytes}")
string(REGEX REPLACE ",\n\t$" "" bytes "${bytes}")
file(WRITE "${OUTPUT}" "/*
*
* Singe 2
* Copyright (C) 2006-${COPYRIGHT_END_YEAR} Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*/
#ifndef ${GUARD}
#define ${GUARD}
// ===== THIS FILE IS AUTOMATICALLY GENERATED - DO NOT EDIT =====
#ifdef EMBED_HERE
const unsigned char ${SYMBOL}[] = {
${bytes}
};
const unsigned int ${SYMBOL}_len = ${byteLength};
#else // EMBED_HERE
extern const unsigned char ${SYMBOL}[];
extern const unsigned int ${SYMBOL}_len;
#endif // EMBED_HERE
#endif // ${GUARD}
")

8
cmake/runToFile.cmake Normal file
View file

@ -0,0 +1,8 @@
# Runs a command and captures its standard output in a file. Invoked in script mode:
# cmake -DOUTPUT=<file> -DCOMMAND=<program> -DARGS=<semicolon list> -P runToFile.cmake
execute_process(COMMAND ${COMMAND} ${ARGS} OUTPUT_FILE "${OUTPUT}" RESULT_VARIABLE result)
if(NOT result EQUAL 0)
file(REMOVE "${OUTPUT}")
message(FATAL_ERROR "${COMMAND} failed with status ${result}")
endif()

3454
docs/Manual.adoc Normal file

File diff suppressed because it is too large Load diff

View file

@ -15,20 +15,20 @@ pixelLow = 0
pixelHigh = 1
pixelUnknown = 2
sprLightOn = spriteLoad("ActionMax/sprite_LightOn.png")
sprLightOff = spriteLoad("ActionMax/sprite_LightOff.png")
sprActionMax = spriteLoad("ActionMax/sprite_ActionMax.png")
sprCrosshair = spriteLoad("ActionMax/sprite_Crosshair.png")
sprBullet = spriteLoad("ActionMax/sprite_Bullet.png")
sprBoxArt = spriteLoad("ActionMax/sprite_" .. gameID .. ".png")
sprLightOn = spriteLoad(DIR .. "sprite_LightOn.png")
sprLightOff = spriteLoad(DIR .. "sprite_LightOff.png")
sprActionMax = spriteLoad(DIR .. "sprite_ActionMax.png")
sprCrosshair = spriteLoad(DIR .. "sprite_Crosshair.png")
sprBullet = spriteLoad(DIR .. "sprite_Bullet.png")
sprBoxArt = spriteLoad(DIR .. "sprite_" .. gameID .. ".png")
sndActionMax = soundLoad("ActionMax/sound_ActionMax.wav")
sndSteadyAim = soundLoad("ActionMax/sound_ASteadyAimIsCritical.wav")
sndGetReady = soundLoad("ActionMax/sound_GetReadyForAction.wav")
sndGunShot = soundLoad("ActionMax/sound_Gunshot.wav")
sndGoodHit = soundLoad("ActionMax/sound_GoodHit.wav")
sndBadHit = soundLoad("ActionMax/sound_BadHit.wav")
sndGameOver = soundLoad("ActionMax/sound_GameOver.wav")
sndActionMax = soundLoad(DIR .. "sound_ActionMax.wav")
sndSteadyAim = soundLoad(DIR .. "sound_ASteadyAimIsCritical.wav")
sndGetReady = soundLoad(DIR .. "sound_GetReadyForAction.wav")
sndGunShot = soundLoad(DIR .. "sound_Gunshot.wav")
sndGoodHit = soundLoad(DIR .. "sound_GoodHit.wav")
sndBadHit = soundLoad(DIR .. "sound_BadHit.wav")
sndGameOver = soundLoad(DIR .. "sound_GameOver.wav")
mouseX = 0
mouseY = 0
@ -63,11 +63,11 @@ thisSeconds = 0
lastSeconds = 0
heartbeat = false
fntBlueStone20 = fontLoad("ActionMax/font_BlueStone.ttf", 20)
fntChemRea16 = fontLoad("ActionMax/font_chemrea.ttf", 16)
fntChemRea32 = fontLoad("ActionMax/font_chemrea.ttf", 32)
fntChemRea48 = fontLoad("ActionMax/font_chemrea.ttf", 48)
fntLEDReal32 = fontLoad("ActionMax/font_LED_Real.ttf", 32)
fntBlueStone20 = fontLoad(DIR .. "font_BlueStone.ttf", 20)
fntChemRea16 = fontLoad(DIR .. "font_chemrea.ttf", 16)
fntChemRea32 = fontLoad(DIR .. "font_chemrea.ttf", 32)
fntChemRea48 = fontLoad(DIR .. "font_chemrea.ttf", 48)
fntLEDReal32 = fontLoad(DIR .. "font_LED_Real.ttf", 32)
colorBackground(0, 0, 0, 0)
fontQuality(FONT_QUALITY_BLENDED)
@ -170,51 +170,6 @@ function onInputPressed(intWhat)
end
--dbgPaused = 0
--dbgSpeed = 1
function onInputReleased(intWhat)
--[[ We don't use this. All this is debug stuff.
if (intWhat == SWITCH_BUTTON1) then
if (currentState == statePlaying) then
discSearch(lengthIntro + lengthGame - 60)
discPlay()
else
if (dbgPaused == 1) then
discPlay()
dbgPaused = 0
else
discPause()
dbgPaused = 1
end
end
end
if (intWhat == SWITCH_RIGHT) then
if (dbgSpeed < 4) then
dbgSpeed = dbgSpeed + 1
end
discChangeSpeed(dbgSpeed, 1)
end
if (intWhat == SWITCH_LEFT) then
if (dbgSpeed > 1) then
dbgSpeed = dbgSpeed - 1
end
discChangeSpeed(dbgSpeed, 1)
end
if (intWhat == SWITCH_UP) then
discStepBackward()
end
if (intWhat == SWITCH_DOWN) then
discStepForward()
end
--]]
end
function onMouseMoved(intX, intY, intXrel, intYrel)
-- Remember the mouse location for use later.
@ -307,9 +262,9 @@ function onOverlayUpdate()
elseif (currentState == stateTitle) then
spriteDraw(logoLeft, logoTop, sprActionMax)
spriteDraw(boxLeft, boxTop, sprBoxArt)
spriteDraw(lastGameLeft, lastGameTop, sprLastGame)
spriteDraw(sprActionMax, logoLeft, logoTop)
spriteDraw(sprBoxArt, boxLeft, boxTop)
spriteDraw(sprLastGame, lastGameLeft, lastGameTop)
y = scoreTop
fontPrint(scoreLeft, y, " Shots Fired: " .. shotsFired)
y = y + scoreHeight
@ -326,16 +281,16 @@ function onOverlayUpdate()
end
fontPrint(scoreLeft, y, " Game Score: " .. scorePercent .. "%")
if (heartbeat) then
spriteDraw(pullToStartLeft, pullToStartTop, sprPullToStart)
spriteDraw(sprPullToStart, pullToStartLeft, pullToStartTop)
end
elseif (currentState == stateMenu) then
spriteDraw(selectGameTypeLeft, selectGameTypeTop, sprSelectGameType)
spriteDraw(standard1Left, standardLimited1Top, sprStandard1)
spriteDraw(standard2Left, standardLimited2Top, sprStandard2)
spriteDraw(limited1Left, standardLimited1Top, sprLimited1)
spriteDraw(limited2Left, standardLimited2Top, sprLimited2)
spriteDraw(sprSelectGameType, selectGameTypeLeft, selectGameTypeTop)
spriteDraw(sprStandard1, standard1Left, standardLimited1Top)
spriteDraw(sprStandard2, standard2Left, standardLimited2Top)
spriteDraw(sprLimited1, limited1Left, standardLimited1Top)
spriteDraw(sprLimited2, limited2Left, standardLimited2Top)
if (currentFrame >= lengthIntro + lengthGame + lengthMenu) then
discSearch(lengthIntro + lengthGame + 2)
@ -345,7 +300,7 @@ function onOverlayUpdate()
elseif (currentState == stateIntro) then
if (heartbeat) then
spriteDraw(getReadyLeft, getReadyTop, sprGetReady)
spriteDraw(sprGetReady, getReadyLeft, getReadyTop)
end
-- Skip into game video when the intro is over.
@ -423,9 +378,9 @@ function onOverlayUpdate()
-- Do we need to light the light?
if (lightDisplay > 0) then
spriteDraw(lightLeft, lightTop, sprLightOn)
spriteDraw(sprLightOn, lightLeft, lightTop)
else
spriteDraw(lightLeft, lightTop, sprLightOff)
spriteDraw(sprLightOff, lightLeft, lightTop)
end
-- Do we need to draw the ammo display?
@ -440,7 +395,7 @@ function onOverlayUpdate()
if (ammoLeft > 0) then
bulletStart = overlayGetWidth() - bulletWidth - 5
for i=1,ammoLeft do
spriteDraw(bulletStart, 0, sprBullet)
spriteDraw(sprBullet, bulletStart, 0)
bulletStart = bulletStart - bulletWidth
end
end
@ -448,7 +403,7 @@ function onOverlayUpdate()
elseif (currentState == stateGameOver) then
spriteDraw(gameOverLeft, gameOverTop, sprGameOver)
spriteDraw(sprGameOver, gameOverLeft, gameOverTop)
if (currentFrame >= lengthIntro + lengthGame + lengthMenu) then
discPause()
@ -462,7 +417,7 @@ function onOverlayUpdate()
-- Draw gun crosshair (This must be the last thing we draw so it's on top.)
if (singeWantsCrosshairs()) then
spriteDraw(mouseX - crosshairCenterX, mouseY - crosshairCenterY, sprCrosshair)
spriteDraw(sprCrosshair, mouseX - crosshairCenterX, mouseY - crosshairCenterY)
end
return(OVERLAY_UPDATED)

18
patches/README Normal file
View file

@ -0,0 +1,18 @@
Game Patches
============
These are corrected copies of scripts from third party Singe games. They
are not part of the engine build; each one replaces the same named file
inside an installed game. Package one as a ".patch" archive (see
"Packaging Your Game" in the manual) or copy the file over the original.
ActionMax/Emulator.singe
Shared emulator script used by every ActionMax title. Fixes sprite
leaks in the original release and uses the Singe 2.20 sprite argument
order. The per-game wrapper script must define gameID, backgroundFrame,
lengthIntro, lengthGame, lengthMenu, highThreshold, lowThreshold,
sensorX, sensorY, sensorLeft, and sensorTop before loading it.
daitarn_3_singe/Script/toolbox.singe
Helper library from "Daitarn 3" (Karis, 2020). The calling script must
define OVLW, OVLH, and bPause.

View file

@ -61,7 +61,9 @@ YELLOW = 2
GREEN = 3
ORANGE = 4
WHITE = 5
GREY = 6; GRAY = 6
GREY = 6
GRAY = 6
PINK = 7
LIGHTBLUE = 7
BLACK = 8
@ -126,7 +128,7 @@ function clockRnd()
q, w = math.modf(j)
s2 = tostring(w)
r = string.find(s2,".")
r = string.find(s2, ".", 1, true)
if (r == nil) then
@ -318,12 +320,8 @@ end
function getMiddle(thisPhrase)
local x = 0
local y = 0
local sprite = fontToSprite(thisPhrase)
x = OVLW/2 - spriteGetWidth(sprite) * 0.5
y = OVLH/2 - spriteGetHeight(sprite) * 0.5
local x = OVLW/2 - spriteGetWidth(sprite) * 0.5
spriteUnload(sprite)

View file

@ -26,13 +26,8 @@
#include <stdint.h>
#define byte unsigned char
#define bool unsigned char
#define true 1
#define false 0
#include <stdbool.h>
#include <inttypes.h>
#endif // COMMON_H

View file

@ -37,7 +37,7 @@
#include "generated/Menu_singe.h"
#include "generated/FreeSansBold_ttf.h"
#include "generated/menuBackground_mkv.h"
//#include "generated/Manual_pdf.h"
#include "generated/Manual_pdf.h"
// LuaSocket
#include "generated/ftp_lua.h"

View file

@ -21,6 +21,9 @@
*/
#include <string.h>
#include <stdlib.h>
#include "../thirdparty/uthash/src/uthash.h"
#include "util.h"
@ -28,64 +31,107 @@
#include "frameFile.h"
extern bool _confShowCalculated;
#define REPORT_COLUMN_WIDTH 8
typedef struct FrameLineS {
int32_t videoHandle;
int64_t lastFramePlayed;
int64_t frame;
int64_t frame; // First laserdisc frame number in this segment
char *filename;
} FrameLineT;
typedef struct FrameFileS {
int32_t id;
int32_t count;
char *videoPath;
int32_t currentIndex; // Segment currently playing
int64_t lastObservedFrame; // Frame of that segment at the last update
FrameLineT *files;
UT_hash_handle hh;
} FrameFileT;
static FrameFileT *_frameFileHash = NULL;
static int32_t _nextId = 0;
static FrameFileT *_frameFileHash = NULL;
static int32_t _nextId = 0;
void _transferProperties(int32_t oldHandle, int32_t newHandle);
static FrameFileT *_getFrameFile(int32_t frameFileHandle, const char *caller);
static void _selectSegment(FrameFileT *f, int32_t index, int64_t frame, int32_t *videoHandle);
static void _showCalculated(const FrameFileT *f);
void _transferProperties(int32_t oldHandle, int32_t newHandle) {
int32_t l = 0;
int32_t r = 0;
static FrameFileT *_getFrameFile(int32_t frameFileHandle, const char *caller) {
FrameFileT *f = NULL;
if ((oldHandle < 0) || (newHandle < 0)) {
// Invalid handle somewhere
return;
HASH_FIND_INT(_frameFileHash, &frameFileHandle, f);
if (!f) {
utilDie("No framefile at index %d in %s.", frameFileHandle, caller);
}
// Transfer previous video's properties to this one
videoGetVolume(oldHandle, &l, &r);
videoSetVolume(newHandle, l, r);
if (videoIsPlaying(oldHandle)) {
videoPlay(newHandle);
} else {
videoPause(newHandle);
return f;
}
// Make segment "index" the active video at "frame", carrying the previous video's state over.
static void _selectSegment(FrameFileT *f, int32_t index, int64_t frame, int32_t *videoHandle) {
int32_t oldHandle = *videoHandle;
int32_t newHandle = f->files[index].videoHandle;
int32_t left = 0;
int32_t right = 0;
int32_t track = 0;
videoSeek(newHandle, frame);
if ((oldHandle >= 0) && (oldHandle != newHandle)) {
// Transfer previous video's properties to this one
videoGetVolume(oldHandle, &left, &right);
videoSetVolume(newHandle, left, right);
track = videoGetAudioTrack(oldHandle);
if ((track >= 0) && (track < videoGetAudioTracks(newHandle))) {
videoSetAudioTrack(newHandle, track);
}
if (videoIsPlaying(oldHandle)) {
videoPlay(newHandle);
} else {
videoPause(newHandle);
}
videoPause(oldHandle);
}
videoPause(oldHandle);
f->currentIndex = index;
f->lastObservedFrame = frame;
*videoHandle = newHandle;
}
static void _showCalculated(const FrameFileT *f) {
int32_t x = 0;
int64_t count = 0;
int64_t next = 0;
// 00000000011111111112222222222333333333344444444445555555555666666666677777777778
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
utilSay("Existing Framefile:\n");
utilSay(" Start Length End File");
utilSay("-------- -------- -------- -------------------------------------------------");
for (x = 0; x < f->count; x++) {
count = videoGetFrameCount(f->files[x].videoHandle);
utilSay("%*" PRId64 " %*" PRId64 " %*" PRId64 " %s", REPORT_COLUMN_WIDTH, f->files[x].frame, REPORT_COLUMN_WIDTH, count, REPORT_COLUMN_WIDTH, f->files[x].frame + count - 1, f->files[x].filename);
}
utilSay("\nIdeal Framefile:\n");
utilSay(" Start File");
utilSay("-------- ---------------------------------------------------------------------");
for (x = 0; x < f->count; x++) {
utilSay("%*" PRId64 " %s", REPORT_COLUMN_WIDTH, next, f->files[x].filename);
next += videoGetFrameCount(f->files[x].videoHandle);
}
utilNewline();
}
int64_t frameFileGetFrame(int32_t frameFileHandle, int32_t videoHandle) {
FrameFileT *f = _getFrameFile(frameFileHandle, "frameFileGetFrame");
int32_t i = 0;
FrameFileT *f = NULL;
// Get our framefile structure
HASH_FIND_INT(_frameFileHash, &frameFileHandle, f);
if (!f) utilDie("No framefile at index %d in frameFileSeek.", frameFileHandle);
// Search through loaded video segments
for (i=0; i<f->count; i++) {
for (i = 0; i < f->count; i++) {
if (f->files[i].videoHandle == videoHandle) {
// Frame is in this video
return videoGetFrame(videoHandle) + f->files[i].frame;
@ -97,193 +143,114 @@ int64_t frameFileGetFrame(int32_t frameFileHandle, int32_t videoHandle) {
}
int32_t frameFileInit(void) {
return 0; // Nothing to do
}
int32_t frameFileLoad(char *filename, char *indexPath, bool stretchVideo, SDL_Renderer *renderer, bool showCalculated) {
int32_t result = 0;
int32_t count = 0;
int64_t frame = 0;
int64_t next = 0;
size_t bytes = 0;
char *audio = NULL;
char *data = NULL;
char *path = NULL;
char *offset = NULL;
char *frameLine = NULL;
char *space = NULL;
char *endptr = NULL;
char *temp = NULL;
FrameLineT *files = NULL;
FrameLineT *newFiles = NULL;
FrameFileT *frameFile = NULL;
int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer *renderer, bool showCalculated) {
int32_t count = 0;
int64_t frame = 0;
size_t bytes = 0;
size_t x = 0;
char *audio = NULL;
char *data = NULL;
char *path = NULL;
char *temp = NULL;
char *frameLine = NULL;
char *space = NULL;
char *endptr = NULL;
const char *name = NULL;
const char *offset = NULL;
FrameLineT *files = NULL;
FrameLineT *newFiles = NULL;
FrameFileT *frameFile = NULL;
data = utilReadFile(filename, &bytes);
if (!data) utilDie("Unable to open framefile: %s", filename);
if (!data) {
utilDie("Unable to open framefile: %s", filename);
}
// Get path where video files live
path = utilReadLine(data, bytes, &offset);
if (!path) utilDie("Cannot read video path from framefile!");
if (!path) {
utilDie("Cannot read video path from framefile!");
}
utilFixPathSeparators(&path, true);
// If it's not an absolute path, pre-pend the path to the framefile
if ((path[0] != utilGetPathSeparator()) && (path[1] != ':')) {
temp = path;
count = strlen(filename) - strlen(utilGetLastPathComponent(filename));
path = malloc(sizeof(char) * (count + strlen(temp) + 1));
memcpy(path, filename, count);
memcpy(&path[count], temp, strlen(temp));
path[count + strlen(temp)] = 0;
temp = utilGetUpToLastPathComponent(filename);
free(path);
path = utilCreateString("%s%s", temp, path);
free(temp);
utilFixPathSeparators(&path, true);
count = 0;
}
// Read frame offsets and filenames - silently ignore bad lines
while ((frameLine = utilReadLine(data, bytes, &offset)) != NULL) {
// Covert tabs to spaces
for (next=0; next<(int64_t)strlen(frameLine); next++) {
if (frameLine[next] == 9) frameLine[next] = 32;
// Convert tabs to spaces
for (x = 0; frameLine[x] != 0; x++) {
if (frameLine[x] == '\t') {
frameLine[x] = ' ';
}
}
// Find first space in this file.
space = strstr(frameLine, " ");
if (space) {
// Write a zero in there to make it two fields.
*space = 0;
// Is the first part an integer?
endptr = NULL;
frame = strtol(frameLine, &endptr, 10);
*space = 32;
if (endptr == space) {
// Got an integer. Point at filename.
space++;
while (space[0] == 32) {
space++;
}
// Copy frame number and filename into array
newFiles = realloc(files, sizeof(FrameLineT) * (count + 1));
if (!newFiles) utilDie("Unable to allocate new framefile entry!");
files = newFiles;
files[count].lastFramePlayed = 0;
files[count].frame = frame;
files[count].filename = utilCreateString("%s%s", path, space);
// Is this an old m2v/ogg pair?
if (strncmp(utilGetFileExtension(files[count].filename), "m2v", 3) == 0) {
// Open split video and audio files
audio = strdup(files[count].filename);
audio[strlen(audio) - 3] = 'o';
audio[strlen(audio) - 2] = 'g';
audio[strlen(audio) - 1] = 'g';
if (utilFileExists(audio)) {
files[count].videoHandle = videoLoadWithAudio(files[count].filename, audio, indexPath, stretchVideo, renderer);
} else {
files[count].videoHandle = videoLoadWithAudio(files[count].filename, NULL, indexPath, stretchVideo, renderer);
}
// Is the first field an integer followed by a space?
frame = strtoll(frameLine, &endptr, 10);
space = strchr(frameLine, ' ');
if ((space != NULL) && (endptr == space)) {
// Got an integer. Point at filename.
name = space;
while (*name == ' ') {
name++;
}
// Copy frame number and filename into array
newFiles = realloc(files, sizeof(FrameLineT) * (size_t)(count + 1));
if (!newFiles) {
utilDie("Unable to allocate new framefile entry!");
}
files = newFiles;
files[count].frame = frame;
files[count].filename = utilCreateString("%s%s", path, name);
// Is this an old m2v/ogg pair?
audio = NULL;
if (utilStricmp(utilGetFileExtension(files[count].filename), "m2v") == 0) {
audio = utilCreateString("%.*s.ogg", (int32_t)(strlen(files[count].filename) - strlen("m2v") - 1), files[count].filename);
if (!utilFileExists(audio)) {
free(audio);
audio = NULL;
} else {
// Open combined video/audio file
files[count].videoHandle = videoLoad(files[count].filename, indexPath, stretchVideo, renderer);
}
count++;
}
files[count].videoHandle = videoLoad(files[count].filename, audio, indexPath, renderer);
free(audio);
count++;
}
free(frameLine);
}
free(data);
free(path);
if (count == 0) {
utilDie("Framefile has no video entries: %s", filename);
}
// Allocate new framefile
frameFile = malloc(sizeof(FrameFileT));
frameFile->id = _nextId;
frameFile->count = count;
frameFile->videoPath = path;
frameFile->files = files;
frameFile = calloc(1, sizeof(FrameFileT));
if (!frameFile) {
utilDie("Unable to allocate framefile.");
}
frameFile->id = _nextId++;
frameFile->count = count;
frameFile->currentIndex = -1;
frameFile->files = files;
HASH_ADD_INT(_frameFileHash, id, frameFile);
result = _nextId++;
// Show debug output?
if (showCalculated) {
// 00000000011111111112222222222333333333344444444445555555555666666666677777777778
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
utilSay("Existing Framefile:\n");
utilSay(" Start Length End File");
utilSay("-------- -------- -------- -------------------------------------------------");
for (count=0; count<frameFile->count; count++) {
frame = videoGetFrameCount(frameFile->files[count].videoHandle);
utilSay("%8ld %8ld %8ld %s", frameFile->files[count].frame, frame, frameFile->files[count].frame + frame, frameFile->files[count].filename);
}
next = 0;
utilSay("\nIdeal Framefile:\n");
utilSay(" Start File");
utilSay("-------- ---------------------------------------------------------------------");
for (count=0; count<frameFile->count; count++) {
frame = videoGetFrameCount(frameFile->files[count].videoHandle);
utilSay("%8ld %s", next, frameFile->files[count].filename);
next += frame + 1;
}
utilSay("");
_showCalculated(frameFile);
}
return result;
return frameFile->id;
}
int32_t frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHandle, int64_t *actualFrame) {
int32_t i = 0;
int32_t found = -1;
FrameFileT *f = NULL;
// Get our framefile structure
HASH_FIND_INT(_frameFileHash, &frameFileHandle, f);
if (!f) utilDie("No framefile at index %d in frameFileSeek.", frameFileHandle);
// Search through loaded video segments
// Daphne-like framefile searching
found = 0;
for (i=0; i<f->count; i++) {
if (seekFrame >= f->files[i].frame) {
found = i;
}
}
/*
// Strict framefile searching
for (i=0; i<f->count; i++) {
if ((seekFrame >= f->files[i].frame) && (seekFrame <= (f->files[i].frame + videoGetFrameCount(f->files[i].videoHandle)))) {
found = i;
break;
}
}
*/
if (found >= 0) {
// Frame is in this video
*actualFrame = seekFrame - f->files[found].frame;
//utilSay("Frame %ld found in file %d - %s at %ld", seekFrame, found, f->files[found].filename, *actualFrame);
f->files[found].lastFramePlayed = *actualFrame;
videoSeek(f->files[found].videoHandle, *actualFrame);
// Is this a different video from the previous one?
if (*videoHandle != f->files[found].videoHandle) {
// Yes
_transferProperties(*videoHandle, f->files[found].videoHandle);
*videoHandle = f->files[found].videoHandle;
}
return 0;
}
// Didn't find it
*videoHandle = -1;
*actualFrame = -1;
return 1;
}
int32_t frameFileQuit(void) {
void frameFileQuit(void) {
FrameFileT *f = NULL;
FrameFileT *t = NULL;
@ -291,63 +258,68 @@ int32_t frameFileQuit(void) {
HASH_ITER(hh, _frameFileHash, f, t) {
frameFileUnload(f->id);
}
return 0;
}
int32_t frameFileUnload(int32_t frameFileHandle) {
int32_t i = 0;
FrameFileT *f = NULL;
// Daphne-like framefile searching: the segment is the last one starting at or before the frame.
bool frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHandle, int64_t *actualFrame) {
FrameFileT *f = _getFrameFile(frameFileHandle, "frameFileSeek");
int32_t i = 0;
int32_t found = 0;
int64_t last = 0;
// Get our framefile structure
HASH_FIND_INT(_frameFileHash, &frameFileHandle, f);
if (!f) utilDie("No framefile at index %d in frameFileUnload.", frameFileHandle);
for (i = 0; i < f->count; i++) {
if (seekFrame >= f->files[i].frame) {
found = i;
}
}
// Clamp inside the segment instead of letting the player wrap it.
*actualFrame = seekFrame - f->files[found].frame;
last = videoGetFrameCount(f->files[found].videoHandle) - 1;
if (*actualFrame < 0) {
*actualFrame = 0;
}
if (*actualFrame > last) {
*actualFrame = last;
}
_selectSegment(f, found, *actualFrame, videoHandle);
return true;
}
void frameFileUnload(int32_t frameFileHandle) {
FrameFileT *f = _getFrameFile(frameFileHandle, "frameFileUnload");
int32_t i = 0;
// Unload videos
for (i=0; i<f->count; i++) {
for (i = 0; i < f->count; i++) {
free(f->files[i].filename);
videoUnload(f->files[i].videoHandle);
}
// Free memory
free(f->files);
free(f->videoPath);
// Remove from hash
HASH_DEL(_frameFileHash, f);
free(f);
return 0;
}
int32_t frameFileUpdate(int32_t frameFileHandle, int32_t *videoHandle) {
int32_t i = 0;
// Did the current video loop back to the start? If so, move to the next segment.
void frameFileUpdate(int32_t frameFileHandle, int32_t *videoHandle) {
FrameFileT *f = _getFrameFile(frameFileHandle, "frameFileUpdate");
int64_t frame = 0;
FrameFileT *f = NULL;
// Get our framefile structure
HASH_FIND_INT(_frameFileHash, &frameFileHandle, f);
if (!f) utilDie("No framefile at index %d in frameFileUpdate.", frameFileHandle);
// Did the current video loop? If so, move to next video.
for (i=0; i<f->count; i++) {
if (f->files[i].videoHandle == *videoHandle) {
frame = videoGetFrame(*videoHandle);
if ((f->files[i].lastFramePlayed > frame) && (frame == 0)) {
// Switch video files
if (i == (f->count - 1)) {
i = 0;
} else {
i++;
}
_transferProperties(*videoHandle, f->files[i].videoHandle);
*videoHandle = f->files[i].videoHandle;
}
break;
}
if ((f->currentIndex < 0) || (f->files[f->currentIndex].videoHandle != *videoHandle)) {
return;
}
frame = videoGetFrame(*videoHandle);
if (frame < f->lastObservedFrame) {
_selectSegment(f, (f->currentIndex + 1) % f->count, 0, videoHandle);
} else {
f->lastObservedFrame = frame;
}
return 0;
}

View file

@ -31,12 +31,11 @@
int64_t frameFileGetFrame(int32_t frameFileHandle, int32_t videoHandle);
int32_t frameFileInit(void);
int32_t frameFileLoad(char *filename, char *indexPath, bool stretchVideo, SDL_Renderer *renderer, bool showCalculated);
int32_t frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHandle, int64_t *actualFrame);
int32_t frameFileQuit(void);
int32_t frameFileUnload(int32_t frameFileHandle);
int32_t frameFileUpdate(int32_t frameFileHandle, int32_t *videoHandle);
int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer *renderer, bool showCalculated);
void frameFileQuit(void);
bool frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHandle, int64_t *actualFrame);
void frameFileUnload(int32_t frameFileHandle);
void frameFileUpdate(int32_t frameFileHandle, int32_t *videoHandle);
#endif // FRAMEFILE_H

1724
src/main.c

File diff suppressed because it is too large Load diff

View file

@ -25,15 +25,22 @@
#define MAIN_H
#include "common.h"
#include "singe.h"
ConfigT *cloneConf(ConfigT *conf);
#define VOLUME_MIN 0
#define VOLUME_MAX 100
#define SCALE_FACTOR_MIN 50
#define SCALE_FACTOR_MAX 100
#define DIRECTORY_MODE 0777
ConfigT *cloneConf(const ConfigT *conf);
char *createDataDir(const char *dataDirBase, const char *filename);
void destroyConf(ConfigT **confPointer);
bool parseSindenString(char **sindenStringPointer, ConfigT *conf);
void queueScript(ConfigT *conf);
bool isFrameFileName(const char *filename);
bool parseSindenString(const char *sindenString, ConfigT *conf);
void queueScript(const ConfigT *conf);
#endif // MAIN_H

File diff suppressed because it is too large Load diff

View file

@ -28,22 +28,20 @@
#include <SDL2/SDL.h>
#include "common.h"
#include "generated/version.h"
// Don't forget to update singe.rc!
#define SINGE_VERSION 2.10
#define VERSION_STRING "v2.10"
#define COPYRIGHT_END_YEAR "2024"
#define SINDEN_ARG_MAX 8
enum {
// Number of --sindengun arguments selects the border style.
typedef enum SindenModeE {
SINDEN_WHITE = 1,
SINDEN_WHITE_BLACK = 2,
SINDEN_CUSTOM_WHITE = 4,
SINDEN_CUSTOM_WHITE_BLACK = 5,
SINDEN_CUSTOM_WHITE_CUSTOM_BLACK = 8,
SINDEN_OPTION_COUNT = 8
};
SINDEN_CUSTOM_WHITE_CUSTOM_BLACK = 8
} SindenModeE;
typedef struct ConfigS {
@ -64,6 +62,7 @@ typedef struct ConfigS {
bool noLogos;
bool programTracing;
bool scriptTracing;
bool legacySpriteArgs;
int32_t bestRatioIndex;
int32_t volumeVldp;
int32_t volumeNonVldp;
@ -71,7 +70,7 @@ typedef struct ConfigS {
int32_t xResolution;
int32_t yResolution;
int32_t sindenArgc;
int32_t sindenArgv[SINDEN_OPTION_COUNT];
int32_t sindenArgv[SINDEN_ARG_MAX];
int32_t audioOutputTrack;
} ConfigT;

View file

@ -1,24 +0,0 @@
101 ICON "/tmp/icon.ico"
1 VERSIONINFO
FILEVERSION 2,1,0,0
PRODUCTVERSION 2,1,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "Kangaroo Punch Studios"
VALUE "FileDescription", "Somewhat Interactive Nostalgic Game Engine"
VALUE "FileVersion", "2.10"
VALUE "InternalName", "Singe"
VALUE "LegalCopyright", "Copyright 2006-2024 Scott C. Duensing"
VALUE "OriginalFilename", "singe.exe"
VALUE "ProductName", "Singe"
VALUE "ProductVersion", "2.10"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END

24
src/singe.rc.in Normal file
View file

@ -0,0 +1,24 @@
101 ICON "@GENERATED_DIR@/icon.ico"
1 VERSIONINFO
FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,0,0
PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "Kangaroo Punch Studios"
VALUE "FileDescription", "Somewhat Interactive Nostalgic Game Engine"
VALUE "FileVersion", "@PROJECT_VERSION@"
VALUE "InternalName", "Singe"
VALUE "LegalCopyright", "Copyright 2006-@SINGE_COPYRIGHT_END_YEAR@ Scott C. Duensing"
VALUE "OriginalFilename", "Singe.exe"
VALUE "ProductName", "Singe"
VALUE "ProductVersion", "@PROJECT_VERSION@"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END

View file

@ -29,14 +29,13 @@
#define ourMkdir(p,m) mkdir(p)
static const int CONSOLE_LINES = 1000;
#define CONSOLE_LINES 1000
#else
#define ourMkdir mkdir
#endif
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <ctype.h>
@ -48,6 +47,29 @@ static bool _outputHappened = false;
static FILE *_utilTraceFile = NULL;
static bool _ensureDirectory(const char *path, const mode_t mode);
static void _printLine(FILE *stream, const char *fmt, va_list args) __attribute__((format(printf, 2, 0)));
static bool _ensureDirectory(const char *path, const mode_t mode) {
struct stat sb;
if (stat(path, &sb) != 0) {
// Does not exist - create it.
return (ourMkdir(path, mode) >= 0);
}
return S_ISDIR(sb.st_mode);
}
static void _printLine(FILE *stream, const char *fmt, va_list args) {
vfprintf(stream, fmt, args);
fputc('\n', stream);
fflush(stream);
}
bool utilChMod(const char *path, const mode_t mode) {
bool result = true;
@ -62,7 +84,7 @@ bool utilChMod(const char *path, const mode_t mode) {
}
char *utilCreateString(char *format, ...) {
char *utilCreateString(const char *format, ...) {
va_list args;
char *string;
@ -74,15 +96,18 @@ char *utilCreateString(char *format, ...) {
}
__attribute__((__format__(__printf__, 1, 0)))
char *utilCreateStringVArgs(char *format, va_list args) {
char *utilCreateStringVArgs(const char *format, va_list args) {
va_list argsCopy;
int32_t size = 0;
char *buffer = NULL;
int32_t size = 0;
char *buffer = NULL;
va_copy(argsCopy, args);
size = vsnprintf(NULL, 0, format, argsCopy) + 1;
size = vsnprintf(NULL, 0, format, argsCopy);
va_end(argsCopy);
if (size < 0) {
return NULL;
}
size++;
buffer = calloc(1, (size_t)size);
if (buffer) {
vsnprintf(buffer, (size_t)size, format, args);
@ -92,58 +117,48 @@ char *utilCreateStringVArgs(char *format, va_list args) {
}
__attribute__((__format__(__printf__, 1, 0)))
__attribute__((noreturn))
void utilDie(char *fmt, ...) {
void utilDie(const char *fmt, ...) {
va_list args;
if (_consoleEnabled) {
va_start(args, fmt);
vfprintf(stderr, fmt, args);
_printLine(stderr, fmt, args);
va_end(args);
printf("\n");
fflush(stderr);
#ifdef _WIN32
getchar();
#endif
utilWaitForKeyOnWindows();
}
exit(1);
exit(EXIT_FAILURE);
}
void utilEnableConsole(bool enable) {
void utilEnableConsole(bool enable) {
_consoleEnabled = enable;
}
bool utilFileExists(char *filename) {
FILE *file;
if ((file = fopen(filename, "r+"))) {
fclose(file);
return true;
bool utilFileExists(const char *filename) {
struct stat sb;
if (stat(filename, &sb) != 0) {
return false;
}
return false;
return S_ISREG(sb.st_mode);
}
void utilFixPathSeparators(char **path, bool slash) {
int32_t i = 0;
int32_t j = 0;
char *work = *path;
char *temp = NULL;
const char separator = utilGetPathSeparator();
size_t i = 0;
size_t j = 0;
char *work = *path;
char *grown = NULL;
// Flip path separators to whatever our OS wants & remove repeated separators.
while (work[i] != 0) {
// Correct separator
if (work[i] == '\\' || work[i] == '/') {
// Was the prior character a seprator?
if (j == 0) {
work[j++] = utilGetPathSeparator();
} else {
if (work[j - 1] != utilGetPathSeparator()) {
// No, accept it.
work[j++] = utilGetPathSeparator();
}
// Only accept a separator if the prior character was not one.
if ((j == 0) || (work[j - 1] != separator)) {
work[j++] = separator;
}
} else {
work[j++] = work[i];
@ -152,18 +167,15 @@ void utilFixPathSeparators(char **path, bool slash) {
}
work[j] = 0;
if (slash) {
// Does this string end with a path separator?
if (work[strlen(work) - 1] != utilGetPathSeparator()) {
// No - append one.
temp = strdup(work);
free(work);
work = malloc(sizeof(char) * (strlen(temp) + 2));
strcpy(work, temp);
work[strlen(temp)] = utilGetPathSeparator();
work[strlen(temp) + 1] = 0;
free(temp);
// Does this string need a trailing separator?
if (slash && ((j == 0) || (work[j - 1] != separator))) {
grown = realloc(work, j + 2);
if (!grown) {
utilDie("Unable to allocate memory for path.");
}
work = grown;
work[j] = separator;
work[j + 1] = 0;
}
*path = work;
@ -175,39 +187,33 @@ bool utilGetConsoleEnabled(void) {
}
char *utilGetFileExtension(char *filename) {
char *start = filename + strlen(filename);
int32_t x;
char *utilGetFileExtension(const char *filename) {
const char *end = filename + strlen(filename);
const char *last = utilGetLastPathComponent(filename);
const char *dot = strrchr(last, '.');
// Scan through name and find the last '.'
for (x=0; x<(int32_t)strlen(filename); x++) {
if (filename[x] == '.') {
start = &filename[x + 1];
}
// Reset if we find a path separator
if (filename[x] == '\\' || filename[x] == '/') {
start = filename + strlen(filename);
}
// No '.' in the last path component means no extension.
if (dot == NULL) {
return (char *)end;
}
return start;
return (char *)(dot + 1);
}
char *utilGetLastPathComponent(char *pathname) {
static char *start;
int32_t x;
char *utilGetLastPathComponent(const char *pathname) {
const char *start = pathname;
const char *slash = strrchr(pathname, '/');
const char *backslash = strrchr(pathname, '\\');
start = pathname;
// Scan through name and find the last path separator
for (x=0; x<(int32_t)strlen(pathname); x++) {
if (pathname[x] == '\\' || pathname[x] == '/') {
start = &pathname[x + 1];
}
if (slash != NULL) {
start = slash + 1;
}
if ((backslash != NULL) && (backslash + 1 > start)) {
start = backslash + 1;
}
return start;
return (char *)start;
}
@ -220,21 +226,17 @@ char utilGetPathSeparator(void) {
}
char *utilGetUpToLastPathComponent(char *pathname) {
static char *copy = NULL;
bool dumb = false; // Using (copy == NULL) below didn't work after optimizations, so enter the dummy.
int32_t x;
// Returns a new string the caller must free. Always ends in a path separator.
char *utilGetUpToLastPathComponent(const char *pathname) {
size_t length = strlen(pathname) - strlen(utilGetLastPathComponent(pathname));
char *copy = NULL;
x = (int32_t)(strlen(pathname) - strlen(utilGetLastPathComponent(pathname))) - 1;
if (x < 0) x = 0;
if (dumb) {
free(copy);
copy = NULL;
if (length == 0) {
// No directory part - use the current directory.
copy = strdup(".");
} else {
dumb = true;
copy = utilStrndup(pathname, length);
}
copy = strdup(pathname);
copy[x] = 0;
utilFixPathSeparators(&copy, true);
return copy;
@ -242,91 +244,74 @@ char *utilGetUpToLastPathComponent(char *pathname) {
bool utilMkDirP(const char *dir, const mode_t mode) {
char tmp[UTIL_PATH_MAX];
char *p = NULL;
struct stat sb;
size_t len;
const char separator = utilGetPathSeparator();
char tmp[UTIL_PATH_MAX];
char *p = NULL;
size_t len;
// Make copy of dir.
len = strnlen(dir, UTIL_PATH_MAX);
if (len == 0 || len == UTIL_PATH_MAX) {
return -1;
return false;
}
memcpy(tmp, dir, len);
tmp[len] = '\0';
tmp[len] = 0;
// Remove trailing slash.
if (tmp[len - 1] == utilGetPathSeparator()) {
tmp[len - 1] = '\0';
}
// Does it already exist?
if (stat(tmp, &sb) == 0) {
if (S_ISDIR (sb.st_mode)) {
return true;
}
if (tmp[len - 1] == separator) {
tmp[len - 1] = 0;
}
// Recursive mkdir.
for (p = tmp + 1; *p; p++) {
if (*p == utilGetPathSeparator()) {
if (*p == separator) {
*p = 0;
if (stat(tmp, &sb) != 0) {
// Does not exist - create it.
if (ourMkdir(tmp, mode) < 0) {
return false;
}
} else {
if (!S_ISDIR(sb.st_mode)) {
// Not a directory
return false;
}
if (!_ensureDirectory(tmp, mode)) {
return false;
}
*p = utilGetPathSeparator();
}
}
// Check path
if (stat(tmp, &sb) != 0) {
// Does not exist - create it.
if (ourMkdir(tmp, mode) < 0) {
return false;
}
} else {
if (!S_ISDIR(sb.st_mode)) {
// Not a directory
return false;
*p = separator;
}
}
return true;
return _ensureDirectory(tmp, mode);
}
bool utilPathExists(char *pathname) {
DIR *dir = opendir(pathname);
if (dir) {
closedir(dir);
return true;
}
return false;
void utilNewline(void) {
utilSay("%s", "");
}
char *utilReadFile(char *filename, size_t *bytes) {
char *data = NULL;
FILE *in = fopen(filename, "rb");
size_t read = 0;
bool utilPathExists(const char *pathname) {
struct stat sb;
(void)read;
if (stat(pathname, &sb) != 0) {
return false;
}
return S_ISDIR(sb.st_mode);
}
// Returns a new NUL terminated buffer the caller must free, or NULL.
char *utilReadFile(const char *filename, size_t *bytes) {
char *data = NULL;
FILE *in = fopen(filename, "rb");
long size = 0;
*bytes = 0;
if (in) {
fseek(in, 0, SEEK_END);
*bytes = ftell(in);
size = ftell(in);
fseek(in, 0, SEEK_SET);
data = malloc(sizeof(char) * (*bytes));
read = fread(data, sizeof(char), *bytes, in);
if (size >= 0) {
data = malloc((size_t)size + 1);
if (data) {
*bytes = fread(data, 1, (size_t)size, in);
data[*bytes] = 0;
}
}
fclose(in);
}
@ -334,123 +319,91 @@ char *utilReadFile(char *filename, size_t *bytes) {
}
char *utilReadLine(char *haystack, size_t length, char **offset) {
size_t bytes = 0;
char *temp = *offset;
char *tail = temp;
char *result = NULL;
// Returns a new string the caller must free, or NULL when no data remains.
char *utilReadLine(const char *haystack, size_t length, const char **offset) {
const char *end = haystack + length;
const char *start = *offset;
const char *tail = NULL;
char *result = NULL;
// They didn't know where to start
if (temp == NULL) {
temp = haystack;
tail = temp;
// They didn't know where to start.
if (start == NULL) {
start = haystack;
}
tail = start;
// Is there still data to read?
while ((size_t)(tail - haystack) < length) {
// Is this the end of a line?
if ((tail[0] == 10) || (tail[0] == 13)) {
// Yep!
bytes = tail - temp + 1;
result = malloc(sizeof(char) * bytes);
memcpy(result, temp, bytes - 1);
result[bytes - 1] = 0;
// Read past any additional CR/LFs
while ((tail[0] == 10) || (tail[0] == 13)) {
tail++;
}
// Return where we left off
temp = tail;
*offset = temp;
return result;
}
// Next character
// Find the end of the line or the end of the data.
while ((tail < end) && (*tail != '\n') && (*tail != '\r')) {
tail++;
}
// Was there data at the end of the block with no CR/LF?
if (tail > temp) {
// Yep. Treat it as a line.
bytes = tail - temp + 1;
result = malloc(sizeof(char) * bytes);
memcpy(result, temp, bytes - 1);
result[bytes - 1] = 0;
temp = tail;
*offset = temp;
// Was there anything on this line?
if (tail > start) {
result = utilStrndup(start, (size_t)(tail - start));
}
// Didn't find anything
// Read past any CR/LFs.
while ((tail < end) && ((*tail == '\n') || (*tail == '\r'))) {
tail++;
}
// Return where we left off.
*offset = tail;
return result;
}
void utilRedirectConsole(void) {
#ifdef _WIN32
// http://dslweb.nwnexus.com/~ast/dload/guicon.htm
int hConHandle; // Not changing this int.
intptr_t lStdHandle;
CONSOLE_SCREEN_BUFFER_INFO coninfo;
FILE *fp;
static bool consoleOpen = false;
if (!consoleOpen) {
if (_consoleEnabled && !consoleOpen) {
consoleOpen = true;
// allocate a console for this app
// Allocate a console for this app.
AllocConsole();
// set the screen buffer to be big enough to let us scroll text
// Set the screen buffer to be big enough to let us scroll text.
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
coninfo.dwSize.Y = CONSOLE_LINES;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
// redirect unbuffered STDOUT to the console
lStdHandle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen(hConHandle, "w");
*stdout = *fp;
// Redirect unbuffered STDOUT, STDIN, and STDERR to the console.
freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0);
// redirect unbuffered STDIN to the console
lStdHandle = (intptr_t)GetStdHandle(STD_INPUT_HANDLE);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen(hConHandle, "r");
*stdin = *fp;
freopen("CONIN$", "r", stdin);
setvbuf(stdin, NULL, _IONBF, 0);
// redirect unbuffered STDERR to the console
lStdHandle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen(hConHandle, "w");
*stderr = *fp;
freopen("CONOUT$", "w", stderr);
setvbuf(stderr, NULL, _IONBF, 0);
}
#endif
}
__attribute__((__format__(__printf__, 1, 0)))
void utilSay(char *fmt, ...) {
void utilSay(const char *fmt, ...) {
va_list args;
if (_consoleEnabled) {
va_start(args, fmt);
vfprintf(stdout, fmt, args);
_printLine(stdout, fmt, args);
va_end(args);
printf("\n");
fflush(stdout);
_outputHappened = true;
}
}
bool utilStartsWith(char *string, char *start) {
bool utilStartsWith(const char *string, const char *start) {
return strncmp(start, string, strlen(start)) == 0;
}
int utilStricmp(char *a, char *b) {
int32_t utilStricmp(const char *a, const char *b) {
int32_t d = 0;
for (;; a++, b++) {
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
if (d != 0 || !*a) {
return d;
}
@ -459,16 +412,23 @@ int utilStricmp(char *a, char *b) {
// Windows does not have strndup() so we implement our own.
char *utilStrndup( const char *s1, size_t n) {
char *copy = (char *)malloc(n + 1);
memcpy(copy, s1, n);
copy[n] = 0;
char *utilStrndup(const char *s1, size_t n) {
size_t length = strnlen(s1, n);
char *copy = malloc(length + 1);
if (!copy) {
utilDie("Unable to allocate memory for string.");
}
memcpy(copy, s1, length);
copy[length] = 0;
return copy;
}
void utilTrace(char *fmt, ...) {
void utilTrace(const char *fmt, ...) {
va_list args;
if (_utilTraceFile) {
va_start(args, fmt);
utilTraceVArgs(fmt, args);
@ -478,36 +438,38 @@ void utilTrace(char *fmt, ...) {
void utilTraceEnd(void) {
if (_utilTraceFile) fclose(_utilTraceFile);
}
FILE *utilTraceGetFile(void) {
return _utilTraceFile;
}
void utilTraceStart(char *filename) {
_utilTraceFile = fopen(filename, "wt");
if (!_utilTraceFile) utilDie("Unable to create trace file: %s", filename);
}
__attribute__((__format__(__printf__, 1, 0)))
void utilTraceVArgs(char *fmt, va_list args) {
#if defined(va_copy)
va_list args2;
#endif
if (_utilTraceFile) {
#if defined(va_copy)
va_copy(args2, args);
vprintf(fmt, args2);
printf("\n");
va_end(args2);
#endif
vfprintf(_utilTraceFile, fmt, args);
fprintf(_utilTraceFile, "\n");
fflush(_utilTraceFile);
fclose(_utilTraceFile);
_utilTraceFile = NULL;
}
}
void utilTraceStart(const char *filename) {
utilTraceEnd();
_utilTraceFile = fopen(filename, "w");
if (!_utilTraceFile) {
utilDie("Unable to create trace file: %s", filename);
}
}
void utilTraceVArgs(const char *fmt, va_list args) {
va_list argsCopy;
if (_utilTraceFile) {
if (_consoleEnabled) {
va_copy(argsCopy, args);
_printLine(stdout, fmt, argsCopy);
va_end(argsCopy);
}
_printLine(_utilTraceFile, fmt, args);
}
}
void utilWaitForKeyOnWindows(void) {
#ifdef _WIN32
getchar();
#endif
}

View file

@ -36,31 +36,33 @@
#define UTIL_PATH_MAX 1024
bool utilChMod(const char *path, const mode_t mode);
char *utilCreateString(char *format, ...);
char *utilCreateStringVArgs(char *format, va_list args);
void utilDie(char *fmt, ...);
void utilEnableConsole(bool enable);
bool utilFileExists(char *filename);
void utilFixPathSeparators(char **path, bool slash);
bool utilGetConsoleEnabled(void);
char *utilGetFileExtension(char *filename);
char *utilGetLastPathComponent(char *pathname);
char utilGetPathSeparator(void);
char *utilGetUpToLastPathComponent(char *pathname);
bool utilMkDirP(const char *dir, const mode_t mode);
bool utilPathExists(char *pathname);
char *utilReadFile(char *filename, size_t *bytes);
char *utilReadLine(char *haystack, size_t length, char **offset);
void utilRedirectConsole(void);
void utilSay(char *fmt, ...);
bool utilStartsWith(char *string, char *start);
int utilStricmp(char *a, char *b);
char *utilStrndup( const char *s1, size_t n);
void utilTrace(char *fmt, ...);
void utilTraceEnd(void);
FILE *utilTraceGetFile(void);
void utilTraceStart(char *filename);
void utilTraceVArgs(char *fmt, va_list args);
bool utilChMod(const char *path, const mode_t mode);
char *utilCreateString(const char *format, ...) __attribute__((format(printf, 1, 2)));
char *utilCreateStringVArgs(const char *format, va_list args) __attribute__((format(printf, 1, 0)));
void utilDie(const char *fmt, ...) __attribute__((format(printf, 1, 2))) __attribute__((noreturn));
void utilEnableConsole(bool enable);
bool utilFileExists(const char *filename);
void utilFixPathSeparators(char **path, bool slash);
bool utilGetConsoleEnabled(void);
char *utilGetFileExtension(const char *filename);
char *utilGetLastPathComponent(const char *pathname);
char utilGetPathSeparator(void);
char *utilGetUpToLastPathComponent(const char *pathname);
bool utilMkDirP(const char *dir, const mode_t mode);
void utilNewline(void);
bool utilPathExists(const char *pathname);
char *utilReadFile(const char *filename, size_t *bytes);
char *utilReadLine(const char *haystack, size_t length, const char **offset);
void utilRedirectConsole(void);
void utilSay(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
bool utilStartsWith(const char *string, const char *start);
int32_t utilStricmp(const char *a, const char *b);
char *utilStrndup(const char *s1, size_t n);
void utilTrace(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
void utilTraceEnd(void);
void utilTraceStart(const char *filename);
void utilTraceVArgs(const char *fmt, va_list args) __attribute__((format(printf, 1, 0)));
void utilWaitForKeyOnWindows(void);
#endif // UTIL_H

38
src/version.h.in Normal file
View file

@ -0,0 +1,38 @@
/*
*
* Singe 2
* Copyright (C) 2006-@SINGE_COPYRIGHT_END_YEAR@ Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*/
#ifndef VERSION_H
#define VERSION_H
// ===== THIS FILE IS AUTOMATICALLY GENERATED FROM version.h.in - DO NOT EDIT =====
// The version number lives in the project() line of CMakeLists.txt.
#define SINGE_VERSION @PROJECT_VERSION@
#define SINGE_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define SINGE_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define VERSION_STRING "v@PROJECT_VERSION@"
#define COPYRIGHT_END_YEAR "@SINGE_COPYRIGHT_END_YEAR@"
#endif // VERSION_H

File diff suppressed because it is too large Load diff

View file

@ -30,31 +30,35 @@
#include "common.h"
typedef void (*videoIndexingCallback)(int32_t);
#define VIDEO_VOLUME_MAX 100
int32_t videoInit(void);
int32_t videoIsPlaying(int32_t playerHandle);
int32_t videoGetAudioTrack(int32_t playerHandle);
int32_t videoGetAudioTracks(int32_t playerHandle);
int64_t videoGetFrame(int32_t playerHandle);
int64_t videoGetFrameCount(int32_t playerHandle);
int32_t videoGetHeight(int32_t playerHandle);
char *videoGetLanguage(int32_t playerHandle, int32_t audioTrack);
char *videoGetLanguageDescription(char *languageCode);
int32_t videoGetWidth(int32_t playerHandle);
int32_t videoGetVolume(int32_t playerHandle, int32_t *leftPercent, int32_t *rightPercent);
int32_t videoLoad(char *filename, char *indexPath, bool stretchVideo, SDL_Renderer *renderer);
int32_t videoLoadWithAudio(char *vFilename, char *aFilename, char *indexPath, bool stretchVideo, SDL_Renderer *renderer);
int32_t videoPause(int32_t playerHandle);
int32_t videoPlay(int32_t playerHandle);
int32_t videoQuit(void);
int32_t videoSeek(int32_t playerHandle, int64_t seekFrame);
int32_t videoSetAudioTrack(int32_t playerHandle, int32_t track);
int32_t videoSetIndexCallback(videoIndexingCallback callback);
int32_t videoSetVolume(int32_t playerHandle, int32_t leftPercent, int32_t rightPercent);
int32_t videoUnload(int32_t playerHandle);
int32_t videoUpdate(int32_t playerHandle, SDL_Texture **texture);
typedef void (*VideoIndexingCallbackT)(int32_t percent);
int32_t videoGetAudioTrack(int32_t playerHandle);
int32_t videoGetAudioTracks(int32_t playerHandle);
int64_t videoGetFrame(int32_t playerHandle);
int64_t videoGetFrameCount(int32_t playerHandle);
int32_t videoGetHeight(int32_t playerHandle);
const char *videoGetLanguage(int32_t playerHandle, int32_t audioTrack);
const char *videoGetLanguageDescription(const char *languageCode);
bool videoGetPixel(int32_t playerHandle, int32_t x, int32_t y, uint8_t *r, uint8_t *g, uint8_t *b);
bool videoGetPixels(int32_t playerHandle, const uint8_t **pixels, int32_t *pitch);
void videoGetVolume(int32_t playerHandle, int32_t *leftPercent, int32_t *rightPercent);
int32_t videoGetWidth(int32_t playerHandle);
void videoInit(int32_t mixerChunkFrames);
bool videoIsPlaying(int32_t playerHandle);
int32_t videoLoad(const char *videoFilename, const char *audioFilename, const char *indexPath, SDL_Renderer *renderer);
void videoPause(int32_t playerHandle);
void videoPlay(int32_t playerHandle);
void videoQuit(void);
void videoSeek(int32_t playerHandle, int64_t seekFrame);
void videoSetAudioTrack(int32_t playerHandle, int32_t track);
void videoSetIndexCallback(VideoIndexingCallbackT callback);
void videoSetVolume(int32_t playerHandle, int32_t leftPercent, int32_t rightPercent);
void videoUnload(int32_t playerHandle);
int64_t videoUpdate(int32_t playerHandle, SDL_Texture **texture);
#endif // VIDEOPLAYER_H

View file

@ -5,7 +5,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@ -15,7 +15,8 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*
--]]
@ -28,25 +29,20 @@ local settings
local line = "-------------------------------------------------------------------------------"
local function utilDump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then
k = '"'..k..'"'
end
s = s .. '['..k..'] = ' .. utilDump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
-- Escapes a string for use in string.gsub patterns.
local function escapePattern(text)
return (text:gsub("%W", "%%%0"))
end
-- Wraps a path in quotes so spaces survive the shell.
local function quote(path)
return '"' .. path .. '"'
end
local function configureSinge(self)
UI = {}
local UI = {}
UI.dlgSinge = wx.wxDialog (wx.NULL, wx.wxID_ANY, "Singe Configuration", wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxDEFAULT_DIALOG_STYLE )
UI.dlgSinge:SetSizeHints( wx.wxSize( 500,-1 ), wx.wxDefaultSize )
@ -116,12 +112,11 @@ local function configureSinge(self)
UI.filePickScript:GetTextCtrl():Connect( wx.wxEVT_KEY_DOWN, function(event) end )
UI.filePickVideo:GetTextCtrl():Connect( wx.wxEVT_KEY_DOWN, function(event) end )
if settings then
if settings.singe then UI.filePickSinge:GetTextCtrl():SetValue(settings.singe) end
if settings.script then UI.filePickScript:GetTextCtrl():SetValue(settings.script) end
if settings.video then UI.filePickVideo:GetTextCtrl():SetValue(settings.video) end
if settings.options then UI.txtOptions:SetValue(settings.options) end
end
settings = settings or {}
if settings.singe then UI.filePickSinge:GetTextCtrl():SetValue(settings.singe) end
if settings.script then UI.filePickScript:GetTextCtrl():SetValue(settings.script) end
if settings.video then UI.filePickVideo:GetTextCtrl():SetValue(settings.video) end
if settings.options then UI.txtOptions:SetValue(settings.options) end
UI.m_sdbSizer1Cancel:Connect( wx.wxEVT_COMMAND_BUTTON_CLICKED, function(event)
event:Skip()
@ -161,10 +156,11 @@ local function startSinge(self)
local env
local ok
-- File pickers leave empty strings behind, so test for those as well as nil.
if settings then
if not settings.singe then message = "You must specify the Singe executable in the Singe configuration." end
if not settings.script then message = "You must specify the script to run in the Singe configuration." end
if not settings.video then message = "You must specify the video to use in the Singe configuration." end
if not settings.singe or settings.singe == "" then message = "You must specify the Singe executable in the Singe configuration." end
if not settings.script or settings.script == "" then message = "You must specify the script to run in the Singe configuration." end
if not settings.video or settings.video == "" then message = "You must specify the video to use in the Singe configuration." end
else
message = "Please configure Singe first."
end
@ -174,17 +170,21 @@ local function startSinge(self)
return
end
-- Run from the Singe directory with paths made relative to it.
wdir = settings.singe:match("(.*[/\\])") or "./"
launch = settings.singe .. ' ' .. settings.options .. ' -v ' .. settings.video .. ' ' .. settings.script
launch = launch:gsub(wdir, "./", 1):gsub(wdir, "")
launch = quote(settings.singe) .. ' ' .. (settings.options or "") .. ' -v ' .. quote(settings.video) .. ' ' .. quote(settings.script)
launch = launch:gsub(escapePattern(wdir), "./", 1):gsub(escapePattern(wdir), "")
wdir = wx.wxFileName.DirName(wdir):GetFullPath()
ide:Print(line)
ide:Print(launch)
-- ZeroBrane's own LUA_CPATH would leak into Singe's embedded Lua.
ok, env = wx.wxGetEnv('LUA_CPATH')
wx.wxUnsetEnv('LUA_CPATH')
ide:ExecuteCommand(launch, wdir, singeOutput, singeEnd)
wx.wxSetEnv('LUA_CPATH', env)
if ok then
wx.wxSetEnv('LUA_CPATH', env)
end
end
@ -192,7 +192,7 @@ return {
name = "Singe Integration",
description = "Adds menu items and a toolbar button to assist with debugging Singe games.",
author = "Scott Duensing",
version = 1.00,
version = 1.01,
dependencies = "1.0",
onRegister = function(self)

View file

@ -1,3 +1,26 @@
--[[
*
* Singe 2
* Copyright (C) 2006-2024 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*
--]]
editor.specmap.singe = 'lua'
editor.tabwidth = 4