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 *.exe filter=lfs diff=lfs merge=lfs -text
*.dll filter=lfs diff=lfs merge=lfs -text *.dll filter=lfs diff=lfs merge=lfs -text
*.dat 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 *.ttf filter=lfs diff=lfs merge=lfs -text
# #

11
.gitignore vendored
View file

@ -1,9 +1,18 @@
*~ *~
**/.builddir/ **/.builddir/
**/.git-hidden/ **/.git-hidden/
**/.github/ thirdparty/**/.github/
cmake-build-debug/ cmake-build-debug/
*.log *.log
*.user *.user
temp/ 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 SINGE 2.10
========== ==========
@ -8,8 +126,8 @@ New Features
------------ ------------
- spriteDraw() now has two more forms. In addition to being able to draw - 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 regular sprites and stretched sprites it can now draw both using the
sprite's center as the anchor instead of the upper right. This is highly sprite's center as the anchor instead of the upper left. This is highly
useful when dealing with rotated sprites. useful when dealing with rotated sprites.
- Animated sprites! Both animated GIF and WEBP images are supported. - 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 directly into the script context. Now there is a proper Lua module search
handler. Scripts can properly require() modules from the following: handler. Scripts can properly require() modules from the following:
- Lua Standard Library - Lua Standard Library
- Lua Auxillary Library - Lua Auxiliary Library
- LuaFileSystem - LuaFileSystem
- LuaSocket - LuaSocket
- LuaSec - LuaSec

View file

@ -20,8 +20,13 @@
cmake_minimum_required(VERSION 3.22) cmake_minimum_required(VERSION 3.22)
set(CMAKE_CXX_STANDARD 17) project(singe2 VERSION 2.20 LANGUAGES C)
project(singe2)
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) if(NOT DEFINED KANGAROO_OS)
@ -31,201 +36,283 @@ if(NOT DEFINED KANGAROO_ARCH)
set(KANGAROO_ARCH x86_64) set(KANGAROO_ARCH x86_64)
endif() 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(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 set(SINGE_SOURCE
src/common.h
src/embedded.h
src/frameFile.c
src/frameFile.h
src/main.c
src/main.h src/main.h
src/singe.c src/singe.c
src/singe.h 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/stddclmr.h
src/frameFile.c
src/videoPlayer.h
src/util.c src/util.c
src/util.h
src/videoPlayer.c
src/videoPlayer.h
) )
set(ARG_PARSER_SOURCE set(ARG_PARSER_SOURCE
thirdparty/arg_parser/carg_parser.c thirdparty/arg_parser/carg_parser.c
thirdparty/arg_parser/carg_parser.h thirdparty/arg_parser/carg_parser.h
) )
file(GLOB LUA_SOURCE thirdparty/lua/src/*.c thirdparty/lua/src/*.h)
set(JBIG_SOURCE list(FILTER LUA_SOURCE EXCLUDE REGEX "/(lua|luac|onelua)\\.c$")
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
)
set(LUA_FILESYSTEM_SOURCE set(LUA_FILESYSTEM_SOURCE
thirdparty/luafilesystem/src/lfs.c thirdparty/luafilesystem/src/lfs.c
thirdparty/luafilesystem/src/lfs.h thirdparty/luafilesystem/src/lfs.h
) )
set(LUA_SOCKET_SOURCE 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/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/compat.c
thirdparty/luasocket/src/options.h thirdparty/luasocket/src/compat.h
thirdparty/luasocket/src/options.c
thirdparty/luasocket/src/inet.h
thirdparty/luasocket/src/inet.c
thirdparty/luasocket/src/except.h
thirdparty/luasocket/src/except.c thirdparty/luasocket/src/except.c
thirdparty/luasocket/src/select.h thirdparty/luasocket/src/except.h
thirdparty/luasocket/src/select.c thirdparty/luasocket/src/inet.c
thirdparty/luasocket/src/tcp.h thirdparty/luasocket/src/inet.h
thirdparty/luasocket/src/tcp.c thirdparty/luasocket/src/io.c
thirdparty/luasocket/src/udp.h thirdparty/luasocket/src/io.h
thirdparty/luasocket/src/udp.c thirdparty/luasocket/src/luasocket.c
thirdparty/luasocket/src/mime.h thirdparty/luasocket/src/luasocket.h
thirdparty/luasocket/src/mime.c 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/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) if(WIN32)
set(LUA_SOCKET_SOURCE list(APPEND LUA_SOCKET_SOURCE
${LUA_SOCKET_SOURCE}
thirdparty/luasocket/src/wsocket.h
thirdparty/luasocket/src/wsocket.c thirdparty/luasocket/src/wsocket.c
thirdparty/luasocket/src/wsocket.h
) )
ELSE() else()
set(LUA_SOCKET_SOURCE list(APPEND LUA_SOCKET_SOURCE
${LUA_SOCKET_SOURCE} thirdparty/luasocket/src/serial.c
thirdparty/luasocket/src/usocket.h
thirdparty/luasocket/src/usocket.c
thirdparty/luasocket/src/unix.h
thirdparty/luasocket/src/unix.c thirdparty/luasocket/src/unix.c
thirdparty/luasocket/src/unixstream.h thirdparty/luasocket/src/unix.h
thirdparty/luasocket/src/unixstream.c
thirdparty/luasocket/src/unixdgram.c thirdparty/luasocket/src/unixdgram.c
thirdparty/luasocket/src/unixdgram.h 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 set(LUASEC_SOURCE
thirdparty/luasec/src/compat.h ${GENERATED_DIR}/luasec_options.c
thirdparty/luasec/src/config.c thirdparty/luasec/src/compat.h
${BUILD_DIR}/generated/luasec_options.c thirdparty/luasec/src/config.c
thirdparty/luasec/src/context.c thirdparty/luasec/src/context.c
thirdparty/luasec/src/context.h thirdparty/luasec/src/context.h
thirdparty/luasec/src/ec.c thirdparty/luasec/src/ec.c
thirdparty/luasec/src/ec.h thirdparty/luasec/src/ec.h
thirdparty/luasec/src/options.h thirdparty/luasec/src/options.h
thirdparty/luasec/src/ssl.c thirdparty/luasec/src/ssl.c
thirdparty/luasec/src/ssl.h thirdparty/luasec/src/ssl.h
thirdparty/luasec/src/x509.c thirdparty/luasec/src/x509.c
thirdparty/luasec/src/x509.h thirdparty/luasec/src/x509.h
) )
set(LUA_RS232_SOURCE set(LUA_RS232_SOURCE
thirdparty/librs232/bindings/lua/luars232.c thirdparty/librs232/bindings/lua/luars232.c
thirdparty/librs232/src/rs232.c thirdparty/librs232/src/rs232.c
) )
if(WIN32) if(WIN32)
set(LUA_RS232_SOURCE list(APPEND LUA_RS232_SOURCE thirdparty/librs232/src/rs232_windows.c)
${LUA_RS232_SOURCE} else()
thirdparty/librs232/src/rs232_windows.c list(APPEND LUA_RS232_SOURCE thirdparty/librs232/src/rs232_posix.c)
) endif()
ELSE()
set(LUA_RS232_SOURCE
${LUA_RS232_SOURCE}
thirdparty/librs232/src/rs232_posix.c
)
ENDIF()
set(UTHASH_SOURCE set(UTHASH_SOURCE
thirdparty/uthash/src/uthash.h thirdparty/uthash/src/uthash.h
thirdparty/uthash/src/utlist.h thirdparty/uthash/src/utlist.h
) )
set(MANYMOUSE_SOURCE set(MANYMOUSE_SOURCE
thirdparty/manymouse/linux_evdev.c thirdparty/manymouse/linux_evdev.c
thirdparty/manymouse/macosx_hidmanager.c thirdparty/manymouse/macosx_hidmanager.c
@ -236,17 +323,19 @@ set(MANYMOUSE_SOURCE
thirdparty/manymouse/x11_xinput2.c thirdparty/manymouse/x11_xinput2.c
) )
set(SDL2_GFX_SOURCE set(SDL2_GFX_SOURCE
thirdparty/SDL2_gfx/SDL2_rotozoom.h thirdparty/SDL2_gfx/SDL2_rotozoom.h
thirdparty/SDL2_gfx/SDL2_rotozoom.c thirdparty/SDL2_gfx/SDL2_rotozoom.c
) )
# ===== Target =====
add_executable(${CMAKE_PROJECT_NAME} add_executable(${CMAKE_PROJECT_NAME}
${SINGE_SOURCE} ${SINGE_SOURCE}
${EMBEDDED_HEADERS}
${GENERATED_DIR}/version.h
${ARG_PARSER_SOURCE} ${ARG_PARSER_SOURCE}
${JBIG_SOURCE}
${LUA_SOURCE} ${LUA_SOURCE}
${LUA_FILESYSTEM_SOURCE} ${LUA_FILESYSTEM_SOURCE}
${LUA_SOCKET_SOURCE} ${LUA_SOCKET_SOURCE}
@ -256,36 +345,40 @@ add_executable(${CMAKE_PROJECT_NAME}
${MANYMOUSE_SOURCE} ${MANYMOUSE_SOURCE}
${SDL2_GFX_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) if(WIN32)
# Note: _WIN32_WINNT=0x0600 sets the minimum compatible version of enable_language(RC)
# Windows to Vista. The function inet_pton() does not exist before then. target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${GENERATED_DIR}/singe.rc ${GENERATED_DIR}/icon.ico)
set(DEFINE_LIST
-Dmain=SDL_main
-D_WIN32_WINNT=0x0600
)
else()
set(DEFINE_LIST)
endif() endif()
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC
${DEFINE_LIST} # Output name matches the release artifact: Singe-v2.20-Linux-x86_64
-DRS232_STATIC string(SUBSTRING ${KANGAROO_OS} 0 1 osInitial)
-DFFMS_STATIC 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 -Wno-deprecated-declarations
) )
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${CMAKE_BINARY_DIR}
${GENERATED_DIR}/include
${BUILD_DIR} ${BUILD_DIR}
${BUILD_DIR}/include ${BUILD_DIR}/include
${BUILD_DIR}/include/SDL2 ${BUILD_DIR}/include/SDL2
@ -294,30 +387,21 @@ target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
thirdparty/librs232/include thirdparty/librs232/include
) )
target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
target_link_directories(${CMAKE_PROJECT_NAME} PUBLIC
${BUILD_DIR}/lib ${BUILD_DIR}/lib
) )
if(${KANGAROO_OS} STREQUAL "linux") # ===== Libraries =====
set(LIB_LIST
-lX11 # Platform specific system libraries.
-lvdpau if(KANGAROO_OS STREQUAL "linux")
${BUILD_DIR}/lib/libjpeg.a # ffmpeg's vdpau hardware context is always compiled in on X11 hosts.
${BUILD_DIR}/lib/libpng.a set(SYSTEM_LIBS -lX11 -lvdpau)
-lstdc++ elseif(KANGAROO_OS STREQUAL "pi")
) set(SYSTEM_LIBS)
endif() elseif(KANGAROO_OS STREQUAL "macos")
if(${KANGAROO_OS} STREQUAL "pi") set(SYSTEM_LIBS
set(LIB_LIST
${BUILD_DIR}/lib/libjpeg.a
${BUILD_DIR}/lib/libpng.a
-lstdc++
)
endif()
if(${KANGAROO_OS} STREQUAL "macos")
set(LIB_LIST
-Wl,-framework,CoreVideo -Wl,-framework,CoreVideo
-Wl,-framework,Cocoa -Wl,-framework,Cocoa
-Wl,-framework,IOKit -Wl,-framework,IOKit
@ -335,9 +419,11 @@ if(${KANGAROO_OS} STREQUAL "macos")
-lc++ -lc++
${OSXCROSS_TARGET_DIR}/darwin/libclang_rt.osx.a ${OSXCROSS_TARGET_DIR}/darwin/libclang_rt.osx.a
) )
endif() if(NOT DEFINED OSXCROSS_TARGET_DIR)
if(${KANGAROO_OS} STREQUAL "windows") message(FATAL_ERROR "OSXCROSS_TARGET_DIR must be set for macOS builds.")
set(LIB_LIST endif()
elseif(KANGAROO_OS STREQUAL "windows")
set(SYSTEM_LIBS
-mwindows -mwindows
-static -static
-lmingw32 -lmingw32
@ -359,29 +445,22 @@ if(${KANGAROO_OS} STREQUAL "windows")
-lbcrypt -lbcrypt
-lssp -lssp
-lcrypt32 -lcrypt32
${BUILD_DIR}/lib/libjpeg.a
${BUILD_DIR}/lib/libpng.a
-lstdc++
) )
else()
message(FATAL_ERROR "Unknown KANGAROO_OS: ${KANGAROO_OS}")
endif() 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/libarchive.a
${BUILD_DIR}/lib/libavcodec.a ${BUILD_DIR}/lib/libavcodec.a
${BUILD_DIR}/lib/libavdevice.a ${BUILD_DIR}/lib/libavdevice.a
${BUILD_DIR}/lib/libavfilter.a ${BUILD_DIR}/lib/libavfilter.a
${BUILD_DIR}/lib/libavformat.a ${BUILD_DIR}/lib/libavformat.a
# ${BUILD_DIR}/lib/libavif.a
${BUILD_DIR}/lib/libavutil.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/libbz2_static.a
# ${BUILD_DIR}/lib/libdav1d.a
${BUILD_DIR}/lib/libffms2.a ${BUILD_DIR}/lib/libffms2.a
${BUILD_DIR}/lib/libfreetype.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/liblzma.a
${BUILD_DIR}/lib/libopus.a ${BUILD_DIR}/lib/libopus.a
${BUILD_DIR}/lib/libopusfile.a ${BUILD_DIR}/lib/libopusfile.a
@ -391,11 +470,9 @@ set(LIB_LIST
${BUILD_DIR}/lib/libSDL2_image.a ${BUILD_DIR}/lib/libSDL2_image.a
${BUILD_DIR}/lib/libSDL2main.a ${BUILD_DIR}/lib/libSDL2main.a
${BUILD_DIR}/lib/libSDL2_mixer.a ${BUILD_DIR}/lib/libSDL2_mixer.a
${BUILD_DIR}/lib/libSDL2_test.a
${BUILD_DIR}/lib/libSDL2_ttf.a ${BUILD_DIR}/lib/libSDL2_ttf.a
${BUILD_DIR}/lib/libswresample.a ${BUILD_DIR}/lib/libswresample.a
${BUILD_DIR}/lib/libswscale.a ${BUILD_DIR}/lib/libswscale.a
# ${BUILD_DIR}/lib/libtiff.a
${BUILD_DIR}/lib/libwavpack.a ${BUILD_DIR}/lib/libwavpack.a
${BUILD_DIR}/lib/libwebp.a ${BUILD_DIR}/lib/libwebp.a
${BUILD_DIR}/lib/libwebpdemux.a ${BUILD_DIR}/lib/libwebpdemux.a
@ -405,15 +482,21 @@ set(LIB_LIST
${BUILD_DIR}/lib/libcrypto.a ${BUILD_DIR}/lib/libcrypto.a
${BUILD_DIR}/lib/libssl.a ${BUILD_DIR}/lib/libssl.a
) )
target_link_libraries(${CMAKE_PROJECT_NAME} if(NOT KANGAROO_OS STREQUAL "macos")
# -Wl,--start-group list(APPEND STATIC_LIBS
${LIB_LIST} ${BUILD_DIR}/lib/libjpeg.a
${LIB_LIST} ${BUILD_DIR}/lib/libpng.a
${LIB_LIST} -lstdc++
# -Wl,--end-group )
-pthread endif()
-lm
) # 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}) 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! Welcome to Singe! The Somewhat Interactive Nostalgic Game Engine!
@ -38,30 +39,7 @@ the new files.
COMMAND LINE 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} 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
-a, --aspect=N:D force aspect ratio is extracted on first run.
-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

View file

@ -9,16 +9,23 @@ bzip2 bzip2-1.0.6 https://sourceware.org/bzip2
copas MIT https://lunarmodules.github.io/copas copas MIT https://lunarmodules.github.io/copas
ffmpeg LGPL-2.1 https://ffmpeg.org ffmpeg LGPL-2.1 https://ffmpeg.org
ffms2 GPL-3.0-only https://github.com/FFMS/ffms2 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 json.lua MIT https://github.com/rxi/json.lua
libarchive BSD-2-Clause https://libarchive.org 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 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 lua MIT https://www.lua.org
luafilesystem MIT https://lunarmodules.github.io/luafilesystem luafilesystem MIT https://lunarmodules.github.io/luafilesystem
luasec MIT https://github.com/lunarmodules/luasec luasec MIT https://github.com/lunarmodules/luasec
luasocket MIT https://lunarmodules.github.io/luasocket luasocket MIT https://lunarmodules.github.io/luasocket
manymouse Zlib https://icculus.org/manymouse manymouse Zlib https://icculus.org/manymouse
openssl Apache-2.0 https://www.openssl.org 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 Zlib https://www.libsdl.org
SDL2_gfx LGPL-2.1 https://github.com/ferzkopp/SDL_gfx SDL2_gfx LGPL-2.1 https://github.com/ferzkopp/SDL_gfx
SDL2_image Zlib https://www.libsdl.org 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 timerwheel.lua MIT https://tieske.github.io/timerwheel.lua
uthash BSD-1-Clause https://troydhanson.github.io/uthash uthash BSD-1-Clause https://troydhanson.github.io/uthash
vlc GPL-2.0 https://www.videolan.org/vlc 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 xz Public-Domain https://github.com/tukaani-project/xz
zlib Zlib https://zlib.net zlib Zlib https://zlib.net
zstd BSD-3-Clause https://facebook.github.io/zstd 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 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 if singeGetScriptPath ~= nil then
DIR = singeGetScriptPath():match("(.*[/\\])") or "./" DIR = singeGetScriptPath():match("(.*[/\\])") or "./"
end end
function utilDeepCopy(orig) function utilDeepCopy(original)
local orig_type = type(orig)
local copy local copy
if orig_type == 'table' then if type(original) == 'table' then
copy = {} copy = {}
for orig_key, orig_value in next, orig, nil do for key, value in next, original, nil do
copy[utilDeepCopy(orig_key)] = utilDeepCopy(orig_value) copy[utilDeepCopy(key)] = utilDeepCopy(value)
end end
setmetatable(copy, utilDeepCopy(getmetatable(orig))) setmetatable(copy, utilDeepCopy(getmetatable(original)))
else -- number, string, boolean, etc else -- number, string, boolean, etc
copy = orig copy = original
end end
return copy return copy
end end
@ -65,16 +67,26 @@ end
function utilGetTableSize(t) function utilGetTableSize(t)
local count = 0 local count = 0
for _, __ in pairs(t) do for _ in pairs(t) do
count = count + 1 count = count + 1
end end
return count 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 end
function utilTrim(s) function utilTrim(s)
return (s:gsub("^%s*(.-)%s*$", "%1")) return (s:gsub("^%s*(.-)%s*$", "%1"))
end end
@ -323,9 +335,6 @@ SCANCODE = {
AUDIOFASTFORWARD = { name = "AUDIOFASTFORWARD", value = 286 } AUDIOFASTFORWARD = { name = "AUDIOFASTFORWARD", value = 286 }
} }
SCANCODE_MIN = 4 -- Lowest value
SCANCODE_MAX = 286 -- Highest value, not the number of items in the table.
MODIFIER = { MODIFIER = {
NONE = { name = "NONE", value = 0x0000 }, NONE = { name = "NONE", value = 0x0000 },
LSHIFT = { name = "LSHIFT", value = 0x0001 }, LSHIFT = { name = "LSHIFT", value = 0x0001 },
@ -347,63 +356,41 @@ MODIFIER = {
GUI = { name = "GUI", value = 0x0400 + 0x0800 } GUI = { name = "GUI", value = 0x0400 + 0x0800 }
} }
GAMEPAD_0 = { -- Controller codes are built from the engine's layout: SINGE_GAMEPAD_BASE plus
AXIS_LEFT_X = { name = "AXIS_LEFT_X", value = 500 }, -- SINGE_GAMEPAD_STRIDE per controller; each axis takes SINGE_AXIS_STRIDE codes
AXIS_LEFT_X_L = { name = "AXIS_LEFT_X_L", value = 501 }, -- (axis, negative direction, positive direction) and buttons follow at
AXIS_LEFT_X_R = { name = "AXIS_LEFT_X_R", value = 502 }, -- SINGE_GAMEPAD_BUTTON_OFFSET in SDL's button order.
AXIS_LEFT_Y = { name = "AXIS_LEFT_Y", value = 503 }, local function gamepadAxisCodes(axis, name, negative, positive)
AXIS_LEFT_Y_U = { name = "AXIS_LEFT_Y_U", value = 504 }, local base = SINGE_GAMEPAD_BASE + axis * SINGE_AXIS_STRIDE
AXIS_LEFT_Y_D = { name = "AXIS_LEFT_Y_D", value = 505 }, return {
AXIS_RIGHT_X = { name = "AXIS_RIGHT_X", value = 506 }, { name, base }, { name .. "_" .. negative, base + 1 }, { name .. "_" .. positive, base + 2 }
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
end end
GAMEPAD_1_MIN = GAMEPAD_0_MAX + 100 local gamepadCodes = {}
GAMEPAD_1_MAX = GAMEPAD_0_MAX + 100 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 local gamepadButtons = {
GAMEPAD_2_MAX = GAMEPAD_0_MAX + 200 "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_0 = {}
GAMEPAD_3_MAX = GAMEPAD_0_MAX + 300 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_X = 0
GAMEPAD_AXIS_LEFT_Y = 1 GAMEPAD_AXIS_LEFT_Y = 1
@ -412,47 +399,57 @@ GAMEPAD_AXIS_RIGHT_Y = 3
GAMEPAD_AXIS_LEFT_TRIGGER = 4 GAMEPAD_AXIS_LEFT_TRIGGER = 4
GAMEPAD_AXIS_RIGHT_TRIGGER = 5 GAMEPAD_AXIS_RIGHT_TRIGGER = 5
MOUSE_0 = { -- Mouse codes: SINGE_MOUSE_BASE plus SINGE_MOUSE_STRIDE per mouse; buttons then wheel.
BUTTON_LEFT = { name = "BUTTON_LEFT", value = 1000 }, local mouseNames = { "BUTTON_LEFT", "BUTTON_RIGHT", "BUTTON_MIDDLE", "BUTTON_X1", "BUTTON_X2", "WHEEL_UP", "WHEEL_DOWN" }
BUTTON_RIGHT = { name = "BUTTON_RIGHT", value = 1001 }, MOUSE_0 = {}
BUTTON_MIDDLE = { name = "BUTTON_MIDDLE", value = 1002 }, for index, name in ipairs(mouseNames) do
BUTTON_X1 = { name = "BUTTON_X1", value = 1003 }, MOUSE_0[name] = { name = name, value = SINGE_MOUSE_BASE + index - 1 }
BUTTON_X2 = { name = "BUTTON_X2", value = 1004 }, end
WHEEL_UP = { name = "WHEEL_UP", value = 1005 }, MOUSE_1 = utilOffsetTable(MOUSE_0, SINGE_MOUSE_STRIDE)
WHEEL_DOWN = { name = "WHEEL_DOWN", value = 1006 } MOUSE_2 = utilOffsetTable(MOUSE_0, SINGE_MOUSE_STRIDE * 2)
} MOUSE_3 = utilOffsetTable(MOUSE_0, SINGE_MOUSE_STRIDE * 3)
MOUSE_0_MIN = 1000 -- Older names for engine functions that gained a setter.
MOUSE_0_MAX = 1006 if mouseSetEnabled ~= nil then
mouseEnable = function() mouseSetEnabled(true) end
MOUSE_1 = utilDeepCopy(MOUSE_0) mouseDisable = function() mouseSetEnabled(false) end
MOUSE_2 = utilDeepCopy(MOUSE_0) end
MOUSE_3 = utilDeepCopy(MOUSE_0) if singeSetPauseKeyEnabled ~= nil then
singeEnablePauseKey = function() singeSetPauseKeyEnabled(true) end
for key, value in pairs(MOUSE_0) do singeDisablePauseKey = function() singeSetPauseKeyEnabled(false) end
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
end end
MOUSE_1_MIN = MOUSE_0_MAX + 100 -- Singe 2.20 moved the sprite handle to the first argument. Games written for
MOUSE_1_MAX = MOUSE_0_MAX + 100 -- 2.10 can set SINGE_LEGACY_SPRITE_ARGS = true (or LEGACY_SPRITE_ARGS = true in
-- games.dat) to keep calling the old way.
MOUSE_2_MIN = MOUSE_0_MAX + 200 if SINGE_LEGACY_SPRITE_ARGS and spriteDraw ~= nil then
MOUSE_2_MAX = MOUSE_0_MAX + 200 local newSpriteDraw = spriteDraw
spriteDraw = function(...)
MOUSE_3_MIN = MOUSE_0_MAX + 300 local args = { ... }
MOUSE_3_MAX = MOUSE_0_MAX + 300 local id = table.remove(args)
return newSpriteDraw(id, table.unpack(args))
SWITCH_BUTTON4 = 21 end
SWITCH_TILT = 22 local newSpriteLoop = spriteLoop
SWITCH_GRAB = 23 spriteLoop = function(loop, id) return newSpriteLoop(id, loop) end
local newSpriteQuality = spriteQuality
MOUSE_SINGLE = 100 spriteQuality = function(smooth, id) return newSpriteQuality(id, smooth) end
MOUSE_MANY = 200 local newSpriteRotate = spriteRotate
spriteRotate = function(angle, id) return newSpriteRotate(id, angle) end
RENDER_SMOOTH = 1 local newSpriteRotateAndScale = spriteRotateAndScale
RENDER_PIXELATED = 0 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 if videoGetLanguageDescription ~= nil then
discGetLanguageDescription = videoGetLanguageDescription discGetLanguageDescription = videoGetLanguageDescription
@ -462,52 +459,6 @@ end
-- Singe 1.xx Features ------------------------------------------------------- -- 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 -- Make old random number calls still work
random = {} random = {}
random.new = math.random random.new = math.random
@ -524,12 +475,18 @@ end
if singeMain ~= nil then if singeMain ~= nil then
local singeThread = coroutine.create(singeMain)
onOverlayUpdate = function() onOverlayUpdate = function()
coroutine.resume(SINGE_SELF) local ok, err = coroutine.resume(singeThread)
return(OVERLAY_UPDATED) if not ok then
error(err, 0)
end
if coroutine.status(singeThread) == "dead" then
singeQuit()
end
return OVERLAY_UPDATED
end end
singeYield = coroutine.yield singeYield = coroutine.yield
SINGE_SELF = coroutine.create(singeMain)
end end

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -26,27 +26,29 @@
DEAD_ZONE = 15000 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_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_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_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_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_1P_START = { SCANCODE.MAIN_1, GAMEPAD_0.BUTTON_RIGHT_BUMPER }
INPUT_2P_START = { SCANCODE.MAIN_2 } INPUT_2P_START = { SCANCODE.MAIN_2 }
INPUT_ACTION_1 = { SCANCODE.SPACE, SCANCODE.LCTRL, GAMEPAD_0.BUTTON_A, MOUSE_0.BUTTON_RIGHT } 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_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_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_EASY = { SCANCODE.KP_DIVIDE }
INPUT_SKILL_MEDIUM = { SCANCODE.KP_MULTIPLY } INPUT_SKILL_MEDIUM = { SCANCODE.KP_MULTIPLY }
INPUT_SKILL_HARD = { SCANCODE.KP_MINUS } INPUT_SKILL_HARD = { SCANCODE.KP_MINUS }
INPUT_SERVICE = { SCANCODE.MAIN_9 } INPUT_SERVICE = { SCANCODE.MAIN_9 }
INPUT_TEST_MODE = { SCANCODE.F2 } INPUT_TEST_MODE = { SCANCODE.F2 }
INPUT_RESET_CPU = { SCANCODE.F3 } 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_QUIT = { SCANCODE.ESCAPE, SCANCODE.Q }
INPUT_PAUSE = { SCANCODE.P, GAMEPAD_0.BUTTON_START } INPUT_PAUSE = { SCANCODE.P, GAMEPAD_0.BUTTON_START }
INPUT_CONSOLE = { SCANCODE.GRAVE } INPUT_CONSOLE = { SCANCODE.GRAVE }
INPUT_ACTION_4 = { SCANCODE.RSHIFT, GAMEPAD_0.BUTTON_Y, MOUSE_0.BUTTON_X1 }
INPUT_TILT = { SCANCODE.T } INPUT_TILT = { SCANCODE.T }
INPUT_GRAB = { SCANCODE.G } INPUT_GRAB = { SCANCODE.G }

View file

@ -1,4 +1,4 @@
#!/bin/bash -x #!/bin/bash
# #
# Singe 2 # Singe 2
@ -40,9 +40,9 @@ function buildAll() {
G_GENERATED=${G_TARGET}/generated G_GENERATED=${G_TARGET}/generated
COMMON="-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${G_TARGET} -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}" COMMON="-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${G_TARGET} -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}"
export LDFLAGS="-L${G_TARGET}/lib ${LDFLAGS}" export LDFLAGS="-L${G_TARGET}/lib ${LDFLAGS:-}"
export CFLAGS="-I${G_TARGET}/include ${CFLAGS}" export CFLAGS="-I${G_TARGET}/include ${CFLAGS:-}"
export CXXFLAGS="-I${G_TARGET}/include ${CXXFLAGS}" export CXXFLAGS="-I${G_TARGET}/include ${CXXFLAGS:-}"
export LD_LIBRARY_PATH="${G_TARGET}/lib" export LD_LIBRARY_PATH="${G_TARGET}/lib"
export PKG_CONFIG_LIBDIR="${G_TARGET}/lib/pkgconfig" 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 sudo chroot ${SYSROOT} apt-get -y install libasound-dev libxi-dev libvdpau-dev
fi fi
if [[ 1 == 1 ]]; then
pushd thirdparty/bzip2 pushd thirdparty/bzip2
clearAndEnterBuild clearAndEnterBuild
cmake ${COMMON} \ cmake ${COMMON} \
@ -92,7 +91,6 @@ if [[ 1 == 1 ]]; then
../build/cmake ../build/cmake
make install make install
popd popd
fi
pushd thirdparty/SDL2 pushd thirdparty/SDL2
clearAndEnterBuild clearAndEnterBuild
@ -129,7 +127,7 @@ fi
-DWEBP_BUILD_WEBPMUX=off \ -DWEBP_BUILD_WEBPMUX=off \
-DWEBP_BUILD_EXTRAS=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 make
cp -f ../include/SDL_image.h "${G_TARGET}/include/SDL2/." cp -f ../include/SDL_image.h "${G_TARGET}/include/SDL2/."
cp -f libSDL2_image.a "${G_TARGET}/lib/." cp -f libSDL2_image.a "${G_TARGET}/lib/."
@ -244,15 +242,10 @@ fi
no-tests \ no-tests \
zlib \ zlib \
enable-zstd enable-zstd
make install make build_sw
make install_sw
popd 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 if [[ "${OS}" == "pi" ]]; then
# Hack to make ffmpeg compile. # Hack to make ffmpeg compile.
mkdir -p "${G_TARGET}/include/sys" mkdir -p "${G_TARGET}/include/sys"
@ -287,14 +280,8 @@ fi
popd popd
pushd thirdparty/ffms2 pushd thirdparty/ffms2
#libtoolize --force # The configure script is not checked in; generate it once.
#aclocal [[ -x configure ]] || NOCONFIGURE=1 ./autogen.sh
#autoheader
#automake --force-missing --add-missing
#autoupdate
#autoconf
#./autogen.sh
#make distclean || true
clearAndEnterBuild clearAndEnterBuild
../configure \ ../configure \
--prefix=${G_TARGET} \ --prefix=${G_TARGET} \
@ -307,97 +294,14 @@ fi
make install-libLTLIBRARIES # This weird target prevents building the command line tools. make install-libLTLIBRARIES # This weird target prevents building the command line tools.
popd popd
# === Known FFMPEG Types === # Embedded resources, the version header, and the manual are generated by CMake.
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
pushd ${G_TARGET} pushd ${G_TARGET}
clearAndEnterBuild clearAndEnterBuild
cmake ${COMMON} ${G_BUILDROOT} cmake ${COMMON} -DKANGAROO_OS=${OS} -DKANGAROO_ARCH=${ARCH} ${G_BUILDROOT}
make make
#upx -9 --force singe2${SUFFIX} # CMake names the binary Singe-v<version>-<Os>-<arch>.
mv -f singe2${SUFFIX} ${G_BUILDROOT}/${G_BUILDDIR}/Singe-v2.10-${OS^}-${ARCH}${SUFFIX} mv -f Singe-v*${SUFFIX} ${G_BUILDROOT}/${G_BUILDDIR}/.
popd 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 # -e = stop script on errors
# -u = stop script on undefined variable # -u = stop script on undefined variable
# -o pipefail = stop pipeline if any step fails # -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} mkdir -p ${G_BUILDDIR}
@ -544,21 +330,28 @@ mkdir -p ${G_BUILDDIR}
# These are required for the build. # These are required for the build.
sudo apt-get install -y \ sudo apt-get install -y \
xxd \
imagemagick \ imagemagick \
lua5.4 \ lua5.4 \
lyx \
ffmpeg \ ffmpeg \
asciidoctor \
ruby-asciidoctor-pdf \
autoconf \
automake \
libtool \
libasound-dev \ libasound-dev \
libxi-dev \ libxi-dev \
libvdpau-dev \ libvdpau-dev
upx-ucl
buildAll linux x86_64 2>&1 | tee ${G_BUILDDIR}/linux-x86_64.log # Usage: build-all.sh [os arch] (default: every supported platform)
buildAll macos aarch64 2>&1 | tee ${G_BUILDDIR}/macos-aarch64.log if [[ $# -eq 2 ]]; then
buildAll pi aarch64 2>&1 | tee ${G_BUILDDIR}/pi-aarch64.log buildAll "$1" "$2" 2>&1 | tee ${G_BUILDDIR}/$1-$2.log
buildAll windows x86_64 2>&1 | tee ${G_BUILDDIR}/windows-x86_64.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 === # === 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 pixelHigh = 1
pixelUnknown = 2 pixelUnknown = 2
sprLightOn = spriteLoad("ActionMax/sprite_LightOn.png") sprLightOn = spriteLoad(DIR .. "sprite_LightOn.png")
sprLightOff = spriteLoad("ActionMax/sprite_LightOff.png") sprLightOff = spriteLoad(DIR .. "sprite_LightOff.png")
sprActionMax = spriteLoad("ActionMax/sprite_ActionMax.png") sprActionMax = spriteLoad(DIR .. "sprite_ActionMax.png")
sprCrosshair = spriteLoad("ActionMax/sprite_Crosshair.png") sprCrosshair = spriteLoad(DIR .. "sprite_Crosshair.png")
sprBullet = spriteLoad("ActionMax/sprite_Bullet.png") sprBullet = spriteLoad(DIR .. "sprite_Bullet.png")
sprBoxArt = spriteLoad("ActionMax/sprite_" .. gameID .. ".png") sprBoxArt = spriteLoad(DIR .. "sprite_" .. gameID .. ".png")
sndActionMax = soundLoad("ActionMax/sound_ActionMax.wav") sndActionMax = soundLoad(DIR .. "sound_ActionMax.wav")
sndSteadyAim = soundLoad("ActionMax/sound_ASteadyAimIsCritical.wav") sndSteadyAim = soundLoad(DIR .. "sound_ASteadyAimIsCritical.wav")
sndGetReady = soundLoad("ActionMax/sound_GetReadyForAction.wav") sndGetReady = soundLoad(DIR .. "sound_GetReadyForAction.wav")
sndGunShot = soundLoad("ActionMax/sound_Gunshot.wav") sndGunShot = soundLoad(DIR .. "sound_Gunshot.wav")
sndGoodHit = soundLoad("ActionMax/sound_GoodHit.wav") sndGoodHit = soundLoad(DIR .. "sound_GoodHit.wav")
sndBadHit = soundLoad("ActionMax/sound_BadHit.wav") sndBadHit = soundLoad(DIR .. "sound_BadHit.wav")
sndGameOver = soundLoad("ActionMax/sound_GameOver.wav") sndGameOver = soundLoad(DIR .. "sound_GameOver.wav")
mouseX = 0 mouseX = 0
mouseY = 0 mouseY = 0
@ -63,11 +63,11 @@ thisSeconds = 0
lastSeconds = 0 lastSeconds = 0
heartbeat = false heartbeat = false
fntBlueStone20 = fontLoad("ActionMax/font_BlueStone.ttf", 20) fntBlueStone20 = fontLoad(DIR .. "font_BlueStone.ttf", 20)
fntChemRea16 = fontLoad("ActionMax/font_chemrea.ttf", 16) fntChemRea16 = fontLoad(DIR .. "font_chemrea.ttf", 16)
fntChemRea32 = fontLoad("ActionMax/font_chemrea.ttf", 32) fntChemRea32 = fontLoad(DIR .. "font_chemrea.ttf", 32)
fntChemRea48 = fontLoad("ActionMax/font_chemrea.ttf", 48) fntChemRea48 = fontLoad(DIR .. "font_chemrea.ttf", 48)
fntLEDReal32 = fontLoad("ActionMax/font_LED_Real.ttf", 32) fntLEDReal32 = fontLoad(DIR .. "font_LED_Real.ttf", 32)
colorBackground(0, 0, 0, 0) colorBackground(0, 0, 0, 0)
fontQuality(FONT_QUALITY_BLENDED) fontQuality(FONT_QUALITY_BLENDED)
@ -170,51 +170,6 @@ function onInputPressed(intWhat)
end 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) function onMouseMoved(intX, intY, intXrel, intYrel)
-- Remember the mouse location for use later. -- Remember the mouse location for use later.
@ -307,9 +262,9 @@ function onOverlayUpdate()
elseif (currentState == stateTitle) then elseif (currentState == stateTitle) then
spriteDraw(logoLeft, logoTop, sprActionMax) spriteDraw(sprActionMax, logoLeft, logoTop)
spriteDraw(boxLeft, boxTop, sprBoxArt) spriteDraw(sprBoxArt, boxLeft, boxTop)
spriteDraw(lastGameLeft, lastGameTop, sprLastGame) spriteDraw(sprLastGame, lastGameLeft, lastGameTop)
y = scoreTop y = scoreTop
fontPrint(scoreLeft, y, " Shots Fired: " .. shotsFired) fontPrint(scoreLeft, y, " Shots Fired: " .. shotsFired)
y = y + scoreHeight y = y + scoreHeight
@ -326,16 +281,16 @@ function onOverlayUpdate()
end end
fontPrint(scoreLeft, y, " Game Score: " .. scorePercent .. "%") fontPrint(scoreLeft, y, " Game Score: " .. scorePercent .. "%")
if (heartbeat) then if (heartbeat) then
spriteDraw(pullToStartLeft, pullToStartTop, sprPullToStart) spriteDraw(sprPullToStart, pullToStartLeft, pullToStartTop)
end end
elseif (currentState == stateMenu) then elseif (currentState == stateMenu) then
spriteDraw(selectGameTypeLeft, selectGameTypeTop, sprSelectGameType) spriteDraw(sprSelectGameType, selectGameTypeLeft, selectGameTypeTop)
spriteDraw(standard1Left, standardLimited1Top, sprStandard1) spriteDraw(sprStandard1, standard1Left, standardLimited1Top)
spriteDraw(standard2Left, standardLimited2Top, sprStandard2) spriteDraw(sprStandard2, standard2Left, standardLimited2Top)
spriteDraw(limited1Left, standardLimited1Top, sprLimited1) spriteDraw(sprLimited1, limited1Left, standardLimited1Top)
spriteDraw(limited2Left, standardLimited2Top, sprLimited2) spriteDraw(sprLimited2, limited2Left, standardLimited2Top)
if (currentFrame >= lengthIntro + lengthGame + lengthMenu) then if (currentFrame >= lengthIntro + lengthGame + lengthMenu) then
discSearch(lengthIntro + lengthGame + 2) discSearch(lengthIntro + lengthGame + 2)
@ -345,7 +300,7 @@ function onOverlayUpdate()
elseif (currentState == stateIntro) then elseif (currentState == stateIntro) then
if (heartbeat) then if (heartbeat) then
spriteDraw(getReadyLeft, getReadyTop, sprGetReady) spriteDraw(sprGetReady, getReadyLeft, getReadyTop)
end end
-- Skip into game video when the intro is over. -- Skip into game video when the intro is over.
@ -423,9 +378,9 @@ function onOverlayUpdate()
-- Do we need to light the light? -- Do we need to light the light?
if (lightDisplay > 0) then if (lightDisplay > 0) then
spriteDraw(lightLeft, lightTop, sprLightOn) spriteDraw(sprLightOn, lightLeft, lightTop)
else else
spriteDraw(lightLeft, lightTop, sprLightOff) spriteDraw(sprLightOff, lightLeft, lightTop)
end end
-- Do we need to draw the ammo display? -- Do we need to draw the ammo display?
@ -440,7 +395,7 @@ function onOverlayUpdate()
if (ammoLeft > 0) then if (ammoLeft > 0) then
bulletStart = overlayGetWidth() - bulletWidth - 5 bulletStart = overlayGetWidth() - bulletWidth - 5
for i=1,ammoLeft do for i=1,ammoLeft do
spriteDraw(bulletStart, 0, sprBullet) spriteDraw(sprBullet, bulletStart, 0)
bulletStart = bulletStart - bulletWidth bulletStart = bulletStart - bulletWidth
end end
end end
@ -448,7 +403,7 @@ function onOverlayUpdate()
elseif (currentState == stateGameOver) then elseif (currentState == stateGameOver) then
spriteDraw(gameOverLeft, gameOverTop, sprGameOver) spriteDraw(sprGameOver, gameOverLeft, gameOverTop)
if (currentFrame >= lengthIntro + lengthGame + lengthMenu) then if (currentFrame >= lengthIntro + lengthGame + lengthMenu) then
discPause() discPause()
@ -462,7 +417,7 @@ function onOverlayUpdate()
-- Draw gun crosshair (This must be the last thing we draw so it's on top.) -- Draw gun crosshair (This must be the last thing we draw so it's on top.)
if (singeWantsCrosshairs()) then if (singeWantsCrosshairs()) then
spriteDraw(mouseX - crosshairCenterX, mouseY - crosshairCenterY, sprCrosshair) spriteDraw(sprCrosshair, mouseX - crosshairCenterX, mouseY - crosshairCenterY)
end end
return(OVERLAY_UPDATED) 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 GREEN = 3
ORANGE = 4 ORANGE = 4
WHITE = 5 WHITE = 5
GREY = 6; GRAY = 6 GREY = 6
GRAY = 6
PINK = 7
LIGHTBLUE = 7 LIGHTBLUE = 7
BLACK = 8 BLACK = 8
@ -126,7 +128,7 @@ function clockRnd()
q, w = math.modf(j) q, w = math.modf(j)
s2 = tostring(w) s2 = tostring(w)
r = string.find(s2,".") r = string.find(s2, ".", 1, true)
if (r == nil) then if (r == nil) then
@ -318,12 +320,8 @@ end
function getMiddle(thisPhrase) function getMiddle(thisPhrase)
local x = 0
local y = 0
local sprite = fontToSprite(thisPhrase) local sprite = fontToSprite(thisPhrase)
local x = OVLW/2 - spriteGetWidth(sprite) * 0.5
x = OVLW/2 - spriteGetWidth(sprite) * 0.5
y = OVLH/2 - spriteGetHeight(sprite) * 0.5
spriteUnload(sprite) spriteUnload(sprite)

View file

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

View file

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

View file

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

View file

@ -31,12 +31,11 @@
int64_t frameFileGetFrame(int32_t frameFileHandle, int32_t videoHandle); int64_t frameFileGetFrame(int32_t frameFileHandle, int32_t videoHandle);
int32_t frameFileInit(void); int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer *renderer, bool showCalculated);
int32_t frameFileLoad(char *filename, char *indexPath, bool stretchVideo, SDL_Renderer *renderer, bool showCalculated); void frameFileQuit(void);
int32_t frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHandle, int64_t *actualFrame); bool frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHandle, int64_t *actualFrame);
int32_t frameFileQuit(void); void frameFileUnload(int32_t frameFileHandle);
int32_t frameFileUnload(int32_t frameFileHandle); void frameFileUpdate(int32_t frameFileHandle, int32_t *videoHandle);
int32_t frameFileUpdate(int32_t frameFileHandle, int32_t *videoHandle);
#endif // FRAMEFILE_H #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 #define MAIN_H
#include "common.h"
#include "singe.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); void destroyConf(ConfigT **confPointer);
bool parseSindenString(char **sindenStringPointer, ConfigT *conf); bool isFrameFileName(const char *filename);
void queueScript(ConfigT *conf); bool parseSindenString(const char *sindenString, ConfigT *conf);
void queueScript(const ConfigT *conf);
#endif // MAIN_H #endif // MAIN_H

File diff suppressed because it is too large Load diff

View file

@ -28,22 +28,20 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "common.h" #include "common.h"
#include "generated/version.h"
// Don't forget to update singe.rc! #define SINDEN_ARG_MAX 8
#define SINGE_VERSION 2.10
#define VERSION_STRING "v2.10"
#define COPYRIGHT_END_YEAR "2024"
enum { // Number of --sindengun arguments selects the border style.
typedef enum SindenModeE {
SINDEN_WHITE = 1, SINDEN_WHITE = 1,
SINDEN_WHITE_BLACK = 2, SINDEN_WHITE_BLACK = 2,
SINDEN_CUSTOM_WHITE = 4, SINDEN_CUSTOM_WHITE = 4,
SINDEN_CUSTOM_WHITE_BLACK = 5, SINDEN_CUSTOM_WHITE_BLACK = 5,
SINDEN_CUSTOM_WHITE_CUSTOM_BLACK = 8, SINDEN_CUSTOM_WHITE_CUSTOM_BLACK = 8
SINDEN_OPTION_COUNT = 8 } SindenModeE;
};
typedef struct ConfigS { typedef struct ConfigS {
@ -64,6 +62,7 @@ typedef struct ConfigS {
bool noLogos; bool noLogos;
bool programTracing; bool programTracing;
bool scriptTracing; bool scriptTracing;
bool legacySpriteArgs;
int32_t bestRatioIndex; int32_t bestRatioIndex;
int32_t volumeVldp; int32_t volumeVldp;
int32_t volumeNonVldp; int32_t volumeNonVldp;
@ -71,7 +70,7 @@ typedef struct ConfigS {
int32_t xResolution; int32_t xResolution;
int32_t yResolution; int32_t yResolution;
int32_t sindenArgc; int32_t sindenArgc;
int32_t sindenArgv[SINDEN_OPTION_COUNT]; int32_t sindenArgv[SINDEN_ARG_MAX];
int32_t audioOutputTrack; int32_t audioOutputTrack;
} ConfigT; } 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) #define ourMkdir(p,m) mkdir(p)
static const int CONSOLE_LINES = 1000; #define CONSOLE_LINES 1000
#else #else
#define ourMkdir mkdir #define ourMkdir mkdir
#endif #endif
#include <stdlib.h> #include <stdlib.h>
#include <dirent.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
@ -48,6 +47,29 @@ static bool _outputHappened = false;
static FILE *_utilTraceFile = NULL; 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 utilChMod(const char *path, const mode_t mode) {
bool result = true; 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; va_list args;
char *string; char *string;
@ -74,15 +96,18 @@ char *utilCreateString(char *format, ...) {
} }
__attribute__((__format__(__printf__, 1, 0))) char *utilCreateStringVArgs(const char *format, va_list args) {
char *utilCreateStringVArgs(char *format, va_list args) {
va_list argsCopy; va_list argsCopy;
int32_t size = 0; int32_t size = 0;
char *buffer = NULL; char *buffer = NULL;
va_copy(argsCopy, args); va_copy(argsCopy, args);
size = vsnprintf(NULL, 0, format, argsCopy) + 1; size = vsnprintf(NULL, 0, format, argsCopy);
va_end(argsCopy); va_end(argsCopy);
if (size < 0) {
return NULL;
}
size++;
buffer = calloc(1, (size_t)size); buffer = calloc(1, (size_t)size);
if (buffer) { if (buffer) {
vsnprintf(buffer, (size_t)size, format, args); vsnprintf(buffer, (size_t)size, format, args);
@ -92,58 +117,48 @@ char *utilCreateStringVArgs(char *format, va_list args) {
} }
__attribute__((__format__(__printf__, 1, 0))) void utilDie(const char *fmt, ...) {
__attribute__((noreturn))
void utilDie(char *fmt, ...) {
va_list args; va_list args;
if (_consoleEnabled) { if (_consoleEnabled) {
va_start(args, fmt); va_start(args, fmt);
vfprintf(stderr, fmt, args); _printLine(stderr, fmt, args);
va_end(args); va_end(args);
printf("\n"); utilWaitForKeyOnWindows();
fflush(stderr);
#ifdef _WIN32
getchar();
#endif
} }
exit(1); exit(EXIT_FAILURE);
} }
void utilEnableConsole(bool enable) { void utilEnableConsole(bool enable) {
_consoleEnabled = enable; _consoleEnabled = enable;
} }
bool utilFileExists(char *filename) { bool utilFileExists(const char *filename) {
FILE *file; struct stat sb;
if ((file = fopen(filename, "r+"))) {
fclose(file); if (stat(filename, &sb) != 0) {
return true; return false;
} }
return false;
return S_ISREG(sb.st_mode);
} }
void utilFixPathSeparators(char **path, bool slash) { void utilFixPathSeparators(char **path, bool slash) {
const char separator = utilGetPathSeparator();
int32_t i = 0; size_t i = 0;
int32_t j = 0; size_t j = 0;
char *work = *path; char *work = *path;
char *temp = NULL; char *grown = NULL;
// Flip path separators to whatever our OS wants & remove repeated separators. // Flip path separators to whatever our OS wants & remove repeated separators.
while (work[i] != 0) { while (work[i] != 0) {
// Correct separator
if (work[i] == '\\' || work[i] == '/') { if (work[i] == '\\' || work[i] == '/') {
// Was the prior character a seprator? // Only accept a separator if the prior character was not one.
if (j == 0) { if ((j == 0) || (work[j - 1] != separator)) {
work[j++] = utilGetPathSeparator(); work[j++] = separator;
} else {
if (work[j - 1] != utilGetPathSeparator()) {
// No, accept it.
work[j++] = utilGetPathSeparator();
}
} }
} else { } else {
work[j++] = work[i]; work[j++] = work[i];
@ -152,18 +167,15 @@ void utilFixPathSeparators(char **path, bool slash) {
} }
work[j] = 0; work[j] = 0;
if (slash) { // Does this string need a trailing separator?
// Does this string end with a path separator? if (slash && ((j == 0) || (work[j - 1] != separator))) {
if (work[strlen(work) - 1] != utilGetPathSeparator()) { grown = realloc(work, j + 2);
// No - append one. if (!grown) {
temp = strdup(work); utilDie("Unable to allocate memory for path.");
free(work);
work = malloc(sizeof(char) * (strlen(temp) + 2));
strcpy(work, temp);
work[strlen(temp)] = utilGetPathSeparator();
work[strlen(temp) + 1] = 0;
free(temp);
} }
work = grown;
work[j] = separator;
work[j + 1] = 0;
} }
*path = work; *path = work;
@ -175,39 +187,33 @@ bool utilGetConsoleEnabled(void) {
} }
char *utilGetFileExtension(char *filename) { char *utilGetFileExtension(const char *filename) {
char *start = filename + strlen(filename); const char *end = filename + strlen(filename);
int32_t x; const char *last = utilGetLastPathComponent(filename);
const char *dot = strrchr(last, '.');
// Scan through name and find the last '.' // No '.' in the last path component means no extension.
for (x=0; x<(int32_t)strlen(filename); x++) { if (dot == NULL) {
if (filename[x] == '.') { return (char *)end;
start = &filename[x + 1];
}
// Reset if we find a path separator
if (filename[x] == '\\' || filename[x] == '/') {
start = filename + strlen(filename);
}
} }
return start; return (char *)(dot + 1);
} }
char *utilGetLastPathComponent(char *pathname) { char *utilGetLastPathComponent(const char *pathname) {
static char *start; const char *start = pathname;
int32_t x; const char *slash = strrchr(pathname, '/');
const char *backslash = strrchr(pathname, '\\');
start = pathname; if (slash != NULL) {
start = slash + 1;
// Scan through name and find the last path separator }
for (x=0; x<(int32_t)strlen(pathname); x++) { if ((backslash != NULL) && (backslash + 1 > start)) {
if (pathname[x] == '\\' || pathname[x] == '/') { start = backslash + 1;
start = &pathname[x + 1];
}
} }
return start; return (char *)start;
} }
@ -220,21 +226,17 @@ char utilGetPathSeparator(void) {
} }
char *utilGetUpToLastPathComponent(char *pathname) { // Returns a new string the caller must free. Always ends in a path separator.
static char *copy = NULL; char *utilGetUpToLastPathComponent(const char *pathname) {
bool dumb = false; // Using (copy == NULL) below didn't work after optimizations, so enter the dummy. size_t length = strlen(pathname) - strlen(utilGetLastPathComponent(pathname));
int32_t x; char *copy = NULL;
x = (int32_t)(strlen(pathname) - strlen(utilGetLastPathComponent(pathname))) - 1; if (length == 0) {
if (x < 0) x = 0; // No directory part - use the current directory.
if (dumb) { copy = strdup(".");
free(copy);
copy = NULL;
} else { } else {
dumb = true; copy = utilStrndup(pathname, length);
} }
copy = strdup(pathname);
copy[x] = 0;
utilFixPathSeparators(&copy, true); utilFixPathSeparators(&copy, true);
return copy; return copy;
@ -242,91 +244,74 @@ char *utilGetUpToLastPathComponent(char *pathname) {
bool utilMkDirP(const char *dir, const mode_t mode) { bool utilMkDirP(const char *dir, const mode_t mode) {
char tmp[UTIL_PATH_MAX]; const char separator = utilGetPathSeparator();
char *p = NULL; char tmp[UTIL_PATH_MAX];
struct stat sb; char *p = NULL;
size_t len; size_t len;
// Make copy of dir. // Make copy of dir.
len = strnlen(dir, UTIL_PATH_MAX); len = strnlen(dir, UTIL_PATH_MAX);
if (len == 0 || len == UTIL_PATH_MAX) { if (len == 0 || len == UTIL_PATH_MAX) {
return -1; return false;
} }
memcpy(tmp, dir, len); memcpy(tmp, dir, len);
tmp[len] = '\0'; tmp[len] = 0;
// Remove trailing slash. // Remove trailing slash.
if (tmp[len - 1] == utilGetPathSeparator()) { if (tmp[len - 1] == separator) {
tmp[len - 1] = '\0'; tmp[len - 1] = 0;
}
// Does it already exist?
if (stat(tmp, &sb) == 0) {
if (S_ISDIR (sb.st_mode)) {
return true;
}
} }
// Recursive mkdir. // Recursive mkdir.
for (p = tmp + 1; *p; p++) { for (p = tmp + 1; *p; p++) {
if (*p == utilGetPathSeparator()) { if (*p == separator) {
*p = 0; *p = 0;
if (stat(tmp, &sb) != 0) { if (!_ensureDirectory(tmp, mode)) {
// Does not exist - create it. return false;
if (ourMkdir(tmp, mode) < 0) {
return false;
}
} else {
if (!S_ISDIR(sb.st_mode)) {
// Not a directory
return false;
}
} }
*p = utilGetPathSeparator(); *p = separator;
}
}
// 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;
} }
} }
return true; return _ensureDirectory(tmp, mode);
} }
bool utilPathExists(char *pathname) { void utilNewline(void) {
DIR *dir = opendir(pathname); utilSay("%s", "");
if (dir) {
closedir(dir);
return true;
}
return false;
} }
char *utilReadFile(char *filename, size_t *bytes) { bool utilPathExists(const char *pathname) {
char *data = NULL; struct stat sb;
FILE *in = fopen(filename, "rb");
size_t read = 0;
(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; *bytes = 0;
if (in) { if (in) {
fseek(in, 0, SEEK_END); fseek(in, 0, SEEK_END);
*bytes = ftell(in); size = ftell(in);
fseek(in, 0, SEEK_SET); fseek(in, 0, SEEK_SET);
data = malloc(sizeof(char) * (*bytes)); if (size >= 0) {
read = fread(data, sizeof(char), *bytes, in); data = malloc((size_t)size + 1);
if (data) {
*bytes = fread(data, 1, (size_t)size, in);
data[*bytes] = 0;
}
}
fclose(in); fclose(in);
} }
@ -334,123 +319,91 @@ char *utilReadFile(char *filename, size_t *bytes) {
} }
char *utilReadLine(char *haystack, size_t length, char **offset) { // Returns a new string the caller must free, or NULL when no data remains.
size_t bytes = 0; char *utilReadLine(const char *haystack, size_t length, const char **offset) {
char *temp = *offset; const char *end = haystack + length;
char *tail = temp; const char *start = *offset;
char *result = NULL; const char *tail = NULL;
char *result = NULL;
// They didn't know where to start // They didn't know where to start.
if (temp == NULL) { if (start == NULL) {
temp = haystack; start = haystack;
tail = temp;
} }
tail = start;
// Is there still data to read? // Find the end of the line or the end of the data.
while ((size_t)(tail - haystack) < length) { while ((tail < end) && (*tail != '\n') && (*tail != '\r')) {
// 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
tail++; tail++;
} }
// Was there data at the end of the block with no CR/LF? // Was there anything on this line?
if (tail > temp) { if (tail > start) {
// Yep. Treat it as a line. result = utilStrndup(start, (size_t)(tail - start));
bytes = tail - temp + 1;
result = malloc(sizeof(char) * bytes);
memcpy(result, temp, bytes - 1);
result[bytes - 1] = 0;
temp = tail;
*offset = temp;
} }
// 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; return result;
} }
void utilRedirectConsole(void) { void utilRedirectConsole(void) {
#ifdef _WIN32 #ifdef _WIN32
// http://dslweb.nwnexus.com/~ast/dload/guicon.htm
int hConHandle; // Not changing this int.
intptr_t lStdHandle;
CONSOLE_SCREEN_BUFFER_INFO coninfo; CONSOLE_SCREEN_BUFFER_INFO coninfo;
FILE *fp;
static bool consoleOpen = false; static bool consoleOpen = false;
if (!consoleOpen) { if (_consoleEnabled && !consoleOpen) {
consoleOpen = true; consoleOpen = true;
// allocate a console for this app // Allocate a console for this app.
AllocConsole(); 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); GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
coninfo.dwSize.Y = CONSOLE_LINES; coninfo.dwSize.Y = CONSOLE_LINES;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
// redirect unbuffered STDOUT to the console // Redirect unbuffered STDOUT, STDIN, and STDERR to the console.
lStdHandle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE); freopen("CONOUT$", "w", stdout);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen(hConHandle, "w");
*stdout = *fp;
setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0);
freopen("CONIN$", "r", stdin);
// 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;
setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0);
freopen("CONOUT$", "w", stderr);
// 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;
setvbuf(stderr, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0);
} }
#endif #endif
} }
__attribute__((__format__(__printf__, 1, 0))) void utilSay(const char *fmt, ...) {
void utilSay(char *fmt, ...) {
va_list args; va_list args;
if (_consoleEnabled) { if (_consoleEnabled) {
va_start(args, fmt); va_start(args, fmt);
vfprintf(stdout, fmt, args); _printLine(stdout, fmt, args);
va_end(args); va_end(args);
printf("\n");
fflush(stdout);
_outputHappened = true; _outputHappened = true;
} }
} }
bool utilStartsWith(char *string, char *start) { bool utilStartsWith(const char *string, const char *start) {
return strncmp(start, string, strlen(start)) == 0; 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++) { 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) { if (d != 0 || !*a) {
return d; return d;
} }
@ -459,16 +412,23 @@ int utilStricmp(char *a, char *b) {
// Windows does not have strndup() so we implement our own. // Windows does not have strndup() so we implement our own.
char *utilStrndup( const char *s1, size_t n) { char *utilStrndup(const char *s1, size_t n) {
char *copy = (char *)malloc(n + 1); size_t length = strnlen(s1, n);
memcpy(copy, s1, n); char *copy = malloc(length + 1);
copy[n] = 0;
if (!copy) {
utilDie("Unable to allocate memory for string.");
}
memcpy(copy, s1, length);
copy[length] = 0;
return copy; return copy;
} }
void utilTrace(char *fmt, ...) { void utilTrace(const char *fmt, ...) {
va_list args; va_list args;
if (_utilTraceFile) { if (_utilTraceFile) {
va_start(args, fmt); va_start(args, fmt);
utilTraceVArgs(fmt, args); utilTraceVArgs(fmt, args);
@ -478,36 +438,38 @@ void utilTrace(char *fmt, ...) {
void utilTraceEnd(void) { 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 (_utilTraceFile) {
#if defined(va_copy) fclose(_utilTraceFile);
va_copy(args2, args); _utilTraceFile = NULL;
vprintf(fmt, args2);
printf("\n");
va_end(args2);
#endif
vfprintf(_utilTraceFile, fmt, args);
fprintf(_utilTraceFile, "\n");
fflush(_utilTraceFile);
} }
} }
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 #define UTIL_PATH_MAX 1024
bool utilChMod(const char *path, const mode_t mode); bool utilChMod(const char *path, const mode_t mode);
char *utilCreateString(char *format, ...); char *utilCreateString(const char *format, ...) __attribute__((format(printf, 1, 2)));
char *utilCreateStringVArgs(char *format, va_list args); char *utilCreateStringVArgs(const char *format, va_list args) __attribute__((format(printf, 1, 0)));
void utilDie(char *fmt, ...); void utilDie(const char *fmt, ...) __attribute__((format(printf, 1, 2))) __attribute__((noreturn));
void utilEnableConsole(bool enable); void utilEnableConsole(bool enable);
bool utilFileExists(char *filename); bool utilFileExists(const char *filename);
void utilFixPathSeparators(char **path, bool slash); void utilFixPathSeparators(char **path, bool slash);
bool utilGetConsoleEnabled(void); bool utilGetConsoleEnabled(void);
char *utilGetFileExtension(char *filename); char *utilGetFileExtension(const char *filename);
char *utilGetLastPathComponent(char *pathname); char *utilGetLastPathComponent(const char *pathname);
char utilGetPathSeparator(void); char utilGetPathSeparator(void);
char *utilGetUpToLastPathComponent(char *pathname); char *utilGetUpToLastPathComponent(const char *pathname);
bool utilMkDirP(const char *dir, const mode_t mode); bool utilMkDirP(const char *dir, const mode_t mode);
bool utilPathExists(char *pathname); void utilNewline(void);
char *utilReadFile(char *filename, size_t *bytes); bool utilPathExists(const char *pathname);
char *utilReadLine(char *haystack, size_t length, char **offset); char *utilReadFile(const char *filename, size_t *bytes);
void utilRedirectConsole(void); char *utilReadLine(const char *haystack, size_t length, const char **offset);
void utilSay(char *fmt, ...); void utilRedirectConsole(void);
bool utilStartsWith(char *string, char *start); void utilSay(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
int utilStricmp(char *a, char *b); bool utilStartsWith(const char *string, const char *start);
char *utilStrndup( const char *s1, size_t n); int32_t utilStricmp(const char *a, const char *b);
void utilTrace(char *fmt, ...); char *utilStrndup(const char *s1, size_t n);
void utilTraceEnd(void); void utilTrace(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
FILE *utilTraceGetFile(void); void utilTraceEnd(void);
void utilTraceStart(char *filename); void utilTraceStart(const char *filename);
void utilTraceVArgs(char *fmt, va_list args); void utilTraceVArgs(const char *fmt, va_list args) __attribute__((format(printf, 1, 0)));
void utilWaitForKeyOnWindows(void);
#endif // UTIL_H #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" #include "common.h"
typedef void (*videoIndexingCallback)(int32_t); #define VIDEO_VOLUME_MAX 100
int32_t videoInit(void); typedef void (*VideoIndexingCallbackT)(int32_t percent);
int32_t videoIsPlaying(int32_t playerHandle);
int32_t videoGetAudioTrack(int32_t playerHandle);
int32_t videoGetAudioTracks(int32_t playerHandle); int32_t videoGetAudioTrack(int32_t playerHandle);
int64_t videoGetFrame(int32_t playerHandle); int32_t videoGetAudioTracks(int32_t playerHandle);
int64_t videoGetFrameCount(int32_t playerHandle); int64_t videoGetFrame(int32_t playerHandle);
int32_t videoGetHeight(int32_t playerHandle); int64_t videoGetFrameCount(int32_t playerHandle);
char *videoGetLanguage(int32_t playerHandle, int32_t audioTrack); int32_t videoGetHeight(int32_t playerHandle);
char *videoGetLanguageDescription(char *languageCode); const char *videoGetLanguage(int32_t playerHandle, int32_t audioTrack);
int32_t videoGetWidth(int32_t playerHandle); const char *videoGetLanguageDescription(const char *languageCode);
int32_t videoGetVolume(int32_t playerHandle, int32_t *leftPercent, int32_t *rightPercent); bool videoGetPixel(int32_t playerHandle, int32_t x, int32_t y, uint8_t *r, uint8_t *g, uint8_t *b);
int32_t videoLoad(char *filename, char *indexPath, bool stretchVideo, SDL_Renderer *renderer); bool videoGetPixels(int32_t playerHandle, const uint8_t **pixels, int32_t *pitch);
int32_t videoLoadWithAudio(char *vFilename, char *aFilename, char *indexPath, bool stretchVideo, SDL_Renderer *renderer); void videoGetVolume(int32_t playerHandle, int32_t *leftPercent, int32_t *rightPercent);
int32_t videoPause(int32_t playerHandle); int32_t videoGetWidth(int32_t playerHandle);
int32_t videoPlay(int32_t playerHandle); void videoInit(int32_t mixerChunkFrames);
int32_t videoQuit(void); bool videoIsPlaying(int32_t playerHandle);
int32_t videoSeek(int32_t playerHandle, int64_t seekFrame); int32_t videoLoad(const char *videoFilename, const char *audioFilename, const char *indexPath, SDL_Renderer *renderer);
int32_t videoSetAudioTrack(int32_t playerHandle, int32_t track); void videoPause(int32_t playerHandle);
int32_t videoSetIndexCallback(videoIndexingCallback callback); void videoPlay(int32_t playerHandle);
int32_t videoSetVolume(int32_t playerHandle, int32_t leftPercent, int32_t rightPercent); void videoQuit(void);
int32_t videoUnload(int32_t playerHandle); void videoSeek(int32_t playerHandle, int64_t seekFrame);
int32_t videoUpdate(int32_t playerHandle, SDL_Texture **texture); 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 #endif // VIDEOPLAYER_H

View file

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