mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a48dc55c41 | ||
|
|
202a458524 | ||
|
|
b4de704a09 | ||
|
|
fef5edfac8 | ||
|
|
a6e74775b1 | ||
|
|
ee66a28d05 | ||
|
|
5e28d82af2 | ||
|
|
c2a06bfa09 | ||
|
|
66510e21aa | ||
|
|
ec5e3355b9 | ||
|
|
0522a2d6a0 | ||
|
|
fad89f9d2d | ||
|
|
97f937ec6d | ||
|
|
9647006a2b | ||
|
|
418ba455f8 | ||
|
|
197d62860b | ||
|
|
402451626f | ||
|
|
3739cbfc70 | ||
|
|
235355ffc7 | ||
|
|
4856d0cac3 | ||
|
|
77a2723bb0 | ||
|
|
41cd6e21c3 | ||
|
|
1fc99c7123 | ||
|
|
fddcc3447c | ||
|
|
70e9f084b7 | ||
|
|
824790a80d | ||
|
|
23d23983bc | ||
|
|
c42655ed91 | ||
|
|
f46ee78c76 | ||
|
|
5d1d188231 | ||
|
|
c55ce543c2 | ||
|
|
de8bc62474 | ||
|
|
397da887a7 | ||
|
|
f44733d7f6 | ||
|
|
5faa28a1a2 | ||
|
|
efaa85ff9b | ||
|
|
8f5580be10 | ||
|
|
c7acd99548 | ||
|
|
64ee13e6f4 | ||
|
|
e134f8afc7 | ||
|
|
dd89e6fbe0 | ||
|
|
a79a1b46f7 | ||
|
|
6467f51ea8 | ||
|
|
141eefb03b | ||
|
|
068c224d83 | ||
|
|
5bc8090ea7 | ||
|
|
466ab1ef8d | ||
|
|
a1c19ed1f5 | ||
|
|
cce4b7d20c | ||
|
|
80925ffc11 | ||
|
|
9eb14e328d | ||
|
|
5ae0cb1821 | ||
|
|
fc594f7f0f | ||
|
|
0c4e406ee7 | ||
|
|
a082ec5251 | ||
|
|
953b5daf0a | ||
|
|
b4ff622f32 | ||
|
|
f26b54c998 | ||
|
|
d7b3f39136 | ||
|
|
5bcf601a40 | ||
|
|
e91ebdac97 | ||
|
|
b2dd12d186 | ||
|
|
9c79bf0ba2 | ||
|
|
f48247a18b | ||
|
|
48e015558e | ||
|
|
0ff3cbc93b | ||
|
|
25f0e162a4 | ||
|
|
0810812cd9 | ||
|
|
15e0b721e6 | ||
|
|
9d12c3ac87 | ||
|
|
a7c3d8d17a | ||
|
|
8fbfdee011 | ||
|
|
bc999e72fa | ||
|
|
c7f8110670 | ||
|
|
b0dbc67281 | ||
|
|
4916f7b08b | ||
|
|
1058d94ec1 | ||
|
|
340dca3c6d | ||
|
|
4ae0f3da22 | ||
|
|
7d2d1ce49e | ||
|
|
fa26f6a10c | ||
|
|
db71a5cdba | ||
|
|
5058fdcd1a | ||
|
|
ef04f199d5 | ||
|
|
d26dd9d9e7 | ||
|
|
d2bc4096de | ||
|
|
08ca1a54d3 | ||
|
|
869eddfc1a | ||
|
|
fbced43f11 | ||
|
|
c996f816b8 | ||
|
|
33ecedb462 | ||
|
|
a58676c9e1 | ||
|
|
1ee9722e5f | ||
|
|
81250657f0 | ||
|
|
7d33cccc8e | ||
|
|
3db79ab2f0 | ||
|
|
ea0f6813d2 | ||
|
|
f6d4d32c03 | ||
|
|
892e82141f | ||
|
|
65ec7fae9c | ||
|
|
a0acb75a19 | ||
|
|
bcae973d82 | ||
|
|
d01a4fe61b | ||
|
|
8d4620b680 | ||
|
|
c2cedb1b6f | ||
|
|
157dcad87b | ||
|
|
8ea5ba171a | ||
|
|
fa876b10a3 | ||
|
|
3c58d10eae | ||
|
|
a3ee8c8849 | ||
|
|
5e14b302bc | ||
|
|
efe3f943d5 | ||
|
|
38e7fc6659 | ||
|
|
a1ceb534de | ||
|
|
0d897dd65d | ||
|
|
721d73f451 | ||
|
|
e8bd64e898 | ||
|
|
7da7ab2ce7 | ||
|
|
3f6183ff15 | ||
|
|
642316a1e2 | ||
|
|
21c6b75839 | ||
|
|
87d34deb0c | ||
|
|
1b3408919c | ||
|
|
23516572b7 | ||
|
|
924848fdad | ||
|
|
4c40e56060 | ||
|
|
cf34444505 | ||
|
|
9ca5e4e470 | ||
|
|
32f2689b6a | ||
|
|
13ae61031f | ||
|
|
1ced96c5e9 | ||
|
|
0d905eb3f6 | ||
|
|
32a5460ea7 | ||
|
|
be5acff69c | ||
|
|
1b0ef5793e | ||
|
|
2bd0caae2e | ||
|
|
3045f2c478 | ||
|
|
1282a142d6 | ||
|
|
ac1a539d22 | ||
|
|
6f47775239 | ||
|
|
0823d72dee | ||
|
|
407ad94f58 | ||
|
|
b2b103362d | ||
|
|
231680944f | ||
|
|
ec7dab05e8 | ||
|
|
9588f6061f | ||
|
|
57c25dcb0b | ||
|
|
38763c4554 | ||
|
|
fac2ea00dd | ||
|
|
74782a1bea | ||
|
|
c1265e7227 | ||
|
|
f0841256e8 | ||
|
|
600efab1bd | ||
|
|
dc9dd2e6bf | ||
|
|
de8fe0e88c | ||
|
|
338432c35f | ||
|
|
11aeb05a6e | ||
|
|
e28a32d922 | ||
|
|
edac39cb2b | ||
|
|
c1954aa4a6 | ||
|
|
ef048fcfd5 | ||
|
|
48a1531d7c | ||
|
|
4b4686ae04 | ||
|
|
cb4a54fefe | ||
|
|
131b2386cd | ||
|
|
4d6d01228c | ||
|
|
a050955113 | ||
|
|
b02789d280 | ||
|
|
6f92d0588d | ||
|
|
45df48ab67 | ||
|
|
aba5c7722e | ||
|
|
07a1a1ee2f | ||
|
|
3b952973de | ||
|
|
ac4adebdf6 | ||
|
|
1bfd9716c5 | ||
|
|
702cf9c804 | ||
|
|
5993fffb64 | ||
|
|
58758d2818 | ||
|
|
21f88fcf39 | ||
|
|
e3f029a446 | ||
|
|
0694d8884f | ||
|
|
0730a34a5b | ||
|
|
f827bb4cbd | ||
|
|
eafd8d91d7 | ||
|
|
56a4d1ddac | ||
|
|
8d3f15602c | ||
|
|
7877c7939d | ||
|
|
c55263b956 | ||
|
|
96ce288120 | ||
|
|
e5326467a1 | ||
|
|
596d8d9985 | ||
|
|
b4b0be9118 | ||
|
|
00e5f0fe97 | ||
|
|
cb140c3691 | ||
|
|
ad85ccb42f | ||
|
|
97e50f15de | ||
|
|
071c03e194 | ||
|
|
6f47c21ce9 | ||
|
|
68bfda24ff | ||
|
|
4f2fbcb9bc | ||
|
|
90ab5dfc2f | ||
|
|
362cbe5deb | ||
|
|
e133662346 | ||
|
|
1fe83ab25a | ||
|
|
1d95b5ee22 | ||
|
|
9dc603d405 | ||
|
|
5cb2863e6e | ||
|
|
98cf4223ab | ||
|
|
ff7843cbe9 | ||
|
|
c4d6b0b7c1 | ||
|
|
fb739621ac | ||
|
|
24e8ca6856 | ||
|
|
1ff9b6a375 | ||
|
|
cb6086635c | ||
|
|
bce679df78 | ||
|
|
22e8c3d895 |
No files matched your search
@@ -2,11 +2,11 @@ include config.mk
|
||||
|
||||
TARGET = ssnes tools/ssnes-joyconfig
|
||||
|
||||
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o screenshot.o
|
||||
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o
|
||||
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o bps.o strl.o screenshot.o thread.o audio/hermite.o
|
||||
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o strl.o
|
||||
HEADERS = $(wildcard */*.h) $(wildcard *.h)
|
||||
|
||||
LIBS = -lm
|
||||
LIBS = -lm -lpthread
|
||||
DEFINES = -DHAVE_CONFIG_H
|
||||
|
||||
ifneq ($(findstring Darwin,$(OS)),)
|
||||
@@ -23,11 +23,8 @@ ifneq ($(findstring BSD,$(OS)),)
|
||||
DYLIB_LIB = -lc
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS)
|
||||
else
|
||||
OBJ += audio/hermite.o
|
||||
ifneq ($(findstring Linux,$(OS)),)
|
||||
LIBS += -lrt
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CONFIGFILE), 1)
|
||||
@@ -89,7 +86,7 @@ ifeq ($(HAVE_COREAUDIO), 1)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
OBJ += gfx/sdl.o gfx/gl.o gfx/sdlwrap.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
DEFINES += $(SDL_CFLAGS) $(BSD_LOCAL_INC)
|
||||
LIBS += $(SDL_LIBS)
|
||||
ifeq ($(OSX),1)
|
||||
@@ -101,8 +98,8 @@ endif
|
||||
|
||||
ifeq ($(HAVE_XVIDEO), 1)
|
||||
OBJ += gfx/xvideo.o input/x11_input.o
|
||||
LIBS += $(XVIDEO_LIBS)
|
||||
DEFINES += $(XVIDEO_CFLAGS)
|
||||
LIBS += $(XVIDEO_LIBS) $(X11_LIBS) $(XEXT_LIBS)
|
||||
DEFINES += $(XVIDEO_CFLAGS) $(X11_CFLAGS) $(XEXT_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CG), 1)
|
||||
@@ -111,11 +108,17 @@ ifeq ($(HAVE_CG), 1)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o gfx/image.o gfx/snes_state.o sha256.o cheats.o
|
||||
OBJ += gfx/shader_glsl.o sha256.o cheats.o
|
||||
LIBS += $(XML_LIBS)
|
||||
DEFINES += $(XML_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/snes_state.o gfx/image.o
|
||||
else ifeq ($(HAVE_CG), 1)
|
||||
OBJ += gfx/snes_state.o gfx/image.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
OBJ += gfx/ext.o audio/ext.o
|
||||
LIBS += $(DYLIB_LIB)
|
||||
@@ -158,9 +161,14 @@ ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
CFLAGS += -Wall -O3 -g -I. -pedantic
|
||||
OPTIMIZE_FLAG = -O3
|
||||
ifeq ($(DEBUG), 1)
|
||||
OPTIMIZE_FLAG = -O0
|
||||
endif
|
||||
|
||||
CFLAGS += -Wall $(OPTIMIZE_FLAG) -g -I. -pedantic
|
||||
ifneq ($(findstring icc,$(CC)),)
|
||||
CFLAGS += -std=c99
|
||||
CFLAGS += -std=c99 -D_GNU_SOURCE
|
||||
else
|
||||
CFLAGS += -std=gnu99
|
||||
endif
|
||||
@@ -172,11 +180,11 @@ config.mk: configure qb/*
|
||||
@exit 1
|
||||
|
||||
ssnes: $(OBJ)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) $(LIBRARY_DIRS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
tools/ssnes-joyconfig: $(JOYCONFIG_OBJ)
|
||||
$(Q)$(CC) -o $@ $(JOYCONFIG_OBJ) $(SDL_LIBS) $(LDFLAGS)
|
||||
$(Q)$(CC) -o $@ $(JOYCONFIG_OBJ) $(SDL_LIBS) $(LDFLAGS) $(LIBRARY_DIRS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
%.o: %.c config.h config.mk $(HEADERS)
|
||||
|
||||
@@ -1,47 +1,41 @@
|
||||
TARGET = ssnes.exe
|
||||
JTARGET = ssnes-joyconfig.exe
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o screenshot.o
|
||||
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o bps.o ups.o strl.o screenshot.o audio/hermite.o thread.o
|
||||
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o strl.o
|
||||
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
|
||||
HAVE_DINPUT = 1
|
||||
HAVE_SRC = 0
|
||||
HAVE_SDL = 1
|
||||
HAVE_SDL_IMAGE = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_DSOUND = 1
|
||||
HAVE_RSOUND = 1
|
||||
HAVE_SDL = 1
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
HAVE_FBO = 1
|
||||
HAVE_CG = 1
|
||||
HAVE_PYTHON = 1
|
||||
HAVE_FFMPEG = 0
|
||||
libsnes ?= -lsnes
|
||||
DYNAMIC = 1
|
||||
|
||||
LIBS = -lm
|
||||
DEFINES = -I. -DHAVE_CONFIGFILE
|
||||
LDFLAGS = -L. -static-libgcc -s
|
||||
|
||||
SRC_LIBS = -lsamplerate-0
|
||||
SDL_LIBS = -lSDL
|
||||
SDL_CFLAGS = -ISDL
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS) -DHAVE_SRC
|
||||
else
|
||||
OBJ += audio/hermite.o
|
||||
ifeq ($(SLIM),)
|
||||
HAVE_SDL_IMAGE = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_RSOUND = 1
|
||||
HAVE_FBO = 1
|
||||
HAVE_CG = 1
|
||||
HAVE_PYTHON = 1
|
||||
HAVE_FFMPEG = 1
|
||||
endif
|
||||
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS = -lm -lshlwapi
|
||||
DEFINES = -I. -DHAVE_CONFIGFILE
|
||||
LDFLAGS = -L. -static-libgcc -s
|
||||
LDCXXFLAGS = -static-libstdc++
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
LIBS += $(SDL_LIBS) -lopengl32
|
||||
DEFINES += $(SDL_CFLAGS) -DHAVE_SDL
|
||||
OBJ += gfx/sdl.o gfx/gl.o gfx/sdlwrap.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
LIBS += -lSDL -lopengl32
|
||||
DEFINES += -ISDL -DHAVE_SDL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL_IMAGE), 1)
|
||||
@@ -56,8 +50,9 @@ ifeq ($(HAVE_CG), 1)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XAUDIO), 1)
|
||||
OBJ += audio/xaudio.o
|
||||
OBJ += audio/xaudio.o audio/xaudio-c/xaudio-c.o
|
||||
DEFINES += -DHAVE_XAUDIO
|
||||
LIBS += -lole32
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DSOUND), 1)
|
||||
@@ -74,8 +69,8 @@ endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o gfx/image.o gfx/snes_state.o sha256.o cheats.o
|
||||
DEFINES += $(XML_CFLAGS) -DHAVE_XML
|
||||
LIBS += -lxml2
|
||||
DEFINES += -Ilibxml2 -DHAVE_XML
|
||||
LIBS += -lxml2 -liconv
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
@@ -91,7 +86,7 @@ endif
|
||||
ifeq ($(HAVE_FREETYPE), 1)
|
||||
OBJ += gfx/fonts.o
|
||||
DEFINES += -DHAVE_FREETYPE -Ifreetype2
|
||||
LIBS += -lz -lfreetype6
|
||||
LIBS += -lfreetype -lz
|
||||
endif
|
||||
|
||||
ifeq ($(DYNAMIC), 1)
|
||||
@@ -116,13 +111,18 @@ endif
|
||||
|
||||
ifeq ($(HAVE_DINPUT), 1)
|
||||
LIBS += -ldinput8 -ldxguid -lole32
|
||||
DEFINES += -DHAVE_DINPUT -Iffmpeg
|
||||
DEFINES += -DHAVE_DINPUT
|
||||
OBJ += input/dinput.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FFMPEG), 1)
|
||||
LIBS += -lavformat -lavcodec -lavutil -lswscale -lws2_32 -lzlib1
|
||||
DEFINES += -DHAVE_FFMPEG
|
||||
LIBS += -lavformat -lavcodec -lavutil -lswscale -lws2_32 -lz
|
||||
DEFINES += -DHAVE_FFMPEG -Iffmpeg
|
||||
DEFINES += -DHAVE_FFMPEG_ALLOC_CONTEXT3
|
||||
DEFINES += -DHAVE_FFMPEG_AVCODEC_OPEN2
|
||||
DEFINES += -DHAVE_FFMPEG_AVIO_OPEN
|
||||
DEFINES += -DHAVE_FFMPEG_AVFORMAT_WRITE_HEADER
|
||||
DEFINES += -DHAVE_X264RGB
|
||||
OBJ += record/ffemu.o
|
||||
endif
|
||||
|
||||
@@ -130,42 +130,57 @@ ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
|
||||
CFLAGS = -Wall -O3 -std=gnu99 -I.
|
||||
CFLAGS += -Wall -O3 -std=gnu99 -I.
|
||||
CXXFLAGS += -Wall -O3 -pedantic -I.
|
||||
|
||||
all: $(TARGET) $(JTARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) $(LDCXXFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
%.o: %.c
|
||||
$(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
|
||||
@$(if $(Q), $(shell echo echo CC $<),)
|
||||
|
||||
%.o: %.cpp
|
||||
$(Q)$(CXX) $(CXXFLAGS) $(DEFINES) -c -o $@ $<
|
||||
@$(if $(Q), $(shell echo echo CXX $<),)
|
||||
|
||||
|
||||
$(JTARGET): $(JOBJ)
|
||||
$(Q)$(CC) -o $@ $(JOBJ) $(SDL_LIBS) $(LDFLAGS)
|
||||
$(Q)$(CC) -o $@ $(JOBJ) -lSDL $(LDFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f audio/*.o
|
||||
rm -f audio/xaudio-c/*.o
|
||||
rm -f conf/*.o
|
||||
rm -f gfx/*.o
|
||||
rm -f gfx/py_state/*.o
|
||||
rm -f record/*.o
|
||||
rm -f hqflt/*.o
|
||||
rm -f input/*.o
|
||||
rm -f hqflt/snes_ntsc/*.o
|
||||
rm -f $(TARGET)
|
||||
rm -f ssnes-joyconfig.exe
|
||||
rm -f tools/*.o
|
||||
|
||||
dist: all
|
||||
zip -r ssnes-win32-0.7.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll cg.dll cgGL.dll libjpeg-8.dll libpng15-15.dll python32.dll SDL_image.dll $(JTARGET)
|
||||
dist_x86: all
|
||||
zip -r ssnes-win32-0.9.1.zip $(TARGET) $(JTARGET) ssnes.cfg snes.dll SDL.dll
|
||||
|
||||
libs:
|
||||
dist_x86_64: all
|
||||
zip -r ssnes-win64-0.9.1.zip $(TARGET) $(JTARGET) ssnes.cfg snes.dll SDL.dll
|
||||
|
||||
libs_x86:
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-libs.zip --no-check-certificate
|
||||
unzip SSNES-win32-libs.zip
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-headers.zip --no-check-certificate
|
||||
unzip SSNES-win32-headers.zip
|
||||
|
||||
.PHONY: all install uninstall clean dist libs
|
||||
libs_x86_64:
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win64-libs.zip --no-check-certificate
|
||||
unzip SSNES-win64-libs.zip
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win64-headers.zip --no-check-certificate
|
||||
unzip SSNES-win64-headers.zip
|
||||
|
||||
.PHONY: all install uninstall clean dist_x86 dist_x86_64 libs_x86 libs_x86_64
|
||||
-158
@@ -1,158 +0,0 @@
|
||||
TARGET = ssnes.exe
|
||||
JTARGET = ssnes-joyconfig.exe
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o screenshot.o
|
||||
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o
|
||||
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
|
||||
HAVE_DINPUT = 1
|
||||
HAVE_SRC = 0
|
||||
HAVE_SDL = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_DSOUND = 1
|
||||
HAVE_RSOUND = 0
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
HAVE_FBO = 1
|
||||
HAVE_CG = 0
|
||||
HAVE_PYTHON = 0
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS = -lm
|
||||
DEFINES = -I. -DHAVE_CONFIGFILE
|
||||
LDFLAGS = -L. -static-libgcc -s
|
||||
|
||||
SRC_LIBS = -lsamplerate-0
|
||||
SDL_LIBS = -lmingw32 -lSDL -lSDLmain -lwinmm -mwindows -lm -luser32 -lgdi32 -lwinmm -ldxguid
|
||||
SDL_CFLAGS = -ISDL
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS) -DHAVE_SRC
|
||||
else
|
||||
OBJ += audio/hermite.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
LIBS += $(SDL_LIBS) -lopengl32
|
||||
DEFINES += $(SDL_CFLAGS) -DHAVE_SDL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CG), 1)
|
||||
OBJ += gfx/shader_cg.o
|
||||
LIBS += -lcg -lcgGL
|
||||
DEFINES += -DHAVE_CG
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XAUDIO), 1)
|
||||
OBJ += audio/xaudio.o
|
||||
DEFINES += -DHAVE_XAUDIO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DSOUND), 1)
|
||||
OBJ += audio/dsound.o
|
||||
DEFINES += -DHAVE_DSOUND
|
||||
LIBS += -ldxguid -ldsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
DEFINES += -DHAVE_RSOUND
|
||||
LIBS += -lrsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o gfx/image.o gfx/snes_state.o sha256.o cheats.o
|
||||
DEFINES += $(XML_CFLAGS) -DHAVE_XML -DLIBXML_STATIC
|
||||
LIBS += -lxml2 -lz -lws2_32
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
DEFINES += -DHAVE_DYLIB
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_NETPLAY), 1)
|
||||
DEFINES += -DHAVE_NETPLAY
|
||||
OBJ += netplay.o
|
||||
LIBS += -lws2_32
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FREETYPE), 1)
|
||||
OBJ += gfx/fonts.o
|
||||
DEFINES += -DHAVE_FREETYPE -Ifreetype2
|
||||
LIBS += -lz -lfreetype6
|
||||
endif
|
||||
|
||||
ifeq ($(DYNAMIC), 1)
|
||||
DEFINES += -DHAVE_DYNAMIC
|
||||
else
|
||||
LIBS += $(libsnes)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FBO), 1)
|
||||
DEFINES += -DHAVE_FBO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
OBJ += gfx/ext.o audio/ext.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_PYTHON), 1)
|
||||
LIBS += -lpython32
|
||||
DEFINES += -DHAVE_PYTHON -Ipython
|
||||
OBJ += gfx/py_state/py_state.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DINPUT), 1)
|
||||
LIBS += -ldinput8 -ldxguid -lole32
|
||||
DEFINES += -DHAVE_DINPUT
|
||||
OBJ += input/dinput.o
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
|
||||
CFLAGS = -Wall -O3 -std=gnu99 -I.
|
||||
|
||||
all: $(TARGET) $(JTARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
%.o: %.c
|
||||
$(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
|
||||
@$(if $(Q), $(shell echo echo CC $<),)
|
||||
|
||||
$(JTARGET): $(JOBJ)
|
||||
$(Q)$(CC) -o $@ $(JOBJ) $(SDL_LIBS) $(LDFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f audio/*.o
|
||||
rm -f conf/*.o
|
||||
rm -f gfx/*.o
|
||||
rm -f record/*.o
|
||||
rm -f hqflt/*.o
|
||||
rm -f input/*.o
|
||||
rm -f hqflt/snes_ntsc/*.o
|
||||
rm -f $(TARGET)
|
||||
rm -f ssnes-joyconfig.exe
|
||||
rm -f tools/*.o
|
||||
|
||||
dist: all
|
||||
zip -r ssnes-win64-0.7.zip $(TARGET) ssnes.cfg snes.dll xaudio-c.dll README.win32.txt $(JTARGET)
|
||||
|
||||
libs:
|
||||
wget https://github.com/downloads/Themaister/SSNES/ssnes-win64-libs.zip --no-check-certificate
|
||||
unzip ssnes-win64-libs.zip
|
||||
|
||||
.PHONY: all install uninstall clean dist libs
|
||||
@@ -1,39 +0,0 @@
|
||||
===================================================
|
||||
SSNES - Simple Super Nintendo Emulator System
|
||||
===================================================
|
||||
|
||||
SSNES is a simplistic emulator frontend designed for use without any GUI.
|
||||
It is used from command line, but is designed to play well with eventual GUI frontends,
|
||||
as the configuration file format is very simple.
|
||||
|
||||
This allows for great portability and focus on what matters in a frontend, such as video and audio performance.
|
||||
Its core libraries for video and input are OpenGL and SDL, with many different audio library supported.
|
||||
|
||||
On first use you should get accustomed to how SSNES works.
|
||||
|
||||
- SSNES emphasizes simplicity. It has no graphical user interface, and is primarily used through the command line.
|
||||
It is recommended for experienced users.
|
||||
It is possible though to create a GUI which can use SSNES.
|
||||
|
||||
- SSNES isn't dependant on any emulator core, such as bSNES, SNES9x or ZSNES.
|
||||
SSNES only talks to the libsnes interface,
|
||||
which was created initially by bSNES but it has an implementation available for SNES9x as well.
|
||||
|
||||
- SSNES is configured through a config file. In the Win32 distribution, there should be a ssnes.cfg included.
|
||||
This config file serves as a base for your custom configuration file.
|
||||
It is recommended that you go through the config file and alter any configurations you might use.
|
||||
This is a good way to learn about the features SSNES have.
|
||||
It is possible to pick which configuration file you want with the '-c' flag.
|
||||
E.g.: ssnes.exe -c myconfig.cfg rom.sfc.
|
||||
|
||||
- When unsure which command line flags to use, use the command: ssnes.exe --help.
|
||||
|
||||
- When something is not working as expected, try using verbose logging first to see what is happening:
|
||||
E.g.: ssnes.exe -v romfile.sfc
|
||||
If something is not working, and you want to file a bug report,
|
||||
please include console output with the -v flag turned on.
|
||||
|
||||
- SSNES does not support zipped roms directly. It only supports roms with extentions .sfc and .smc.
|
||||
However, SSNES can read roms from stdin,
|
||||
which makes it possible to create wrapper programs that can support every zip format under the sun.
|
||||
This can also be performed by a GUI frontend.
|
||||
+10
-15
@@ -21,7 +21,7 @@
|
||||
#include <asoundlib.h>
|
||||
#include "general.h"
|
||||
|
||||
#define TRY_ALSA(x) if ( x < 0 ) { \
|
||||
#define TRY_ALSA(x) if (x < 0) { \
|
||||
goto error; \
|
||||
}
|
||||
|
||||
@@ -52,14 +52,14 @@ static bool find_float_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
|
||||
static void* __alsa_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
alsa_t *alsa = calloc(1, sizeof(alsa_t));
|
||||
if ( alsa == NULL )
|
||||
if (!alsa)
|
||||
return NULL;
|
||||
|
||||
snd_pcm_hw_params_t *params = NULL;
|
||||
snd_pcm_sw_params_t *sw_params = NULL;
|
||||
|
||||
const char *alsa_dev = "default";
|
||||
if ( device != NULL )
|
||||
if (device)
|
||||
alsa_dev = device;
|
||||
|
||||
TRY_ALSA(snd_pcm_open(&alsa->pcm, alsa_dev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK));
|
||||
@@ -117,7 +117,7 @@ error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t __alsa_write(void* data, const void* buf, size_t size)
|
||||
static ssize_t __alsa_write(void *data, const void *buf, size_t size)
|
||||
{
|
||||
alsa_t *alsa = data;
|
||||
|
||||
@@ -141,16 +141,16 @@ static ssize_t __alsa_write(void* data, const void* buf, size_t size)
|
||||
|
||||
frames = snd_pcm_writei(alsa->pcm, (const char*)buf + written * 2 * (alsa->has_float ? sizeof(float) : sizeof(int16_t)), size - written);
|
||||
|
||||
if ( frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE )
|
||||
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
|
||||
{
|
||||
if ( snd_pcm_recover(alsa->pcm, frames, 1) < 0 )
|
||||
if (snd_pcm_recover(alsa->pcm, frames, 1) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if ( frames == -EAGAIN && alsa->nonblock )
|
||||
else if (frames == -EAGAIN && alsa->nonblock)
|
||||
return 0;
|
||||
else if ( frames < 0 )
|
||||
else if (frames < 0)
|
||||
return -1;
|
||||
|
||||
written += frames;
|
||||
@@ -178,9 +178,9 @@ static bool __alsa_start(void *data)
|
||||
static void __alsa_free(void *data)
|
||||
{
|
||||
alsa_t *alsa = data;
|
||||
if ( alsa )
|
||||
if (alsa)
|
||||
{
|
||||
if ( alsa->pcm )
|
||||
if (alsa->pcm)
|
||||
{
|
||||
snd_pcm_drop(alsa->pcm);
|
||||
snd_pcm_close(alsa->pcm);
|
||||
@@ -200,8 +200,3 @@ const audio_driver_t audio_alsa = {
|
||||
.ident = "alsa"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+6
-11
@@ -20,13 +20,12 @@
|
||||
#include "hermite.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MAX_CHANS 8
|
||||
#define CHANNELS 2
|
||||
|
||||
struct hermite_resampler
|
||||
{
|
||||
float chan_data[MAX_CHANS][4];
|
||||
float chan_data[CHANNELS][4];
|
||||
double r_frac;
|
||||
unsigned chans;
|
||||
};
|
||||
|
||||
static inline float hermite_kernel(float mu1, float a, float b, float c, float d)
|
||||
@@ -47,21 +46,17 @@ static inline float hermite_kernel(float mu1, float a, float b, float c, float d
|
||||
return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
|
||||
}
|
||||
|
||||
hermite_resampler_t *hermite_new(unsigned channels)
|
||||
hermite_resampler_t *hermite_new(void)
|
||||
{
|
||||
if (channels > MAX_CHANS)
|
||||
return NULL;
|
||||
|
||||
hermite_resampler_t *re = calloc(1, sizeof(*re));
|
||||
if (!re)
|
||||
return NULL;
|
||||
re->chans = channels;
|
||||
return re;
|
||||
}
|
||||
|
||||
void hermite_process(hermite_resampler_t *re, struct hermite_data *data)
|
||||
{
|
||||
double r_step = 1.0 / data->src_ratio;
|
||||
double r_step = 1.0 / data->ratio;
|
||||
size_t processed_out = 0;
|
||||
size_t processed_in = 0;
|
||||
|
||||
@@ -75,7 +70,7 @@ void hermite_process(hermite_resampler_t *re, struct hermite_data *data)
|
||||
while (re->r_frac <= 1.0 && processed_out < out_frames)
|
||||
{
|
||||
re->r_frac += r_step;
|
||||
for (unsigned i = 0; i < re->chans; i++)
|
||||
for (unsigned i = 0; i < CHANNELS; i++)
|
||||
{
|
||||
float res = hermite_kernel(re->r_frac,
|
||||
re->chan_data[i][0], re->chan_data[i][1], re->chan_data[i][2], re->chan_data[i][3]);
|
||||
@@ -88,7 +83,7 @@ void hermite_process(hermite_resampler_t *re, struct hermite_data *data)
|
||||
{
|
||||
re->r_frac -= 1.0;
|
||||
|
||||
for (unsigned i = 0; i < re->chans; i++)
|
||||
for (unsigned i = 0; i < CHANNELS; i++)
|
||||
{
|
||||
re->chan_data[i][0] = re->chan_data[i][1];
|
||||
re->chan_data[i][1] = re->chan_data[i][2];
|
||||
|
||||
+2
-3
@@ -26,7 +26,7 @@
|
||||
|
||||
typedef struct hermite_resampler hermite_resampler_t;
|
||||
|
||||
hermite_resampler_t *hermite_new(unsigned channels);
|
||||
hermite_resampler_t *hermite_new(void);
|
||||
|
||||
struct hermite_data
|
||||
{
|
||||
@@ -39,8 +39,7 @@ struct hermite_data
|
||||
size_t input_frames_used;
|
||||
size_t output_frames_gen;
|
||||
|
||||
bool end_of_input; // Just used to clone the SRC API.
|
||||
double src_ratio;
|
||||
double ratio;
|
||||
};
|
||||
|
||||
void hermite_process(hermite_resampler_t *re, struct hermite_data *data);
|
||||
|
||||
+3
-4
@@ -60,7 +60,7 @@ static void* __al_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
(void)device;
|
||||
al_t *al = calloc(1, sizeof(al_t));
|
||||
if ( al == NULL )
|
||||
if (!al)
|
||||
return NULL;
|
||||
|
||||
al->handle = alcOpenDevice(NULL);
|
||||
@@ -133,7 +133,7 @@ static bool al_get_buffer(al_t *al, ALuint *buffer)
|
||||
};
|
||||
#endif
|
||||
|
||||
for(;;)
|
||||
for (;;)
|
||||
{
|
||||
if (al_unqueue_buffers(al))
|
||||
break;
|
||||
@@ -221,7 +221,7 @@ static void __al_free(void *data)
|
||||
{
|
||||
alSourceStop(al->source);
|
||||
alDeleteSources(1, &al->source);
|
||||
if ( al->buffers )
|
||||
if (al->buffers)
|
||||
{
|
||||
alDeleteBuffers(al->num_buffers, al->buffers);
|
||||
free(al->buffers);
|
||||
@@ -244,4 +244,3 @@ const audio_driver_t audio_openal = {
|
||||
.ident = "openal"
|
||||
};
|
||||
|
||||
|
||||
+5
-5
@@ -57,12 +57,12 @@ static void err_cb(void *userdata)
|
||||
static void* __rsd_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
rsd_t *rsd = calloc(1, sizeof(rsd_t));
|
||||
if ( rsd == NULL )
|
||||
if (!rsd)
|
||||
return NULL;
|
||||
|
||||
rsound_t *rd;
|
||||
|
||||
if ( rsd_init(&rd) < 0 )
|
||||
if (rsd_init(&rd) < 0)
|
||||
{
|
||||
free(rsd);
|
||||
return NULL;
|
||||
@@ -80,14 +80,14 @@ static void* __rsd_init(const char* device, unsigned rate, unsigned latency)
|
||||
rsd_set_param(rd, RSD_SAMPLERATE, &rate);
|
||||
rsd_set_param(rd, RSD_LATENCY, &latency);
|
||||
|
||||
if ( device != NULL )
|
||||
if (device)
|
||||
rsd_set_param(rd, RSD_HOST, (void*)device);
|
||||
|
||||
rsd_set_param(rd, RSD_FORMAT, &format);
|
||||
|
||||
rsd_set_callback(rd, audio_cb, err_cb, 256, rsd);
|
||||
|
||||
if ( rsd_start(rd) < 0 )
|
||||
if (rsd_start(rd) < 0)
|
||||
{
|
||||
free(rsd);
|
||||
rsd_free(rd);
|
||||
@@ -161,7 +161,7 @@ static void __rsd_set_nonblock_state(void *data, bool state)
|
||||
static bool __rsd_start(void *data)
|
||||
{
|
||||
rsd_t *rsd = data;
|
||||
if ( rsd_start(rsd->rd) < 0)
|
||||
if (rsd_start(rsd->rd) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef AUDIO_UTILS_H
|
||||
#define AUDIO_UTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __SSE2__
|
||||
#include <mmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
#define audio_convert_s16_to_float audio_convert_s16_to_float_SSE2
|
||||
#define audio_convert_float_to_s16 audio_convert_float_to_s16_SSE2
|
||||
#else
|
||||
#define audio_convert_s16_to_float audio_convert_s16_to_float_C
|
||||
#define audio_convert_float_to_s16 audio_convert_float_to_s16_C
|
||||
#endif
|
||||
|
||||
static inline void audio_convert_s16_to_float_C(float *out,
|
||||
const int16_t *in, unsigned samples)
|
||||
{
|
||||
for (unsigned i = 0; i < samples; i++)
|
||||
out[i] = (float)in[i] / 0x8000;
|
||||
}
|
||||
|
||||
static inline void audio_convert_float_to_s16_C(int16_t *out,
|
||||
const float *in, unsigned samples)
|
||||
{
|
||||
for (unsigned i = 0; i < samples; i++)
|
||||
{
|
||||
int32_t val = in[i] * 0x8000;
|
||||
out[i] = (val > 0x7FFF) ? 0x7FFF : (val < -0x8000 ? -0x8000 : (int16_t)val);
|
||||
}
|
||||
}
|
||||
|
||||
#if __SSE2__
|
||||
static inline void audio_convert_s16_to_float_SSE2(float *out,
|
||||
const int16_t *in, unsigned samples)
|
||||
{
|
||||
__m128 factor = _mm_set1_ps(1.0f / (0x7fff * 0x10000));
|
||||
unsigned i;
|
||||
for (i = 0; i + 8 <= samples; i += 8, in += 8, out += 8)
|
||||
{
|
||||
__m128i input = _mm_loadu_si128((const __m128i *)in);
|
||||
__m128i regs[2] = {
|
||||
_mm_unpacklo_epi16(_mm_setzero_si128(), input),
|
||||
_mm_unpackhi_epi16(_mm_setzero_si128(), input),
|
||||
};
|
||||
|
||||
__m128 output[2] = {
|
||||
_mm_mul_ps(_mm_cvtepi32_ps(regs[0]), factor),
|
||||
_mm_mul_ps(_mm_cvtepi32_ps(regs[1]), factor),
|
||||
};
|
||||
|
||||
_mm_storeu_ps(out + 0, output[0]);
|
||||
_mm_storeu_ps(out + 4, output[1]);
|
||||
}
|
||||
|
||||
audio_convert_s16_to_float_C(out, in, samples - i);
|
||||
}
|
||||
|
||||
static inline void audio_convert_float_to_s16_SSE2(int16_t *out,
|
||||
const float *in, unsigned samples)
|
||||
{
|
||||
__m128 factor = _mm_set1_ps((float)0x7fff);
|
||||
unsigned i;
|
||||
for (i = 0; i + 8 <= samples; i += 8, in += 8, out += 8)
|
||||
{
|
||||
__m128 input[2] = { _mm_loadu_ps(in + 0), _mm_loadu_ps(in + 4) };
|
||||
__m128 res[2] = { _mm_mul_ps(input[0], factor), _mm_mul_ps(input[1], factor) };
|
||||
|
||||
__m128i ints[2] = { _mm_cvtps_epi32(res[0]), _mm_cvtps_epi32(res[1]) };
|
||||
__m128i packed = _mm_packs_epi32(ints[0], ints[1]);
|
||||
|
||||
_mm_storeu_si128((__m128i *)out, packed);
|
||||
}
|
||||
|
||||
audio_convert_float_to_s16_C(out, in, samples - i);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
Simple C interface for XAudio2
|
||||
Author: Hans-Kristian Arntzen
|
||||
License: Public Domain
|
||||
*/
|
||||
|
||||
// This will currently only compile with MSVC2k10.
|
||||
// Should be compilable with some MinGW trickery as well.
|
||||
|
||||
#include "xaudio-c.h"
|
||||
|
||||
struct xaudio2 : public IXAudio2VoiceCallback
|
||||
{
|
||||
xaudio2(unsigned samplerate, unsigned channels, unsigned bits, unsigned size) :
|
||||
buf(nullptr), pXAudio2(nullptr), pMasterVoice(nullptr), pSourceVoice(nullptr),
|
||||
buffers(0), bufptr(0)
|
||||
{
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = XAudio2Create(&pXAudio2, 0)))
|
||||
throw -1;
|
||||
if (FAILED(hr = pXAudio2->CreateMasteringVoice(
|
||||
&pMasterVoice, channels, samplerate, 0, 0, nullptr)))
|
||||
throw -1;
|
||||
WAVEFORMATEX wfx;
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = channels;
|
||||
wfx.nSamplesPerSec = samplerate;
|
||||
wfx.nBlockAlign = channels * bits / 8;
|
||||
wfx.wBitsPerSample = bits;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
wfx.cbSize = 0;
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(
|
||||
&pSourceVoice, &wfx, XAUDIO2_VOICE_NOSRC, XAUDIO2_DEFAULT_FREQ_RATIO, this, nullptr, nullptr)))
|
||||
throw -1;
|
||||
|
||||
pSourceVoice->Start();
|
||||
|
||||
hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
if (!hEvent)
|
||||
throw -1;
|
||||
|
||||
max_buffers = 8;
|
||||
bufsize = size / max_buffers;
|
||||
buf = new uint8_t[bufsize * max_buffers];
|
||||
write_buffer = 0;
|
||||
}
|
||||
|
||||
~xaudio2()
|
||||
{
|
||||
if (pSourceVoice)
|
||||
{
|
||||
pSourceVoice->Stop();
|
||||
pSourceVoice->DestroyVoice();
|
||||
}
|
||||
|
||||
if (pMasterVoice)
|
||||
pMasterVoice->DestroyVoice();
|
||||
|
||||
if (pXAudio2)
|
||||
pXAudio2->Release();
|
||||
|
||||
if (hEvent)
|
||||
CloseHandle(hEvent);
|
||||
|
||||
if (buf)
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
size_t write_avail() const
|
||||
{
|
||||
return bufsize * (max_buffers - buffers - 1);
|
||||
}
|
||||
|
||||
size_t write(const void *buffer, size_t bytes)
|
||||
{
|
||||
size_t written = 0;
|
||||
while (written < bytes)
|
||||
{
|
||||
size_t need = min(bytes - written, bufsize - bufptr);
|
||||
memcpy((uint8_t*)buf + write_buffer * bufsize + bufptr, (const uint8_t*)buffer + written, need);
|
||||
written += need;
|
||||
bufptr += need;
|
||||
|
||||
if (bufptr == bufsize)
|
||||
{
|
||||
while (buffers == max_buffers - 1)
|
||||
WaitForSingleObject(hEvent, INFINITE);
|
||||
|
||||
XAUDIO2_BUFFER xa2buffer = {0};
|
||||
xa2buffer.AudioBytes = bufsize;
|
||||
xa2buffer.pAudioData = buf + write_buffer * bufsize;
|
||||
xa2buffer.pContext = nullptr;
|
||||
InterlockedIncrement(&buffers);
|
||||
pSourceVoice->SubmitSourceBuffer(&xa2buffer);
|
||||
bufptr = 0;
|
||||
write_buffer = (write_buffer + 1) % max_buffers;
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
STDMETHOD_(void, OnBufferStart) (void *) {}
|
||||
STDMETHOD_(void, OnBufferEnd) (void *)
|
||||
{
|
||||
InterlockedDecrement(&buffers);
|
||||
SetEvent(hEvent);
|
||||
}
|
||||
STDMETHOD_(void, OnLoopEnd) (void *) {}
|
||||
STDMETHOD_(void, OnStreamEnd) () {}
|
||||
STDMETHOD_(void, OnVoiceError) (void *, HRESULT) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd) ()
|
||||
{
|
||||
SetEvent(hEvent);
|
||||
}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) {}
|
||||
|
||||
uint8_t *buf;
|
||||
IXAudio2 *pXAudio2;
|
||||
IXAudio2MasteringVoice *pMasterVoice;
|
||||
IXAudio2SourceVoice *pSourceVoice;
|
||||
HANDLE hEvent;
|
||||
|
||||
volatile long buffers;
|
||||
unsigned max_buffers;
|
||||
unsigned bufsize;
|
||||
unsigned bufptr;
|
||||
unsigned write_buffer;
|
||||
};
|
||||
|
||||
XAUDIOC_API xaudio2_t *xaudio2_new(unsigned samplerate, unsigned channels, unsigned bits, size_t size)
|
||||
{
|
||||
xaudio2 *handle = nullptr;
|
||||
try {
|
||||
handle = new xaudio2(samplerate, channels, bits, size);
|
||||
} catch(...) { delete handle; return nullptr; }
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
XAUDIOC_API size_t xaudio2_write_avail(xaudio2_t *handle)
|
||||
{
|
||||
try {
|
||||
return handle->write_avail();
|
||||
} catch(...) { return 0; }
|
||||
}
|
||||
|
||||
XAUDIOC_API size_t xaudio2_write(xaudio2_t *handle, const void *buf, size_t bytes)
|
||||
{
|
||||
try {
|
||||
return handle->write(buf, bytes);
|
||||
} catch(...) { return 0; }
|
||||
}
|
||||
|
||||
XAUDIOC_API void xaudio2_free(xaudio2_t *handle)
|
||||
{
|
||||
delete handle;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
Simple C interface for XAudio2
|
||||
Author: Hans-Kristian Arntzen
|
||||
License: Public Domain
|
||||
*/
|
||||
|
||||
#ifndef XAUDIO_C_H
|
||||
#define XAUDIO_C_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef XAUDIOC_EXPORTS
|
||||
#define XAUDIOC_API __declspec(dllexport)
|
||||
#else
|
||||
#define XAUDIOC_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
typedef struct xaudio2 xaudio2_t;
|
||||
|
||||
XAUDIOC_API xaudio2_t* xaudio2_new(unsigned samplerate, unsigned channels, unsigned bits, size_t bufsize);
|
||||
XAUDIOC_API size_t xaudio2_write_avail(xaudio2_t *handle);
|
||||
XAUDIOC_API size_t xaudio2_write(xaudio2_t *handle, const void *data, size_t bytes);
|
||||
XAUDIOC_API void xaudio2_free(xaudio2_t *handle);
|
||||
|
||||
typedef xaudio2_t* (*xaudio2_new_t)(unsigned, unsigned, unsigned, size_t);
|
||||
typedef size_t (*xaudio2_write_avail_t)(xaudio2_t*);
|
||||
typedef size_t (*xaudio2_write_t)(xaudio2_t*, const void*, size_t);
|
||||
typedef void (*xaudio2_free_t)(xaudio2_t*);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
Simple C interface for XAudio2
|
||||
Author: Hans-Kristian Arntzen
|
||||
License: Public Domain
|
||||
*/
|
||||
|
||||
#include "xaudio-c.h"
|
||||
#include "xaudio2.hpp"
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
|
||||
struct xaudio2 : public IXAudio2VoiceCallback
|
||||
{
|
||||
xaudio2(unsigned samplerate, unsigned channels, unsigned size) :
|
||||
buf(0), pXAudio2(0), pMasterVoice(0), pSourceVoice(0),
|
||||
buffers(0), bufptr(0)
|
||||
{
|
||||
CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||
if (FAILED(XAudio2Create(&pXAudio2, 0)))
|
||||
throw -1;
|
||||
if (FAILED(pXAudio2->CreateMasteringVoice(
|
||||
&pMasterVoice, channels, samplerate, 0, 0, 0)))
|
||||
throw -1;
|
||||
|
||||
WAVEFORMATEX wfx;
|
||||
wfx.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
wfx.nChannels = channels;
|
||||
wfx.nSamplesPerSec = samplerate;
|
||||
wfx.nBlockAlign = channels * sizeof(float);
|
||||
wfx.wBitsPerSample = sizeof(float) * 8;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
wfx.cbSize = 0;
|
||||
if (FAILED(pXAudio2->CreateSourceVoice(
|
||||
&pSourceVoice, &wfx, XAUDIO2_VOICE_NOSRC, XAUDIO2_DEFAULT_FREQ_RATIO, this, 0, 0)))
|
||||
throw -1;
|
||||
|
||||
hEvent = CreateEvent(0, FALSE, FALSE, 0);
|
||||
if (!hEvent)
|
||||
throw -1;
|
||||
|
||||
pSourceVoice->Start(0);
|
||||
|
||||
bufsize = size / max_buffers;
|
||||
buf = new uint8_t[bufsize * max_buffers];
|
||||
write_buffer = 0;
|
||||
}
|
||||
|
||||
virtual ~xaudio2()
|
||||
{
|
||||
if (pSourceVoice)
|
||||
{
|
||||
pSourceVoice->Stop(0);
|
||||
pSourceVoice->DestroyVoice();
|
||||
}
|
||||
|
||||
if (pMasterVoice)
|
||||
pMasterVoice->DestroyVoice();
|
||||
|
||||
if (pXAudio2)
|
||||
pXAudio2->Release();
|
||||
|
||||
if (hEvent)
|
||||
CloseHandle(hEvent);
|
||||
|
||||
if (buf)
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
size_t write_avail() const
|
||||
{
|
||||
return bufsize * (max_buffers - buffers - 1);
|
||||
}
|
||||
|
||||
size_t write(const void *buffer_, size_t bytes_)
|
||||
{
|
||||
unsigned bytes = bytes_;
|
||||
const uint8_t *buffer = reinterpret_cast<const uint8_t*>(buffer_);
|
||||
while (bytes > 0)
|
||||
{
|
||||
unsigned need = std::min(bytes, bufsize - bufptr);
|
||||
memcpy(buf + write_buffer * bufsize + bufptr,
|
||||
buffer, need);
|
||||
|
||||
bufptr += need;
|
||||
buffer += need;
|
||||
bytes -= need;
|
||||
|
||||
if (bufptr == bufsize)
|
||||
{
|
||||
WaitForSingleObject(hEvent, 0); // Clear out ready state.
|
||||
while (buffers == max_buffers - 1)
|
||||
WaitForSingleObject(hEvent, INFINITE);
|
||||
|
||||
XAUDIO2_BUFFER xa2buffer = {0};
|
||||
xa2buffer.AudioBytes = bufsize;
|
||||
xa2buffer.pAudioData = buf + write_buffer * bufsize;
|
||||
xa2buffer.pContext = 0;
|
||||
if (FAILED(pSourceVoice->SubmitSourceBuffer(&xa2buffer)))
|
||||
return 0;
|
||||
|
||||
InterlockedIncrement(&buffers);
|
||||
bufptr = 0;
|
||||
write_buffer = (write_buffer + 1) & (max_buffers_mask);
|
||||
}
|
||||
}
|
||||
|
||||
return bytes_;
|
||||
}
|
||||
|
||||
enum { max_buffers = 16, max_buffers_mask = max_buffers - 1 };
|
||||
|
||||
STDMETHOD_(void, OnBufferStart) (void *) {}
|
||||
STDMETHOD_(void, OnBufferEnd) (void *)
|
||||
{
|
||||
InterlockedDecrement(&buffers);
|
||||
SetEvent(hEvent);
|
||||
}
|
||||
STDMETHOD_(void, OnLoopEnd) (void *) {}
|
||||
STDMETHOD_(void, OnStreamEnd) () {}
|
||||
STDMETHOD_(void, OnVoiceError) (void *, HRESULT) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd) () {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) {}
|
||||
|
||||
uint8_t *buf;
|
||||
IXAudio2 *pXAudio2;
|
||||
IXAudio2MasteringVoice *pMasterVoice;
|
||||
IXAudio2SourceVoice *pSourceVoice;
|
||||
HANDLE hEvent;
|
||||
|
||||
volatile long buffers;
|
||||
unsigned bufsize;
|
||||
unsigned bufptr;
|
||||
unsigned write_buffer;
|
||||
};
|
||||
|
||||
xaudio2_t *xaudio2_new(unsigned samplerate, unsigned channels, size_t size)
|
||||
{
|
||||
xaudio2 *handle = 0;
|
||||
try {
|
||||
handle = new xaudio2(samplerate, channels, size);
|
||||
} catch(...) { delete handle; return 0; }
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
size_t xaudio2_write_avail(xaudio2_t *handle)
|
||||
{
|
||||
try {
|
||||
return handle->write_avail();
|
||||
} catch(...) { return 0; }
|
||||
}
|
||||
|
||||
size_t xaudio2_write(xaudio2_t *handle, const void *buf, size_t bytes)
|
||||
{
|
||||
try {
|
||||
return handle->write(buf, bytes);
|
||||
} catch(...) { return 0; }
|
||||
}
|
||||
|
||||
void xaudio2_free(xaudio2_t *handle)
|
||||
{
|
||||
delete handle;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Simple C interface for XAudio2
|
||||
Author: Hans-Kristian Arntzen
|
||||
License: Public Domain
|
||||
*/
|
||||
|
||||
#ifndef XAUDIO_C_H
|
||||
#define XAUDIO_C_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct xaudio2 xaudio2_t;
|
||||
|
||||
xaudio2_t* xaudio2_new(unsigned samplerate, unsigned channels, size_t bufsize);
|
||||
size_t xaudio2_write_avail(xaudio2_t *handle);
|
||||
size_t xaudio2_write(xaudio2_t *handle, const void *data, size_t bytes);
|
||||
void xaudio2_free(xaudio2_t *handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Executable
+329
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
xaudio2.hpp (2010-08-14)
|
||||
author: OV2
|
||||
*/
|
||||
|
||||
#ifndef XAUDIO2_MINGW_H
|
||||
#define XAUDIO2_MINGW_H
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#include <basetyps.h>
|
||||
#include <objbase.h>
|
||||
#include <mmreg.h>
|
||||
|
||||
// 64-bit GCC fix
|
||||
#define GUID_EXT EXTERN_C
|
||||
#define GUID_SECT
|
||||
|
||||
#define DEFINE_GUID_X(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) GUID_EXT const GUID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
|
||||
#define DEFINE_CLSID_X(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
DEFINE_GUID_X(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
||||
#define DEFINE_IID_X(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
DEFINE_GUID_X(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
||||
#define X2DEFAULT(x) =x
|
||||
|
||||
DEFINE_CLSID_X(XAudio2, e21a7345, eb21, 468e, be, 50, 80, 4d, b9, 7c, f7, 08);
|
||||
DEFINE_CLSID_X(XAudio2_Debug, f7a76c21, 53d4, 46bb, ac, 53, 8b, 45, 9c, ae, 46, bd);
|
||||
DEFINE_IID_X(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb);
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2Voice);
|
||||
|
||||
#define XAUDIO2_COMMIT_NOW 0
|
||||
#define XAUDIO2_DEFAULT_CHANNELS 0
|
||||
#define XAUDIO2_DEFAULT_SAMPLERATE 0
|
||||
#define XAUDIO2_DEFAULT_FREQ_RATIO 4.0f
|
||||
#define XAUDIO2_DEBUG_ENGINE 0x0001
|
||||
#define XAUDIO2_VOICE_NOSRC 0x0004
|
||||
|
||||
typedef enum XAUDIO2_DEVICE_ROLE
|
||||
{
|
||||
NotDefaultDevice = 0x0,
|
||||
DefaultConsoleDevice = 0x1,
|
||||
DefaultMultimediaDevice = 0x2,
|
||||
DefaultCommunicationsDevice = 0x4,
|
||||
DefaultGameDevice = 0x8,
|
||||
GlobalDefaultDevice = 0xf,
|
||||
InvalidDeviceRole = ~GlobalDefaultDevice
|
||||
} XAUDIO2_DEVICE_ROLE;
|
||||
|
||||
typedef struct XAUDIO2_DEVICE_DETAILS
|
||||
{
|
||||
WCHAR DeviceID[256];
|
||||
WCHAR DisplayName[256];
|
||||
XAUDIO2_DEVICE_ROLE Role;
|
||||
WAVEFORMATEXTENSIBLE OutputFormat;
|
||||
} XAUDIO2_DEVICE_DETAILS;
|
||||
|
||||
typedef struct XAUDIO2_VOICE_DETAILS
|
||||
{
|
||||
UINT32 CreationFlags;
|
||||
UINT32 InputChannels;
|
||||
UINT32 InputSampleRate;
|
||||
} XAUDIO2_VOICE_DETAILS;
|
||||
|
||||
typedef enum XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER
|
||||
{
|
||||
Processor1 = 0x00000001,
|
||||
Processor2 = 0x00000002,
|
||||
Processor3 = 0x00000004,
|
||||
Processor4 = 0x00000008,
|
||||
Processor5 = 0x00000010,
|
||||
Processor6 = 0x00000020,
|
||||
Processor7 = 0x00000040,
|
||||
Processor8 = 0x00000080,
|
||||
Processor9 = 0x00000100,
|
||||
Processor10 = 0x00000200,
|
||||
Processor11 = 0x00000400,
|
||||
Processor12 = 0x00000800,
|
||||
Processor13 = 0x00001000,
|
||||
Processor14 = 0x00002000,
|
||||
Processor15 = 0x00004000,
|
||||
Processor16 = 0x00008000,
|
||||
Processor17 = 0x00010000,
|
||||
Processor18 = 0x00020000,
|
||||
Processor19 = 0x00040000,
|
||||
Processor20 = 0x00080000,
|
||||
Processor21 = 0x00100000,
|
||||
Processor22 = 0x00200000,
|
||||
Processor23 = 0x00400000,
|
||||
Processor24 = 0x00800000,
|
||||
Processor25 = 0x01000000,
|
||||
Processor26 = 0x02000000,
|
||||
Processor27 = 0x04000000,
|
||||
Processor28 = 0x08000000,
|
||||
Processor29 = 0x10000000,
|
||||
Processor30 = 0x20000000,
|
||||
Processor31 = 0x40000000,
|
||||
Processor32 = 0x80000000,
|
||||
XAUDIO2_ANY_PROCESSOR = 0xffffffff,
|
||||
XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR
|
||||
} XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER, XAUDIO2_PROCESSOR;
|
||||
|
||||
typedef struct XAUDIO2_VOICE_SENDS
|
||||
{
|
||||
UINT32 OutputCount;
|
||||
IXAudio2Voice** pOutputVoices;
|
||||
} XAUDIO2_VOICE_SENDS;
|
||||
|
||||
typedef struct XAUDIO2_EFFECT_DESCRIPTOR
|
||||
{
|
||||
IUnknown* pEffect;
|
||||
BOOL InitialState;
|
||||
UINT32 OutputChannels;
|
||||
} XAUDIO2_EFFECT_DESCRIPTOR;
|
||||
|
||||
typedef struct XAUDIO2_EFFECT_CHAIN
|
||||
{
|
||||
UINT32 EffectCount;
|
||||
const XAUDIO2_EFFECT_DESCRIPTOR* pEffectDescriptors;
|
||||
} XAUDIO2_EFFECT_CHAIN;
|
||||
|
||||
typedef enum XAUDIO2_FILTER_TYPE
|
||||
{
|
||||
LowPassFilter,
|
||||
BandPassFilter,
|
||||
HighPassFilter
|
||||
} XAUDIO2_FILTER_TYPE;
|
||||
|
||||
typedef struct XAUDIO2_FILTER_PARAMETERS
|
||||
{
|
||||
XAUDIO2_FILTER_TYPE Type;
|
||||
float Frequency;
|
||||
float OneOverQ;
|
||||
|
||||
} XAUDIO2_FILTER_PARAMETERS;
|
||||
|
||||
typedef struct XAUDIO2_BUFFER
|
||||
{
|
||||
UINT32 Flags;
|
||||
UINT32 AudioBytes;
|
||||
const BYTE* pAudioData;
|
||||
UINT32 PlayBegin;
|
||||
UINT32 PlayLength;
|
||||
UINT32 LoopBegin;
|
||||
UINT32 LoopLength;
|
||||
UINT32 LoopCount;
|
||||
void* pContext;
|
||||
} XAUDIO2_BUFFER;
|
||||
|
||||
typedef struct XAUDIO2_BUFFER_WMA
|
||||
{
|
||||
const UINT32* pDecodedPacketCumulativeBytes;
|
||||
UINT32 PacketCount;
|
||||
} XAUDIO2_BUFFER_WMA;
|
||||
|
||||
typedef struct XAUDIO2_VOICE_STATE
|
||||
{
|
||||
void* pCurrentBufferContext;
|
||||
UINT32 BuffersQueued;
|
||||
UINT64 SamplesPlayed;
|
||||
} XAUDIO2_VOICE_STATE;
|
||||
|
||||
typedef struct XAUDIO2_PERFORMANCE_DATA
|
||||
{
|
||||
UINT64 AudioCyclesSinceLastQuery;
|
||||
UINT64 TotalCyclesSinceLastQuery;
|
||||
UINT32 MinimumCyclesPerQuantum;
|
||||
UINT32 MaximumCyclesPerQuantum;
|
||||
UINT32 MemoryUsageInBytes;
|
||||
UINT32 CurrentLatencyInSamples;
|
||||
UINT32 GlitchesSinceEngineStarted;
|
||||
UINT32 ActiveSourceVoiceCount;
|
||||
UINT32 TotalSourceVoiceCount;
|
||||
UINT32 ActiveSubmixVoiceCount;
|
||||
UINT32 TotalSubmixVoiceCount;
|
||||
UINT32 ActiveXmaSourceVoices;
|
||||
UINT32 ActiveXmaStreams;
|
||||
} XAUDIO2_PERFORMANCE_DATA;
|
||||
|
||||
typedef struct XAUDIO2_DEBUG_CONFIGURATION
|
||||
{
|
||||
UINT32 TraceMask;
|
||||
UINT32 BreakMask;
|
||||
BOOL LogThreadID;
|
||||
BOOL LogFileline;
|
||||
BOOL LogFunctionName;
|
||||
BOOL LogTiming;
|
||||
} XAUDIO2_DEBUG_CONFIGURATION;
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2EngineCallback)
|
||||
{
|
||||
STDMETHOD_(void, OnProcessingPassStart) (THIS) PURE;
|
||||
STDMETHOD_(void, OnProcessingPassEnd) (THIS) PURE;
|
||||
STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT Error) PURE;
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2VoiceCallback)
|
||||
{
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired) PURE;
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS) PURE;
|
||||
STDMETHOD_(void, OnStreamEnd) (THIS) PURE;
|
||||
STDMETHOD_(void, OnBufferStart) (THIS_ void* pBufferContext) PURE;
|
||||
STDMETHOD_(void, OnBufferEnd) (THIS_ void* pBufferContext) PURE;
|
||||
STDMETHOD_(void, OnLoopEnd) (THIS_ void* pBufferContext) PURE;
|
||||
STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) PURE;
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2Voice)
|
||||
{
|
||||
#define Declare_IXAudio2Voice_Methods() \
|
||||
STDMETHOD_(void, GetVoiceDetails) (THIS_ XAUDIO2_VOICE_DETAILS* pVoiceDetails) PURE; \
|
||||
STDMETHOD(SetOutputVoices) (THIS_ const XAUDIO2_VOICE_SENDS* pSendList) PURE; \
|
||||
STDMETHOD(SetEffectChain) (THIS_ const XAUDIO2_EFFECT_CHAIN* pEffectChain) PURE; \
|
||||
STDMETHOD(EnableEffect) (THIS_ UINT32 EffectIndex, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD(DisableEffect) (THIS_ UINT32 EffectIndex, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetEffectState) (THIS_ UINT32 EffectIndex, BOOL* pEnabled) PURE; \
|
||||
STDMETHOD(SetEffectParameters) (THIS_ UINT32 EffectIndex, \
|
||||
const void* pParameters, \
|
||||
UINT32 ParametersByteSize, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD(GetEffectParameters) (THIS_ UINT32 EffectIndex, void* pParameters, \
|
||||
UINT32 ParametersByteSize) PURE; \
|
||||
STDMETHOD(SetFilterParameters) (THIS_ const XAUDIO2_FILTER_PARAMETERS* pParameters, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetFilterParameters) (THIS_ XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \
|
||||
STDMETHOD(SetVolume) (THIS_ float Volume, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetVolume) (THIS_ float* pVolume) PURE; \
|
||||
STDMETHOD(SetChannelVolumes) (THIS_ UINT32 Channels, const float* pVolumes, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetChannelVolumes) (THIS_ UINT32 Channels, float* pVolumes) PURE; \
|
||||
STDMETHOD(SetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \
|
||||
UINT32 SourceChannels, UINT32 DestinationChannels, \
|
||||
const float* pLevelMatrix, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \
|
||||
UINT32 SourceChannels, UINT32 DestinationChannels, \
|
||||
float* pLevelMatrix) PURE; \
|
||||
STDMETHOD_(void, DestroyVoice) (THIS) PURE
|
||||
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
};
|
||||
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2MasteringVoice, IXAudio2Voice)
|
||||
{
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2SubmixVoice, IXAudio2Voice)
|
||||
{
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2SourceVoice, IXAudio2Voice)
|
||||
{
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
STDMETHOD(Start) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD(Stop) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD(SubmitSourceBuffer) (THIS_ const XAUDIO2_BUFFER* pBuffer, const XAUDIO2_BUFFER_WMA* pBufferWMA X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(FlushSourceBuffers) (THIS) PURE;
|
||||
STDMETHOD(Discontinuity) (THIS) PURE;
|
||||
STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD_(void, GetState) (THIS_ XAUDIO2_VOICE_STATE* pVoiceState) PURE;
|
||||
STDMETHOD(SetFrequencyRatio) (THIS_ float Ratio,
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD_(void, GetFrequencyRatio) (THIS_ float* pRatio) PURE;
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2, IUnknown)
|
||||
{
|
||||
STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvInterface) PURE;
|
||||
STDMETHOD_(ULONG, AddRef) (THIS) PURE;
|
||||
STDMETHOD_(ULONG, Release) (THIS) PURE;
|
||||
STDMETHOD(GetDeviceCount) (THIS_ UINT32* pCount) PURE;
|
||||
STDMETHOD(GetDeviceDetails) (THIS_ UINT32 Index, XAUDIO2_DEVICE_DETAILS* pDeviceDetails) PURE;
|
||||
STDMETHOD(Initialize) (THIS_ UINT32 Flags X2DEFAULT(0),
|
||||
XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) PURE;
|
||||
STDMETHOD(RegisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE;
|
||||
STDMETHOD_(void, UnregisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE;
|
||||
STDMETHOD(CreateSourceVoice) (THIS_ IXAudio2SourceVoice** ppSourceVoice,
|
||||
const WAVEFORMATEX* pSourceFormat,
|
||||
UINT32 Flags X2DEFAULT(0),
|
||||
float MaxFrequencyRatio X2DEFAULT(XAUDIO2_DEFAULT_FREQ_RATIO),
|
||||
IXAudio2VoiceCallback* pCallback X2DEFAULT(NULL),
|
||||
const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL),
|
||||
const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(CreateSubmixVoice) (THIS_ IXAudio2SubmixVoice** ppSubmixVoice,
|
||||
UINT32 InputChannels, UINT32 InputSampleRate,
|
||||
UINT32 Flags X2DEFAULT(0), UINT32 ProcessingStage X2DEFAULT(0),
|
||||
const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL),
|
||||
const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(CreateMasteringVoice) (THIS_ IXAudio2MasteringVoice** ppMasteringVoice,
|
||||
UINT32 InputChannels X2DEFAULT(XAUDIO2_DEFAULT_CHANNELS),
|
||||
UINT32 InputSampleRate X2DEFAULT(XAUDIO2_DEFAULT_SAMPLERATE),
|
||||
UINT32 Flags X2DEFAULT(0), UINT32 DeviceIndex X2DEFAULT(0),
|
||||
const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(StartEngine) (THIS) PURE;
|
||||
STDMETHOD_(void, StopEngine) (THIS) PURE;
|
||||
STDMETHOD(CommitChanges) (THIS_ UINT32 OperationSet) PURE;
|
||||
STDMETHOD_(void, GetPerformanceData) (THIS_ XAUDIO2_PERFORMANCE_DATA* pPerfData) PURE;
|
||||
STDMETHOD_(void, SetDebugConfiguration) (THIS_ const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration,
|
||||
void* pReserved X2DEFAULT(NULL)) PURE;
|
||||
};
|
||||
|
||||
static inline HRESULT XAudio2Create(IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0),
|
||||
XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR))
|
||||
{
|
||||
IXAudio2* pXAudio2;
|
||||
HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? CLSID_XAudio2_Debug : CLSID_XAudio2,
|
||||
NULL, CLSCTX_INPROC_SERVER, IID_IXAudio2, reinterpret_cast<void**>(&pXAudio2));
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = pXAudio2->Initialize(Flags, XAudio2Processor);
|
||||
if (SUCCEEDED(hr))
|
||||
*ppXAudio2 = pXAudio2;
|
||||
else
|
||||
pXAudio2->Release();
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+19
-58
@@ -15,63 +15,19 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include "xaudio-c.h"
|
||||
#include "xaudio-c/xaudio-c.h"
|
||||
#include "general.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
xaudio2_t *xa;
|
||||
bool nonblock;
|
||||
} xa_t;
|
||||
|
||||
static xaudio2_new_t pxanew = NULL;
|
||||
static xaudio2_write_t pxawrite = NULL;
|
||||
static xaudio2_write_avail_t pxawrite_avail = NULL;
|
||||
static xaudio2_free_t pxafree = NULL;
|
||||
static HMODULE lib = NULL;
|
||||
|
||||
#define LIB_NAME "xaudio-c.dll"
|
||||
#define SYM(X) ((void*)GetProcAddress(lib, "xaudio2_" #X))
|
||||
|
||||
static void deinit_lib(void)
|
||||
{
|
||||
FreeModule(lib);
|
||||
lib = NULL;
|
||||
}
|
||||
|
||||
static bool init_lib(void)
|
||||
{
|
||||
if (lib)
|
||||
return true;
|
||||
|
||||
lib = LoadLibrary(LIB_NAME);
|
||||
if (!lib)
|
||||
return false;
|
||||
|
||||
pxanew = SYM(new);
|
||||
pxawrite = SYM(write);
|
||||
pxawrite_avail = SYM(write_avail);
|
||||
pxafree = SYM(free);
|
||||
|
||||
if (!pxanew || !pxawrite || !pxawrite_avail || !pxafree)
|
||||
{
|
||||
deinit_lib();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void* __xa_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
if (!init_lib())
|
||||
return NULL;
|
||||
|
||||
if (latency < 8)
|
||||
latency = 8; // Do not allow shenanigans.
|
||||
|
||||
@@ -80,11 +36,10 @@ static void* __xa_init(const char* device, unsigned rate, unsigned latency)
|
||||
return NULL;
|
||||
|
||||
size_t bufsize = latency * rate / 1000;
|
||||
bufsize = next_pow2(bufsize);
|
||||
|
||||
SSNES_LOG("XAudio2: Requesting %d ms latency, using %d ms latency.\n", latency, (int)bufsize * 1000 / rate);
|
||||
|
||||
xa->xa = pxanew(rate, 2, 16, bufsize << 2);
|
||||
xa->xa = xaudio2_new(rate, 2, bufsize * 2 * sizeof(float));
|
||||
if (!xa->xa)
|
||||
{
|
||||
SSNES_ERR("Failed to init XAudio2.\n");
|
||||
@@ -99,11 +54,16 @@ static ssize_t __xa_write(void* data, const void* buf, size_t size)
|
||||
xa_t *xa = data;
|
||||
if (xa->nonblock)
|
||||
{
|
||||
size_t avail = pxawrite_avail(xa->xa);
|
||||
size_t avail = xaudio2_write_avail(xa->xa);
|
||||
if (avail == 0)
|
||||
return 0;
|
||||
if (avail < size)
|
||||
size = avail;
|
||||
}
|
||||
return pxawrite(xa->xa, buf, size);
|
||||
size_t ret = xaudio2_write(xa->xa, buf, size);
|
||||
if (ret == 0)
|
||||
return -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool __xa_stop(void *data)
|
||||
@@ -124,16 +84,21 @@ static bool __xa_start(void *data)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool __xa_use_float(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __xa_free(void *data)
|
||||
{
|
||||
xa_t *xa = data;
|
||||
if (xa && xa->xa)
|
||||
if (xa)
|
||||
{
|
||||
if (pxafree)
|
||||
pxafree(xa->xa);
|
||||
if (xa->xa)
|
||||
xaudio2_free(xa->xa);
|
||||
free(xa);
|
||||
}
|
||||
deinit_lib();
|
||||
}
|
||||
|
||||
const audio_driver_t audio_xa = {
|
||||
@@ -142,12 +107,8 @@ const audio_driver_t audio_xa = {
|
||||
.stop = __xa_stop,
|
||||
.start = __xa_start,
|
||||
.set_nonblock_state = __xa_set_nonblock_state,
|
||||
.use_float = __xa_use_float,
|
||||
.free = __xa_free,
|
||||
.ident = "xaudio"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+50
-35
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
#include "autosave.h"
|
||||
#include "SDL.h"
|
||||
#include "thread.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
@@ -26,11 +26,11 @@
|
||||
struct autosave
|
||||
{
|
||||
volatile bool quit;
|
||||
SDL_mutex *lock;
|
||||
slock_t *lock;
|
||||
|
||||
SDL_mutex *cond_lock;
|
||||
SDL_cond *cond;
|
||||
SDL_Thread *thread;
|
||||
slock_t *cond_lock;
|
||||
scond_t *cond;
|
||||
sthread_t *thread;
|
||||
|
||||
void *buffer;
|
||||
const void *snes_buffer;
|
||||
@@ -39,35 +39,49 @@ struct autosave
|
||||
unsigned interval;
|
||||
};
|
||||
|
||||
static int autosave_thread(void *data)
|
||||
static void autosave_thread(void *data)
|
||||
{
|
||||
autosave_t *save = data;
|
||||
|
||||
bool first_log = true;
|
||||
|
||||
while (!save->quit)
|
||||
{
|
||||
autosave_lock(save);
|
||||
memcpy(save->buffer, save->snes_buffer, save->bufsize);
|
||||
bool differ = memcmp(save->buffer, save->snes_buffer, save->bufsize) != 0;
|
||||
if (differ)
|
||||
memcpy(save->buffer, save->snes_buffer, save->bufsize);
|
||||
autosave_unlock(save);
|
||||
|
||||
// Should probably deal with this more elegantly.
|
||||
FILE *file = fopen(save->path, "wb");
|
||||
if (file)
|
||||
if (differ)
|
||||
{
|
||||
SSNES_LOG("Autosaving SRAM to \"%s\"\n", save->path);
|
||||
bool failed = false;
|
||||
failed |= fwrite(save->buffer, 1, save->bufsize, file) != save->bufsize;
|
||||
failed |= fflush(file) != 0;
|
||||
failed |= fclose(file) != 0;
|
||||
if (failed)
|
||||
SSNES_WARN("Failed to autosave SRAM! Disk might be full.\n");
|
||||
// Should probably deal with this more elegantly.
|
||||
FILE *file = fopen(save->path, "wb");
|
||||
if (file)
|
||||
{
|
||||
// Avoid spamming down stderr ... :)
|
||||
if (first_log)
|
||||
{
|
||||
SSNES_LOG("Autosaving SRAM to \"%s\", will continue to check every %u seconds ...\n", save->path, save->interval);
|
||||
first_log = false;
|
||||
}
|
||||
else
|
||||
SSNES_LOG("SRAM changed ... autosaving ...\n");
|
||||
|
||||
bool failed = false;
|
||||
failed |= fwrite(save->buffer, 1, save->bufsize, file) != save->bufsize;
|
||||
failed |= fflush(file) != 0;
|
||||
failed |= fclose(file) != 0;
|
||||
if (failed)
|
||||
SSNES_WARN("Failed to autosave SRAM! Disk might be full.\n");
|
||||
}
|
||||
}
|
||||
|
||||
SDL_mutexP(save->cond_lock);
|
||||
slock_lock(save->cond_lock);
|
||||
if (!save->quit)
|
||||
SDL_CondWaitTimeout(save->cond, save->cond_lock, save->interval * 1000);
|
||||
SDL_mutexV(save->cond_lock);
|
||||
scond_wait_timeout(save->cond, save->cond_lock, save->interval * 1000);
|
||||
slock_unlock(save->cond_lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval)
|
||||
@@ -87,37 +101,38 @@ autosave_t *autosave_new(const char *path, const void *data, size_t size, unsign
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(handle->buffer, handle->snes_buffer, handle->bufsize);
|
||||
|
||||
handle->lock = SDL_CreateMutex();
|
||||
handle->cond_lock = SDL_CreateMutex();
|
||||
handle->cond = SDL_CreateCond();
|
||||
handle->lock = slock_new();
|
||||
handle->cond_lock = slock_new();
|
||||
handle->cond = scond_new();
|
||||
|
||||
handle->thread = SDL_CreateThread(autosave_thread, handle);
|
||||
handle->thread = sthread_create(autosave_thread, handle);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void autosave_lock(autosave_t *handle)
|
||||
{
|
||||
SDL_mutexP(handle->lock);
|
||||
slock_lock(handle->lock);
|
||||
}
|
||||
|
||||
void autosave_unlock(autosave_t *handle)
|
||||
{
|
||||
SDL_mutexV(handle->lock);
|
||||
slock_unlock(handle->lock);
|
||||
}
|
||||
|
||||
void autosave_free(autosave_t *handle)
|
||||
{
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
slock_lock(handle->cond_lock);
|
||||
handle->quit = true;
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
SDL_CondSignal(handle->cond);
|
||||
SDL_WaitThread(handle->thread, NULL);
|
||||
slock_unlock(handle->cond_lock);
|
||||
scond_signal(handle->cond);
|
||||
sthread_join(handle->thread);
|
||||
|
||||
SDL_DestroyMutex(handle->lock);
|
||||
SDL_DestroyMutex(handle->cond_lock);
|
||||
SDL_DestroyCond(handle->cond);
|
||||
slock_free(handle->lock);
|
||||
slock_free(handle->cond_lock);
|
||||
scond_free(handle->cond);
|
||||
|
||||
free(handle->buffer);
|
||||
free(handle);
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES 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 Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES 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 SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "bps.h"
|
||||
#include "movie.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum bps_mode
|
||||
{
|
||||
SOURCE_READ = 0,
|
||||
TARGET_READ,
|
||||
SOURCE_COPY,
|
||||
TARGET_COPY
|
||||
};
|
||||
|
||||
struct bps_data
|
||||
{
|
||||
const uint8_t *modify_data, *source_data;
|
||||
uint8_t *target_data;
|
||||
size_t modify_length, source_length, target_length;
|
||||
size_t modify_offset, source_offset, target_offset;
|
||||
uint32_t modify_checksum, source_checksum, target_checksum;
|
||||
|
||||
size_t source_relative_offset, target_relative_offset, output_offset;
|
||||
};
|
||||
|
||||
static uint8_t bps_read(struct bps_data *bps)
|
||||
{
|
||||
uint8_t data = bps->modify_data[bps->modify_offset++];
|
||||
bps->modify_checksum = crc32_adjust(bps->modify_checksum, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
static uint64_t bps_decode(struct bps_data *bps)
|
||||
{
|
||||
uint64_t data = 0, shift = 1;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
uint8_t x = bps_read(bps);
|
||||
data += (x & 0x7f) * shift;
|
||||
if (x & 0x80)
|
||||
break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void bps_write(struct bps_data *bps, uint8_t data)
|
||||
{
|
||||
bps->target_data[bps->output_offset++] = data;
|
||||
bps->target_checksum = crc32_adjust(bps->target_checksum, data);
|
||||
}
|
||||
|
||||
bps_error_t bps_apply_patch(
|
||||
const uint8_t *modify_data, size_t modify_length,
|
||||
const uint8_t *source_data, size_t source_length,
|
||||
uint8_t *target_data, size_t *target_length)
|
||||
{
|
||||
if (modify_length < 19)
|
||||
return BPS_PATCH_TOO_SMALL;
|
||||
|
||||
struct bps_data bps = {
|
||||
.modify_data = modify_data,
|
||||
.modify_length = modify_length,
|
||||
.target_data = target_data,
|
||||
.target_length = *target_length,
|
||||
.source_data = source_data,
|
||||
.source_length = source_length,
|
||||
|
||||
.modify_checksum = ~0,
|
||||
.target_checksum = ~0,
|
||||
};
|
||||
|
||||
if ((bps_read(&bps) != 'B') || (bps_read(&bps) != 'P') || (bps_read(&bps) != 'S') || (bps_read(&bps) != '1'))
|
||||
return BPS_PATCH_INVALID_HEADER;
|
||||
|
||||
size_t modify_source_size = bps_decode(&bps);
|
||||
size_t modify_target_size = bps_decode(&bps);
|
||||
size_t modify_markup_size = bps_decode(&bps);
|
||||
for (size_t i = 0; i < modify_markup_size; i++)
|
||||
bps_read(&bps);
|
||||
|
||||
if (modify_source_size > bps.source_length)
|
||||
return BPS_SOURCE_TOO_SMALL;
|
||||
if (modify_target_size > bps.target_length)
|
||||
return BPS_TARGET_TOO_SMALL;
|
||||
|
||||
while (bps.modify_offset < bps.modify_length - 12)
|
||||
{
|
||||
size_t length = bps_decode(&bps);
|
||||
unsigned mode = length & 3;
|
||||
length = (length >> 2) + 1;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case SOURCE_READ:
|
||||
while (length--)
|
||||
bps_write(&bps, bps.source_data[bps.output_offset]);
|
||||
break;
|
||||
|
||||
case TARGET_READ:
|
||||
while (length--)
|
||||
bps_write(&bps, bps_read(&bps));
|
||||
break;
|
||||
|
||||
case SOURCE_COPY:
|
||||
case TARGET_COPY:
|
||||
{
|
||||
int offset = bps_decode(&bps);
|
||||
bool negative = offset & 1;
|
||||
offset >>= 1;
|
||||
if (negative)
|
||||
offset = -offset;
|
||||
|
||||
if (mode == SOURCE_COPY)
|
||||
{
|
||||
bps.source_offset += offset;
|
||||
while (length--)
|
||||
bps_write(&bps, bps.source_data[bps.source_offset++]);
|
||||
}
|
||||
else
|
||||
{
|
||||
bps.target_offset += offset;
|
||||
while (length--)
|
||||
bps_write(&bps, bps.target_data[bps.target_offset++]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t modify_source_checksum = 0, modify_target_checksum = 0, modify_modify_checksum = 0;
|
||||
for (unsigned i = 0; i < 32; i += 8)
|
||||
modify_source_checksum |= bps_read(&bps) << i;
|
||||
for (unsigned i = 0; i < 32; i += 8)
|
||||
modify_target_checksum |= bps_read(&bps) << i;
|
||||
|
||||
uint32_t checksum = ~bps.modify_checksum;
|
||||
for (unsigned i = 0; i < 32; i += 8)
|
||||
modify_modify_checksum |= bps_read(&bps) << i;
|
||||
|
||||
bps.source_checksum = crc32_calculate(bps.source_data, bps.source_length);
|
||||
bps.target_checksum = ~bps.target_checksum;
|
||||
|
||||
if (bps.source_checksum != modify_source_checksum)
|
||||
return BPS_SOURCE_CHECKSUM_INVALID;
|
||||
if (bps.target_checksum != modify_target_checksum)
|
||||
return BPS_TARGET_CHECKSUM_INVALID;
|
||||
if (checksum != modify_modify_checksum)
|
||||
return BPS_PATCH_CHECKSUM_INVALID;
|
||||
|
||||
*target_length = modify_target_size;
|
||||
|
||||
return BPS_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES 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 Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES 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 SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __BPS_H
|
||||
#define __BPS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// BPS implementation from bSNES (nall::).
|
||||
|
||||
typedef enum bps_error
|
||||
{
|
||||
BPS_UNKNOWN,
|
||||
BPS_SUCCESS,
|
||||
BPS_PATCH_TOO_SMALL,
|
||||
BPS_PATCH_INVALID_HEADER,
|
||||
BPS_SOURCE_TOO_SMALL,
|
||||
BPS_TARGET_TOO_SMALL,
|
||||
BPS_SOURCE_CHECKSUM_INVALID,
|
||||
BPS_TARGET_CHECKSUM_INVALID,
|
||||
BPS_PATCH_CHECKSUM_INVALID
|
||||
} bps_error_t;
|
||||
|
||||
bps_error_t bps_apply_patch(
|
||||
const uint8_t *patch_data, size_t patch_length,
|
||||
const uint8_t *source_data, size_t source_length,
|
||||
uint8_t *target_data, size_t *target_length);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,10 +21,17 @@
|
||||
#include "general.h"
|
||||
#include "strl.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
#include "conf/config_file.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/tree.h>
|
||||
@@ -51,7 +58,8 @@ static char* strcat_alloc(char *dest, const char *input)
|
||||
size_t required_len = dest_len + input_len + 1;
|
||||
|
||||
char *output = realloc(dest, required_len);
|
||||
assert(output);
|
||||
if (!output)
|
||||
return NULL;
|
||||
|
||||
if (dest)
|
||||
strlcat(output, input, required_len);
|
||||
@@ -61,62 +69,175 @@ static char* strcat_alloc(char *dest, const char *input)
|
||||
return output;
|
||||
}
|
||||
|
||||
static void xml_grab_cheat(struct cheat *cht, xmlNodePtr ptr)
|
||||
static bool xml_grab_cheat(struct cheat *cht, xmlNodePtr ptr)
|
||||
{
|
||||
if (!ptr)
|
||||
return false;
|
||||
|
||||
memset(cht, 0, sizeof(struct cheat));
|
||||
bool first = true;
|
||||
for (xmlNodePtr node = ptr; node; node = node->next)
|
||||
|
||||
for (; ptr; ptr = ptr->next)
|
||||
{
|
||||
if (strcmp((const char*)node->name, "description") == 0)
|
||||
if (strcmp((const char*)ptr->name, "description") == 0)
|
||||
{
|
||||
cht->desc = (char*)xmlNodeGetContent(node);
|
||||
cht->desc = (char*)xmlNodeGetContent(ptr);
|
||||
}
|
||||
else if (strcmp((const char*)node->name, "code") == 0)
|
||||
else if (strcmp((const char*)ptr->name, "code") == 0)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
cht->code = strcat_alloc(cht->code, "+");
|
||||
if (!cht->code)
|
||||
return false;
|
||||
}
|
||||
|
||||
xmlChar *code = xmlNodeGetContent(node);
|
||||
assert(code);
|
||||
xmlChar *code = xmlNodeGetContent(ptr);
|
||||
if (!code)
|
||||
return false;
|
||||
|
||||
cht->code = strcat_alloc(cht->code, (const char*)code);
|
||||
xmlFree(code);
|
||||
if (!cht->code)
|
||||
return false;
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void xml_grab_cheats(cheat_manager_t *handle, xmlNodePtr ptr)
|
||||
static bool xml_grab_cheats(cheat_manager_t *handle, xmlNodePtr ptr)
|
||||
{
|
||||
xmlNodePtr node = NULL;
|
||||
for (node = ptr; node; node = node->next)
|
||||
for (; ptr; ptr = ptr->next)
|
||||
{
|
||||
if (strcmp((const char*)node->name, "name") == 0)
|
||||
if (strcmp((const char*)ptr->name, "name") == 0)
|
||||
{
|
||||
xmlChar *name = xmlNodeGetContent(node);
|
||||
xmlChar *name = xmlNodeGetContent(ptr);
|
||||
if (name)
|
||||
{
|
||||
SSNES_LOG("Found cheat for game: \"%s\"\n", name);
|
||||
xmlFree(name);
|
||||
}
|
||||
}
|
||||
else if (strcmp((const char*)node->name, "cheat") == 0)
|
||||
else if (strcmp((const char*)ptr->name, "cheat") == 0)
|
||||
{
|
||||
if (handle->size == handle->buf_size)
|
||||
{
|
||||
handle->buf_size *= 2;
|
||||
handle->cheats = realloc(handle->cheats, handle->buf_size * sizeof(struct cheat));
|
||||
assert(handle->cheats);
|
||||
if (!handle->cheats)
|
||||
return false;
|
||||
}
|
||||
|
||||
xml_grab_cheat(&handle->cheats[handle->size++], node->children);
|
||||
if (xml_grab_cheat(&handle->cheats[handle->size], ptr->children))
|
||||
handle->size++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cheat_manager_apply_cheats(cheat_manager_t *handle)
|
||||
{
|
||||
unsigned index = 0;
|
||||
psnes_cheat_reset();
|
||||
for (unsigned i = 0; i < handle->size; i++)
|
||||
{
|
||||
if (handle->cheats[i].state)
|
||||
psnes_cheat_set(index++, true, handle->cheats[i].code);
|
||||
}
|
||||
}
|
||||
|
||||
static void cheat_manager_load_config(cheat_manager_t *handle, const char *path, const char *sha256)
|
||||
{
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
if (!(*path))
|
||||
return;
|
||||
|
||||
config_file_t *conf = config_file_new(path);
|
||||
if (!conf)
|
||||
return;
|
||||
|
||||
char *str;
|
||||
if (!config_get_string(conf, sha256, &str))
|
||||
{
|
||||
config_file_free(conf);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *num = strtok(str, ";");
|
||||
while (num)
|
||||
{
|
||||
unsigned index = strtoul(num, NULL, 0);
|
||||
if (index < handle->size)
|
||||
handle->cheats[index].state = true;
|
||||
|
||||
num = strtok(NULL, ";");
|
||||
}
|
||||
|
||||
free(str);
|
||||
config_file_free(conf);
|
||||
|
||||
cheat_manager_apply_cheats(handle);
|
||||
|
||||
#else
|
||||
(void)handle;
|
||||
(void)path;
|
||||
(void)sha256;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cheat_manager_save_config(cheat_manager_t *handle, const char *path, const char *sha256)
|
||||
{
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
if (!(*path))
|
||||
return;
|
||||
|
||||
config_file_t *conf = config_file_new(path);
|
||||
if (!conf)
|
||||
conf = config_file_new(NULL);
|
||||
|
||||
if (!conf)
|
||||
{
|
||||
SSNES_ERR("Cannot save XML cheat settings!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char conf_str[512] = {0};
|
||||
char tmp[32] = {0};
|
||||
|
||||
for (unsigned i = 0; i < handle->size; i++)
|
||||
{
|
||||
if (handle->cheats[i].state)
|
||||
{
|
||||
snprintf(tmp, sizeof(tmp), "%u;", i);
|
||||
strlcat(conf_str, tmp, sizeof(conf_str));
|
||||
}
|
||||
}
|
||||
|
||||
if (*conf_str)
|
||||
conf_str[strlen(conf_str) - 1] = '\0'; // Remove the trailing ';'
|
||||
|
||||
config_set_string(conf, sha256, conf_str);
|
||||
|
||||
if (!config_file_write(conf, path))
|
||||
SSNES_ERR("Failed to write XML cheat settings to \"%s\"! Check permissions!\n", path);
|
||||
|
||||
config_file_free(conf);
|
||||
|
||||
#else
|
||||
(void)handle;
|
||||
(void)path;
|
||||
(void)sha256;
|
||||
#endif
|
||||
}
|
||||
|
||||
cheat_manager_t* cheat_manager_new(const char *path)
|
||||
{
|
||||
LIBXML_TEST_VERSION;
|
||||
|
||||
psnes_cheat_reset();
|
||||
|
||||
xmlParserCtxtPtr ctx = NULL;
|
||||
@@ -125,8 +246,8 @@ cheat_manager_t* cheat_manager_new(const char *path)
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
handle->buf_size = 32;
|
||||
handle->cheats = malloc(handle->buf_size * sizeof(struct cheat));
|
||||
handle->buf_size = 1;
|
||||
handle->cheats = calloc(handle->buf_size, sizeof(struct cheat));
|
||||
if (!handle->cheats)
|
||||
{
|
||||
handle->buf_size = 0;
|
||||
@@ -172,7 +293,7 @@ cheat_manager_t* cheat_manager_new(const char *path)
|
||||
if (!sha256)
|
||||
continue;
|
||||
|
||||
if (memcmp(sha256, g_extern.sha256, 64) == 0)
|
||||
if (strcmp((const char*)sha256, g_extern.sha256) == 0)
|
||||
{
|
||||
xmlFree(sha256);
|
||||
break;
|
||||
@@ -185,7 +306,19 @@ cheat_manager_t* cheat_manager_new(const char *path)
|
||||
if (!cur)
|
||||
goto error;
|
||||
|
||||
xml_grab_cheats(handle, cur->children);
|
||||
if (!xml_grab_cheats(handle, cur->children))
|
||||
{
|
||||
SSNES_ERR("Failed to grab cheats. This should not happen.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (handle->size == 0)
|
||||
{
|
||||
SSNES_ERR("Did not find any cheats in XML file: %s\n", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cheat_manager_load_config(handle, g_settings.cheat_settings_path, g_extern.sha256);
|
||||
|
||||
xmlFreeDoc(doc);
|
||||
xmlFreeParserCtxt(ctx);
|
||||
@@ -207,6 +340,7 @@ void cheat_manager_free(cheat_manager_t *handle)
|
||||
|
||||
if (handle->cheats)
|
||||
{
|
||||
cheat_manager_save_config(handle, g_settings.cheat_settings_path, g_extern.sha256);
|
||||
for (unsigned i = 0; i < handle->size; i++)
|
||||
{
|
||||
xmlFree(handle->cheats[i].desc);
|
||||
@@ -228,18 +362,11 @@ static void cheat_manager_update(cheat_manager_t *handle)
|
||||
SSNES_LOG("%s\n", msg);
|
||||
}
|
||||
|
||||
|
||||
void cheat_manager_toggle(cheat_manager_t *handle)
|
||||
{
|
||||
handle->cheats[handle->ptr].state ^= true;
|
||||
|
||||
unsigned index = 0;
|
||||
psnes_cheat_reset();
|
||||
for (unsigned i = 0; i < handle->size; i++)
|
||||
{
|
||||
if (handle->cheats[handle->ptr].state)
|
||||
psnes_cheat_set(index++, true, handle->cheats[handle->ptr].code);
|
||||
}
|
||||
|
||||
cheat_manager_apply_cheats(handle);
|
||||
cheat_manager_update(handle);
|
||||
}
|
||||
|
||||
|
||||
+350
-76
@@ -21,19 +21,52 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include "strl.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/param.h> // MAXPATHLEN
|
||||
#else
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifndef MAXPATHLEN
|
||||
#ifdef PATH_MAX
|
||||
#define MAXPATHLEN PATH_MAX
|
||||
#else
|
||||
#define MAXPATHLEN 512
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAX_INCLUDE_DEPTH 16
|
||||
|
||||
struct entry_list
|
||||
{
|
||||
bool readonly; // If we got this from an #include, do not allow write.
|
||||
char *key;
|
||||
char *value;
|
||||
struct entry_list *next;
|
||||
};
|
||||
|
||||
struct include_list
|
||||
{
|
||||
char *path;
|
||||
struct include_list *next;
|
||||
};
|
||||
|
||||
struct config_file
|
||||
{
|
||||
char *path;
|
||||
struct entry_list *entries;
|
||||
struct entry_list *tail;
|
||||
unsigned include_depth;
|
||||
|
||||
struct include_list *includes;
|
||||
};
|
||||
|
||||
static config_file_t *config_file_new_internal(const char *path, unsigned depth);
|
||||
|
||||
static char *getaline(FILE *file)
|
||||
{
|
||||
char *newline = malloc(9);
|
||||
@@ -56,13 +89,189 @@ static char *getaline(FILE *file)
|
||||
return newline;
|
||||
}
|
||||
|
||||
static bool parse_line(struct entry_list *list, char *line)
|
||||
static char* extract_value(char *line, bool is_value)
|
||||
{
|
||||
if (is_value)
|
||||
{
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
|
||||
// If we don't have an equal sign here, we've got an invalid string...
|
||||
if (*line != '=')
|
||||
return false;
|
||||
|
||||
line++;
|
||||
}
|
||||
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
|
||||
// We have a full string. Read until next ".
|
||||
if (*line == '"')
|
||||
{
|
||||
line++;
|
||||
char *tok = strtok(line, "\"");
|
||||
if (!tok)
|
||||
return false;
|
||||
return strdup(tok);
|
||||
}
|
||||
else if (*line == '\0') // Nothing :(
|
||||
return NULL;
|
||||
else // We don't have that... Read till next space.
|
||||
{
|
||||
char *tok = strtok(line, " \n\t\f\r\v");
|
||||
if (tok)
|
||||
return strdup(tok);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void set_list_readonly(struct entry_list *list)
|
||||
{
|
||||
while (list)
|
||||
{
|
||||
list->readonly = true;
|
||||
list = list->next;
|
||||
}
|
||||
}
|
||||
|
||||
// Move semantics? :)
|
||||
static void add_child_list(config_file_t *parent, config_file_t *child)
|
||||
{
|
||||
if (parent->entries)
|
||||
{
|
||||
struct entry_list *head = parent->entries;
|
||||
while (head->next)
|
||||
head = head->next;
|
||||
|
||||
set_list_readonly(child->entries);
|
||||
head->next = child->entries;
|
||||
}
|
||||
else
|
||||
{
|
||||
set_list_readonly(child->entries);
|
||||
parent->entries = child->entries;
|
||||
}
|
||||
|
||||
child->entries = NULL;
|
||||
|
||||
// Rebase tail.
|
||||
if (parent->entries)
|
||||
{
|
||||
struct entry_list *head = parent->entries;
|
||||
while (head->next)
|
||||
head = head->next;
|
||||
parent->tail = head;
|
||||
}
|
||||
else
|
||||
parent->tail = NULL;
|
||||
}
|
||||
|
||||
static void add_include_list(config_file_t *conf, const char *path)
|
||||
{
|
||||
struct include_list *head = conf->includes;
|
||||
struct include_list *node = calloc(1, sizeof(*node));
|
||||
node->path = strdup(path);
|
||||
|
||||
if (head)
|
||||
{
|
||||
while (head->next)
|
||||
head = head->next;
|
||||
|
||||
head->next = node;
|
||||
}
|
||||
else
|
||||
conf->includes = node;
|
||||
}
|
||||
|
||||
static void add_sub_conf(config_file_t *conf, char *line)
|
||||
{
|
||||
char *path = extract_value(line, false);
|
||||
if (!path)
|
||||
return;
|
||||
|
||||
add_include_list(conf, path);
|
||||
|
||||
char real_path[MAXPATHLEN];
|
||||
|
||||
#ifndef _WIN32
|
||||
if (*path == '/')
|
||||
{
|
||||
strlcpy(real_path, path, sizeof(real_path));
|
||||
}
|
||||
else if (*path == '~')
|
||||
{
|
||||
const char *home = getenv("HOME");
|
||||
strlcpy(real_path, home ? home : "/", sizeof(real_path));
|
||||
strlcat(real_path, path + 1, sizeof(real_path));
|
||||
}
|
||||
else
|
||||
{
|
||||
strlcpy(real_path, conf->path, sizeof(real_path));
|
||||
char *split = strrchr(real_path, '/');
|
||||
if (split)
|
||||
{
|
||||
split[1] = '\0';
|
||||
strlcat(real_path, path, sizeof(real_path));
|
||||
}
|
||||
else
|
||||
strlcpy(real_path, path, sizeof(real_path));
|
||||
}
|
||||
#else
|
||||
// Accomodate POSIX systems on Win32.
|
||||
bool is_full_path = *path == '/';
|
||||
|
||||
if (is_full_path)
|
||||
strlcpy(real_path, path, sizeof(real_path));
|
||||
else
|
||||
GetFullPathNameA(path, sizeof(real_path), real_path, NULL);
|
||||
|
||||
if (strcmp(path, real_path) != 0)
|
||||
{
|
||||
strlcpy(real_path, conf->path, sizeof(real_path));
|
||||
char *split = strrchr(real_path, '/');
|
||||
if (!split)
|
||||
split = strrchr(real_path, '\\');
|
||||
|
||||
split[1] = '\0';
|
||||
strlcat(real_path, path, sizeof(real_path));
|
||||
}
|
||||
#endif
|
||||
|
||||
config_file_t *sub_conf = config_file_new_internal(real_path, conf->include_depth + 1);
|
||||
if (!sub_conf)
|
||||
{
|
||||
free(path);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pilfer internal list! :D
|
||||
add_child_list(conf, sub_conf);
|
||||
config_file_free(sub_conf);
|
||||
free(path);
|
||||
}
|
||||
|
||||
static bool parse_line(config_file_t *conf, struct entry_list *list, char *line)
|
||||
{
|
||||
// Remove everything after comment.
|
||||
char *comment = strchr(line, '#');
|
||||
if (comment)
|
||||
*comment = '\0';
|
||||
|
||||
// Starting line with # and include includes config files. :)
|
||||
if ((comment == line) && (conf->include_depth < MAX_INCLUDE_DEPTH))
|
||||
{
|
||||
comment++;
|
||||
if (strstr(comment, "include ") == comment)
|
||||
{
|
||||
add_sub_conf(conf, comment + strlen("include "));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (conf->include_depth >= MAX_INCLUDE_DEPTH)
|
||||
fprintf(stderr, "!!! #include depth exceeded for config! Might be a cycle.\n");
|
||||
|
||||
// Skips to first character.
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
@@ -84,66 +293,42 @@ static bool parse_line(struct entry_list *list, char *line)
|
||||
key[index] = '\0';
|
||||
list->key = key;
|
||||
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
|
||||
// If we don't have an equal sign here, we've got an invalid string...
|
||||
if (*line != '=')
|
||||
list->value = extract_value(line, true);
|
||||
if (!list->value)
|
||||
{
|
||||
list->key = NULL;
|
||||
free(key);
|
||||
return false;
|
||||
}
|
||||
line++;
|
||||
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
|
||||
// We have a full string. Read until next ".
|
||||
if (*line == '"')
|
||||
{
|
||||
char *tok = strtok(line + 1, "\"");
|
||||
if (tok == NULL)
|
||||
{
|
||||
list->key = NULL;
|
||||
free(key);
|
||||
return false;
|
||||
}
|
||||
list->value = strdup(tok);
|
||||
}
|
||||
else // We don't have that... Read till next space.
|
||||
{
|
||||
char *tok = strtok(line, " \t\f");
|
||||
if (tok == NULL)
|
||||
{
|
||||
list->key = NULL;
|
||||
free(key);
|
||||
return false;
|
||||
}
|
||||
list->value = strdup(tok);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
config_file_t *config_file_new(const char *path)
|
||||
static config_file_t *config_file_new_internal(const char *path, unsigned depth)
|
||||
{
|
||||
|
||||
struct config_file *conf = calloc(1, sizeof(*conf));
|
||||
if (conf == NULL)
|
||||
if (!conf)
|
||||
return NULL;
|
||||
|
||||
if (path == NULL)
|
||||
if (!path)
|
||||
return conf;
|
||||
|
||||
FILE *file = fopen(path, "r");
|
||||
if (!file)
|
||||
conf->path = strdup(path);
|
||||
if (!conf->path)
|
||||
{
|
||||
free(conf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct entry_list *tail = conf->entries;
|
||||
conf->include_depth = depth;
|
||||
|
||||
FILE *file = fopen(path, "r");
|
||||
if (!file)
|
||||
{
|
||||
free(conf->path);
|
||||
free(conf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (!feof(file))
|
||||
{
|
||||
@@ -152,50 +337,69 @@ config_file_t *config_file_new(const char *path)
|
||||
|
||||
if (line)
|
||||
{
|
||||
if (parse_line(list, line))
|
||||
if (parse_line(conf, list, line))
|
||||
{
|
||||
if (conf->entries == NULL)
|
||||
if (conf->entries)
|
||||
{
|
||||
conf->entries = list;
|
||||
tail = list;
|
||||
conf->tail->next = list;
|
||||
conf->tail = list;
|
||||
}
|
||||
else
|
||||
{
|
||||
tail->next = list;
|
||||
tail = list;
|
||||
conf->entries = list;
|
||||
conf->tail = list;
|
||||
}
|
||||
}
|
||||
|
||||
free(line);
|
||||
}
|
||||
|
||||
if (list != conf->tail)
|
||||
free(list);
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
config_file_t *config_file_new(const char *path)
|
||||
{
|
||||
return config_file_new_internal(path, 0);
|
||||
}
|
||||
|
||||
void config_file_free(config_file_t *conf)
|
||||
{
|
||||
if (conf != NULL)
|
||||
if (!conf)
|
||||
return;
|
||||
|
||||
struct entry_list *tmp = conf->entries;
|
||||
while (tmp)
|
||||
{
|
||||
struct entry_list *tmp = conf->entries;
|
||||
struct entry_list *old = tmp;
|
||||
while (tmp != NULL)
|
||||
{
|
||||
free(tmp->key);
|
||||
free(tmp->value);
|
||||
old = tmp;
|
||||
tmp = tmp->next;
|
||||
free(old);
|
||||
}
|
||||
free(conf);
|
||||
free(tmp->key);
|
||||
free(tmp->value);
|
||||
struct entry_list *hold = tmp;
|
||||
tmp = tmp->next;
|
||||
free(hold);
|
||||
}
|
||||
|
||||
struct include_list *inc_tmp = conf->includes;
|
||||
while (inc_tmp)
|
||||
{
|
||||
free(inc_tmp->path);
|
||||
struct include_list *hold = inc_tmp;
|
||||
inc_tmp = inc_tmp->next;
|
||||
free(hold);
|
||||
}
|
||||
|
||||
free(conf->path);
|
||||
free(conf);
|
||||
}
|
||||
|
||||
bool config_get_double(config_file_t *conf, const char *key, double *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
@@ -211,12 +415,19 @@ bool config_get_int(config_file_t *conf, const char *key, int *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
*in = strtol(list->value, NULL, 0);
|
||||
return true;
|
||||
errno = 0;
|
||||
int val = strtol(list->value, NULL, 0);
|
||||
if (errno == 0)
|
||||
{
|
||||
*in = val;
|
||||
return true;
|
||||
}
|
||||
return
|
||||
false;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
@@ -227,12 +438,19 @@ bool config_get_hex(config_file_t *conf, const char *key, unsigned *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
*in = strtol(list->value, NULL, 16);
|
||||
return true;
|
||||
errno = 0;
|
||||
unsigned val = strtoul(list->value, NULL, 16);
|
||||
if (errno == 0)
|
||||
{
|
||||
*in = val;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
@@ -243,11 +461,11 @@ bool config_get_char(config_file_t *conf, const char *key, char *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
if (strlen(list->value) > 1)
|
||||
if (list->value[0] && list->value[1])
|
||||
return false;
|
||||
*in = *list->value;
|
||||
return true;
|
||||
@@ -261,7 +479,7 @@ bool config_get_string(config_file_t *conf, const char *key, char **str)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
@@ -273,11 +491,27 @@ bool config_get_string(config_file_t *conf, const char *key, char **str)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool config_get_array(config_file_t *conf, const char *key, char *buf, size_t size)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
strlcpy(buf, list->value, size);
|
||||
return true;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool config_get_bool(config_file_t *conf, const char *key, bool *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
@@ -303,14 +537,15 @@ void config_set_string(config_file_t *conf, const char *key, const char *val)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
struct entry_list *last = list;
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
if (!list->readonly && (strcmp(key, list->key) == 0))
|
||||
{
|
||||
free(list->value);
|
||||
list->value = strdup(val);
|
||||
return;
|
||||
}
|
||||
|
||||
last = list;
|
||||
list = list->next;
|
||||
}
|
||||
@@ -374,11 +609,50 @@ bool config_file_write(config_file_t *conf, const char *path)
|
||||
|
||||
void config_file_dump(config_file_t *conf, FILE *file)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
struct include_list *includes = conf->includes;
|
||||
while (includes)
|
||||
{
|
||||
fprintf(file, "%s = \"%s\"\n", list->key, list->value);
|
||||
fprintf(file, "#include \"%s\"\n", includes->path);
|
||||
includes = includes->next;
|
||||
}
|
||||
|
||||
struct entry_list *list = conf->entries;
|
||||
while (list)
|
||||
{
|
||||
if (!list->readonly)
|
||||
fprintf(file, "%s = \"%s\"\n", list->key, list->value);
|
||||
list = list->next;
|
||||
}
|
||||
}
|
||||
|
||||
void config_file_dump_all(config_file_t *conf, FILE *file)
|
||||
{
|
||||
struct include_list *includes = conf->includes;
|
||||
while (includes)
|
||||
{
|
||||
fprintf(file, "#include \"%s\"\n", includes->path);
|
||||
includes = includes->next;
|
||||
}
|
||||
|
||||
struct entry_list *list = conf->entries;
|
||||
while (list)
|
||||
{
|
||||
fprintf(file, "%s = \"%s\" %s\n", list->key, list->value, list->readonly ? "(included)" : "");
|
||||
list = list->next;
|
||||
}
|
||||
}
|
||||
|
||||
bool config_entry_exists(config_file_t *conf, const char *entry)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(entry, list->key) == 0)
|
||||
return true;
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
+18
-5
@@ -22,6 +22,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct config_file config_file_t;
|
||||
|
||||
@@ -30,13 +31,21 @@ typedef struct config_file config_file_t;
|
||||
// - # are treated as comments. Rest of the line is ignored.
|
||||
// - Format is: key = value. There can be as many spaces as you like in-between.
|
||||
// - Value can be wrapped inside "" for multiword strings. (foo = "hai u")
|
||||
//
|
||||
// - #include includes a config file in-place.
|
||||
// Path is relative to where config file was loaded unless an absolute path is chosen.
|
||||
// Key/value pairs from an #include are read-only, and cannot be modified.
|
||||
|
||||
// Loads a config file. Returns NULL if file doesn't exist. NULL path will create an empty config file.
|
||||
// Loads a config file. Returns NULL if file doesn't exist.
|
||||
// NULL path will create an empty config file.
|
||||
config_file_t *config_file_new(const char *path);
|
||||
// Frees config file.
|
||||
void config_file_free(config_file_t *conf);
|
||||
|
||||
// All extract functions return true when value is valid and exists. Returns false otherwise.
|
||||
// All extract functions return true when value is valid and exists.
|
||||
// Returns false otherwise.
|
||||
|
||||
bool config_entry_exists(config_file_t *conf, const char *entry);
|
||||
|
||||
// Extracts a double from config file.
|
||||
bool config_get_double(config_file_t *conf, const char *entry, double *in);
|
||||
@@ -48,10 +57,13 @@ bool config_get_hex(config_file_t *conf, const char *entry, unsigned *in);
|
||||
bool config_get_char(config_file_t *conf, const char *entry, char *in);
|
||||
// Extracts an allocated string in *in. This must be free()-d if this function succeeds.
|
||||
bool config_get_string(config_file_t *conf, const char *entry, char **in);
|
||||
// Extracts a string to a preallocated buffer. Avoid memory allocation.
|
||||
bool config_get_array(config_file_t *conf, const char *entry, char *in, size_t size);
|
||||
// Extracts a boolean from config. Valid boolean true are "true" and "1". Valid false are "false" and "0". Other values will be treated as an error.
|
||||
bool config_get_bool(config_file_t *conf, const char *entry, bool *in);
|
||||
|
||||
// Setters. Similiar to the getters.
|
||||
// Setters. Similar to the getters. Will not write to entry if the entry
|
||||
// was obtained from an #include.
|
||||
void config_set_double(config_file_t *conf, const char *entry, double value);
|
||||
void config_set_int(config_file_t *conf, const char *entry, int val);
|
||||
void config_set_char(config_file_t *conf, const char *entry, char val);
|
||||
@@ -63,7 +75,8 @@ bool config_file_write(config_file_t *conf, const char *path);
|
||||
|
||||
// Dump the current config to an already opened file. Does not close the file.
|
||||
void config_file_dump(config_file_t *conf, FILE *file);
|
||||
|
||||
|
||||
// Also dumps inherited values, useful for logging.
|
||||
void config_file_dump_all(config_file_t *conf, FILE *file);
|
||||
|
||||
#endif
|
||||
|
||||
+61
-66
@@ -30,16 +30,7 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
#include "SDL.h"
|
||||
#else
|
||||
#error "HAVE_SDL is not defined!"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#endif
|
||||
|
||||
#include "input/keysym.h"
|
||||
|
||||
///////////////// Drivers
|
||||
#define VIDEO_GL 0
|
||||
@@ -118,8 +109,8 @@
|
||||
////////////////
|
||||
|
||||
// Windowed
|
||||
static const float xscale = 3.0; // Real x res = 296 * xscale
|
||||
static const float yscale = 3.0; // Real y res = 224 * yscale
|
||||
static const float xscale = 3.0; // Real x res = aspect * base_size * xscale
|
||||
static const float yscale = 3.0; // Real y res = base_size * yscale
|
||||
|
||||
// Fullscreen
|
||||
static const bool fullscreen = false; // To start in Fullscreen or not
|
||||
@@ -150,6 +141,8 @@ static const unsigned font_size = 48;
|
||||
// Offset for where messages will be placed on-screen. Values are in range [0.0, 1.0].
|
||||
static const float message_pos_offset_x = 0.05;
|
||||
static const float message_pos_offset_y = 0.05;
|
||||
// Color of the message.
|
||||
static const uint32_t message_color = 0xffff00; // RGB hex value.
|
||||
|
||||
// Render-to-texture before rendering to screen (multi-pass shaders)
|
||||
static const bool render_to_texture = false;
|
||||
@@ -163,8 +156,6 @@ static const bool hires_record = false;
|
||||
// Record post-filtered (CPU filter) video rather than raw SNES output.
|
||||
static const bool post_filter_record = false;
|
||||
|
||||
#define SNES_ASPECT_RATIO (4.0/3)
|
||||
|
||||
////////////////
|
||||
// Audio
|
||||
////////////////
|
||||
@@ -191,11 +182,6 @@ static const int out_latency = 64;
|
||||
// Will sync audio. (recommended)
|
||||
static const bool audio_sync = true;
|
||||
|
||||
// Defines the quality (and cpu reqirements) of samplerate conversion.
|
||||
#ifdef HAVE_SRC
|
||||
#define SAMPLERATE_QUALITY SRC_LINEAR
|
||||
#endif
|
||||
|
||||
// Enables use of rewind. This will incur some memory footprint depending on the save state buffer.
|
||||
// This rewind only works when using bSNES core atm.
|
||||
static const bool rewind_enable = false;
|
||||
@@ -215,6 +201,14 @@ static const unsigned autosave_interval = 0;
|
||||
// When being client over netplay, use keybinds for player 1 rather than player 2.
|
||||
static const bool netplay_client_swap_input = true;
|
||||
|
||||
// On save state load, block SRAM from being overwritten.
|
||||
// This could potentially lead to buggy games.
|
||||
static const bool block_sram_overwrite = false;
|
||||
|
||||
// When saving savestates, state index is automatically incremented before saving.
|
||||
// When the ROM is loaded, state index will be set to the highest existing value.
|
||||
static const bool savestate_auto_index = false;
|
||||
|
||||
|
||||
////////////////////
|
||||
// Keybinds, Joypad
|
||||
@@ -222,64 +216,65 @@ static const bool netplay_client_swap_input = true;
|
||||
|
||||
// Axis threshold (between 0.0 and 1.0)
|
||||
// How far an axis must be tilted to result in a button press
|
||||
#define AXIS_THRESHOLD 0.5
|
||||
static const float axis_threshold = 0.5;
|
||||
|
||||
// To figure out which joypad buttons to use, check jstest or similar.
|
||||
// SDL sometimes reverses the axes for some odd reason, but hey. :D
|
||||
|
||||
// Player 1
|
||||
static const struct snes_keybind snes_keybinds_1[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_x, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_z, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_s, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_q, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_w, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_KEY, SDLK_SPACE, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_HOLD_KEY, SDLK_l, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SAVE_STATE_KEY, SDLK_F2, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_LOAD_STATE_KEY, SDLK_F4, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FULLSCREEN_TOGGLE_KEY, SDLK_f, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_QUIT_KEY, SDLK_ESCAPE, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_STATE_SLOT_MINUS, SDLK_F6, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_STATE_SLOT_PLUS, SDLK_F7, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_AUDIO_INPUT_RATE_PLUS, SDLK_KP_PLUS, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_AUDIO_INPUT_RATE_MINUS, SDLK_KP_MINUS, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_REWIND, SDLK_r, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_MOVIE_RECORD_TOGGLE, SDLK_o, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_PAUSE_TOGGLE, SDLK_p, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_RESET, SDLK_h, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SHADER_NEXT, SDLK_m, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SHADER_PREV, SDLK_n, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_INDEX_PLUS, SDLK_y, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_INDEX_MINUS, SDLK_t, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_TOGGLE, SDLK_u, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SCREENSHOT, SDLK_PRINT, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_DSP_CONFIG, SDLK_c, NO_BTN, AXIS_NONE },
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SK_x, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SK_z, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SK_s, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SK_a, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SK_q, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SK_w, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SK_LEFT, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SK_RIGHT, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SK_UP, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SK_DOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SK_RETURN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SK_RSHIFT, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_KEY, SK_SPACE, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_HOLD_KEY, SK_l, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SAVE_STATE_KEY, SK_F2, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_LOAD_STATE_KEY, SK_F4, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FULLSCREEN_TOGGLE_KEY, SK_f, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_QUIT_KEY, SK_ESCAPE, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_STATE_SLOT_MINUS, SK_F6, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_STATE_SLOT_PLUS, SK_F7, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_AUDIO_INPUT_RATE_PLUS, SK_KP_PLUS, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_AUDIO_INPUT_RATE_MINUS, SK_KP_MINUS, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_REWIND, SK_r, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_MOVIE_RECORD_TOGGLE, SK_o, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_PAUSE_TOGGLE, SK_p, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FRAMEADVANCE, SK_k, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_RESET, SK_h, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SHADER_NEXT, SK_m, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SHADER_PREV, SK_n, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_INDEX_PLUS, SK_y, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_INDEX_MINUS, SK_t, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_TOGGLE, SK_u, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SCREENSHOT, SK_PRINT, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_DSP_CONFIG, SK_c, NO_BTN, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
// Player 2-5
|
||||
static const struct snes_keybind snes_keybinds_rest[] = {
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -110,10 +110,10 @@ static const bool _ffmpeg_supp = true;
|
||||
static const bool _ffmpeg_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
static const bool _src_supp = true;
|
||||
#ifdef HAVE_X264RGB
|
||||
static const bool _x264rgb_supp = true;
|
||||
#else
|
||||
static const bool _src_supp = false;
|
||||
static const bool _x264rgb_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" ssnes-joyconfig.1:
|
||||
|
||||
.TH "SSNES-JOYCONFIG" "1" "January 2011" "SSNES-JOYCONFIG" "System Manager's Manual: ssnes-joyconfig"
|
||||
.TH "SSNES-JOYCONFIG" "1" "October 2011" "SSNES-JOYCONFIG" "System Manager's Manual: ssnes-joyconfig"
|
||||
|
||||
.SH NAME
|
||||
|
||||
|
||||
+24
-3
@@ -1,6 +1,6 @@
|
||||
.\" ssnes.1:
|
||||
|
||||
.TH "SSNES" "1" "January 2011" "SSNES" "System Manager's Manual: ssnes"
|
||||
.TH "SSNES" "1" "October 2011" "SSNES" "System Manager's Manual: ssnes"
|
||||
|
||||
.SH NAME
|
||||
|
||||
@@ -27,17 +27,24 @@ If no rom file path is defined on the command line, \fBssnes\fR will try to load
|
||||
\fB--help, -h\fR
|
||||
Prints help text.
|
||||
|
||||
\fB--features\fR
|
||||
Prints available features compiled into SSNES, then exits.
|
||||
|
||||
.TP
|
||||
\fB--save PATH, -s PATH\fR
|
||||
Overrides the path used for save ram (*.srm).
|
||||
Without this flag, the save ram path will be inferred from the rom path name, and put in the same directory as the rom file with the extention replaced with '.srm'.
|
||||
When rom is loaded from \fBstdin\fR, this flag is mandatory to define as no path can be inferred.
|
||||
If PATH is a directory, SSNES will treat this as the save file directory, where the save file name will be inferred from the rom name.
|
||||
When loading a rom from stdin, the path must be a full path, however.
|
||||
|
||||
.TP
|
||||
\fB--savestate PATH, -S path\fR
|
||||
\fB--savestate PATH, -S PATH\fR
|
||||
Overrides the path used for save states.
|
||||
Without this flag, the save state path will be inferred from the rom path name, and put in the same directory as the rom file with the extention replace with '.state'.
|
||||
When rom is loaded from \fBstdin\fR, this flag is mandatory to define as no path can be inferred.
|
||||
If PATH is a directory, SSNES will treat this as the save state directory, where the state file name will be inferred from the rom name.
|
||||
When loading a rom from stdin, the path must be a full path, however.
|
||||
Do note that save states are bound to the libsnes implementation being used. Using a different libsnes could invalidate the save state file.
|
||||
|
||||
.TP
|
||||
@@ -49,6 +56,9 @@ Always starts SSNES in fullscreen. Disregards settings in configuration file.
|
||||
Sets the configuration file path. \fBssnes\fR will use this path to load the configuration file.
|
||||
Should this not be defined, \fBssnes\fR will look in platform specific paths to attempt finding the config file.
|
||||
/etc/ssnes.cfg (when installed), or ssnes.cfg in the source tarball serves as a skeleton configuration file.
|
||||
If PATH is a directory, SSNES will treat this as the config file directory, where the state file name will be inferred from the rom name (*.cfg).
|
||||
When loading a rom from stdin, the path must be a full path, however.
|
||||
If a config cannot be found when using directory path, the default config path will be used instead.
|
||||
|
||||
.IP
|
||||
Unix-like systems will look in $XDG_CONFIG_HOME/ssnes/ssnes.cfg first. Then it will try $HOME/.ssnes.cfg. Last, it will try /etc/ssnes.cfg. If no configuration is found, default settings will be assumed. A configuration file does not need to define every possible option, only those that should be overridden.
|
||||
@@ -105,7 +115,13 @@ Connects a four-way multitap into port 2 of the SNES. This allows for up to 5 pl
|
||||
|
||||
.TP
|
||||
\fB--record PATH, -r PATH\fR
|
||||
Activates video recording of gameplay into PATH. Video recording options can be configured in config file.
|
||||
Activates video recording of gameplay into PATH. Using .mkv extension is recommended.
|
||||
Codecs used are FFV1/FLAC, suitable for processing the material further.
|
||||
|
||||
.TP
|
||||
\fB--size WIDTHxHEIGHT\fR
|
||||
Allows specifying the exact output width and height of FFmpeg recording. This option will override any configuration settings.
|
||||
The video input is scaled with point filtering before being encoded at the correct size.
|
||||
|
||||
.TP
|
||||
\fB--bsvplay PATH, -P PATH\fR
|
||||
@@ -138,6 +154,11 @@ Network port used for netplay. This defaults to 55435. This option affects both
|
||||
Attempts to apply an UPS patch to the current ROM image. No files are altered.
|
||||
If this flag is not specified, SSNES will look for a .ups file with same basename as ROM specified.
|
||||
|
||||
.TP
|
||||
\fB--bps PATCH\fR
|
||||
Attempts to apply a BPS patch to the current ROM image. No files are altered.
|
||||
If this flag is not specified, SSNES will look for a .bps file with same basename as ROM specified.
|
||||
|
||||
.TP
|
||||
\fB--xml MAP, -X MAP\fR
|
||||
Specifies path to XML memory map for the given ROM.
|
||||
|
||||
@@ -91,7 +91,7 @@ static const input_driver_t *input_drivers[] = {
|
||||
|
||||
static void find_audio_driver(void)
|
||||
{
|
||||
for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
|
||||
for (unsigned i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
|
||||
{
|
||||
if (strcasecmp(g_settings.audio.driver, audio_drivers[i]->ident) == 0)
|
||||
{
|
||||
@@ -101,7 +101,7 @@ static void find_audio_driver(void)
|
||||
}
|
||||
SSNES_ERR("Couldn't find any audio driver named \"%s\"\n", g_settings.audio.driver);
|
||||
fprintf(stderr, "Available audio drivers are:\n");
|
||||
for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
|
||||
for (unsigned i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
|
||||
fprintf(stderr, "\t%s\n", audio_drivers[i]->ident);
|
||||
|
||||
exit(1);
|
||||
@@ -109,7 +109,7 @@ static void find_audio_driver(void)
|
||||
|
||||
static void find_video_driver(void)
|
||||
{
|
||||
for (int i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++)
|
||||
for (unsigned i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++)
|
||||
{
|
||||
if (strcasecmp(g_settings.video.driver, video_drivers[i]->ident) == 0)
|
||||
{
|
||||
@@ -127,7 +127,7 @@ static void find_video_driver(void)
|
||||
|
||||
static void find_input_driver(void)
|
||||
{
|
||||
for (int i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++)
|
||||
for (unsigned i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++)
|
||||
{
|
||||
if (strcasecmp(g_settings.input.driver, input_drivers[i]->ident) == 0)
|
||||
{
|
||||
@@ -237,7 +237,7 @@ void init_audio(void)
|
||||
g_extern.audio_data.nonblock_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING;
|
||||
|
||||
driver.audio_data = driver.audio->init(strlen(g_settings.audio.device) ? g_settings.audio.device : NULL, g_settings.audio.out_rate, g_settings.audio.latency);
|
||||
if ( driver.audio_data == NULL )
|
||||
if (!driver.audio_data)
|
||||
g_extern.audio_active = false;
|
||||
|
||||
if (g_extern.audio_active && driver.audio->use_float && driver.audio->use_float(driver.audio_data))
|
||||
@@ -251,26 +251,28 @@ void init_audio(void)
|
||||
else
|
||||
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
int err;
|
||||
g_extern.audio_data.source = src_new(g_settings.audio.src_quality, 2, &err);
|
||||
g_extern.audio_data.source = hermite_new();
|
||||
if (!g_extern.audio_data.source)
|
||||
g_extern.audio_active = false;
|
||||
#else
|
||||
g_extern.audio_data.source = hermite_new(2);
|
||||
if (!g_extern.audio_data.source)
|
||||
g_extern.audio_active = false;
|
||||
#endif
|
||||
|
||||
size_t max_bufsamples = g_extern.audio_data.block_chunk_size > g_extern.audio_data.nonblock_chunk_size ?
|
||||
g_extern.audio_data.block_chunk_size : g_extern.audio_data.nonblock_chunk_size;
|
||||
|
||||
assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO);
|
||||
max_bufsamples *= 2; // Accomodate rewind since at some point we might have two full buffers.
|
||||
|
||||
assert((g_extern.audio_data.data = malloc(max_bufsamples * sizeof(float))));
|
||||
g_extern.audio_data.data_ptr = 0;
|
||||
assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO);
|
||||
assert((g_extern.audio_data.outsamples = malloc(max_bufsamples * sizeof(float) * AUDIO_MAX_RATIO)));
|
||||
assert((g_extern.audio_data.conv_outsamples = malloc(max_bufsamples * sizeof(int16_t) * AUDIO_MAX_RATIO)));
|
||||
|
||||
// Needs to be able to hold full content of a full max_bufsamples in addition to its own.
|
||||
assert((g_extern.audio_data.rewind_buf = malloc(max_bufsamples * sizeof(int16_t))));
|
||||
g_extern.audio_data.rewind_size = max_bufsamples;
|
||||
|
||||
g_extern.audio_data.src_ratio =
|
||||
(double)g_settings.audio.out_rate / g_settings.audio.in_rate;
|
||||
|
||||
init_dsp_plugin();
|
||||
}
|
||||
|
||||
@@ -282,21 +284,16 @@ void uninit_audio(void)
|
||||
return;
|
||||
}
|
||||
|
||||
if ( driver.audio_data && driver.audio )
|
||||
if (driver.audio_data && driver.audio)
|
||||
driver.audio->free(driver.audio_data);
|
||||
|
||||
if ( g_extern.audio_data.source )
|
||||
{
|
||||
#ifdef HAVE_SRC
|
||||
src_delete(g_extern.audio_data.source);
|
||||
#else
|
||||
if (g_extern.audio_data.source)
|
||||
hermite_free(g_extern.audio_data.source);
|
||||
#endif
|
||||
}
|
||||
|
||||
free(g_extern.audio_data.data); g_extern.audio_data.data = NULL;
|
||||
free(g_extern.audio_data.outsamples); g_extern.audio_data.outsamples = NULL;
|
||||
free(g_extern.audio_data.conv_outsamples); g_extern.audio_data.conv_outsamples = NULL;
|
||||
free(g_extern.audio_data.rewind_buf); g_extern.audio_data.rewind_buf = NULL;
|
||||
|
||||
deinit_dsp_plugin();
|
||||
}
|
||||
@@ -333,17 +330,17 @@ static void init_filter(void)
|
||||
|
||||
g_extern.filter.active = true;
|
||||
|
||||
unsigned width = 512;
|
||||
unsigned height = 512;
|
||||
unsigned width = g_extern.system.geom.max_width;
|
||||
unsigned height = g_extern.system.geom.max_height;
|
||||
g_extern.filter.psize(&width, &height);
|
||||
|
||||
unsigned pow2_x = next_pow2(ceil(width));
|
||||
unsigned pow2_y = next_pow2(ceil(height));
|
||||
unsigned maxsize = pow2_x > pow2_y ? pow2_x : pow2_y;
|
||||
g_extern.filter.scale = maxsize / 256;
|
||||
g_extern.filter.scale = maxsize / SSNES_SCALE_BASE;
|
||||
|
||||
g_extern.filter.buffer = malloc(256 * 256 * g_extern.filter.scale * g_extern.filter.scale * sizeof(uint32_t));
|
||||
g_extern.filter.pitch = 256 * g_extern.filter.scale * sizeof(uint32_t);
|
||||
g_extern.filter.buffer = malloc(SSNES_SCALE_BASE * SSNES_SCALE_BASE * g_extern.filter.scale * g_extern.filter.scale * sizeof(uint32_t));
|
||||
g_extern.filter.pitch = SSNES_SCALE_BASE * g_extern.filter.scale * sizeof(uint32_t);
|
||||
assert(g_extern.filter.buffer);
|
||||
|
||||
g_extern.filter.colormap = malloc(32768 * sizeof(uint32_t));
|
||||
@@ -399,6 +396,9 @@ static void deinit_shader_dir(void)
|
||||
{
|
||||
// It handles NULL, no worries :D
|
||||
dir_list_free(g_extern.shader_dir.elems);
|
||||
g_extern.shader_dir.elems = NULL;
|
||||
g_extern.shader_dir.size = 0;
|
||||
g_extern.shader_dir.ptr = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -412,8 +412,9 @@ void init_video_input(void)
|
||||
init_shader_dir();
|
||||
#endif
|
||||
|
||||
// We use at least 512x512 textures to accomodate for hi-res games.
|
||||
unsigned scale = 2;
|
||||
unsigned max_dim = max(g_extern.system.geom.max_width, g_extern.system.geom.max_height);
|
||||
unsigned scale = max_dim / SSNES_SCALE_BASE;
|
||||
scale = max(scale, 1);
|
||||
|
||||
find_video_driver();
|
||||
find_input_driver();
|
||||
@@ -430,18 +431,26 @@ void init_video_input(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_settings.video.force_aspect)
|
||||
if (g_settings.video.force_aspect && (g_settings.video.aspect_ratio > 0.0f))
|
||||
{
|
||||
width = 224 * g_settings.video.xscale * g_settings.video.aspect_ratio;
|
||||
height = 224 * g_settings.video.yscale;
|
||||
width = roundf(g_extern.system.geom.base_height * g_settings.video.xscale * g_settings.video.aspect_ratio);
|
||||
height = roundf(g_extern.system.geom.base_height * g_settings.video.yscale);
|
||||
}
|
||||
else
|
||||
{
|
||||
width = 256 * g_settings.video.xscale;
|
||||
height = 224 * g_settings.video.yscale;
|
||||
width = roundf(g_extern.system.geom.base_width * g_settings.video.xscale);
|
||||
height = roundf(g_extern.system.geom.base_height * g_settings.video.yscale);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_settings.video.aspect_ratio < 0.0f)
|
||||
{
|
||||
g_settings.video.aspect_ratio = (float)g_extern.system.geom.base_width / g_extern.system.geom.base_height;
|
||||
SSNES_LOG("Adjusting aspect ratio to %.2f\n", g_settings.video.aspect_ratio);
|
||||
}
|
||||
|
||||
SSNES_LOG("Video @ %ux%u\n", width, height);
|
||||
|
||||
video_info_t video = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
@@ -456,7 +465,7 @@ void init_video_input(void)
|
||||
const input_driver_t *tmp = driver.input;
|
||||
driver.video_data = driver.video->init(&video, &driver.input, &driver.input_data);
|
||||
|
||||
if ( driver.video_data == NULL )
|
||||
if (driver.video_data == NULL)
|
||||
{
|
||||
SSNES_ERR("Cannot open video driver ... Exiting ...\n");
|
||||
exit(1);
|
||||
@@ -469,7 +478,7 @@ void init_video_input(void)
|
||||
if (driver.input != NULL)
|
||||
{
|
||||
driver.input_data = driver.input->init();
|
||||
if ( driver.input_data == NULL )
|
||||
if (driver.input_data == NULL)
|
||||
{
|
||||
SSNES_ERR("Cannot init input driver. Exiting ...\n");
|
||||
exit(1);
|
||||
@@ -485,10 +494,10 @@ void init_video_input(void)
|
||||
|
||||
void uninit_video_input(void)
|
||||
{
|
||||
if ( driver.input_data != driver.video_data && driver.input )
|
||||
if (driver.input_data != driver.video_data && driver.input)
|
||||
driver.input->free(driver.input_data);
|
||||
|
||||
if ( driver.video_data && driver.video )
|
||||
if (driver.video_data && driver.video)
|
||||
driver.video->free(driver.video_data);
|
||||
|
||||
deinit_filter();
|
||||
|
||||
@@ -24,10 +24,13 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include "input/keysym.h"
|
||||
|
||||
// SNES has 12 buttons from 0-11 (libsnes.hpp)
|
||||
#define SSNES_FIRST_META_KEY 12
|
||||
enum
|
||||
{
|
||||
SSNES_FAST_FORWARD_KEY = 0x666, // Hurr, durr
|
||||
SSNES_FAST_FORWARD_KEY = SSNES_FIRST_META_KEY,
|
||||
SSNES_FAST_FORWARD_HOLD_KEY,
|
||||
SSNES_LOAD_STATE_KEY,
|
||||
SSNES_SAVE_STATE_KEY,
|
||||
@@ -40,6 +43,7 @@ enum
|
||||
SSNES_REWIND,
|
||||
SSNES_MOVIE_RECORD_TOGGLE,
|
||||
SSNES_PAUSE_TOGGLE,
|
||||
SSNES_FRAMEADVANCE,
|
||||
SSNES_RESET,
|
||||
SSNES_SHADER_NEXT,
|
||||
SSNES_SHADER_PREV,
|
||||
@@ -47,14 +51,16 @@ enum
|
||||
SSNES_CHEAT_INDEX_MINUS,
|
||||
SSNES_CHEAT_TOGGLE,
|
||||
SSNES_SCREENSHOT,
|
||||
SSNES_DSP_CONFIG
|
||||
SSNES_DSP_CONFIG,
|
||||
|
||||
SSNES_BIND_LIST_END
|
||||
};
|
||||
|
||||
|
||||
struct snes_keybind
|
||||
{
|
||||
int id;
|
||||
uint16_t key;
|
||||
enum ssnes_key key;
|
||||
uint16_t joykey;
|
||||
uint32_t joyaxis;
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
#include "strl.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
@@ -38,16 +39,15 @@
|
||||
#define SYM(type, x) do { \
|
||||
p##x = (type)DLSYM(lib_handle, x); \
|
||||
if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define OPT_SYM(type, x) do { \
|
||||
p##x = (type)DLSYM(lib_handle, x); \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
static dylib_t lib_handle = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
void (*psnes_init)(void);
|
||||
|
||||
void (*psnes_set_video_refresh)(snes_video_refresh_t);
|
||||
@@ -96,6 +96,10 @@ unsigned (*psnes_get_memory_size)(unsigned);
|
||||
void (*psnes_unload_cartridge)(void);
|
||||
void (*psnes_term)(void);
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
static void set_environment(void);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DYNAMIC
|
||||
static void load_dynamic(void)
|
||||
{
|
||||
@@ -112,7 +116,7 @@ static void load_dynamic(void)
|
||||
SYM(void (*)(snes_audio_sample_t), snes_set_audio_sample);
|
||||
SYM(void (*)(snes_input_poll_t), snes_set_input_poll);
|
||||
SYM(void (*)(snes_input_state_t), snes_set_input_state);
|
||||
OPT_SYM(const char *(*)(void), snes_library_id);
|
||||
SYM(const char *(*)(void), snes_library_id);
|
||||
SYM(unsigned (*)(void), snes_library_revision_minor);
|
||||
SYM(unsigned (*)(void), snes_library_revision_major);
|
||||
SYM(void (*)(void), snes_cheat_reset);
|
||||
@@ -144,7 +148,7 @@ static void load_dynamic(void)
|
||||
|
||||
#define SSYM(x) do { \
|
||||
p##x = x; \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#ifndef HAVE_DYNAMIC
|
||||
static void set_statics(void)
|
||||
@@ -156,6 +160,7 @@ static void set_statics(void)
|
||||
SSYM(snes_set_input_state);
|
||||
SSYM(snes_library_revision_minor);
|
||||
SSYM(snes_library_revision_major);
|
||||
SSYM(snes_library_id);
|
||||
SSYM(snes_cheat_reset);
|
||||
SSYM(snes_cheat_set);
|
||||
SSYM(snes_reset);
|
||||
@@ -180,19 +185,41 @@ static void set_statics(void)
|
||||
|
||||
void init_dlsym(void)
|
||||
{
|
||||
// Guarantee that we can do "dirty" casting. Every OS that this program supports should pass this ...
|
||||
// Guarantee that we can do "dirty" casting.
|
||||
// Every OS that this program supports should pass this ...
|
||||
assert(sizeof(void*) == sizeof(void (*)(void)));
|
||||
|
||||
#ifdef HAVE_DYNAMIC
|
||||
if (strlen(g_settings.libsnes) > 0)
|
||||
load_dynamic();
|
||||
else
|
||||
|
||||
// Try to verify that -lsnes was not linked in from other modules
|
||||
// since loading it dynamically and with -l will fail hard.
|
||||
function_t sym = dylib_proc(NULL, "snes_init");
|
||||
if (sym)
|
||||
{
|
||||
SSNES_ERR("This binary is built to use runtime dynamic binding of libsnes. Set libsnes_path in config to load a libsnes library dynamically.\n");
|
||||
SSNES_ERR("Serious problem! SSNES wants to load libsnes dyamically, but it is already linked!\n");
|
||||
SSNES_ERR("This could happen if other modules SSNES depends on link against libsnes directly.\n");
|
||||
SSNES_ERR("Proceeding could cause a crash! Aborting ...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!*g_settings.libsnes)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
strlcpy(g_settings.libsnes, "snes.dll", sizeof(g_settings.libsnes));
|
||||
#elif defined(__APPLE__)
|
||||
strlcpy(g_settings.libsnes, "libsnes.dylib", sizeof(g_settings.libsnes));
|
||||
#else
|
||||
set_statics();
|
||||
strlcpy(g_settings.libsnes, "libsnes.so", sizeof(g_settings.libsnes));
|
||||
#endif
|
||||
}
|
||||
|
||||
load_dynamic();
|
||||
#else
|
||||
set_statics();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
set_environment();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -217,16 +244,26 @@ dylib_t dylib_load(const char *path)
|
||||
function_t dylib_proc(dylib_t lib, const char *proc)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
function_t sym = (function_t)GetProcAddress(lib, proc);
|
||||
function_t sym = (function_t)GetProcAddress(lib ? lib : GetModuleHandle(NULL), proc);
|
||||
#else
|
||||
void *ptr_sym = NULL;
|
||||
if (lib)
|
||||
ptr_sym = dlsym(lib, proc);
|
||||
else
|
||||
{
|
||||
void *handle = dlopen(NULL, RTLD_LAZY);
|
||||
if (handle)
|
||||
{
|
||||
ptr_sym = dlsym(handle, proc);
|
||||
dlclose(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Dirty hack to workaround the non-legality of void* -> fn-pointer casts.
|
||||
void *ptr_sym = dlsym(lib, proc);
|
||||
function_t sym;
|
||||
memcpy(&sym, &ptr_sym, sizeof(void*));
|
||||
#endif
|
||||
|
||||
if (!sym)
|
||||
SSNES_WARN("Failed to load symbol \"%s\"\n", proc);
|
||||
return sym;
|
||||
}
|
||||
|
||||
@@ -239,3 +276,68 @@ void dylib_close(dylib_t lib)
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool environment_cb(unsigned cmd, void *data)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case SNES_ENVIRONMENT_GET_FULLPATH:
|
||||
*(const char**)data = g_extern.system.fullpath;
|
||||
SSNES_LOG("FULLPATH: \"%s\"\n", g_extern.system.fullpath);
|
||||
break;
|
||||
|
||||
case SNES_ENVIRONMENT_SET_GEOMETRY:
|
||||
g_extern.system.geom = *(const struct snes_geometry*)data;
|
||||
g_extern.system.geom.max_width = next_pow2(g_extern.system.geom.max_width);
|
||||
g_extern.system.geom.max_height = next_pow2(g_extern.system.geom.max_height);
|
||||
SSNES_LOG("SET_GEOMETRY: (%ux%u) / (%ux%u)\n",
|
||||
g_extern.system.geom.base_width,
|
||||
g_extern.system.geom.base_height,
|
||||
g_extern.system.geom.max_width,
|
||||
g_extern.system.geom.max_height);
|
||||
break;
|
||||
|
||||
case SNES_ENVIRONMENT_SET_PITCH:
|
||||
g_extern.system.pitch = *(const unsigned*)data;
|
||||
SSNES_LOG("SET_PITCH: %u\n", g_extern.system.pitch);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
// Assume SNES as defaults.
|
||||
static void set_environment_defaults(void)
|
||||
{
|
||||
g_extern.system.pitch = 0; // 0 is classic libsnes semantics.
|
||||
g_extern.system.geom = (struct snes_geometry) {
|
||||
.base_width = 256,
|
||||
.base_height = 224,
|
||||
.max_width = 512,
|
||||
.max_height = 512,
|
||||
};
|
||||
}
|
||||
|
||||
// SSNES extension hooks. Totally optional 'n shizz :)
|
||||
static void set_environment(void)
|
||||
{
|
||||
#ifdef HAVE_DYNAMIC
|
||||
dylib_t lib = lib_handle;
|
||||
#else
|
||||
dylib_t lib = NULL;
|
||||
#endif
|
||||
|
||||
void (*psnes_set_environment)(snes_environment_t) =
|
||||
(void (*)(snes_environment_t))dylib_proc(lib, "snes_set_environment");
|
||||
|
||||
if (psnes_set_environment)
|
||||
psnes_set_environment(environment_cb);
|
||||
|
||||
set_environment_defaults();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -42,18 +42,12 @@ fifo_buffer_t* fifo_new(size_t size)
|
||||
|
||||
void fifo_free(fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
|
||||
free(buffer->buffer);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
size_t fifo_read_avail(fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
|
||||
size_t first = buffer->first;
|
||||
size_t end = buffer->end;
|
||||
if (end < first)
|
||||
@@ -63,9 +57,6 @@ size_t fifo_read_avail(fifo_buffer_t* buffer)
|
||||
|
||||
size_t fifo_write_avail(fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
|
||||
size_t first = buffer->first;
|
||||
size_t end = buffer->end;
|
||||
if (end < first)
|
||||
@@ -76,11 +67,6 @@ size_t fifo_write_avail(fifo_buffer_t* buffer)
|
||||
|
||||
void fifo_write(fifo_buffer_t* buffer, const void* in_buf, size_t size)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
assert(in_buf);
|
||||
assert(fifo_write_avail(buffer) >= size);
|
||||
|
||||
size_t first_write = size;
|
||||
size_t rest_write = 0;
|
||||
if (buffer->end + size > buffer->bufsize)
|
||||
@@ -99,11 +85,6 @@ void fifo_write(fifo_buffer_t* buffer, const void* in_buf, size_t size)
|
||||
|
||||
void fifo_read(fifo_buffer_t* buffer, void* in_buf, size_t size)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
assert(in_buf);
|
||||
assert(fifo_read_avail(buffer) >= size);
|
||||
|
||||
size_t first_read = size;
|
||||
size_t rest_read = 0;
|
||||
if (buffer->first + size > buffer->bufsize)
|
||||
|
||||
@@ -21,9 +21,12 @@
|
||||
#include <stdbool.h>
|
||||
#include <libsnes.hpp>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include "dynamic.h"
|
||||
#include "movie.h"
|
||||
#include "ups.h"
|
||||
#include "bps.h"
|
||||
#include "strl.h"
|
||||
|
||||
#ifdef HAVE_XML
|
||||
@@ -34,8 +37,10 @@
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
@@ -116,6 +121,114 @@ error:
|
||||
return false;
|
||||
}
|
||||
|
||||
enum patch_type
|
||||
{
|
||||
PATCH_NONE,
|
||||
PATCH_UPS,
|
||||
PATCH_BPS
|
||||
};
|
||||
|
||||
static void patch_rom(uint8_t **buf, ssize_t *size)
|
||||
{
|
||||
uint8_t *ret_buf = *buf;
|
||||
ssize_t ret_size = *size;
|
||||
|
||||
ssize_t patch_size = 0;
|
||||
void *patch_data = NULL;
|
||||
enum patch_type type = PATCH_NONE;
|
||||
|
||||
if (g_extern.ups_pref && g_extern.bps_pref)
|
||||
{
|
||||
SSNES_WARN("Both UPS and BPS patch explicitly defined, ignoring both ...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_extern.bps_pref && *g_extern.ups_name && (patch_size = read_file(g_extern.ups_name, &patch_data)) >= 0)
|
||||
type = PATCH_UPS;
|
||||
else if (!g_extern.ups_pref && *g_extern.bps_name && (patch_size = read_file(g_extern.bps_name, &patch_data)) >= 0)
|
||||
type = PATCH_BPS;
|
||||
|
||||
if (type == PATCH_NONE)
|
||||
{
|
||||
SSNES_LOG("Did not find a valid ROM patch.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case PATCH_UPS:
|
||||
SSNES_LOG("Found UPS file in \"%s\", attempting to patch ...\n", g_extern.ups_name);
|
||||
break;
|
||||
case PATCH_BPS:
|
||||
SSNES_LOG("Found BPS file in \"%s\", attempting to patch ...\n", g_extern.bps_name);
|
||||
break;
|
||||
|
||||
default:
|
||||
return; // Should not happen, but.
|
||||
}
|
||||
|
||||
size_t target_size = ret_size * 4; // Just to be sure ...
|
||||
uint8_t *patched_rom = malloc(target_size);
|
||||
if (!patched_rom)
|
||||
{
|
||||
SSNES_ERR("Failed to allocate memory for patched ROM ...\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
switch (type)
|
||||
{
|
||||
case PATCH_UPS:
|
||||
{
|
||||
ups_error_t err = ups_apply_patch(patch_data, patch_size, ret_buf, ret_size, patched_rom, &target_size);
|
||||
if (err == UPS_SUCCESS)
|
||||
{
|
||||
SSNES_LOG("ROM patched successfully (UPS)!\n");
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
SSNES_ERR("Failed to patch UPS: Error #%u\n", (unsigned)err);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PATCH_BPS:
|
||||
{
|
||||
bps_error_t err = bps_apply_patch(patch_data, patch_size, ret_buf, ret_size, patched_rom, &target_size);
|
||||
if (err == BPS_SUCCESS)
|
||||
{
|
||||
SSNES_LOG("ROM patched successfully (BPS)!\n");
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
SSNES_ERR("Failed to patch BPS: Error #%u\n", (unsigned)err);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
free(ret_buf);
|
||||
*buf = patched_rom;
|
||||
*size = target_size;
|
||||
}
|
||||
|
||||
if (patch_data)
|
||||
free(patch_data);
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
*buf = ret_buf;
|
||||
*size = ret_size;
|
||||
if (patch_data)
|
||||
free(patch_data);
|
||||
}
|
||||
|
||||
// Load SNES rom only. Applies a hack for headered ROMs.
|
||||
static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
{
|
||||
@@ -138,7 +251,7 @@ static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(;;)
|
||||
for (;;)
|
||||
{
|
||||
size_t ret = fread(rom_buf + buf_ptr, 1, buf_size - buf_ptr, stdin);
|
||||
buf_ptr += ret;
|
||||
@@ -183,37 +296,6 @@ static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
ret_buf = rom_buf;
|
||||
}
|
||||
|
||||
// Patch with UPS.
|
||||
ssize_t ups_patch_size;
|
||||
void *ups_patch = NULL;
|
||||
if (*g_extern.ups_name && (ups_patch_size = read_file(g_extern.ups_name, &ups_patch)) >= 0)
|
||||
{
|
||||
SSNES_LOG("Found UPS file in \"%s\", attempting to patch ...\n", g_extern.ups_name);
|
||||
|
||||
size_t target_size = ret * 4; // Just to be sure ...
|
||||
uint8_t *patched_rom = malloc(target_size);
|
||||
if (patched_rom)
|
||||
{
|
||||
ups_error_t err = ups_apply_patch(ups_patch, ups_patch_size, ret_buf, ret, patched_rom, &target_size);
|
||||
if (err == UPS_SUCCESS)
|
||||
{
|
||||
free(ret_buf);
|
||||
ret_buf = patched_rom;
|
||||
ret = target_size;
|
||||
SSNES_LOG("ROM patched successfully (UPS)!\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
free(patched_rom);
|
||||
SSNES_LOG("ROM failed to patch (UPS).\n");
|
||||
}
|
||||
|
||||
free(ups_patch);
|
||||
}
|
||||
}
|
||||
else if (*g_extern.ups_name)
|
||||
SSNES_LOG("Could not find UPS patch in: \"%s\".\n", g_extern.ups_name);
|
||||
|
||||
// Remove copier header if present (512 first bytes).
|
||||
if ((ret & 0x7fff) == 512)
|
||||
{
|
||||
@@ -221,6 +303,9 @@ static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
ret -= 512;
|
||||
}
|
||||
|
||||
// Attempt to apply a patch :)
|
||||
patch_rom(&ret_buf, &ret);
|
||||
|
||||
g_extern.cart_crc = crc32_calculate(ret_buf, ret);
|
||||
#ifdef HAVE_XML
|
||||
sha256_hash(g_extern.sha256, ret_buf, ret);
|
||||
@@ -239,13 +324,72 @@ static bool dump_to_file(const char *path, const void *data, size_t size)
|
||||
return false;
|
||||
else
|
||||
{
|
||||
fwrite(data, 1, size, file);
|
||||
bool ret = fwrite(data, 1, size, file) == size;
|
||||
fclose(file);
|
||||
return true;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
bool save_state(const char* path)
|
||||
static const char *ramtype2str(int type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SNES_MEMORY_CARTRIDGE_RAM:
|
||||
case SNES_MEMORY_GAME_BOY_RAM:
|
||||
case SNES_MEMORY_BSX_RAM:
|
||||
return ".srm";
|
||||
|
||||
case SNES_MEMORY_CARTRIDGE_RTC:
|
||||
case SNES_MEMORY_GAME_BOY_RTC:
|
||||
return ".rtc";
|
||||
|
||||
case SNES_MEMORY_BSX_PRAM:
|
||||
return ".pram";
|
||||
|
||||
case SNES_MEMORY_SUFAMI_TURBO_A_RAM:
|
||||
return ".aram";
|
||||
case SNES_MEMORY_SUFAMI_TURBO_B_RAM:
|
||||
return ".bram";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to save valuable RAM data somewhere ...
|
||||
static void dump_to_file_desperate(const void *data, size_t size, int type)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const char *base = getenv("APPDATA");
|
||||
#else
|
||||
const char *base = getenv("HOME");
|
||||
#endif
|
||||
|
||||
if (!base)
|
||||
goto error;
|
||||
|
||||
char path[MAXPATHLEN];
|
||||
snprintf(path, sizeof(path), "%s/SSNES-recovery-", base);
|
||||
char timebuf[MAXPATHLEN];
|
||||
|
||||
time_t time_;
|
||||
time(&time_);
|
||||
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d-%H-%M-%S", localtime(&time_));
|
||||
strlcat(path, timebuf, sizeof(path));
|
||||
strlcat(path, ramtype2str(type), sizeof(path));
|
||||
|
||||
if (dump_to_file(path, data, size))
|
||||
SSNES_WARN("Succeeded in saving RAM data to \"%s\". Phew! :D\n", path);
|
||||
else
|
||||
goto error;
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
SSNES_WARN("Failed ... Tough luck ... :(\n");
|
||||
}
|
||||
|
||||
bool save_state(const char *path)
|
||||
{
|
||||
SSNES_LOG("Saving state: \"%s\".\n", path);
|
||||
size_t size = psnes_serialize_size();
|
||||
@@ -268,7 +412,7 @@ bool save_state(const char* path)
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool load_state(const char* path)
|
||||
bool load_state(const char *path)
|
||||
{
|
||||
SSNES_LOG("Loading state: \"%s\".\n", path);
|
||||
void *buf = NULL;
|
||||
@@ -281,14 +425,81 @@ bool load_state(const char* path)
|
||||
else
|
||||
{
|
||||
SSNES_LOG("State size: %d bytes.\n", (int)size);
|
||||
|
||||
uint8_t *block_buf[2] = {NULL, NULL};
|
||||
int block_type[2] = {-1, -1};
|
||||
unsigned block_size[2] = {0};
|
||||
|
||||
if (g_settings.block_sram_overwrite)
|
||||
{
|
||||
SSNES_LOG("Blocking SRAM overwrite!\n");
|
||||
switch (g_extern.game_type)
|
||||
{
|
||||
case SSNES_CART_NORMAL:
|
||||
block_type[0] = SNES_MEMORY_CARTRIDGE_RAM;
|
||||
block_type[1] = SNES_MEMORY_CARTRIDGE_RTC;
|
||||
break;
|
||||
|
||||
case SSNES_CART_BSX:
|
||||
case SSNES_CART_BSX_SLOTTED:
|
||||
block_type[0] = SNES_MEMORY_BSX_RAM;
|
||||
block_type[1] = SNES_MEMORY_BSX_PRAM;
|
||||
break;
|
||||
|
||||
case SSNES_CART_SUFAMI:
|
||||
block_type[0] = SNES_MEMORY_SUFAMI_TURBO_A_RAM;
|
||||
block_type[1] = SNES_MEMORY_SUFAMI_TURBO_B_RAM;
|
||||
break;
|
||||
|
||||
case SSNES_CART_SGB:
|
||||
block_type[0] = SNES_MEMORY_GAME_BOY_RAM;
|
||||
block_type[1] = SNES_MEMORY_GAME_BOY_RTC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
if (block_type[i] != -1)
|
||||
block_size[i] = psnes_get_memory_size(block_type[i]);
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
if (block_size[i])
|
||||
block_buf[i] = malloc(block_size[i]);
|
||||
|
||||
// Backup current SRAM which is overwritten by unserialize.
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (block_buf[i])
|
||||
{
|
||||
const uint8_t *ptr = psnes_get_memory_data(block_type[i]);
|
||||
if (ptr)
|
||||
memcpy(block_buf[i], ptr, block_size[i]);
|
||||
}
|
||||
}
|
||||
|
||||
psnes_unserialize(buf, size);
|
||||
|
||||
// Flush back :D
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (block_buf[i])
|
||||
{
|
||||
uint8_t *ptr = psnes_get_memory_data(block_type[i]);
|
||||
if (ptr)
|
||||
memcpy(ptr, block_buf[i], block_size[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
if (block_buf[i])
|
||||
free(block_buf[i]);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
void load_ram_file(const char* path, int type)
|
||||
void load_ram_file(const char *path, int type)
|
||||
{
|
||||
size_t size = psnes_get_memory_size(type);
|
||||
uint8_t *data = psnes_get_memory_data(type);
|
||||
@@ -299,18 +510,25 @@ void load_ram_file(const char* path, int type)
|
||||
void *buf = NULL;
|
||||
ssize_t rc = read_file(path, &buf);
|
||||
if (rc <= size)
|
||||
memcpy(data, buf, size);
|
||||
memcpy(data, buf, rc);
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
void save_ram_file(const char* path, int type)
|
||||
void save_ram_file(const char *path, int type)
|
||||
{
|
||||
size_t size = psnes_get_memory_size(type);
|
||||
uint8_t *data = psnes_get_memory_data(type);
|
||||
|
||||
if (data && size > 0)
|
||||
dump_to_file(path, data, size);
|
||||
{
|
||||
if (!dump_to_file(path, data, size))
|
||||
{
|
||||
SSNES_ERR("Failed to save SNES RAM!\n");
|
||||
SSNES_WARN("Attempting to recover ...\n");
|
||||
dump_to_file_desperate(data, size, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char* load_xml_map(const char *path)
|
||||
@@ -585,8 +803,6 @@ bool init_rom_file(enum ssnes_game_type type)
|
||||
// Yep, this is C alright ;)
|
||||
char** dir_list_new(const char *dir, const char *ext)
|
||||
{
|
||||
size_t path_len = strlen(dir);
|
||||
|
||||
size_t cur_ptr = 0;
|
||||
size_t cur_size = 32;
|
||||
char **dir_list = NULL;
|
||||
@@ -595,18 +811,22 @@ char** dir_list_new(const char *dir, const char *ext)
|
||||
WIN32_FIND_DATAW ffd;
|
||||
HANDLE hFind = INVALID_HANDLE_VALUE;
|
||||
|
||||
wchar_t wchar_buf[MAXPATHLEN];
|
||||
char utf8_buf[MAXPATHLEN];
|
||||
|
||||
size_t final_off = MAX_PATH + path_len + 2;
|
||||
if (strlcpy(utf8_buf, dir, sizeof(utf8_buf)) >= sizeof(utf8_buf))
|
||||
goto error;
|
||||
if (strlcat(utf8_buf, "/*", sizeof(utf8_buf)) >= sizeof(utf8_buf))
|
||||
goto error;
|
||||
|
||||
wchar_t wchar_buf[MAX_PATH + 1];
|
||||
char utf8_buf[MAX_PATH + 3];
|
||||
if (ext)
|
||||
{
|
||||
if (strlcat(utf8_buf, ext, sizeof(utf8_buf)) >= sizeof(utf8_buf))
|
||||
goto error;
|
||||
}
|
||||
|
||||
strlcpy(utf8_buf, dir, sizeof(utf8_buf));
|
||||
strlcat(utf8_buf, "/*", sizeof(utf8_buf));
|
||||
utf8_buf[MAX_PATH + 2] = '\0';
|
||||
|
||||
int ret = MultiByteToWideChar(CP_UTF8, 0, utf8_buf, strlen(utf8_buf), wchar_buf, MAX_PATH);
|
||||
wchar_buf[ret] = 0;
|
||||
if (MultiByteToWideChar(CP_UTF8, 0, utf8_buf, -1, wchar_buf, MAXPATHLEN) == 0)
|
||||
goto error;
|
||||
|
||||
hFind = FindFirstFileW(wchar_buf, &ffd);
|
||||
if (hFind == INVALID_HANDLE_VALUE)
|
||||
@@ -614,7 +834,6 @@ char** dir_list_new(const char *dir, const char *ext)
|
||||
#else
|
||||
DIR *directory = NULL;
|
||||
const struct dirent *entry = NULL;
|
||||
size_t final_off = sizeof(entry->d_name) + path_len + 2;
|
||||
|
||||
directory = opendir(dir);
|
||||
if (!directory)
|
||||
@@ -635,8 +854,8 @@ char** dir_list_new(const char *dir, const char *ext)
|
||||
#ifdef _WIN32
|
||||
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
continue;
|
||||
int ret = WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, wcslen(ffd.cFileName), utf8_buf, MAX_PATH, NULL, NULL);
|
||||
utf8_buf[ret] = '\0';
|
||||
if (WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, utf8_buf, MAXPATHLEN, NULL, NULL) == 0)
|
||||
continue;
|
||||
if (ext && !strstr(utf8_buf, ext))
|
||||
continue;
|
||||
#else
|
||||
@@ -644,16 +863,16 @@ char** dir_list_new(const char *dir, const char *ext)
|
||||
continue;
|
||||
#endif
|
||||
|
||||
dir_list[cur_ptr] = malloc(final_off);
|
||||
dir_list[cur_ptr] = malloc(MAXPATHLEN);
|
||||
if (!dir_list[cur_ptr])
|
||||
goto error;
|
||||
|
||||
strlcpy(dir_list[cur_ptr], dir, final_off);
|
||||
strlcat(dir_list[cur_ptr], "/", final_off);
|
||||
strlcpy(dir_list[cur_ptr], dir, MAXPATHLEN);
|
||||
strlcat(dir_list[cur_ptr], "/", MAXPATHLEN);
|
||||
#ifdef _WIN32
|
||||
strlcat(dir_list[cur_ptr], utf8_buf, final_off);
|
||||
strlcat(dir_list[cur_ptr], utf8_buf, MAXPATHLEN);
|
||||
#else
|
||||
strlcat(dir_list[cur_ptr], entry->d_name, final_off);
|
||||
strlcat(dir_list[cur_ptr], entry->d_name, MAXPATHLEN);
|
||||
#endif
|
||||
|
||||
cur_ptr++;
|
||||
@@ -670,7 +889,6 @@ char** dir_list_new(const char *dir, const char *ext)
|
||||
}
|
||||
#ifdef _WIN32
|
||||
while (FindNextFileW(hFind, &ffd) != 0);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -703,3 +921,64 @@ void dir_list_free(char **dir_list)
|
||||
free(*dir_list++);
|
||||
free(orig);
|
||||
}
|
||||
|
||||
bool path_is_directory(const char *path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
wchar_t buf[MAXPATHLEN];
|
||||
if (MultiByteToWideChar(CP_UTF8, 0, path, -1, buf, MAXPATHLEN) == 0)
|
||||
return false;
|
||||
return PathIsDirectoryW(buf) == FILE_ATTRIBUTE_DIRECTORY;
|
||||
#else
|
||||
struct stat buf;
|
||||
if (stat(path, &buf) < 0)
|
||||
return false;
|
||||
|
||||
return S_ISDIR(buf.st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool path_file_exists(const char *path)
|
||||
{
|
||||
FILE *dummy = fopen(path, "rb");
|
||||
if (dummy)
|
||||
{
|
||||
fclose(dummy);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void fill_pathname(char *out_path, const char *in_path, const char *replace, size_t size)
|
||||
{
|
||||
char tmp_path[MAXPATHLEN];
|
||||
assert(strlcpy(tmp_path, in_path, sizeof(tmp_path)) < sizeof(tmp_path));
|
||||
char *tok = strrchr(tmp_path, '.');
|
||||
if (tok != NULL)
|
||||
*tok = '\0';
|
||||
assert(strlcpy(out_path, tmp_path, size) < size);
|
||||
assert(strlcat(out_path, replace, size) < size);
|
||||
}
|
||||
|
||||
void fill_pathname_noext(char *out_path, const char *in_path, const char *replace, size_t size)
|
||||
{
|
||||
assert(strlcpy(out_path, in_path, size) < size);
|
||||
assert(strlcat(out_path, replace, size) < size);
|
||||
}
|
||||
|
||||
void fill_pathname_dir(char *in_dir, const char *in_basename, const char *replace, size_t size)
|
||||
{
|
||||
assert(strlcat(in_dir, "/", size) < size);
|
||||
|
||||
const char *base = strrchr(in_basename, '/');
|
||||
if (!base)
|
||||
base = strrchr(in_basename, '\\');
|
||||
|
||||
if (base)
|
||||
base++;
|
||||
else
|
||||
base = in_basename;
|
||||
|
||||
assert(strlcat(in_dir, base, size) < size);
|
||||
assert(strlcat(in_dir, replace, size) < size);
|
||||
}
|
||||
@@ -26,18 +26,34 @@
|
||||
#include <sys/types.h>
|
||||
#include "general.h"
|
||||
|
||||
// Generic file, path and directory handling.
|
||||
|
||||
ssize_t read_file(const char *path, void **buf);
|
||||
|
||||
bool load_state(const char* path);
|
||||
bool save_state(const char* path);
|
||||
bool load_state(const char *path);
|
||||
bool save_state(const char *path);
|
||||
|
||||
void load_ram_file(const char* path, int type);
|
||||
void save_ram_file(const char* path, int type);
|
||||
void load_ram_file(const char *path, int type);
|
||||
void save_ram_file(const char *path, int type);
|
||||
|
||||
bool init_rom_file(enum ssnes_game_type type);
|
||||
|
||||
// Returns a NULL-terminated list of files in a directory with full paths.
|
||||
// If ext is NULL, any file will be picked.
|
||||
// If non-NULL, only files with extension ext are added.
|
||||
char** dir_list_new(const char *dir, const char *ext);
|
||||
void dir_list_free(char **dir_list);
|
||||
|
||||
bool path_is_directory(const char *path);
|
||||
bool path_file_exists(const char *path);
|
||||
|
||||
// Path-name operations.
|
||||
// Replaces filename extension with replace.
|
||||
void fill_pathname(char *out_path, const char *in_path, const char *replace, size_t size);
|
||||
// Sets filename extension. Assumes in_path has no extension.
|
||||
void fill_pathname_noext(char *out_path, const char *in_path, const char *replace, size_t size);
|
||||
// Concatenates in_basename and replace to in_dir.
|
||||
void fill_pathname_dir(char *in_dir, const char *in_basename, const char *replace, size_t size);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -20,8 +20,9 @@
|
||||
#define __SSNES_GENERAL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "driver.h"
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include "driver.h"
|
||||
#include "record/ffemu.h"
|
||||
#include "message.h"
|
||||
#include "rewind.h"
|
||||
@@ -36,15 +37,25 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#else
|
||||
#include "audio/hermite.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/param.h> // MAXPATHLEN
|
||||
#else
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifndef MAXPATHLEN
|
||||
#ifdef PATH_MAX
|
||||
#define MAXPATHLEN PATH_MAX
|
||||
#else
|
||||
#define MAXPATHLEN 512
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAX_PLAYERS 5
|
||||
#define MAX_BINDS 34 // Needs to be increased every time there are new binds added.
|
||||
#define MAX_BINDS (SSNES_BIND_LIST_END + 1)
|
||||
#define SSNES_NO_JOYPAD 0xFFFF
|
||||
|
||||
enum ssnes_shader_type
|
||||
@@ -71,22 +82,25 @@ struct settings
|
||||
bool force_aspect;
|
||||
bool crop_overscan;
|
||||
float aspect_ratio;
|
||||
char cg_shader_path[256];
|
||||
char bsnes_shader_path[256];
|
||||
char filter_path[256];
|
||||
char cg_shader_path[MAXPATHLEN];
|
||||
char bsnes_shader_path[MAXPATHLEN];
|
||||
char filter_path[MAXPATHLEN];
|
||||
enum ssnes_shader_type shader_type;
|
||||
|
||||
bool render_to_texture;
|
||||
double fbo_scale_x;
|
||||
double fbo_scale_y;
|
||||
char second_pass_shader[256];
|
||||
char second_pass_shader[MAXPATHLEN];
|
||||
bool second_pass_smooth;
|
||||
char shader_dir[256];
|
||||
char shader_dir[MAXPATHLEN];
|
||||
|
||||
char font_path[256];
|
||||
char font_path[MAXPATHLEN];
|
||||
unsigned font_size;
|
||||
float msg_pos_x;
|
||||
float msg_pos_y;
|
||||
float msg_color_r;
|
||||
float msg_color_g;
|
||||
float msg_color_b;
|
||||
|
||||
bool force_16bit;
|
||||
bool disable_composition;
|
||||
@@ -94,7 +108,7 @@ struct settings
|
||||
bool hires_record;
|
||||
bool post_filter_record;
|
||||
|
||||
char external_driver[256];
|
||||
char external_driver[MAXPATHLEN];
|
||||
} video;
|
||||
|
||||
struct
|
||||
@@ -104,13 +118,12 @@ struct settings
|
||||
unsigned out_rate;
|
||||
float in_rate;
|
||||
float rate_step;
|
||||
char device[256];
|
||||
char device[MAXPATHLEN];
|
||||
unsigned latency;
|
||||
bool sync;
|
||||
int src_quality;
|
||||
|
||||
char dsp_plugin[256];
|
||||
char external_driver[256];
|
||||
char dsp_plugin[MAXPATHLEN];
|
||||
char external_driver[MAXPATHLEN];
|
||||
} audio;
|
||||
|
||||
struct
|
||||
@@ -122,10 +135,11 @@ struct settings
|
||||
bool netplay_client_swap_input;
|
||||
} input;
|
||||
|
||||
char libsnes[256];
|
||||
char cheat_database[256];
|
||||
char libsnes[MAXPATHLEN];
|
||||
char cheat_database[MAXPATHLEN];
|
||||
char cheat_settings_path[MAXPATHLEN];
|
||||
|
||||
char screenshot_directory[256];
|
||||
char screenshot_directory[MAXPATHLEN];
|
||||
|
||||
bool rewind_enable;
|
||||
unsigned rewind_buffer_size;
|
||||
@@ -133,6 +147,9 @@ struct settings
|
||||
|
||||
bool pause_nonactive;
|
||||
unsigned autosave_interval;
|
||||
|
||||
bool block_sram_overwrite;
|
||||
bool savestate_auto_index;
|
||||
};
|
||||
|
||||
enum ssnes_game_type
|
||||
@@ -164,35 +181,42 @@ struct global
|
||||
enum ssnes_game_type game_type;
|
||||
uint32_t cart_crc;
|
||||
|
||||
char gb_rom_path[256];
|
||||
char bsx_rom_path[256];
|
||||
char sufami_rom_path[2][256];
|
||||
char gb_rom_path[MAXPATHLEN];
|
||||
char bsx_rom_path[MAXPATHLEN];
|
||||
char sufami_rom_path[2][MAXPATHLEN];
|
||||
bool has_set_save_path;
|
||||
bool has_set_state_path;
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
char config_path[256];
|
||||
char config_path[MAXPATHLEN];
|
||||
#endif
|
||||
|
||||
char basename[256];
|
||||
char savefile_name_srm[256];
|
||||
char savefile_name_rtc[512]; // Make sure that fill_pathname has space.
|
||||
char savefile_name_psrm[512];
|
||||
char savefile_name_asrm[512];
|
||||
char savefile_name_bsrm[512];
|
||||
char savestate_name[256];
|
||||
char ups_name[512];
|
||||
char xml_name[512];
|
||||
char basename[MAXPATHLEN];
|
||||
char savefile_name_srm[MAXPATHLEN];
|
||||
char savefile_name_rtc[MAXPATHLEN]; // Make sure that fill_pathname has space.
|
||||
char savefile_name_psrm[MAXPATHLEN];
|
||||
char savefile_name_asrm[MAXPATHLEN];
|
||||
char savefile_name_bsrm[MAXPATHLEN];
|
||||
char savestate_name[MAXPATHLEN];
|
||||
char xml_name[MAXPATHLEN];
|
||||
|
||||
bool ups_pref;
|
||||
bool bps_pref;
|
||||
char ups_name[MAXPATHLEN];
|
||||
char bps_name[MAXPATHLEN];
|
||||
|
||||
unsigned state_slot;
|
||||
|
||||
struct
|
||||
{
|
||||
#ifdef HAVE_SRC
|
||||
SRC_STATE *source;
|
||||
#else
|
||||
struct snes_geometry geom;
|
||||
unsigned pitch; // If 0, has classic libsnes semantics.
|
||||
char fullpath[MAXPATHLEN];
|
||||
} system;
|
||||
|
||||
struct
|
||||
{
|
||||
hermite_resampler_t *source;
|
||||
#endif
|
||||
|
||||
float *data;
|
||||
size_t data_ptr;
|
||||
@@ -200,11 +224,17 @@ struct global
|
||||
size_t nonblock_chunk_size;
|
||||
size_t block_chunk_size;
|
||||
|
||||
double src_ratio;
|
||||
|
||||
bool use_float;
|
||||
|
||||
float *outsamples;
|
||||
int16_t *conv_outsamples;
|
||||
|
||||
int16_t *rewind_buf;
|
||||
size_t rewind_ptr;
|
||||
size_t rewind_size;
|
||||
|
||||
dylib_t dsp_lib;
|
||||
const ssnes_dsp_plugin_t *dsp_plugin;
|
||||
void *dsp_handle;
|
||||
@@ -233,19 +263,20 @@ struct global
|
||||
|
||||
// Movie record support
|
||||
bsv_movie_t *bsv_movie;
|
||||
char bsv_movie_path[256];
|
||||
char bsv_movie_path[MAXPATHLEN];
|
||||
bool bsv_movie_end;
|
||||
bool bsv_movie_playback;
|
||||
|
||||
// Pausing support
|
||||
bool is_paused;
|
||||
bool is_oneshot;
|
||||
|
||||
// Autosave support.
|
||||
autosave_t *autosave[2];
|
||||
|
||||
// Netplay.
|
||||
netplay_t *netplay;
|
||||
char netplay_server[256];
|
||||
char netplay_server[MAXPATHLEN];
|
||||
bool netplay_enable;
|
||||
bool netplay_is_client;
|
||||
unsigned netplay_sync_frames;
|
||||
@@ -254,10 +285,19 @@ struct global
|
||||
// FFmpeg record.
|
||||
#ifdef HAVE_FFMPEG
|
||||
ffemu_t *rec;
|
||||
char record_path[256];
|
||||
char record_path[MAXPATHLEN];
|
||||
bool recording;
|
||||
unsigned record_width;
|
||||
unsigned record_height;
|
||||
#endif
|
||||
|
||||
struct
|
||||
{
|
||||
const uint16_t *data;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
} frame_cache;
|
||||
|
||||
char title_buf[64];
|
||||
|
||||
struct
|
||||
@@ -269,8 +309,6 @@ struct global
|
||||
|
||||
char sha256[64 + 1];
|
||||
|
||||
bool do_screenshot;
|
||||
|
||||
#ifdef HAVE_XML
|
||||
cheat_manager_t *cheat;
|
||||
#endif
|
||||
@@ -285,17 +323,27 @@ extern struct global g_extern;
|
||||
if (g_extern.verbose) \
|
||||
fprintf(stderr, "SSNES: " __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define SSNES_ERR(...) do { \
|
||||
fprintf(stderr, "SSNES [ERROR] :: " __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define SSNES_WARN(...) do { \
|
||||
fprintf(stderr, "SSNES [WARN] :: " __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#ifndef max
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#define SSNES_SCALE_BASE 256
|
||||
|
||||
static inline uint32_t next_pow2(uint32_t v)
|
||||
{
|
||||
@@ -321,6 +369,19 @@ static inline uint8_t is_little_endian(void)
|
||||
return u.y[0];
|
||||
}
|
||||
|
||||
static inline void ssnes_sleep(unsigned usec)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
Sleep(10);
|
||||
#else
|
||||
struct timespec tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_nsec = 10000000
|
||||
};
|
||||
nanosleep(&tv, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
#include <stdint.h>
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
#include "sdlwrap.h"
|
||||
#include "gfx_common.h"
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
#include "fonts.h"
|
||||
#endif
|
||||
|
||||
static bool g_input_dead = true;
|
||||
static bool g_video_dead = true;
|
||||
@@ -37,7 +43,7 @@ typedef struct
|
||||
void *handle;
|
||||
} input_ext_t;
|
||||
|
||||
static void* input_ext_init(void)
|
||||
static void *input_ext_init(void)
|
||||
{
|
||||
g_input_dead = false;
|
||||
return calloc(1, sizeof(input_ext_t));
|
||||
@@ -193,7 +199,7 @@ static bool video_ext_frame(void *data, const void *frame, unsigned width, unsig
|
||||
return ext->driver->frame(ext->handle, frame, width, height, pitch, msg);
|
||||
}
|
||||
|
||||
static void* setup_input(ext_t *ext, const ssnes_input_driver_t *driver)
|
||||
static void *setup_input(ext_t *ext, const ssnes_input_driver_t *driver)
|
||||
{
|
||||
int joypad_index[5];
|
||||
for (unsigned i = 0; i < 5; i++)
|
||||
@@ -232,6 +238,27 @@ static bool setup_video(ext_t *ext, const video_info_t *video, const input_drive
|
||||
else if ((type == SSNES_SHADER_BSNES || type == SSNES_SHADER_AUTO) && *g_settings.video.bsnes_shader_path)
|
||||
xml_shader = g_settings.video.bsnes_shader_path;
|
||||
|
||||
int font_color_r = g_settings.video.msg_color_r * 255;
|
||||
int font_color_g = g_settings.video.msg_color_g * 255;
|
||||
int font_color_b = g_settings.video.msg_color_b * 255;
|
||||
font_color_r = font_color_r > 255 ? 255 : (font_color_r < 0 ? 0 : font_color_r);
|
||||
font_color_g = font_color_g > 255 ? 255 : (font_color_g < 0 ? 0 : font_color_g);
|
||||
font_color_b = font_color_b > 255 ? 255 : (font_color_b < 0 ? 0 : font_color_b);
|
||||
|
||||
const char *font = NULL;
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (*g_settings.video.font_path)
|
||||
font = g_settings.video.font_path;
|
||||
else
|
||||
font = font_renderer_get_default_font();
|
||||
#else
|
||||
font = *g_settings.video.font_path ?
|
||||
g_settings.video.font_path : NULL;
|
||||
#endif
|
||||
|
||||
char title_buf[128];
|
||||
gfx_window_title_reset();
|
||||
gfx_window_title(title_buf, sizeof(title_buf));
|
||||
|
||||
ssnes_video_info_t info = {
|
||||
.width = video->width,
|
||||
@@ -245,8 +272,10 @@ static bool setup_video(ext_t *ext, const video_info_t *video, const input_drive
|
||||
.color_format = video->rgb32 ? SSNES_COLOR_FORMAT_ARGB8888 : SSNES_COLOR_FORMAT_XRGB1555,
|
||||
.xml_shader = xml_shader,
|
||||
.cg_shader = cg_shader,
|
||||
.ttf_font = *g_settings.video.font_path ? g_settings.video.font_path : NULL,
|
||||
.ttf_font_size = g_settings.video.font_size
|
||||
.ttf_font = font,
|
||||
.ttf_font_size = g_settings.video.font_size,
|
||||
.ttf_font_color = (font_color_r << 16) | (font_color_g << 8) | (font_color_b << 0),
|
||||
.title_hint = title_buf,
|
||||
};
|
||||
|
||||
const ssnes_input_driver_t *input_driver = NULL;
|
||||
@@ -263,7 +292,7 @@ static bool setup_video(ext_t *ext, const video_info_t *video, const input_drive
|
||||
return true;
|
||||
}
|
||||
|
||||
static void* video_ext_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
static void *video_ext_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
ext_t *ext = calloc(1, sizeof(*ext));
|
||||
if (!ext)
|
||||
|
||||
@@ -108,7 +108,10 @@ typedef struct ssnes_video_info
|
||||
// Can be disregarded.
|
||||
const char *ttf_font;
|
||||
unsigned ttf_font_size;
|
||||
unsigned ttf_font_color; // Font color, in format ARGB8888. Alpha should be disregarded.
|
||||
|
||||
// A title that should be displayed in the title bar of the window.
|
||||
const char *title_hint;
|
||||
} ssnes_video_info_t;
|
||||
|
||||
// Some convenience macros.
|
||||
@@ -119,7 +122,7 @@ typedef struct ssnes_video_info
|
||||
|
||||
// I hope no joypad will ever have this many buttons or axes ... ;)
|
||||
// If joykey is this value, do not check that button.
|
||||
#define SSNES_NO_AXIS ((unsigned short)0xFFFFU)
|
||||
#define SSNES_NO_AXIS (0xFFFFFFFFU)
|
||||
#define SSNES_NO_BTN ((unsigned short)0xFFFFU)
|
||||
|
||||
// Masks to test on joykey which hat direction is to be tested for.
|
||||
@@ -155,8 +158,9 @@ struct ssnes_keybind
|
||||
// last frame and current frame in the Y-axis.
|
||||
int analog_y;
|
||||
|
||||
// Keyboard key. The key values use the SDL keysyms,
|
||||
// Keyboard key. The key values use the SDL 1.2 keysyms,
|
||||
// which probably need to be transformed to the native format.
|
||||
// The actual keysyms SSNES uses are found in input/keysym.h.
|
||||
unsigned short key;
|
||||
|
||||
// Joypad key. Joypad POV (hats) are embedded into this key as well.
|
||||
@@ -235,7 +239,7 @@ typedef struct ssnes_video_driver
|
||||
const char *ident;
|
||||
|
||||
// Needs to be defined to SSNES_GRAPHICS_API_VERSION.
|
||||
// This is used to detect API mismatches.
|
||||
// This is used to detect API/ABI mismatches.
|
||||
int api_version;
|
||||
} ssnes_video_driver_t;
|
||||
|
||||
|
||||
+39
-5
@@ -16,10 +16,10 @@
|
||||
*/
|
||||
|
||||
#include "fonts.h"
|
||||
#include "file.h"
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
@@ -79,12 +79,16 @@ void font_renderer_msg(font_renderer_t *handle, const char *msg, struct font_out
|
||||
if (!err)
|
||||
{
|
||||
struct font_output *tmp = calloc(1, sizeof(*tmp));
|
||||
assert(tmp);
|
||||
|
||||
//fprintf(stderr, "Char: %c, off_x: %d, off_y: %d, bmp_left: %d, bmp_top: %d\n", msg[i], off_x, off_y, slot->bitmap_left, slot->bitmap_top);
|
||||
if (!tmp)
|
||||
break;
|
||||
|
||||
tmp->output = malloc(slot->bitmap.pitch * slot->bitmap.rows);
|
||||
assert(tmp->output);
|
||||
if (!tmp->output)
|
||||
{
|
||||
free(tmp);
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(tmp->output, slot->bitmap.buffer, slot->bitmap.pitch * slot->bitmap.rows);
|
||||
|
||||
tmp->width = slot->bitmap.width;
|
||||
@@ -128,3 +132,33 @@ void font_renderer_free(font_renderer_t *handle)
|
||||
if (handle->lib)
|
||||
FT_Done_FreeType(handle->lib);
|
||||
}
|
||||
|
||||
// Not the cleanest way to do things for sure, but should hopefully work ... :)
|
||||
|
||||
#if defined(_WIN32)
|
||||
static const char *font_paths[] = {
|
||||
"C:\\Windows\\Fonts\\consola.ttf",
|
||||
"C:\\Windows\\Fonts\\verdana.ttf",
|
||||
#elif defined(__APPLE__)
|
||||
static const char *font_paths[] = {
|
||||
"/Library/Fonts/Microsoft/Candara.ttf",
|
||||
#else
|
||||
static const char *font_paths[] = {
|
||||
"/usr/share/fonts/TTF/DejaVuSansMono.ttf",
|
||||
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
||||
#endif
|
||||
"osd-font.ttf", // Magic font to search for, useful for distribution.
|
||||
};
|
||||
|
||||
// Highly OS/platform dependent.
|
||||
const char *font_renderer_get_default_font(void)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(font_paths) / sizeof(font_paths[0]); i++)
|
||||
{
|
||||
if (path_file_exists(font_paths[i]))
|
||||
return font_paths[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+7
-3
@@ -25,10 +25,10 @@ typedef struct font_renderer font_renderer_t;
|
||||
|
||||
struct font_output
|
||||
{
|
||||
uint8_t *output;
|
||||
uint8_t *output; // 8-bit intensity.
|
||||
unsigned width, height, pitch;
|
||||
int off_x, off_y;
|
||||
struct font_output *next;
|
||||
struct font_output *next; // linked list.
|
||||
};
|
||||
|
||||
struct font_output_list
|
||||
@@ -37,8 +37,12 @@ struct font_output_list
|
||||
};
|
||||
|
||||
font_renderer_t *font_renderer_new(const char *font_path, unsigned font_size);
|
||||
void font_renderer_msg(font_renderer_t *handle, const char *msg, struct font_output_list *output);
|
||||
void font_renderer_msg(font_renderer_t *handle, const char *msg,
|
||||
struct font_output_list *output);
|
||||
|
||||
void font_renderer_free_output(struct font_output_list *list);
|
||||
void font_renderer_free(font_renderer_t *handle);
|
||||
|
||||
const char *font_renderer_get_default_font(void);
|
||||
|
||||
#endif
|
||||
+1
-30
@@ -84,7 +84,7 @@ void gfx_set_dwm(void)
|
||||
dwmlib = dylib_load("dwmapi.dll");
|
||||
if (!dwmlib)
|
||||
{
|
||||
SSNES_LOG("Did not find dwmapi.dll");
|
||||
SSNES_LOG("Did not find dwmapi.dll.\n");
|
||||
return;
|
||||
}
|
||||
atexit(gfx_dwm_shutdown);
|
||||
@@ -112,32 +112,3 @@ void gfx_set_dwm(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#include "SDL_syswm.h"
|
||||
#include "SDL.h"
|
||||
|
||||
void gfx_get_window_size(unsigned *width, unsigned *height)
|
||||
{
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
SDL_Event evnt;
|
||||
while (SDL_PollEvent(&evnt));
|
||||
const SDL_VideoInfo *info = SDL_GetVideoInfo();
|
||||
*width = info->current_w;
|
||||
*height = info->current_h;
|
||||
#else
|
||||
// It seems that we need to go hardcore to get the actual
|
||||
// window sizes properly right after startup ... :D
|
||||
SDL_SysWMinfo info;
|
||||
SDL_VERSION(&info.version);
|
||||
SDL_GetWMInfo(&info);
|
||||
XWindowAttributes target;
|
||||
info.info.x11.lock_func();
|
||||
XGetWindowAttributes(info.info.x11.display, info.info.x11.window,
|
||||
&target);
|
||||
info.info.x11.unlock_func();
|
||||
|
||||
*width = target.width;
|
||||
*height = target.height;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -28,6 +28,4 @@ void gfx_window_title_reset(void);
|
||||
void gfx_set_dwm(void);
|
||||
#endif
|
||||
|
||||
void gfx_get_window_size(unsigned *width, unsigned *height);
|
||||
|
||||
#endif
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
#include "gl_common.h"
|
||||
#include "gfx_common.h"
|
||||
#include "sdlwrap.h"
|
||||
#include "strl.h"
|
||||
|
||||
#define NO_SDL_GLEXT
|
||||
#include "SDL.h"
|
||||
@@ -147,6 +149,7 @@ typedef struct gl
|
||||
|
||||
bool should_resize;
|
||||
bool quitting;
|
||||
bool fullscreen;
|
||||
bool keep_aspect;
|
||||
|
||||
unsigned full_x, full_y;
|
||||
@@ -170,6 +173,10 @@ typedef struct gl
|
||||
#ifdef HAVE_FREETYPE
|
||||
font_renderer_t *font;
|
||||
GLuint font_tex;
|
||||
int font_tex_w, font_tex_h;
|
||||
void *font_tex_empty_buf;
|
||||
char font_last_msg[256];
|
||||
int font_last_width, font_last_height;
|
||||
#endif
|
||||
|
||||
} gl_t;
|
||||
@@ -330,9 +337,13 @@ static void gl_shader_scale(unsigned index, struct gl_fbo_scale *scale)
|
||||
static inline void gl_init_font(gl_t *gl, const char *font_path, unsigned font_size)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (strlen(font_path) > 0)
|
||||
const char *path = font_path;
|
||||
if (!*path)
|
||||
path = font_renderer_get_default_font();
|
||||
|
||||
if (path)
|
||||
{
|
||||
gl->font = font_renderer_new(font_path, font_size);
|
||||
gl->font = font_renderer_new(path, font_size);
|
||||
if (gl->font)
|
||||
{
|
||||
glGenTextures(1, &gl->font_tex);
|
||||
@@ -346,6 +357,12 @@ static inline void gl_init_font(gl_t *gl, const char *font_path, unsigned font_s
|
||||
else
|
||||
SSNES_WARN("Couldn't init font renderer with font \"%s\"...\n", font_path);
|
||||
}
|
||||
else
|
||||
SSNES_LOG("Did not find default font.\n");
|
||||
#else
|
||||
(void)gl;
|
||||
(void)font_path;
|
||||
(void)font_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -498,7 +515,7 @@ static void gl_init_fbo(gl_t *gl, unsigned width, unsigned height)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_type);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, gl->fbo_rect[i].width, gl->fbo_rect[i].height, 0, GL_RGBA,
|
||||
0, GL_RGBA, gl->fbo_rect[i].width, gl->fbo_rect[i].height, 0, GL_BGRA,
|
||||
GL_UNSIGNED_INT_8_8_8_8, NULL);
|
||||
}
|
||||
|
||||
@@ -521,7 +538,7 @@ static void gl_init_fbo(gl_t *gl, unsigned width, unsigned height)
|
||||
error:
|
||||
glDeleteTextures(gl->fbo_pass, gl->fbo_texture);
|
||||
pglDeleteFramebuffers(gl->fbo_pass, gl->fbo);
|
||||
SSNES_WARN("Failed to set up FBO. Two-pass shading will not work.\n");
|
||||
SSNES_ERR("Failed to set up frame buffer objects. Multi-pass shading will not work.\n");
|
||||
#else
|
||||
(void)gl;
|
||||
(void)width;
|
||||
@@ -536,7 +553,12 @@ static inline void gl_deinit_font(gl_t *gl)
|
||||
{
|
||||
font_renderer_free(gl->font);
|
||||
glDeleteTextures(1, &gl->font_tex);
|
||||
|
||||
if (gl->font_tex_empty_buf)
|
||||
free(gl->font_tex_empty_buf);
|
||||
}
|
||||
#else
|
||||
(void)gl;
|
||||
#endif
|
||||
}
|
||||
////////////
|
||||
@@ -603,63 +625,192 @@ static void set_viewport(gl_t *gl, unsigned width, unsigned height, bool force_f
|
||||
//SSNES_LOG("Setting viewport @ %ux%u\n", width, height);
|
||||
}
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
|
||||
// Somewhat overwhelming code just to render some damn fonts.
|
||||
// We aim to use NPOT textures for compatibility with old and shitty cards.
|
||||
// Also, we want to avoid reallocating a texture for each glyph (performance dips), so we
|
||||
// contruct the whole texture using one call, and copy straight to it with
|
||||
// glTexSubImage.
|
||||
|
||||
struct font_rect
|
||||
{
|
||||
int x, y;
|
||||
int width, height;
|
||||
int pot_width, pot_height;
|
||||
};
|
||||
|
||||
static void calculate_msg_geometry(const struct font_output *head, struct font_rect *rect)
|
||||
{
|
||||
int x_min = head->off_x;
|
||||
int x_max = head->off_x + head->width;
|
||||
int y_min = head->off_y;
|
||||
int y_max = head->off_y + head->height;
|
||||
|
||||
while ((head = head->next))
|
||||
{
|
||||
int left = head->off_x;
|
||||
int right = head->off_x + head->width;
|
||||
int bottom = head->off_y;
|
||||
int top = head->off_y + head->height;
|
||||
|
||||
if (left < x_min)
|
||||
x_min = left;
|
||||
if (right > x_max)
|
||||
x_max = right;
|
||||
|
||||
if (bottom < y_min)
|
||||
y_min = bottom;
|
||||
if (top > y_max)
|
||||
y_max = top;
|
||||
}
|
||||
|
||||
rect->x = x_min;
|
||||
rect->y = y_min;
|
||||
rect->width = x_max - x_min;
|
||||
rect->height = y_max - y_min;
|
||||
}
|
||||
|
||||
static void adjust_power_of_two(gl_t *gl, struct font_rect *geom)
|
||||
{
|
||||
// Some systems really hate NPOT textures.
|
||||
geom->pot_width = next_pow2(geom->width);
|
||||
geom->pot_height = next_pow2(geom->height);
|
||||
|
||||
if ((geom->pot_width > gl->font_tex_w) || (geom->pot_height > gl->font_tex_h))
|
||||
{
|
||||
gl->font_tex_empty_buf = realloc(gl->font_tex_empty_buf, geom->pot_width * geom->pot_height);
|
||||
memset(gl->font_tex_empty_buf, 0, geom->pot_width * geom->pot_height);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 8);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, geom->pot_width);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY8, geom->pot_width, geom->pot_height,
|
||||
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, gl->font_tex_empty_buf);
|
||||
|
||||
gl->font_tex_w = geom->pot_width;
|
||||
gl->font_tex_h = geom->pot_height;
|
||||
}
|
||||
}
|
||||
|
||||
// Old style "blitting", so we can render all the fonts in one go.
|
||||
// TODO: Is it possible that fonts could overlap if we blit without alpha blending?
|
||||
static void blit_fonts(gl_t *gl, const struct font_output *head, const struct font_rect *geom)
|
||||
{
|
||||
// Clear out earlier fonts.
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 8);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->font_tex_w);
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0, 0, 0, gl->font_tex_w, gl->font_tex_h,
|
||||
GL_LUMINANCE, GL_UNSIGNED_BYTE, gl->font_tex_empty_buf);
|
||||
|
||||
while (head)
|
||||
{
|
||||
// head has top-left oriented coords.
|
||||
int x = head->off_x - geom->x;
|
||||
int y = head->off_y - geom->y;
|
||||
y = gl->font_tex_h - head->height - y - 1;
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(head->pitch));
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, head->pitch);
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0, x, y, head->width, head->height,
|
||||
GL_LUMINANCE, GL_UNSIGNED_BYTE, head->output);
|
||||
|
||||
head = head->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void calculate_font_coords(gl_t *gl,
|
||||
GLfloat font_vertex[8], GLfloat font_tex_coords[8])
|
||||
{
|
||||
GLfloat lx = g_settings.video.msg_pos_x;
|
||||
GLfloat hx = (GLfloat)gl->font_last_width / gl->vp_width + lx;
|
||||
GLfloat ly = g_settings.video.msg_pos_y;
|
||||
GLfloat hy = (GLfloat)gl->font_last_height / gl->vp_height + ly;
|
||||
|
||||
font_vertex[0] = lx;
|
||||
font_vertex[1] = ly;
|
||||
font_vertex[2] = lx;
|
||||
font_vertex[3] = hy;
|
||||
font_vertex[4] = hx;
|
||||
font_vertex[5] = hy;
|
||||
font_vertex[6] = hx;
|
||||
font_vertex[7] = ly;
|
||||
|
||||
lx = 0.0f;
|
||||
hx = (GLfloat)gl->font_last_width / gl->font_tex_w;
|
||||
ly = 1.0f - (GLfloat)gl->font_last_height / gl->font_tex_h;
|
||||
hy = 1.0f;
|
||||
|
||||
font_tex_coords[0] = lx;
|
||||
font_tex_coords[1] = hy;
|
||||
font_tex_coords[2] = lx;
|
||||
font_tex_coords[3] = ly;
|
||||
font_tex_coords[4] = hx;
|
||||
font_tex_coords[5] = ly;
|
||||
font_tex_coords[6] = hx;
|
||||
font_tex_coords[7] = hy;
|
||||
}
|
||||
|
||||
static void gl_render_msg(gl_t *gl, const char *msg)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (!gl->font)
|
||||
return;
|
||||
|
||||
GLfloat font_vertex[8];
|
||||
GLfloat font_tex_coords[8];
|
||||
|
||||
// Deactivate custom shaders. Enable the font texture.
|
||||
gl_shader_use(0);
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->font_tex);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), font_vertex);
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), tex_coords); // Use the static one (uses whole texture).
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), font_tex_coords);
|
||||
|
||||
// Need blending.
|
||||
// Using fixed function pipeline here since we cannot guarantee presence of shaders (would be kinda overkill anyways).
|
||||
glEnable(GL_BLEND);
|
||||
glColor4f(g_settings.video.msg_color_r, g_settings.video.msg_color_g,
|
||||
g_settings.video.msg_color_b, 1);
|
||||
|
||||
struct font_output_list out;
|
||||
font_renderer_msg(gl->font, msg, &out);
|
||||
struct font_output *head = out.head;
|
||||
|
||||
while (head != NULL)
|
||||
// If we get the same message, there's obviously no need to render fonts again ...
|
||||
if (strcmp(gl->font_last_msg, msg) != 0)
|
||||
{
|
||||
GLfloat lx = (GLfloat)head->off_x / gl->vp_width + g_settings.video.msg_pos_x;
|
||||
GLfloat hx = (GLfloat)(head->off_x + head->width) / gl->vp_width + g_settings.video.msg_pos_x;
|
||||
GLfloat ly = (GLfloat)head->off_y / gl->vp_height + g_settings.video.msg_pos_y;
|
||||
GLfloat hy = (GLfloat)(head->off_y + head->height) / gl->vp_height + g_settings.video.msg_pos_y;
|
||||
font_renderer_msg(gl->font, msg, &out);
|
||||
struct font_output *head = out.head;
|
||||
|
||||
font_vertex[0] = lx;
|
||||
font_vertex[1] = ly;
|
||||
font_vertex[2] = lx;
|
||||
font_vertex[3] = hy;
|
||||
font_vertex[4] = hx;
|
||||
font_vertex[5] = hy;
|
||||
font_vertex[6] = hx;
|
||||
font_vertex[7] = ly;
|
||||
struct font_rect geom;
|
||||
calculate_msg_geometry(head, &geom);
|
||||
adjust_power_of_two(gl, &geom);
|
||||
blit_fonts(gl, head, &geom);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(head->pitch));
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, head->pitch);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_INTENSITY8, head->width, head->height, 0, GL_LUMINANCE,
|
||||
GL_UNSIGNED_BYTE, head->output);
|
||||
font_renderer_free_output(&out);
|
||||
strlcpy(gl->font_last_msg, msg, sizeof(gl->font_last_msg));
|
||||
|
||||
head = head->next;
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
gl->font_last_width = geom.width;
|
||||
gl->font_last_height = geom.height;
|
||||
}
|
||||
font_renderer_free_output(&out);
|
||||
calculate_font_coords(gl, font_vertex, font_tex_coords);
|
||||
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
// Go back to old rendering path.
|
||||
glColor4f(1, 1, 1, 1);
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
static void gl_render_msg(gl_t *gl, const char *msg)
|
||||
{
|
||||
(void)gl;
|
||||
(void)msg;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void set_lut_texture_coords(const GLfloat *coords)
|
||||
{
|
||||
@@ -684,46 +835,21 @@ static inline void set_texture_coords(GLfloat *coords, GLfloat xamt, GLfloat yam
|
||||
|
||||
static void check_window(gl_t *gl)
|
||||
{
|
||||
SDL_Event event;
|
||||
bool quit, resize;
|
||||
|
||||
// Search for events we care about ...
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
gl->quitting = true;
|
||||
break;
|
||||
sdlwrap_check_window(&quit,
|
||||
&resize, &gl->win_width, &gl->win_height,
|
||||
gl->frame_count);
|
||||
|
||||
case SDL_VIDEORESIZE:
|
||||
gl->should_resize = true;
|
||||
gl->win_width = event.resize.w;
|
||||
gl->win_height = event.resize.h;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gl->should_resize)
|
||||
{
|
||||
unsigned new_width, new_height;
|
||||
gfx_get_window_size(&new_width, &new_height);
|
||||
if ((new_width != gl->win_width || new_height != gl->win_height) || (gl->frame_count == 10)) // Ugly hack :D
|
||||
{
|
||||
gl->should_resize = true;
|
||||
gl->win_width = new_width;
|
||||
gl->win_height = new_height;
|
||||
SSNES_LOG("GL: Verified window size: %u x %u\n", gl->win_width, gl->win_height);
|
||||
}
|
||||
}
|
||||
if (quit)
|
||||
gl->quitting = true;
|
||||
else if (resize)
|
||||
gl->should_resize = true;
|
||||
}
|
||||
|
||||
static bool gl_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
|
||||
{
|
||||
gl_t *gl = data;
|
||||
check_window(gl);
|
||||
|
||||
gl_shader_use(1);
|
||||
gl->frame_count++;
|
||||
@@ -797,7 +923,8 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
if (gl->should_resize)
|
||||
{
|
||||
gl->should_resize = false;
|
||||
SDL_SetVideoMode(gl->win_width, gl->win_height, 0, SDL_OPENGL | SDL_RESIZABLE | (g_settings.video.fullscreen ? SDL_FULLSCREEN : 0));
|
||||
|
||||
sdlwrap_set_resize(gl->win_width, gl->win_height);
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
if (!gl->render_to_tex)
|
||||
@@ -820,7 +947,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
pglBindFramebuffer(GL_FRAMEBUFFER, gl->fbo[i]);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->fbo_texture[i]);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, gl->fbo_rect[i].width, gl->fbo_rect[i].height, 0, GL_RGBA,
|
||||
0, GL_RGBA, gl->fbo_rect[i].width, gl->fbo_rect[i].height, 0, GL_BGRA,
|
||||
GL_UNSIGNED_INT_8_8_8_8, NULL);
|
||||
|
||||
pglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->fbo_texture[i], 0);
|
||||
@@ -869,7 +996,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
// Need to preserve the "flipped" state when in FBO too to have
|
||||
// Need to preserve the "flipped" state when in FBO as well to have
|
||||
// consistent texture coordinates.
|
||||
if (gl->render_to_tex)
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes);
|
||||
@@ -981,8 +1108,9 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
SDL_WM_SetCaption(buf, NULL);
|
||||
SDL_GL_SwapBuffers();
|
||||
sdlwrap_wm_set_caption(buf);
|
||||
|
||||
sdlwrap_swap_buffers();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1004,7 +1132,7 @@ static void gl_free(void *data)
|
||||
pglDeleteFramebuffers(gl->fbo_pass, gl->fbo);
|
||||
}
|
||||
#endif
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
sdlwrap_destroy();
|
||||
|
||||
if (gl->empty_buf)
|
||||
free(gl->empty_buf);
|
||||
@@ -1012,38 +1140,13 @@ static void gl_free(void *data)
|
||||
free(gl);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#endif
|
||||
|
||||
static void gl_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
gl_t *gl = data;
|
||||
if (gl->vsync)
|
||||
{
|
||||
SSNES_LOG("GL VSync => %s\n", state ? "off" : "on");
|
||||
#if defined(_WIN32)
|
||||
static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL;
|
||||
if (!wgl_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(wgl_swap_interval, "wglSwapIntervalEXT");
|
||||
}
|
||||
if (wgl_swap_interval) wgl_swap_interval(state ? 0 : 1);
|
||||
#elif defined(__APPLE__)
|
||||
GLint val = state ? 0 : 1;
|
||||
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &val);
|
||||
#else
|
||||
static int (*glx_swap_interval)(int) = NULL;
|
||||
if (!glx_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalSGI");
|
||||
}
|
||||
if (!glx_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalMESA");
|
||||
}
|
||||
if (glx_swap_interval) glx_swap_interval(state ? 0 : 1);
|
||||
#endif
|
||||
sdlwrap_set_swap_interval(state ? 0 : 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1053,7 +1156,7 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
gfx_set_dwm();
|
||||
#endif
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
if (!sdlwrap_init())
|
||||
return NULL;
|
||||
|
||||
const SDL_VideoInfo *video_info = SDL_GetVideoInfo();
|
||||
@@ -1062,16 +1165,24 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
unsigned full_y = video_info->current_h;
|
||||
SSNES_LOG("Detecting desktop resolution %ux%u.\n", full_x, full_y);
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, video->vsync ? 1 : 0);
|
||||
sdlwrap_set_swap_interval(video->vsync ? 1 : 0, false);
|
||||
|
||||
if (!SDL_SetVideoMode(video->width, video->height, g_settings.video.force_16bit ? 16 : 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
|
||||
unsigned win_width = video->width;
|
||||
unsigned win_height = video->height;
|
||||
if (video->fullscreen && (win_width == 0) && (win_height == 0))
|
||||
{
|
||||
win_width = full_x;
|
||||
win_height = full_y;
|
||||
}
|
||||
|
||||
if (!sdlwrap_set_video_mode(win_width, win_height,
|
||||
g_settings.video.force_16bit ? 15 : 0, video->fullscreen))
|
||||
return NULL;
|
||||
|
||||
gfx_window_title_reset();
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
SDL_WM_SetCaption(buf, NULL);
|
||||
sdlwrap_wm_set_caption(buf);
|
||||
|
||||
// Remove that ugly mouse :D
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
@@ -1082,7 +1193,7 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
// Need to load dynamically :(
|
||||
if (!load_gl_proc())
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
sdlwrap_destroy();
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
@@ -1090,43 +1201,24 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
gl_t *gl = calloc(1, sizeof(gl_t));
|
||||
if (!gl)
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
sdlwrap_destroy();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gl->vsync = video->vsync;
|
||||
int attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr);
|
||||
if (attr <= 0 && video->vsync)
|
||||
{
|
||||
SSNES_WARN("SDL failed to setup VSync, attempting to recover using native calls!\n");
|
||||
gl_set_nonblock_state(gl, false);
|
||||
}
|
||||
attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &attr);
|
||||
if (attr <= 0)
|
||||
SSNES_WARN("GL double buffer has not been enabled!\n");
|
||||
|
||||
gl->fullscreen = video->fullscreen;
|
||||
|
||||
gl->full_x = full_x;
|
||||
gl->full_y = full_y;
|
||||
|
||||
if (video->fullscreen)
|
||||
{
|
||||
gl->win_width = video->width ? video->width : gl->full_x;
|
||||
gl->win_height = video->height ? video->height : gl->full_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
gl->win_width = video->width;
|
||||
gl->win_height = video->height;
|
||||
}
|
||||
gl->win_width = win_width;
|
||||
gl->win_height = win_height;
|
||||
|
||||
SSNES_LOG("GL: Using resolution %ux%u\n", gl->win_width, gl->win_height);
|
||||
|
||||
if (!gl_shader_init())
|
||||
{
|
||||
SSNES_ERR("Shader init failed.\n");
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
sdlwrap_destroy();
|
||||
free(gl);
|
||||
return NULL;
|
||||
}
|
||||
@@ -1134,7 +1226,8 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
SSNES_LOG("GL: Loaded %u program(s).\n", gl_shader_num());
|
||||
|
||||
// Set up render to texture.
|
||||
gl_init_fbo(gl, 256 * video->input_scale, 256 * video->input_scale);
|
||||
gl_init_fbo(gl, SSNES_SCALE_BASE * video->input_scale,
|
||||
SSNES_SCALE_BASE * video->input_scale);
|
||||
|
||||
gl->keep_aspect = video->force_aspect;
|
||||
|
||||
@@ -1185,15 +1278,16 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
|
||||
set_lut_texture_coords(tex_coords);
|
||||
|
||||
gl->tex_w = 256 * video->input_scale;
|
||||
gl->tex_h = 256 * video->input_scale;
|
||||
gl->tex_w = SSNES_SCALE_BASE * video->input_scale;
|
||||
gl->tex_h = SSNES_SCALE_BASE * video->input_scale;
|
||||
|
||||
// Empty buffer that we use to clear out the texture with on res change.
|
||||
gl->empty_buf = calloc(gl->base_size, gl->tex_w * gl->tex_h);
|
||||
gl->empty_buf = calloc(gl->tex_w * gl->tex_h, gl->base_size);
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[i]);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->tex_w);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, gl->tex_w, gl->tex_h, 0, gl->texture_type,
|
||||
gl->texture_fmt, gl->empty_buf ? gl->empty_buf : NULL);
|
||||
@@ -1227,7 +1321,7 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
sdlwrap_destroy();
|
||||
free(gl);
|
||||
return NULL;
|
||||
}
|
||||
@@ -1238,13 +1332,14 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
static bool gl_alive(void *data)
|
||||
{
|
||||
gl_t *gl = data;
|
||||
check_window(gl);
|
||||
return !gl->quitting;
|
||||
}
|
||||
|
||||
static bool gl_focus(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return (SDL_GetAppState() & (SDL_APPINPUTFOCUS | SDL_APPACTIVE)) == (SDL_APPINPUTFOCUS | SDL_APPACTIVE);
|
||||
return sdlwrap_window_has_focus();
|
||||
}
|
||||
|
||||
#ifdef HAVE_XML
|
||||
|
||||
@@ -106,13 +106,6 @@ struct gl_tex_info
|
||||
GLfloat coord[8];
|
||||
};
|
||||
|
||||
// Not legal to cast void* to fn-pointer. Need dirty hack to be compilant.
|
||||
// sizeof(void*) == sizeof(void (*)(void)) is asserted earlier.
|
||||
#define SDL_SYM_WRAP(sym, symbol) { \
|
||||
void *sym__ = SDL_GL_GetProcAddress(symbol); \
|
||||
memcpy(&(sym), &sym__, sizeof(void*)); \
|
||||
}
|
||||
|
||||
// Windows ... <_<
|
||||
#if (defined(HAVE_XML) || defined(HAVE_CG)) && defined(_WIN32)
|
||||
extern PFNGLCLIENTACTIVETEXTUREPROC pglClientActiveTexture;
|
||||
|
||||
+8
-8
@@ -63,7 +63,7 @@ bool texture_image_load(const char *path, struct texture_image *out_img)
|
||||
uint32_t g = (src[x] & fmt->Gmask) >> fmt->Gshift;
|
||||
uint32_t b = (src[x] & fmt->Bmask) >> fmt->Bshift;
|
||||
uint32_t a = (src[x] & fmt->Amask) >> fmt->Ashift;
|
||||
dst[x] = (r << 24) | (g << 16) | (b << 8) | a;
|
||||
dst[x] = (b << 24) | (g << 16) | (r << 8) | a;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ bool texture_image_load(const char *path, struct texture_image *out_img)
|
||||
uint32_t r = (color & fmt->Rmask) >> fmt->Rshift;
|
||||
uint32_t g = (color & fmt->Gmask) >> fmt->Gshift;
|
||||
uint32_t b = (color & fmt->Bmask) >> fmt->Bshift;
|
||||
dst[x] = (r << 24) | (g << 16) | (b << 8) | 0xff;
|
||||
dst[x] = (b << 24) | (g << 16) | (r << 8) | 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,24 +148,24 @@ bool texture_image_load(const char *path, struct texture_image *out_img)
|
||||
{
|
||||
for (unsigned i = 0; i < width * height; i++)
|
||||
{
|
||||
uint32_t r = tmp[i * 4 + 2];
|
||||
uint32_t g = tmp[i * 4 + 1];
|
||||
uint32_t b = tmp[i * 4 + 0];
|
||||
uint32_t g = tmp[i * 4 + 1];
|
||||
uint32_t r = tmp[i * 4 + 2];
|
||||
uint32_t a = tmp[i * 4 + 3];
|
||||
|
||||
out_img->pixels[i] = (r << 24) | (g << 16) | (b << 8) | a;
|
||||
out_img->pixels[i] = (b << 24) | (g << 16) | (r << 8) | a;
|
||||
}
|
||||
}
|
||||
else if (bits == 24)
|
||||
{
|
||||
for (unsigned i = 0; i < width * height; i++)
|
||||
{
|
||||
uint32_t r = tmp[i * 3 + 2];
|
||||
uint32_t g = tmp[i * 3 + 1];
|
||||
uint32_t b = tmp[i * 3 + 0];
|
||||
uint32_t g = tmp[i * 3 + 1];
|
||||
uint32_t r = tmp[i * 3 + 2];
|
||||
uint32_t a = 0xff;
|
||||
|
||||
out_img->pixels[i] = (r << 24) | (g << 16) | (b << 8) | a;
|
||||
out_img->pixels[i] = (b << 24) | (g << 16) | (r << 8) | a;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
+61
-26
@@ -60,7 +60,7 @@ PY_READ_FUNC(APURAM)
|
||||
PY_READ_FUNC(CGRAM)
|
||||
PY_READ_FUNC(OAM)
|
||||
|
||||
static PyObject* py_read_input(PyObject *self, PyObject *args)
|
||||
static PyObject *py_read_input(PyObject *self, PyObject *args)
|
||||
{
|
||||
(void)self;
|
||||
|
||||
@@ -72,7 +72,7 @@ static PyObject* py_read_input(PyObject *self, PyObject *args)
|
||||
if (!PyArg_ParseTuple(args, "II", &player, &key))
|
||||
return NULL;
|
||||
|
||||
if (player > MAX_PLAYERS || player < 1)
|
||||
if (player > MAX_PLAYERS || player < 1 || key >= SSNES_FIRST_META_KEY)
|
||||
return NULL;
|
||||
|
||||
const struct snes_keybind *binds[MAX_PLAYERS];
|
||||
@@ -88,6 +88,24 @@ static PyObject* py_read_input(PyObject *self, PyObject *args)
|
||||
return PyBool_FromLong((long)res);
|
||||
}
|
||||
|
||||
static PyObject *py_read_input_meta(PyObject *self, PyObject *args)
|
||||
{
|
||||
(void)self;
|
||||
|
||||
if (!driver.input_data)
|
||||
return PyBool_FromLong(0);
|
||||
|
||||
unsigned key;
|
||||
if (!PyArg_ParseTuple(args, "I", &key))
|
||||
return NULL;
|
||||
|
||||
if (key < SSNES_FIRST_META_KEY)
|
||||
return NULL;
|
||||
|
||||
bool ret = driver.input->key_pressed(driver.input_data, key);
|
||||
return PyBool_FromLong((long)ret);
|
||||
}
|
||||
|
||||
static PyMethodDef SNESMethods[] = {
|
||||
{ "read_wram", PY_READ_FUNC_DECL(WRAM), METH_VARARGS, "Read WRAM from SNES." },
|
||||
{ "read_vram", PY_READ_FUNC_DECL(VRAM), METH_VARARGS, "Read VRAM from SNES." },
|
||||
@@ -95,23 +113,49 @@ static PyMethodDef SNESMethods[] = {
|
||||
{ "read_cgram", PY_READ_FUNC_DECL(CGRAM), METH_VARARGS, "Read CGRAM from SNES." },
|
||||
{ "read_oam", PY_READ_FUNC_DECL(OAM), METH_VARARGS, "Read OAM from SNES." },
|
||||
{ "input", py_read_input, METH_VARARGS, "Read input state from SNES." },
|
||||
{ "input_meta", py_read_input_meta, METH_VARARGS, "Read SSNES specific input." },
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
#define DECL_ATTR_SNES(attr) PyObject_SetAttrString(mod, #attr, PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_##attr))
|
||||
#define DECL_ATTR_SSNES(attr) PyObject_SetAttrString(mod, #attr, PyLong_FromLong(SSNES_##attr))
|
||||
static void py_set_attrs(PyObject *mod)
|
||||
{
|
||||
PyObject_SetAttrString(mod, "B", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_B));
|
||||
PyObject_SetAttrString(mod, "Y", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_Y));
|
||||
PyObject_SetAttrString(mod, "SELECT", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_SELECT));
|
||||
PyObject_SetAttrString(mod, "START", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_START));
|
||||
PyObject_SetAttrString(mod, "UP", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_UP));
|
||||
PyObject_SetAttrString(mod, "DOWN", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_DOWN));
|
||||
PyObject_SetAttrString(mod, "LEFT", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_LEFT));
|
||||
PyObject_SetAttrString(mod, "RIGHT", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_RIGHT));
|
||||
PyObject_SetAttrString(mod, "A", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_A));
|
||||
PyObject_SetAttrString(mod, "X", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_X));
|
||||
PyObject_SetAttrString(mod, "L", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_L));
|
||||
PyObject_SetAttrString(mod, "R", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_R));
|
||||
DECL_ATTR_SNES(B);
|
||||
DECL_ATTR_SNES(Y);
|
||||
DECL_ATTR_SNES(SELECT);
|
||||
DECL_ATTR_SNES(START);
|
||||
DECL_ATTR_SNES(UP);
|
||||
DECL_ATTR_SNES(DOWN);
|
||||
DECL_ATTR_SNES(LEFT);
|
||||
DECL_ATTR_SNES(RIGHT);
|
||||
DECL_ATTR_SNES(A);
|
||||
DECL_ATTR_SNES(X);
|
||||
DECL_ATTR_SNES(L);
|
||||
DECL_ATTR_SNES(R);
|
||||
|
||||
DECL_ATTR_SSNES(FAST_FORWARD_KEY);
|
||||
DECL_ATTR_SSNES(FAST_FORWARD_HOLD_KEY);
|
||||
DECL_ATTR_SSNES(LOAD_STATE_KEY);
|
||||
DECL_ATTR_SSNES(SAVE_STATE_KEY);
|
||||
DECL_ATTR_SSNES(FULLSCREEN_TOGGLE_KEY);
|
||||
DECL_ATTR_SSNES(QUIT_KEY);
|
||||
DECL_ATTR_SSNES(STATE_SLOT_PLUS);
|
||||
DECL_ATTR_SSNES(STATE_SLOT_MINUS);
|
||||
DECL_ATTR_SSNES(AUDIO_INPUT_RATE_PLUS);
|
||||
DECL_ATTR_SSNES(AUDIO_INPUT_RATE_MINUS);
|
||||
DECL_ATTR_SSNES(REWIND);
|
||||
DECL_ATTR_SSNES(MOVIE_RECORD_TOGGLE);
|
||||
DECL_ATTR_SSNES(PAUSE_TOGGLE);
|
||||
DECL_ATTR_SSNES(FRAMEADVANCE);
|
||||
DECL_ATTR_SSNES(RESET);
|
||||
DECL_ATTR_SSNES(SHADER_NEXT);
|
||||
DECL_ATTR_SSNES(SHADER_PREV);
|
||||
DECL_ATTR_SSNES(CHEAT_INDEX_PLUS);
|
||||
DECL_ATTR_SSNES(CHEAT_INDEX_MINUS);
|
||||
DECL_ATTR_SSNES(CHEAT_TOGGLE);
|
||||
DECL_ATTR_SSNES(SCREENSHOT);
|
||||
DECL_ATTR_SSNES(DSP_CONFIG);
|
||||
}
|
||||
|
||||
static PyModuleDef SNESModule = {
|
||||
@@ -260,7 +304,7 @@ void py_state_free(py_state_t *handle)
|
||||
Py_Finalize();
|
||||
}
|
||||
|
||||
int py_state_get(py_state_t *handle, const char *id,
|
||||
float py_state_get(py_state_t *handle, const char *id,
|
||||
unsigned frame_count)
|
||||
{
|
||||
PyObject *ret = PyObject_CallMethod(handle->inst, (char*)id, (char*)"I", frame_count);
|
||||
@@ -269,19 +313,10 @@ int py_state_get(py_state_t *handle, const char *id,
|
||||
if (!handle->warned_ret)
|
||||
SSNES_WARN("Didn't get return value from script! Bug?\n");
|
||||
handle->warned_ret = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int retval = 0;
|
||||
if (PyLong_Check(ret))
|
||||
retval = (int)PyLong_AsLong(ret);
|
||||
else
|
||||
{
|
||||
if (!handle->warned_type)
|
||||
SSNES_WARN("Didn't get long compatible value from script! Bug?\n");
|
||||
handle->warned_type = true;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float retval = (float)PyFloat_AsDouble(ret);
|
||||
Py_DECREF(ret);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ typedef struct py_state py_state_t;
|
||||
py_state_t *py_state_new(const char *program, bool is_file, const char *pyclass);
|
||||
void py_state_free(py_state_t *handle);
|
||||
|
||||
int py_state_get(py_state_t *handle,
|
||||
float py_state_get(py_state_t *handle,
|
||||
const char *id, unsigned frame_count);
|
||||
|
||||
#endif
|
||||
@@ -45,11 +45,16 @@ struct sdl_video
|
||||
bool rgb32;
|
||||
bool upsample;
|
||||
|
||||
bool render32;
|
||||
|
||||
void (*convert_15_func)(uint16_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned, const SDL_PixelFormat*);
|
||||
void (*convert_32_func)(uint32_t*, unsigned, const uint32_t*, unsigned, unsigned, unsigned, const SDL_PixelFormat*);
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
font_renderer_t *font;
|
||||
uint8_t font_r;
|
||||
uint8_t font_g;
|
||||
uint8_t font_b;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -75,12 +80,40 @@ static void sdl_gfx_free(void *data)
|
||||
static void sdl_init_font(sdl_video_t *vid, const char *font_path, unsigned font_size)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (*font_path)
|
||||
const char *path = font_path;
|
||||
if (!*path)
|
||||
path = font_renderer_get_default_font();
|
||||
|
||||
if (path)
|
||||
{
|
||||
vid->font = font_renderer_new(font_path, font_size);
|
||||
if (!vid->font)
|
||||
vid->font = font_renderer_new(path, font_size);
|
||||
if (vid->font)
|
||||
{
|
||||
int r = g_settings.video.msg_color_r * 255;
|
||||
int g = g_settings.video.msg_color_g * 255;
|
||||
int b = g_settings.video.msg_color_b * 255;
|
||||
|
||||
r = r < 0 ? 0 : (r > 255 ? 255 : r);
|
||||
g = g < 0 ? 0 : (g > 255 ? 255 : g);
|
||||
b = b < 0 ? 0 : (b > 255 ? 255 : b);
|
||||
|
||||
// RGB888 -> RGB555
|
||||
if (!vid->render32)
|
||||
{
|
||||
r >>= 3;
|
||||
g >>= 3;
|
||||
b >>= 3;
|
||||
}
|
||||
|
||||
vid->font_r = r;
|
||||
vid->font_g = g;
|
||||
vid->font_b = b;
|
||||
}
|
||||
else
|
||||
SSNES_WARN("Failed to init font.\n");
|
||||
}
|
||||
else
|
||||
SSNES_LOG("Did not find default font.\n");
|
||||
#else
|
||||
(void)vid;
|
||||
(void)font_path;
|
||||
@@ -128,9 +161,9 @@ static void sdl_render_msg_15(sdl_video_t *vid, SDL_Surface *buffer, const char
|
||||
unsigned g = (out_pix >> gshift) & 0x1f;
|
||||
unsigned b = (out_pix >> bshift) & 0x1f;
|
||||
|
||||
unsigned out_r = (r * (256 - blend) + 0x1f * blend) >> 8;
|
||||
unsigned out_g = (g * (256 - blend) + 0x1f * blend) >> 8;
|
||||
unsigned out_b = (b * (256 - blend) + 0x1f * blend) >> 8;
|
||||
unsigned out_r = (r * (256 - blend) + vid->font_r * blend) >> 8;
|
||||
unsigned out_g = (g * (256 - blend) + vid->font_g * blend) >> 8;
|
||||
unsigned out_b = (b * (256 - blend) + vid->font_b * blend) >> 8;
|
||||
out[x] = (out_r << rshift) | (out_g << gshift) | (out_b << bshift);
|
||||
}
|
||||
}
|
||||
@@ -189,9 +222,9 @@ static void sdl_render_msg_32(sdl_video_t *vid, SDL_Surface *buffer, const char
|
||||
unsigned g = (out_pix >> gshift) & 0xff;
|
||||
unsigned b = (out_pix >> bshift) & 0xff;
|
||||
|
||||
unsigned out_r = (r * (256 - blend) + 0xff * blend) >> 8;
|
||||
unsigned out_g = (g * (256 - blend) + 0xff * blend) >> 8;
|
||||
unsigned out_b = (b * (256 - blend) + 0xff * blend) >> 8;
|
||||
unsigned out_r = (r * (256 - blend) + vid->font_r * blend) >> 8;
|
||||
unsigned out_g = (g * (256 - blend) + vid->font_g * blend) >> 8;
|
||||
unsigned out_b = (b * (256 - blend) + vid->font_b * blend) >> 8;
|
||||
out[x] = (out_r << rshift) | (out_g << gshift) | (out_b << bshift);
|
||||
}
|
||||
}
|
||||
@@ -232,13 +265,15 @@ static void* sdl_gfx_init(const video_info_t *video, const input_driver_t **inpu
|
||||
if (!video->fullscreen)
|
||||
SSNES_LOG("Creating window @ %ux%u\n", video->width, video->height);
|
||||
|
||||
vid->screen = SDL_SetVideoMode(video->width, video->height, (g_settings.video.force_16bit || !video->rgb32) ? 15 : 32, SDL_HWSURFACE | SDL_HWACCEL | SDL_DOUBLEBUF | (video->fullscreen ? SDL_FULLSCREEN : 0));
|
||||
vid->render32 = video->rgb32 && !g_settings.video.force_16bit;
|
||||
vid->screen = SDL_SetVideoMode(video->width, video->height, vid->render32 ? 32 : 15, SDL_HWSURFACE | SDL_HWACCEL | SDL_DOUBLEBUF | (video->fullscreen ? SDL_FULLSCREEN : 0));
|
||||
|
||||
if (!vid->screen && !g_settings.video.force_16bit && !video->rgb32)
|
||||
{
|
||||
vid->upsample = true;
|
||||
vid->screen = SDL_SetVideoMode(video->width, video->height, 32, SDL_HWSURFACE | SDL_HWACCEL | SDL_DOUBLEBUF | (video->fullscreen ? SDL_FULLSCREEN : 0));
|
||||
SSNES_WARN("SDL: 15-bit colors failed, attempting 32-bit colors.\n");
|
||||
vid->render32 = true;
|
||||
}
|
||||
|
||||
if (!vid->screen)
|
||||
@@ -250,16 +285,18 @@ static void* sdl_gfx_init(const video_info_t *video, const input_driver_t **inpu
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
|
||||
const SDL_PixelFormat *fmt = vid->screen->format;
|
||||
if (!g_settings.video.force_16bit && (video->rgb32 || vid->upsample))
|
||||
if (vid->render32)
|
||||
{
|
||||
SSNES_LOG("SDL: Creating 32-bit buffer.\n");
|
||||
vid->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, 256 * video->input_scale, 256 * video->input_scale, 32,
|
||||
vid->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, SSNES_SCALE_BASE * video->input_scale,
|
||||
SSNES_SCALE_BASE * video->input_scale, 32,
|
||||
fmt->Rmask, fmt->Bmask, fmt->Gmask, fmt->Amask);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_LOG("SDL: Creating 15-bit buffer.\n");
|
||||
vid->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, 256 * video->input_scale, 256 * video->input_scale, 15,
|
||||
vid->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, SSNES_SCALE_BASE * video->input_scale,
|
||||
SSNES_SCALE_BASE * video->input_scale, 15,
|
||||
fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask);
|
||||
}
|
||||
SSNES_LOG("[Debug]: SDL Pixel format: Rshift = %u, Gshift = %u, Bshift = %u\n",
|
||||
@@ -285,7 +322,7 @@ static void* sdl_gfx_init(const video_info_t *video, const input_driver_t **inpu
|
||||
sdl_init_font(vid, g_settings.video.font_path, g_settings.video.font_size);
|
||||
|
||||
|
||||
if (fmt->Rshift == 10 && fmt->Gshift == 5 && fmt->Bshift == 0)
|
||||
if (fmt->Rshift == 10 && fmt->Gshift == 5 && fmt->Bshift == 0) // XRGB1555
|
||||
{
|
||||
SSNES_LOG("SDL: 15-bit format matches. Fast blit.\n");
|
||||
vid->convert_15_func = convert_15bit_15bit_direct;
|
||||
@@ -296,7 +333,7 @@ static void* sdl_gfx_init(const video_info_t *video, const input_driver_t **inpu
|
||||
vid->convert_15_func = convert_15bit_15bit_shift;
|
||||
}
|
||||
|
||||
if (fmt->Rshift == 16 && fmt->Gshift == 8 && fmt->Bshift == 0)
|
||||
if (fmt->Rshift == 16 && fmt->Gshift == 8 && fmt->Bshift == 0) // ARGB8888
|
||||
{
|
||||
SSNES_LOG("SDL: 32-bit format matches. Fast blit.\n");
|
||||
vid->convert_32_func = convert_32bit_32bit_direct;
|
||||
@@ -430,7 +467,6 @@ static void check_window(sdl_video_t *vid)
|
||||
static bool sdl_gfx_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
|
||||
{
|
||||
sdl_video_t *vid = data;
|
||||
check_window(vid);
|
||||
|
||||
if (SDL_MUSTLOCK(vid->buffer))
|
||||
SDL_LockSurface(vid->buffer);
|
||||
@@ -456,15 +492,7 @@ static bool sdl_gfx_frame(void *data, const void* frame, unsigned width, unsigne
|
||||
{
|
||||
vid->convert_32_func(vid->buffer->pixels, vid->buffer->pitch, frame, width, height, pitch, vid->screen->format);
|
||||
}
|
||||
|
||||
if (msg)
|
||||
{
|
||||
if ((!vid->rgb32 || g_settings.video.force_16bit) && !vid->upsample)
|
||||
sdl_render_msg_15(vid, vid->buffer, msg, width, height, vid->screen->format);
|
||||
else
|
||||
sdl_render_msg_32(vid, vid->buffer, msg, width, height, vid->screen->format);
|
||||
}
|
||||
|
||||
|
||||
if (SDL_MUSTLOCK(vid->buffer))
|
||||
SDL_UnlockSurface(vid->buffer);
|
||||
|
||||
@@ -484,6 +512,14 @@ static bool sdl_gfx_frame(void *data, const void* frame, unsigned width, unsigne
|
||||
|
||||
SDL_SoftStretch(vid->buffer, &src, vid->screen, &dest);
|
||||
|
||||
if (msg)
|
||||
{
|
||||
if ((!vid->rgb32 || g_settings.video.force_16bit) && !vid->upsample)
|
||||
sdl_render_msg_15(vid, vid->screen, msg, vid->screen->w, vid->screen->h, vid->screen->format);
|
||||
else
|
||||
sdl_render_msg_32(vid, vid->screen, msg, vid->screen->w, vid->screen->h, vid->screen->format);
|
||||
}
|
||||
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
SDL_WM_SetCaption(buf, NULL);
|
||||
@@ -502,6 +538,7 @@ static void sdl_gfx_set_nonblock_state(void *data, bool state)
|
||||
static bool sdl_gfx_alive(void *data)
|
||||
{
|
||||
sdl_video_t *vid = data;
|
||||
check_window(vid);
|
||||
return !vid->quitting;
|
||||
}
|
||||
|
||||
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES 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 Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES 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 SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "sdlwrap.h"
|
||||
#include "SDL_syswm.h"
|
||||
#include "general.h"
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include <OpenGL/gl.h>
|
||||
#endif
|
||||
|
||||
// SDL 1.2 is portable, sure, but you still need some platform specific workarounds ;)
|
||||
// Hopefully SDL 1.3 will solve this more cleanly :D
|
||||
// Welcome to #ifdef HELL! :D
|
||||
//
|
||||
|
||||
#if SDL_MODERN
|
||||
static SDL_Window* g_window;
|
||||
static SDL_GLContext g_ctx;
|
||||
#endif
|
||||
|
||||
static bool g_fullscreen;
|
||||
static unsigned g_interval;
|
||||
|
||||
void sdlwrap_set_swap_interval(unsigned interval, bool inited)
|
||||
{
|
||||
g_interval = interval;
|
||||
|
||||
#if SDL_MODERN
|
||||
if (g_window)
|
||||
SDL_GL_SetSwapInterval(g_interval);
|
||||
#else
|
||||
if (inited)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL;
|
||||
if (!wgl_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(wgl_swap_interval, "wglSwapIntervalEXT");
|
||||
}
|
||||
if (wgl_swap_interval) wgl_swap_interval(g_interval);
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
GLint val = g_interval;
|
||||
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &val);
|
||||
#else
|
||||
static int (*glx_swap_interval)(int) = NULL;
|
||||
if (!glx_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalSGI");
|
||||
}
|
||||
if (!glx_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalMESA");
|
||||
}
|
||||
if (glx_swap_interval)
|
||||
glx_swap_interval(g_interval);
|
||||
else
|
||||
SSNES_WARN("Could not find GLX VSync call. :(\n");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sdlwrap_init(void)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
return SDL_VideoInit(NULL) == 0;
|
||||
#else
|
||||
return SDL_Init(SDL_INIT_VIDEO) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SDL_MODERN
|
||||
void sdlwrap_destroy(void)
|
||||
{
|
||||
if (g_ctx)
|
||||
SDL_GL_DeleteContext(g_ctx);
|
||||
if (g_window)
|
||||
SDL_DestroyWindow(g_window);
|
||||
|
||||
g_ctx = NULL;
|
||||
g_window = NULL;
|
||||
SDL_VideoQuit();
|
||||
}
|
||||
#else
|
||||
void sdlwrap_destroy(void)
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool sdlwrap_set_video_mode(
|
||||
unsigned width, unsigned height,
|
||||
unsigned bits, bool fullscreen)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
if (g_window)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#if SDL_MODERN
|
||||
static const int resizable = SDL_WINDOW_RESIZABLE;
|
||||
#else
|
||||
#ifndef __APPLE__ // Resizing on OSX is broken in 1.2 it seems :)
|
||||
static const int resizable = SDL_RESIZABLE;
|
||||
#else
|
||||
static const int resizable = 0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
||||
#if !SDL_MODERN
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, g_interval);
|
||||
#endif
|
||||
|
||||
#if SDL_MODERN
|
||||
if (bits == 15)
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
|
||||
}
|
||||
g_window = SDL_CreateWindow("SSNES", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | (fullscreen ? SDL_WINDOW_FULLSCREEN : resizable));
|
||||
if (!g_window)
|
||||
return false;
|
||||
g_ctx = SDL_GL_CreateContext(g_window);
|
||||
#else
|
||||
if (!SDL_SetVideoMode(width, height, bits,
|
||||
SDL_OPENGL | (fullscreen ? SDL_FULLSCREEN : resizable)))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
int attr = 0;
|
||||
#if SDL_MODERN
|
||||
SDL_GL_SetSwapInterval(g_interval);
|
||||
#else
|
||||
SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr);
|
||||
if (attr <= 0 && g_interval)
|
||||
{
|
||||
SSNES_WARN("SDL failed to setup VSync, attempting to recover using native calls!\n");
|
||||
sdlwrap_set_swap_interval(g_interval, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
g_fullscreen = fullscreen;
|
||||
|
||||
attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &attr);
|
||||
if (attr <= 0)
|
||||
SSNES_WARN("GL double buffer has not been enabled!\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// SDL 1.2 has an awkward model where you need to "confirm" window resizing.
|
||||
// SDL 1.3 luckily removes this quirk.
|
||||
void sdlwrap_set_resize(unsigned width, unsigned height)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
(void)width;
|
||||
(void)height;
|
||||
#else
|
||||
#ifndef __APPLE__ // Resizing on OSX is broken in 1.2 it seems :)
|
||||
static const int resizable = SDL_RESIZABLE;
|
||||
#else
|
||||
static const int resizable = 0;
|
||||
#endif
|
||||
SDL_SetVideoMode(width, height, 0, SDL_OPENGL | (g_fullscreen ? SDL_FULLSCREEN : resizable));
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdlwrap_wm_set_caption(const char *str)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
SDL_SetWindowTitle(g_window, str);
|
||||
#else
|
||||
SDL_WM_SetCaption(str, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdlwrap_swap_buffers(void)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
SDL_GL_SwapWindow(g_window);
|
||||
#else
|
||||
SDL_GL_SwapBuffers();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sdlwrap_key_pressed(int key)
|
||||
{
|
||||
int num_keys;
|
||||
#if SDL_MODERN
|
||||
Uint8 *keymap = SDL_GetKeyboardState(&num_keys);
|
||||
key = SDL_GetScancodeFromKey(key);
|
||||
if (key >= num_keys)
|
||||
return false;
|
||||
|
||||
return keymap[key];
|
||||
#else
|
||||
Uint8 *keymap = SDL_GetKeyState(&num_keys);
|
||||
if (key >= num_keys)
|
||||
return false;
|
||||
|
||||
return keymap[key];
|
||||
#endif
|
||||
}
|
||||
|
||||
// 1.2 specific workaround for tiling WMs. In 1.3 we call GetSize directly, so we don't need to rely on
|
||||
// proper event handling (I hope).
|
||||
#if !defined(__APPLE__) && !defined(_WIN32) && !SDL_MODERN
|
||||
static void sdlwrap_get_window_size(unsigned *width, unsigned *height)
|
||||
{
|
||||
SDL_SysWMinfo info;
|
||||
SDL_VERSION(&info.version);
|
||||
SDL_GetWMInfo(&info);
|
||||
XWindowAttributes target;
|
||||
|
||||
info.info.x11.lock_func();
|
||||
XGetWindowAttributes(info.info.x11.display, info.info.x11.window,
|
||||
&target);
|
||||
info.info.x11.unlock_func();
|
||||
|
||||
*width = target.width;
|
||||
*height = target.height;
|
||||
}
|
||||
#endif
|
||||
|
||||
void sdlwrap_check_window(bool *quit,
|
||||
bool *resize, unsigned *width, unsigned *height, unsigned frame_count)
|
||||
{
|
||||
*quit = false;
|
||||
*resize = false;
|
||||
SDL_Event event;
|
||||
#if SDL_MODERN
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
*quit = true;
|
||||
return;
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (event.window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
*quit = true;
|
||||
return;
|
||||
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
*resize = true;
|
||||
*width = event.window.data1;
|
||||
*height = event.window.data2;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!*resize)
|
||||
{
|
||||
int w, h;
|
||||
SDL_GetWindowSize(g_window, &w, &h);
|
||||
if (*width != (unsigned)w || *height != (unsigned)h)
|
||||
{
|
||||
*resize = true;
|
||||
*width = w;
|
||||
*height = h;
|
||||
}
|
||||
}
|
||||
#else
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
*quit = true;
|
||||
break;
|
||||
|
||||
case SDL_VIDEORESIZE:
|
||||
*resize = true;
|
||||
*width = event.resize.w;
|
||||
*height = event.resize.h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(__APPLE__) && !defined(_WIN32)
|
||||
// Hack to workaround limitations in tiling WMs ...
|
||||
if (!*resize && !g_fullscreen)
|
||||
{
|
||||
unsigned new_width, new_height;
|
||||
sdlwrap_get_window_size(&new_width, &new_height);
|
||||
if ((new_width != *width || new_height != *height) || (frame_count == 10)) // Ugly hack :D
|
||||
{
|
||||
*resize = true;
|
||||
*width = new_width;
|
||||
*height = new_height;
|
||||
SSNES_LOG("GL: Verified window size: %u x %u\n", *width, *height);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sdlwrap_get_wm_info(SDL_SysWMinfo *info)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
if (g_window)
|
||||
return SDL_GetWindowWMInfo(g_window, info);
|
||||
else
|
||||
return SDL_GetWMInfo(info) == 1;
|
||||
#else
|
||||
return SDL_GetWMInfo(info) == 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sdlwrap_window_has_focus(void)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
Uint32 flags = SDL_GetWindowFlags(g_window);
|
||||
flags &= SDL_WINDOW_INPUT_FOCUS;
|
||||
return flags == SDL_WINDOW_INPUT_FOCUS;
|
||||
#else
|
||||
return (SDL_GetAppState() & (SDL_APPINPUTFOCUS | SDL_APPACTIVE)) == (SDL_APPINPUTFOCUS | SDL_APPACTIVE);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES 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 Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES 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 SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Compatibility wrapper between SDL 1.2/1.3 for OpenGL.
|
||||
// Wraps functions which differ in 1.2 and 1.3.
|
||||
|
||||
#ifndef __SDLWRAP_H
|
||||
#define __SDLWRAP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_version.h"
|
||||
#include "SDL_syswm.h"
|
||||
|
||||
#if SDL_VERSION_ATLEAST(1, 3, 0)
|
||||
#define SDL_MODERN 1
|
||||
#else
|
||||
#define SDL_MODERN 0
|
||||
#endif
|
||||
|
||||
// Not legal to cast void* to fn-pointer. Need workaround to be compliant.
|
||||
#define SDL_SYM_WRAP(sym, symbol) { \
|
||||
assert(sizeof(void*) == sizeof(void (*)(void))); \
|
||||
void *sym__ = SDL_GL_GetProcAddress(symbol); \
|
||||
memcpy(&(sym), &sym__, sizeof(void*)); \
|
||||
}
|
||||
|
||||
void sdlwrap_set_swap_interval(unsigned interval, bool inited);
|
||||
|
||||
bool sdlwrap_set_video_mode(
|
||||
unsigned width, unsigned height,
|
||||
unsigned bits, bool fullscreen);
|
||||
|
||||
bool sdlwrap_init(void);
|
||||
void sdlwrap_destroy(void);
|
||||
|
||||
void sdlwrap_wm_set_caption(const char *str);
|
||||
|
||||
void sdlwrap_swap_buffers(void);
|
||||
|
||||
bool sdlwrap_key_pressed(int key);
|
||||
|
||||
void sdlwrap_check_window(bool *quit,
|
||||
bool *resize, unsigned *width, unsigned *height, unsigned frame_count);
|
||||
|
||||
void sdlwrap_set_resize(unsigned width, unsigned height);
|
||||
|
||||
bool sdlwrap_get_wm_info(SDL_SysWMinfo *info);
|
||||
|
||||
bool sdlwrap_window_has_focus(void);
|
||||
|
||||
#endif
|
||||
|
||||
+20
-15
@@ -37,20 +37,23 @@ static const char* stock_cg_program =
|
||||
"("
|
||||
" float4 position : POSITION,"
|
||||
" float2 texCoord : TEXCOORD0,"
|
||||
" float4 color : COLOR,"
|
||||
""
|
||||
" uniform float4x4 modelViewProj,"
|
||||
""
|
||||
" out float4 oPosition : POSITION,"
|
||||
" out float2 otexCoord : TEXCOORD0"
|
||||
" out float2 otexCoord : TEXCOORD0,"
|
||||
" out float4 oColor : COLOR"
|
||||
")"
|
||||
"{"
|
||||
" oPosition = mul(modelViewProj, position);"
|
||||
" otexCoord = texCoord;"
|
||||
" oColor = color;"
|
||||
"}"
|
||||
""
|
||||
"float4 main_fragment(float2 tex : TEXCOORD0, uniform sampler2D s0 : TEXUNIT0) : COLOR"
|
||||
"float4 main_fragment(in float4 color : COLOR, float2 tex : TEXCOORD0, uniform sampler2D s0 : TEXUNIT0) : COLOR"
|
||||
"{"
|
||||
" return tex2D(s0, tex);"
|
||||
" return color * tex2D(s0, tex);"
|
||||
"}";
|
||||
|
||||
#ifdef SSNES_CG_DEBUG
|
||||
@@ -128,7 +131,9 @@ static unsigned lut_textures[MAX_TEXTURES];
|
||||
static unsigned lut_textures_num = 0;
|
||||
static char lut_textures_uniform[MAX_TEXTURES][64];
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
static snes_tracker_t *snes_tracker = NULL;
|
||||
#endif
|
||||
|
||||
void gl_cg_set_proj_matrix(void)
|
||||
{
|
||||
@@ -239,6 +244,7 @@ void gl_cg_set_params(unsigned width, unsigned height,
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
// Set state parameters
|
||||
if (snes_tracker)
|
||||
{
|
||||
@@ -256,6 +262,7 @@ void gl_cg_set_params(unsigned width, unsigned height,
|
||||
set_param_1f(param_f, info[i].value);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,11 +280,13 @@ void gl_cg_deinit(void)
|
||||
glDeleteTextures(lut_textures_num, lut_textures);
|
||||
lut_textures_num = 0;
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
if (snes_tracker)
|
||||
{
|
||||
snes_tracker_free(snes_tracker);
|
||||
snes_tracker = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
cgDestroyContext(cgCtx);
|
||||
}
|
||||
@@ -285,8 +294,6 @@ void gl_cg_deinit(void)
|
||||
static bool load_plain(const char *path)
|
||||
{
|
||||
SSNES_LOG("Loading Cg file: %s\n", path);
|
||||
if (strlen(g_settings.video.second_pass_shader) > 0)
|
||||
SSNES_LOG("Loading 2nd pass: %s\n", g_settings.video.second_pass_shader);
|
||||
|
||||
char *listing[3] = {NULL};
|
||||
const char *list = NULL;
|
||||
@@ -305,8 +312,9 @@ static bool load_plain(const char *path)
|
||||
if (list)
|
||||
listing[1] = strdup(list);
|
||||
|
||||
if (strlen(g_settings.video.second_pass_shader) > 0)
|
||||
if (*g_settings.video.second_pass_shader && g_settings.video.render_to_texture)
|
||||
{
|
||||
SSNES_LOG("Loading 2nd pass: %s\n", g_settings.video.second_pass_shader);
|
||||
prg[2].fprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, g_settings.video.second_pass_shader, cgFProf, "main_fragment", 0);
|
||||
prg[2].vprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, g_settings.video.second_pass_shader, cgVProf, "main_vertex", 0);
|
||||
|
||||
@@ -398,7 +406,7 @@ static bool load_textures(const char *dir_path, config_file_t *conf)
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, img.width);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, img.width, img.height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, img.pixels);
|
||||
0, GL_RGBA, img.width, img.height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, img.pixels);
|
||||
|
||||
lut_textures_num++;
|
||||
|
||||
@@ -488,8 +496,8 @@ static bool load_imports(const char *dir_path, config_file_t *conf)
|
||||
unsigned addr = 0;
|
||||
#ifdef HAVE_PYTHON
|
||||
if (tracker_type != SSNES_STATE_PYTHON)
|
||||
{
|
||||
#endif
|
||||
{
|
||||
unsigned input_slot = 0;
|
||||
if (config_get_hex(conf, input_slot_buf, &input_slot))
|
||||
{
|
||||
@@ -523,11 +531,9 @@ static bool load_imports(const char *dir_path, config_file_t *conf)
|
||||
SSNES_ERR("No address assigned to semantic.\n");
|
||||
goto error;
|
||||
}
|
||||
#ifdef HAVE_PYTHON
|
||||
}
|
||||
#endif
|
||||
|
||||
int memtype = 0;
|
||||
unsigned memtype;
|
||||
switch (ram_type)
|
||||
{
|
||||
case SSNES_STATE_WRAM:
|
||||
@@ -547,10 +553,10 @@ static bool load_imports(const char *dir_path, config_file_t *conf)
|
||||
break;
|
||||
|
||||
default:
|
||||
memtype = SNES_MEMORY_WRAM;
|
||||
memtype = -1u;
|
||||
}
|
||||
|
||||
if (addr >= psnes_get_memory_size(memtype))
|
||||
if ((memtype != -1u) && (addr >= psnes_get_memory_size(memtype)))
|
||||
{
|
||||
SSNES_ERR("Address out of bounds.\n");
|
||||
goto error;
|
||||
@@ -579,7 +585,7 @@ static bool load_imports(const char *dir_path, config_file_t *conf)
|
||||
.oam = psnes_get_memory_data(SNES_MEMORY_OAM),
|
||||
.apuram = psnes_get_memory_data(SNES_MEMORY_APURAM),
|
||||
.info = info,
|
||||
.info_elem = info_cnt
|
||||
.info_elem = info_cnt,
|
||||
};
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
@@ -611,7 +617,6 @@ static bool load_imports(const char *dir_path, config_file_t *conf)
|
||||
#endif
|
||||
|
||||
free(imports);
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
|
||||
+13
-12
@@ -33,7 +33,7 @@
|
||||
|
||||
|
||||
#define NO_SDL_GLEXT
|
||||
#include "SDL.h"
|
||||
#include "sdlwrap.h"
|
||||
#include "SDL_opengl.h"
|
||||
#include <stdlib.h>
|
||||
#include <libxml/parser.h>
|
||||
@@ -55,6 +55,7 @@
|
||||
#define pglLinkProgram glLinkProgram
|
||||
#define pglGetUniformLocation glGetUniformLocation
|
||||
#define pglUniform1i glUniform1i
|
||||
#define pglUniform1f glUniform1f
|
||||
#define pglUniform2fv glUniform2fv
|
||||
#define pglUniform4fv glUniform4fv
|
||||
#define pglGetShaderiv glGetShaderiv
|
||||
@@ -78,6 +79,7 @@ static PFNGLDETACHSHADERPROC pglDetachShader = NULL;
|
||||
static PFNGLLINKPROGRAMPROC pglLinkProgram = NULL;
|
||||
static PFNGLGETUNIFORMLOCATIONPROC pglGetUniformLocation = NULL;
|
||||
static PFNGLUNIFORM1IPROC pglUniform1i = NULL;
|
||||
static PFNGLUNIFORM1FPROC pglUniform1f = NULL;
|
||||
static PFNGLUNIFORM2FVPROC pglUniform2fv = NULL;
|
||||
static PFNGLUNIFORM4FVPROC pglUniform4fv = NULL;
|
||||
static PFNGLGETSHADERIVPROC pglGetShaderiv = NULL;
|
||||
@@ -356,7 +358,7 @@ static bool get_texture_image(const char *shader_path, xmlNodePtr ptr)
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, img.width);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, img.width, img.height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, img.pixels);
|
||||
0, GL_RGBA, img.width, img.height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, img.pixels);
|
||||
|
||||
pglActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
@@ -481,13 +483,13 @@ static bool get_import_value(xmlNodePtr ptr)
|
||||
goto error;
|
||||
}
|
||||
|
||||
enum snes_ram_type ram_type = SSNES_STATE_WRAM;
|
||||
enum snes_ram_type ram_type = SSNES_STATE_NONE;
|
||||
uint32_t addr = 0;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (tracker_type != SSNES_STATE_PYTHON)
|
||||
{
|
||||
#endif
|
||||
{
|
||||
if (input)
|
||||
{
|
||||
unsigned slot = strtoul((const char*)input, NULL, 0);
|
||||
@@ -515,11 +517,9 @@ static bool get_import_value(xmlNodePtr ptr)
|
||||
SSNES_ERR("No RAM address specificed for import value.\n");
|
||||
goto error;
|
||||
}
|
||||
#ifdef HAVE_PYTHON
|
||||
}
|
||||
#endif
|
||||
|
||||
int memtype = 0;
|
||||
unsigned memtype;
|
||||
switch (ram_type)
|
||||
{
|
||||
case SSNES_STATE_WRAM:
|
||||
@@ -539,10 +539,10 @@ static bool get_import_value(xmlNodePtr ptr)
|
||||
break;
|
||||
|
||||
default:
|
||||
memtype = SNES_MEMORY_WRAM;
|
||||
memtype = -1u;
|
||||
}
|
||||
|
||||
if (addr >= psnes_get_memory_size(memtype))
|
||||
if ((memtype != -1u) && (addr >= psnes_get_memory_size(memtype)))
|
||||
{
|
||||
SSNES_ERR("Address out of bounds.\n");
|
||||
goto error;
|
||||
@@ -842,6 +842,7 @@ bool gl_glsl_init(const char *path)
|
||||
LOAD_GL_SYM(LinkProgram);
|
||||
LOAD_GL_SYM(GetUniformLocation);
|
||||
LOAD_GL_SYM(Uniform1i);
|
||||
LOAD_GL_SYM(Uniform1f);
|
||||
LOAD_GL_SYM(Uniform2fv);
|
||||
LOAD_GL_SYM(Uniform4fv);
|
||||
LOAD_GL_SYM(GetShaderiv);
|
||||
@@ -862,7 +863,7 @@ bool gl_glsl_init(const char *path)
|
||||
bool shader_support = pglCreateProgram && pglUseProgram && pglCreateShader
|
||||
&& pglDeleteShader && pglShaderSource && pglCompileShader && pglAttachShader
|
||||
&& pglDetachShader && pglLinkProgram && pglGetUniformLocation
|
||||
&& pglUniform1i && pglUniform2fv && pglUniform4fv
|
||||
&& pglUniform1i && pglUniform1f && pglUniform2fv && pglUniform4fv
|
||||
&& pglGetShaderiv && pglGetShaderInfoLog && pglGetProgramiv && pglGetProgramInfoLog
|
||||
&& pglDeleteProgram && pglGetAttachedShaders
|
||||
&& pglGetAttribLocation && pglEnableVertexAttribArray
|
||||
@@ -900,7 +901,7 @@ bool gl_glsl_init(const char *path)
|
||||
return false;
|
||||
|
||||
// SSNES custom two-pass with two different files.
|
||||
if (num_progs == 1 && *g_settings.video.second_pass_shader)
|
||||
if (num_progs == 1 && *g_settings.video.second_pass_shader && g_settings.video.render_to_texture)
|
||||
{
|
||||
unsigned secondary_progs = get_xml_shaders(g_settings.video.second_pass_shader, progs, 1);
|
||||
if (secondary_progs == 1)
|
||||
@@ -1148,7 +1149,7 @@ void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
for (unsigned i = 0; i < cnt; i++)
|
||||
{
|
||||
location = pglGetUniformLocation(gl_program[active_index], info[i].id);
|
||||
pglUniform1i(location, info[i].value);
|
||||
pglUniform1f(location, info[i].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -88,32 +88,32 @@ snes_tracker_t* snes_tracker_init(const struct snes_tracker_info *info)
|
||||
strlcpy(tracker->info[i].id, info->info[i].id, sizeof(tracker->info[i].id));
|
||||
tracker->info[i].addr = info->info[i].addr;
|
||||
tracker->info[i].type = info->info[i].type;
|
||||
tracker->info[i].mask = (info->info[i].mask == 0) ? 0xffffffffu : info->info[i].mask;
|
||||
tracker->info[i].mask = (info->info[i].mask == 0) ? -1u : info->info[i].mask;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (info->info[i].type == SSNES_STATE_PYTHON)
|
||||
tracker->info[i].py = tracker->py;
|
||||
#endif
|
||||
|
||||
assert(info->wram && info->vram && info->cgram &&
|
||||
info->oam && info->apuram);
|
||||
// If we don't have a valid pointer.
|
||||
static const uint8_t empty = 0;
|
||||
|
||||
switch (info->info[i].ram_type)
|
||||
{
|
||||
case SSNES_STATE_WRAM:
|
||||
tracker->info[i].ptr = info->wram;
|
||||
tracker->info[i].ptr = info->wram ? info->wram : ∅
|
||||
break;
|
||||
case SSNES_STATE_APURAM:
|
||||
tracker->info[i].ptr = info->apuram;
|
||||
tracker->info[i].ptr = info->apuram ? info->apuram : ∅
|
||||
break;
|
||||
case SSNES_STATE_OAM:
|
||||
tracker->info[i].ptr = info->oam;
|
||||
tracker->info[i].ptr = info->oam ? info->oam : ∅
|
||||
break;
|
||||
case SSNES_STATE_CGRAM:
|
||||
tracker->info[i].ptr = info->cgram;
|
||||
tracker->info[i].ptr = info->cgram ? info->cgram : ∅
|
||||
break;
|
||||
case SSNES_STATE_VRAM:
|
||||
tracker->info[i].ptr = info->vram;
|
||||
tracker->info[i].ptr = info->vram ? info->vram : ∅
|
||||
break;
|
||||
case SSNES_STATE_INPUT_SLOT1:
|
||||
tracker->info[i].input_ptr = &tracker->input_state[0];
|
||||
@@ -125,7 +125,7 @@ snes_tracker_t* snes_tracker_init(const struct snes_tracker_info *info)
|
||||
break;
|
||||
|
||||
default:
|
||||
tracker->info[i].ptr = NULL;
|
||||
tracker->info[i].ptr = ∅
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -39,6 +39,7 @@ enum snes_tracker_type
|
||||
|
||||
enum snes_ram_type
|
||||
{
|
||||
SSNES_STATE_NONE,
|
||||
SSNES_STATE_WRAM,
|
||||
SSNES_STATE_APURAM,
|
||||
SSNES_STATE_OAM,
|
||||
@@ -78,7 +79,7 @@ struct snes_tracker_info
|
||||
struct snes_tracker_uniform
|
||||
{
|
||||
const char *id;
|
||||
int value;
|
||||
float value;
|
||||
};
|
||||
|
||||
typedef struct snes_tracker snes_tracker_t;
|
||||
|
||||
+116
-55
@@ -63,11 +63,17 @@ struct xv
|
||||
uint8_t *ytable;
|
||||
uint8_t *utable;
|
||||
uint8_t *vtable;
|
||||
unsigned luma_i;
|
||||
unsigned chroma_i;
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
font_renderer_t *font;
|
||||
|
||||
unsigned luma_index[2];
|
||||
unsigned chroma_u_index;
|
||||
unsigned chroma_v_index;
|
||||
|
||||
uint8_t font_y;
|
||||
uint8_t font_u;
|
||||
uint8_t font_v;
|
||||
#endif
|
||||
|
||||
void (*render_func)(xv_t*, const void *frame, unsigned width, unsigned height, unsigned pitch);
|
||||
@@ -89,6 +95,17 @@ static void sighandler(int sig)
|
||||
g_quit = 1;
|
||||
}
|
||||
|
||||
static inline void calculate_yuv(uint8_t *y, uint8_t *u, uint8_t *v, unsigned r, unsigned g, unsigned b)
|
||||
{
|
||||
int y_ = (int)(+((double)r * 0.257) + ((double)g * 0.504) + ((double)b * 0.098) + 16.0);
|
||||
int u_ = (int)(-((double)r * 0.148) - ((double)g * 0.291) + ((double)b * 0.439) + 128.0);
|
||||
int v_ = (int)(+((double)r * 0.439) - ((double)g * 0.368) - ((double)b * 0.071) + 128.0);
|
||||
|
||||
*y = y_ < 0 ? 0 : (y_ > 255 ? 255 : y_);
|
||||
*u = y_ < 0 ? 0 : (u_ > 255 ? 255 : u_);
|
||||
*v = v_ < 0 ? 0 : (v_ > 255 ? 255 : v_);
|
||||
}
|
||||
|
||||
static void init_yuv_tables(xv_t *xv)
|
||||
{
|
||||
xv->ytable = malloc(0x8000);
|
||||
@@ -98,18 +115,12 @@ static void init_yuv_tables(xv_t *xv)
|
||||
for (unsigned i = 0; i < 0x8000; i++)
|
||||
{
|
||||
// Extract RGB555 color data from i
|
||||
uint8_t r = (i >> 10) & 0x1F, g = (i >> 5) & 0x1F, b = (i) & 0x1F;
|
||||
r = (r << 3) | (r >> 2); //R5->R8
|
||||
g = (g << 3) | (g >> 2); //G5->G8
|
||||
b = (b << 3) | (b >> 2); //B5->B8
|
||||
unsigned r = (i >> 10) & 0x1F, g = (i >> 5) & 0x1F, b = (i) & 0x1F;
|
||||
r = (r << 3) | (r >> 2); // R5->R8
|
||||
g = (g << 3) | (g >> 2); // G5->G8
|
||||
b = (b << 3) | (b >> 2); // B5->B8
|
||||
|
||||
int y = (int)(+((double)r * 0.257) + ((double)g * 0.504) + ((double)b * 0.098) + 16.0);
|
||||
int u = (int)(-((double)r * 0.148) - ((double)g * 0.291) + ((double)b * 0.439) + 128.0);
|
||||
int v = (int)(+((double)r * 0.439) - ((double)g * 0.368) - ((double)b * 0.071) + 128.0);
|
||||
|
||||
xv->ytable[i] = y < 0 ? 0 : y > 255 ? 255 : y;
|
||||
xv->utable[i] = u < 0 ? 0 : u > 255 ? 255 : u;
|
||||
xv->vtable[i] = v < 0 ? 0 : v > 255 ? 255 : v;
|
||||
calculate_yuv(&xv->ytable[i], &xv->utable[i], &xv->vtable[i], r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,12 +184,30 @@ static void set_fullscreen(xv_t *xv)
|
||||
static void xv_init_font(xv_t *xv, const char *font_path, unsigned font_size)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (*font_path)
|
||||
const char *path = font_path;
|
||||
if (!*path)
|
||||
path = font_renderer_get_default_font();
|
||||
|
||||
if (path)
|
||||
{
|
||||
xv->font = font_renderer_new(font_path, font_size);
|
||||
if (!xv->font)
|
||||
xv->font = font_renderer_new(path, font_size);
|
||||
if (xv->font)
|
||||
{
|
||||
int r = g_settings.video.msg_color_r * 255;
|
||||
r = (r < 0 ? 0 : (r > 255 ? 255 : r));
|
||||
int g = g_settings.video.msg_color_g * 255;
|
||||
g = (g < 0 ? 0 : (g > 255 ? 255 : g));
|
||||
int b = g_settings.video.msg_color_b * 255;
|
||||
b = (b < 0 ? 0 : (b > 255 ? 255 : b));
|
||||
|
||||
calculate_yuv(&xv->font_y, &xv->font_u, &xv->font_v,
|
||||
r, g, b);
|
||||
}
|
||||
else
|
||||
SSNES_WARN("Failed to init font.\n");
|
||||
}
|
||||
else
|
||||
SSNES_LOG("Did not find default font.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -297,7 +326,6 @@ static void render32_uyvy(xv_t *xv, const void *input_, unsigned width, unsigned
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void* xv_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
xv_t *xv = calloc(1, sizeof(*xv));
|
||||
@@ -359,8 +387,8 @@ static void* xv_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
attributes.border_pixel = 0;
|
||||
attributes.event_mask = StructureNotifyMask | DestroyNotify | ClientMessage;
|
||||
|
||||
unsigned width = video->fullscreen ? ((video->width == 0) ? 256 : video->width) : video->width;
|
||||
unsigned height = video->fullscreen ? ((video->height == 0) ? 224 : video->height) : video->height;
|
||||
unsigned width = video->fullscreen ? ((video->width == 0) ? g_extern.system.geom.base_width : video->width) : video->width;
|
||||
unsigned height = video->fullscreen ? ((video->height == 0) ? g_extern.system.geom.base_height : video->height) : video->height;
|
||||
xv->window = XCreateWindow(xv->display, DefaultRootWindow(xv->display),
|
||||
0, 0, width, height,
|
||||
0, xv->depth, InputOutput, visualinfo->visual,
|
||||
@@ -383,7 +411,7 @@ static void* xv_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
|
||||
// Set colorkey to auto paint, so that Xv video output is always visible
|
||||
Atom atom = XInternAtom(xv->display, "XV_AUTOPAINT_COLORKEY", true);
|
||||
if(atom != None) XvSetPortAttribute(xv->display, xv->port, atom, 1);
|
||||
if (atom != None) XvSetPortAttribute(xv->display, xv->port, atom, 1);
|
||||
|
||||
int format_count;
|
||||
XvImageFormatValues *format = XvListImageFormats(xv->display, xv->port, &format_count);
|
||||
@@ -399,29 +427,43 @@ static void* xv_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
has_format = true;
|
||||
xv->fourcc = format[i].id;
|
||||
xv->render_func = video->rgb32 ? render32_yuy2 : render16_yuy2;
|
||||
xv->luma_i = 0; xv->chroma_i = 1;
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
xv->luma_index[0] = 0;
|
||||
xv->luma_index[1] = 2;
|
||||
xv->chroma_u_index = 1;
|
||||
xv->chroma_v_index = 3;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_format) for (int i = 0; i < format_count; i++)
|
||||
if (!has_format)
|
||||
{
|
||||
if (format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked)
|
||||
for (int i = 0; i < format_count; i++)
|
||||
{
|
||||
if (format[i].component_order[0] == 'U' && format[i].component_order[1] == 'Y'
|
||||
&& format[i].component_order[2] == 'V' && format[i].component_order[3] == 'Y')
|
||||
if (format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked)
|
||||
{
|
||||
has_format = true;
|
||||
xv->fourcc = format[i].id;
|
||||
xv->render_func = video->rgb32 ? render32_uyvy : render16_uyvy;
|
||||
xv->chroma_i = 0; xv->luma_i = 1;
|
||||
break;
|
||||
if (format[i].component_order[0] == 'U' && format[i].component_order[1] == 'Y'
|
||||
&& format[i].component_order[2] == 'V' && format[i].component_order[3] == 'Y')
|
||||
{
|
||||
has_format = true;
|
||||
xv->fourcc = format[i].id;
|
||||
xv->render_func = video->rgb32 ? render32_uyvy : render16_uyvy;
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
xv->luma_index[0] = 1;
|
||||
xv->luma_index[1] = 3;
|
||||
xv->chroma_u_index = 0;
|
||||
xv->chroma_v_index = 2;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(format);
|
||||
if (!has_format)
|
||||
{
|
||||
@@ -429,8 +471,8 @@ static void* xv_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
goto error;
|
||||
}
|
||||
|
||||
xv->width = 512;
|
||||
xv->height = 512;
|
||||
xv->width = g_extern.system.geom.max_width;
|
||||
xv->height = g_extern.system.geom.max_height;
|
||||
|
||||
xv->image = XvShmCreateImage(xv->display, xv->port, xv->fourcc, NULL, xv->width, xv->height, &xv->shminfo);
|
||||
if (!xv->image)
|
||||
@@ -466,8 +508,6 @@ static void* xv_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
xv_set_nonblock_state(xv, !video->vsync);
|
||||
xv->focus = true;
|
||||
|
||||
xv_init_font(xv, g_settings.video.font_path, g_settings.video.font_size);
|
||||
|
||||
void *xinput = input_x.init();
|
||||
if (xinput)
|
||||
{
|
||||
@@ -478,6 +518,8 @@ static void* xv_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
*input = NULL;
|
||||
|
||||
init_yuv_tables(xv);
|
||||
xv_init_font(xv, g_settings.video.font_path, g_settings.video.font_size);
|
||||
|
||||
return xv;
|
||||
|
||||
error:
|
||||
@@ -561,6 +603,7 @@ static void calc_out_rect(bool keep_aspect, unsigned *x, unsigned *y, unsigned *
|
||||
}
|
||||
|
||||
// TODO: Is there some way to render directly like GL? :(
|
||||
// Hacky C code is hacky :D Yay.
|
||||
static void xv_render_msg(xv_t *xv, const char *msg, unsigned width, unsigned height)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
@@ -574,12 +617,17 @@ static void xv_render_msg(xv_t *xv, const char *msg, unsigned width, unsigned he
|
||||
int _base_x = g_settings.video.msg_pos_x * width;
|
||||
int _base_y = height - g_settings.video.msg_pos_y * height;
|
||||
|
||||
unsigned luma_i = xv->luma_i;
|
||||
unsigned chroma_i = xv->chroma_i;
|
||||
unsigned luma_index[2] = { xv->luma_index[0], xv->luma_index[1] };
|
||||
unsigned chroma_u_index = xv->chroma_u_index;
|
||||
unsigned chroma_v_index = xv->chroma_v_index;
|
||||
|
||||
unsigned pitch = width << 1; // YUV formats used are 16 bpp.
|
||||
|
||||
while (head)
|
||||
{
|
||||
int base_x = _base_x + head->off_x;
|
||||
int base_x = (_base_x + head->off_x) << 1;
|
||||
base_x &= ~3; // Make sure we always start on the correct boundary so the indices are correct.
|
||||
|
||||
int base_y = _base_y - head->off_y;
|
||||
if (base_y >= 0)
|
||||
{
|
||||
@@ -588,21 +636,33 @@ static void xv_render_msg(xv_t *xv, const char *msg, unsigned width, unsigned he
|
||||
if (base_x < 0)
|
||||
continue;
|
||||
|
||||
const uint8_t *a = head->output + head->pitch * y;
|
||||
uint8_t *out = (uint8_t*)xv->image->data + (base_y - head->height + y) * (width << 1) + (base_x << 1);
|
||||
const uint8_t * restrict a = head->output + head->pitch * y;
|
||||
uint8_t * restrict out = (uint8_t*)xv->image->data + (base_y - head->height + y) * pitch + base_x;
|
||||
|
||||
for (int x = 0; x < (head->width << 1) && (base_x + x) < width; x += 2)
|
||||
for (int x = 0; x < (head->width << 1) && (base_x + x) < pitch; x += 4)
|
||||
{
|
||||
// Blend luma
|
||||
uint8_t blend = a[x >> 1];
|
||||
unsigned blended = blend + (((256 - blend) * (unsigned)out[x + luma_i]) >> 8);
|
||||
blended = blended > 255 ? 255 : blended;
|
||||
out[x + luma_i] = blended;
|
||||
unsigned alpha[2];
|
||||
alpha[0] = a[(x >> 1) + 0];
|
||||
|
||||
// Blend chroma
|
||||
blended = (128 * blend + ((256 - blend) * (unsigned)out[x + chroma_i])) >> 8;
|
||||
blended = blended > 255 ? 255 : blended;
|
||||
out[x + chroma_i] = blended;
|
||||
if (((x >> 1) + 1) == head->width) // We reached the end, uhoh! Branching like a BOSS! :D
|
||||
alpha[1] = 0;
|
||||
else
|
||||
alpha[1] = a[(x >> 1) + 1];
|
||||
|
||||
unsigned alpha_sub = (alpha[0] + alpha[1]) >> 1; // Blended alpha for the sub-samples U/V channels.
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
unsigned blended = (xv->font_y * alpha[i] + ((256 - alpha[i]) * out[x + luma_index[i]])) >> 8;
|
||||
out[x + luma_index[i]] = blended;
|
||||
}
|
||||
|
||||
// Blend chroma channels
|
||||
unsigned blended = (xv->font_u * alpha_sub + ((256 - alpha_sub) * out[x + chroma_u_index])) >> 8;
|
||||
out[x + chroma_u_index] = blended;
|
||||
|
||||
blended = (xv->font_v * alpha_sub + ((256 - alpha_sub) * out[x + chroma_v_index])) >> 8;
|
||||
out[x + chroma_v_index] = blended;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,11 +690,12 @@ static bool xv_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
XGetWindowAttributes(xv->display, xv->window, &target);
|
||||
xv->render_func(xv, frame, width, height, pitch);
|
||||
|
||||
if (msg)
|
||||
xv_render_msg(xv, msg, xv->width, xv->height);
|
||||
|
||||
unsigned x, y, owidth, oheight;
|
||||
calc_out_rect(xv->keep_aspect, &x, &y, &owidth, &oheight, target.width, target.height);
|
||||
|
||||
if (msg)
|
||||
xv_render_msg(xv, msg, width << 1, height << 1);
|
||||
|
||||
XvShmPutImage(xv->display, xv->port, xv->window, xv->gc, xv->image,
|
||||
0, 0, width << 1, height << 1,
|
||||
x, y, owidth, oheight,
|
||||
@@ -693,9 +754,9 @@ static void xv_free(void *data)
|
||||
shmctl(xv->shminfo.shmid, IPC_RMID, NULL);
|
||||
XFree(xv->image);
|
||||
|
||||
if(xv->window)
|
||||
if (xv->window)
|
||||
XUnmapWindow(xv->display, xv->window);
|
||||
if(xv->colormap)
|
||||
if (xv->colormap)
|
||||
XFreeColormap(xv->display, xv->colormap);
|
||||
|
||||
XCloseDisplay(xv->display);
|
||||
|
||||
+11
-3
@@ -15,13 +15,14 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ssnes_dinput.h"
|
||||
#include "SDL.h"
|
||||
#include "SDL_syswm.h"
|
||||
#include <stdbool.h>
|
||||
#include "ssnes_dinput.h"
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "../gfx/sdlwrap.h"
|
||||
|
||||
void sdl_dinput_free(sdl_dinput_t *di)
|
||||
{
|
||||
@@ -45,7 +46,7 @@ void sdl_dinput_free(sdl_dinput_t *di)
|
||||
|
||||
static BOOL CALLBACK enum_axes_cb(const DIDEVICEOBJECTINSTANCE *inst, void *p)
|
||||
{
|
||||
IDirectInputDevice8 *joypad = p;
|
||||
LPDIRECTINPUTDEVICE8 joypad = p;
|
||||
|
||||
DIPROPRANGE range;
|
||||
memset(&range, 0, sizeof(range));
|
||||
@@ -103,12 +104,19 @@ sdl_dinput_t* sdl_dinput_init(void)
|
||||
CoInitialize(NULL);
|
||||
|
||||
SDL_SysWMinfo info;
|
||||
if (!SDL_GetWMInfo(&info))
|
||||
SDL_VERSION(&info.version);
|
||||
|
||||
if (!sdlwrap_get_wm_info(&info))
|
||||
{
|
||||
SSNES_ERR("Failed to get SysWM info.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
#if SDL_MODERN
|
||||
di->hWnd = info.info.win.window;
|
||||
#else
|
||||
di->hWnd = info.window;
|
||||
#endif
|
||||
|
||||
if (FAILED(DirectInput8Create(
|
||||
GetModuleHandle(NULL), DIRECTINPUT_VERSION, &IID_IDirectInput8,
|
||||
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES 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 Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES 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 SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SSNES_KEYSYM_H
|
||||
#define __SSNES_KEYSYM_H
|
||||
|
||||
// Global keysym table for SSNES.
|
||||
// As you may have noticed, it's the same as SDL 1.2 since I'm lazy.
|
||||
|
||||
enum ssnes_key
|
||||
{
|
||||
SK_UNKNOWN = 0,
|
||||
SK_FIRST = 0,
|
||||
SK_BACKSPACE = 8,
|
||||
SK_TAB = 9,
|
||||
SK_CLEAR = 12,
|
||||
SK_RETURN = 13,
|
||||
SK_PAUSE = 19,
|
||||
SK_ESCAPE = 27,
|
||||
SK_SPACE = 32,
|
||||
SK_EXCLAIM = 33,
|
||||
SK_QUOTEDBL = 34,
|
||||
SK_HASH = 35,
|
||||
SK_DOLLAR = 36,
|
||||
SK_AMPERSAND = 38,
|
||||
SK_QUOTE = 39,
|
||||
SK_LEFTPAREN = 40,
|
||||
SK_RIGHTPAREN = 41,
|
||||
SK_ASTERISK = 42,
|
||||
SK_PLUS = 43,
|
||||
SK_COMMA = 44,
|
||||
SK_MINUS = 45,
|
||||
SK_PERIOD = 46,
|
||||
SK_SLASH = 47,
|
||||
SK_0 = 48,
|
||||
SK_1 = 49,
|
||||
SK_2 = 50,
|
||||
SK_3 = 51,
|
||||
SK_4 = 52,
|
||||
SK_5 = 53,
|
||||
SK_6 = 54,
|
||||
SK_7 = 55,
|
||||
SK_8 = 56,
|
||||
SK_9 = 57,
|
||||
SK_COLON = 58,
|
||||
SK_SEMICOLON = 59,
|
||||
SK_LESS = 60,
|
||||
SK_EQUALS = 61,
|
||||
SK_GREATER = 62,
|
||||
SK_QUESTION = 63,
|
||||
SK_AT = 64,
|
||||
SK_LEFTBRACKET = 91,
|
||||
SK_BACKSLASH = 92,
|
||||
SK_RIGHTBRACKET = 93,
|
||||
SK_CARET = 94,
|
||||
SK_UNDERSCORE = 95,
|
||||
SK_BACKQUOTE = 96,
|
||||
SK_a = 97,
|
||||
SK_b = 98,
|
||||
SK_c = 99,
|
||||
SK_d = 100,
|
||||
SK_e = 101,
|
||||
SK_f = 102,
|
||||
SK_g = 103,
|
||||
SK_h = 104,
|
||||
SK_i = 105,
|
||||
SK_j = 106,
|
||||
SK_k = 107,
|
||||
SK_l = 108,
|
||||
SK_m = 109,
|
||||
SK_n = 110,
|
||||
SK_o = 111,
|
||||
SK_p = 112,
|
||||
SK_q = 113,
|
||||
SK_r = 114,
|
||||
SK_s = 115,
|
||||
SK_t = 116,
|
||||
SK_u = 117,
|
||||
SK_v = 118,
|
||||
SK_w = 119,
|
||||
SK_x = 120,
|
||||
SK_y = 121,
|
||||
SK_z = 122,
|
||||
SK_DELETE = 127,
|
||||
|
||||
SK_KP0 = 256,
|
||||
SK_KP1 = 257,
|
||||
SK_KP2 = 258,
|
||||
SK_KP3 = 259,
|
||||
SK_KP4 = 260,
|
||||
SK_KP5 = 261,
|
||||
SK_KP6 = 262,
|
||||
SK_KP7 = 263,
|
||||
SK_KP8 = 264,
|
||||
SK_KP9 = 265,
|
||||
SK_KP_PERIOD = 266,
|
||||
SK_KP_DIVIDE = 267,
|
||||
SK_KP_MULTIPLY = 268,
|
||||
SK_KP_MINUS = 269,
|
||||
SK_KP_PLUS = 270,
|
||||
SK_KP_ENTER = 271,
|
||||
SK_KP_EQUALS = 272,
|
||||
|
||||
SK_UP = 273,
|
||||
SK_DOWN = 274,
|
||||
SK_RIGHT = 275,
|
||||
SK_LEFT = 276,
|
||||
SK_INSERT = 277,
|
||||
SK_HOME = 278,
|
||||
SK_END = 279,
|
||||
SK_PAGEUP = 280,
|
||||
SK_PAGEDOWN = 281,
|
||||
|
||||
SK_F1 = 282,
|
||||
SK_F2 = 283,
|
||||
SK_F3 = 284,
|
||||
SK_F4 = 285,
|
||||
SK_F5 = 286,
|
||||
SK_F6 = 287,
|
||||
SK_F7 = 288,
|
||||
SK_F8 = 289,
|
||||
SK_F9 = 290,
|
||||
SK_F10 = 291,
|
||||
SK_F11 = 292,
|
||||
SK_F12 = 293,
|
||||
SK_F13 = 294,
|
||||
SK_F14 = 295,
|
||||
SK_F15 = 296,
|
||||
|
||||
SK_NUMLOCK = 300,
|
||||
SK_CAPSLOCK = 301,
|
||||
SK_SCROLLOCK = 302,
|
||||
SK_RSHIFT = 303,
|
||||
SK_LSHIFT = 304,
|
||||
SK_RCTRL = 305,
|
||||
SK_LCTRL = 306,
|
||||
SK_RALT = 307,
|
||||
SK_LALT = 308,
|
||||
SK_RMETA = 309,
|
||||
SK_LMETA = 310,
|
||||
SK_LSUPER = 311,
|
||||
SK_RSUPER = 312,
|
||||
SK_MODE = 313,
|
||||
SK_COMPOSE = 314,
|
||||
|
||||
SK_HELP = 315,
|
||||
SK_PRINT = 316,
|
||||
SK_SYSREQ = 317,
|
||||
SK_BREAK = 318,
|
||||
SK_MENU = 319,
|
||||
SK_POWER = 320,
|
||||
SK_EURO = 321,
|
||||
SK_UNDO = 322,
|
||||
|
||||
SK_LAST
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+100
-9
@@ -17,17 +17,115 @@
|
||||
|
||||
#include "driver.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "../gfx/sdlwrap.h"
|
||||
#include <stdbool.h>
|
||||
#include "general.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <libsnes.hpp>
|
||||
#include "ssnes_sdl_input.h"
|
||||
#include "keysym.h"
|
||||
|
||||
struct key_bind
|
||||
{
|
||||
unsigned sdl;
|
||||
enum ssnes_key sk;
|
||||
};
|
||||
|
||||
static unsigned keysym_lut[SK_LAST];
|
||||
static const struct key_bind lut_binds[] = {
|
||||
{ SDLK_LEFT, SK_LEFT },
|
||||
{ SDLK_RIGHT, SK_RIGHT },
|
||||
{ SDLK_UP, SK_UP },
|
||||
{ SDLK_DOWN, SK_DOWN },
|
||||
{ SDLK_RETURN, SK_RETURN },
|
||||
{ SDLK_TAB, SK_TAB },
|
||||
{ SDLK_INSERT, SK_INSERT },
|
||||
{ SDLK_DELETE, SK_DELETE },
|
||||
{ SDLK_RSHIFT, SK_RSHIFT },
|
||||
{ SDLK_LSHIFT, SK_LSHIFT },
|
||||
{ SDLK_LCTRL, SK_LCTRL },
|
||||
{ SDLK_LALT, SK_LALT },
|
||||
{ SDLK_SPACE, SK_SPACE },
|
||||
{ SDLK_ESCAPE, SK_ESCAPE },
|
||||
{ SDLK_BACKSPACE, SK_BACKSPACE },
|
||||
{ SDLK_KP_ENTER, SK_KP_ENTER },
|
||||
{ SDLK_KP_PLUS, SK_KP_PLUS },
|
||||
{ SDLK_KP_MINUS, SK_KP_MINUS },
|
||||
{ SDLK_KP_MULTIPLY, SK_KP_MULTIPLY },
|
||||
{ SDLK_KP_DIVIDE, SK_KP_DIVIDE },
|
||||
{ SDLK_BACKQUOTE, SK_BACKQUOTE },
|
||||
{ SDLK_PAUSE, SK_PAUSE },
|
||||
{ SDLK_KP0, SK_KP0 },
|
||||
{ SDLK_KP1, SK_KP1 },
|
||||
{ SDLK_KP2, SK_KP2 },
|
||||
{ SDLK_KP3, SK_KP3 },
|
||||
{ SDLK_KP4, SK_KP4 },
|
||||
{ SDLK_KP5, SK_KP5 },
|
||||
{ SDLK_KP6, SK_KP6 },
|
||||
{ SDLK_KP7, SK_KP7 },
|
||||
{ SDLK_KP8, SK_KP8 },
|
||||
{ SDLK_KP9, SK_KP9 },
|
||||
{ SDLK_0, SK_0 },
|
||||
{ SDLK_1, SK_1 },
|
||||
{ SDLK_2, SK_2 },
|
||||
{ SDLK_3, SK_3 },
|
||||
{ SDLK_4, SK_4 },
|
||||
{ SDLK_5, SK_5 },
|
||||
{ SDLK_6, SK_6 },
|
||||
{ SDLK_7, SK_7 },
|
||||
{ SDLK_8, SK_8 },
|
||||
{ SDLK_9, SK_9 },
|
||||
{ SDLK_F1, SK_F1 },
|
||||
{ SDLK_F2, SK_F2 },
|
||||
{ SDLK_F3, SK_F3 },
|
||||
{ SDLK_F4, SK_F4 },
|
||||
{ SDLK_F5, SK_F5 },
|
||||
{ SDLK_F6, SK_F6 },
|
||||
{ SDLK_F7, SK_F7 },
|
||||
{ SDLK_F8, SK_F8 },
|
||||
{ SDLK_F9, SK_F9 },
|
||||
{ SDLK_F10, SK_F10 },
|
||||
{ SDLK_F11, SK_F11 },
|
||||
{ SDLK_F12, SK_F12 },
|
||||
{ SDLK_a, SK_a },
|
||||
{ SDLK_b, SK_b },
|
||||
{ SDLK_c, SK_c },
|
||||
{ SDLK_d, SK_d },
|
||||
{ SDLK_e, SK_e },
|
||||
{ SDLK_f, SK_f },
|
||||
{ SDLK_g, SK_g },
|
||||
{ SDLK_h, SK_h },
|
||||
{ SDLK_i, SK_i },
|
||||
{ SDLK_j, SK_j },
|
||||
{ SDLK_k, SK_k },
|
||||
{ SDLK_l, SK_l },
|
||||
{ SDLK_m, SK_m },
|
||||
{ SDLK_n, SK_n },
|
||||
{ SDLK_o, SK_o },
|
||||
{ SDLK_p, SK_p },
|
||||
{ SDLK_q, SK_q },
|
||||
{ SDLK_r, SK_r },
|
||||
{ SDLK_s, SK_s },
|
||||
{ SDLK_t, SK_t },
|
||||
{ SDLK_u, SK_u },
|
||||
{ SDLK_v, SK_v },
|
||||
{ SDLK_w, SK_w },
|
||||
{ SDLK_x, SK_x },
|
||||
{ SDLK_y, SK_y },
|
||||
{ SDLK_z, SK_z },
|
||||
};
|
||||
|
||||
static void init_lut(void)
|
||||
{
|
||||
memset(keysym_lut, 0, sizeof(keysym_lut));
|
||||
for (unsigned i = 0; i < sizeof(lut_binds) / sizeof(lut_binds[0]); i++)
|
||||
keysym_lut[lut_binds[i].sk] = lut_binds[i].sdl;
|
||||
}
|
||||
|
||||
static void* sdl_input_init(void)
|
||||
{
|
||||
init_lut();
|
||||
sdl_input_t *sdl = calloc(1, sizeof(*sdl));
|
||||
if (!sdl)
|
||||
return NULL;
|
||||
@@ -76,16 +174,9 @@ static void* sdl_input_init(void)
|
||||
return sdl;
|
||||
}
|
||||
|
||||
|
||||
static bool sdl_key_pressed(int key)
|
||||
{
|
||||
int num_keys;
|
||||
Uint8 *keymap = SDL_GetKeyState(&num_keys);
|
||||
|
||||
if (key >= num_keys)
|
||||
return false;
|
||||
|
||||
return keymap[key];
|
||||
return sdlwrap_key_pressed(keysym_lut[key]);
|
||||
}
|
||||
|
||||
#ifndef HAVE_DINPUT
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
typedef struct sdl_dinput
|
||||
{
|
||||
HWND hWnd;
|
||||
IDirectInput8 *ctx;
|
||||
IDirectInputDevice8 *joypad[MAX_PLAYERS];
|
||||
LPDIRECTINPUT8 ctx;
|
||||
LPDIRECTINPUTDEVICE8 joypad[MAX_PLAYERS];
|
||||
unsigned joypad_cnt;
|
||||
DIJOYSTATE2 joy_state[MAX_PLAYERS];
|
||||
} sdl_dinput_t;
|
||||
|
||||
+85
-85
@@ -23,6 +23,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "ssnes_sdl_input.h"
|
||||
#include "keysym.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
@@ -37,100 +38,99 @@ typedef struct x11_input
|
||||
|
||||
struct key_bind
|
||||
{
|
||||
int x;
|
||||
int sdl;
|
||||
unsigned x;
|
||||
enum ssnes_key sk;
|
||||
};
|
||||
|
||||
static int keysym_lut[SDLK_LAST];
|
||||
static unsigned keysym_lut[SK_LAST];
|
||||
static const struct key_bind lut_binds[] = {
|
||||
{ XK_Left, SDLK_LEFT },
|
||||
{ XK_Right, SDLK_RIGHT },
|
||||
{ XK_Up, SDLK_UP },
|
||||
{ XK_Down, SDLK_DOWN },
|
||||
{ XK_Return, SDLK_RETURN },
|
||||
{ XK_Tab, SDLK_TAB },
|
||||
{ XK_Insert, SDLK_INSERT },
|
||||
{ XK_Delete, SDLK_DELETE },
|
||||
{ XK_Shift_R, SDLK_RSHIFT },
|
||||
{ XK_Shift_L, SDLK_LSHIFT },
|
||||
{ XK_Control_L, SDLK_LCTRL },
|
||||
{ XK_Alt_L, SDLK_LALT },
|
||||
{ XK_space, SDLK_SPACE },
|
||||
{ XK_Escape, SDLK_ESCAPE },
|
||||
{ XK_KP_Add, SDLK_KP_PLUS },
|
||||
{ XK_KP_Subtract, SDLK_KP_MINUS },
|
||||
{ XK_BackSpace, SDLK_BACKSPACE },
|
||||
{ XK_KP_Enter, SDLK_KP_ENTER },
|
||||
{ XK_KP_Add, SDLK_KP_PLUS },
|
||||
{ XK_KP_Subtract, SDLK_KP_MINUS },
|
||||
{ XK_KP_Multiply, SDLK_KP_MULTIPLY },
|
||||
{ XK_KP_Divide, SDLK_KP_DIVIDE },
|
||||
{ XK_grave, SDLK_BACKQUOTE },
|
||||
{ XK_KP_0, SDLK_KP0 },
|
||||
{ XK_KP_1, SDLK_KP1 },
|
||||
{ XK_KP_2, SDLK_KP2 },
|
||||
{ XK_KP_3, SDLK_KP3 },
|
||||
{ XK_KP_4, SDLK_KP4 },
|
||||
{ XK_KP_5, SDLK_KP5 },
|
||||
{ XK_KP_6, SDLK_KP6 },
|
||||
{ XK_KP_7, SDLK_KP7 },
|
||||
{ XK_KP_8, SDLK_KP8 },
|
||||
{ XK_KP_9, SDLK_KP9 },
|
||||
{ XK_0, SDLK_0 },
|
||||
{ XK_1, SDLK_1 },
|
||||
{ XK_2, SDLK_2 },
|
||||
{ XK_3, SDLK_3 },
|
||||
{ XK_4, SDLK_4 },
|
||||
{ XK_5, SDLK_5 },
|
||||
{ XK_6, SDLK_6 },
|
||||
{ XK_7, SDLK_7 },
|
||||
{ XK_8, SDLK_8 },
|
||||
{ XK_9, SDLK_9 },
|
||||
{ XK_F1, SDLK_F1 },
|
||||
{ XK_F2, SDLK_F2 },
|
||||
{ XK_F3, SDLK_F3 },
|
||||
{ XK_F4, SDLK_F4 },
|
||||
{ XK_F5, SDLK_F5 },
|
||||
{ XK_F6, SDLK_F6 },
|
||||
{ XK_F7, SDLK_F7 },
|
||||
{ XK_F8, SDLK_F8 },
|
||||
{ XK_F9, SDLK_F9 },
|
||||
{ XK_F10, SDLK_F10 },
|
||||
{ XK_F11, SDLK_F11 },
|
||||
{ XK_F12, SDLK_F12 },
|
||||
{ XK_a, SDLK_a },
|
||||
{ XK_b, SDLK_b },
|
||||
{ XK_c, SDLK_c },
|
||||
{ XK_d, SDLK_d },
|
||||
{ XK_e, SDLK_e },
|
||||
{ XK_f, SDLK_f },
|
||||
{ XK_g, SDLK_g },
|
||||
{ XK_h, SDLK_h },
|
||||
{ XK_i, SDLK_i },
|
||||
{ XK_j, SDLK_j },
|
||||
{ XK_k, SDLK_k },
|
||||
{ XK_l, SDLK_l },
|
||||
{ XK_m, SDLK_m },
|
||||
{ XK_n, SDLK_n },
|
||||
{ XK_o, SDLK_o },
|
||||
{ XK_p, SDLK_p },
|
||||
{ XK_q, SDLK_q },
|
||||
{ XK_r, SDLK_r },
|
||||
{ XK_s, SDLK_s },
|
||||
{ XK_t, SDLK_t },
|
||||
{ XK_u, SDLK_u },
|
||||
{ XK_v, SDLK_v },
|
||||
{ XK_w, SDLK_w },
|
||||
{ XK_x, SDLK_x },
|
||||
{ XK_y, SDLK_y },
|
||||
{ XK_z, SDLK_z },
|
||||
{ XK_Left, SK_LEFT },
|
||||
{ XK_Right, SK_RIGHT },
|
||||
{ XK_Up, SK_UP },
|
||||
{ XK_Down, SK_DOWN },
|
||||
{ XK_Return, SK_RETURN },
|
||||
{ XK_Tab, SK_TAB },
|
||||
{ XK_Insert, SK_INSERT },
|
||||
{ XK_Delete, SK_DELETE },
|
||||
{ XK_Shift_R, SK_RSHIFT },
|
||||
{ XK_Shift_L, SK_LSHIFT },
|
||||
{ XK_Control_L, SK_LCTRL },
|
||||
{ XK_Alt_L, SK_LALT },
|
||||
{ XK_space, SK_SPACE },
|
||||
{ XK_Escape, SK_ESCAPE },
|
||||
{ XK_BackSpace, SK_BACKSPACE },
|
||||
{ XK_KP_Enter, SK_KP_ENTER },
|
||||
{ XK_KP_Add, SK_KP_PLUS },
|
||||
{ XK_KP_Subtract, SK_KP_MINUS },
|
||||
{ XK_KP_Multiply, SK_KP_MULTIPLY },
|
||||
{ XK_KP_Divide, SK_KP_DIVIDE },
|
||||
{ XK_grave, SK_BACKQUOTE },
|
||||
{ XK_Pause, SK_PAUSE },
|
||||
{ XK_KP_0, SK_KP0 },
|
||||
{ XK_KP_1, SK_KP1 },
|
||||
{ XK_KP_2, SK_KP2 },
|
||||
{ XK_KP_3, SK_KP3 },
|
||||
{ XK_KP_4, SK_KP4 },
|
||||
{ XK_KP_5, SK_KP5 },
|
||||
{ XK_KP_6, SK_KP6 },
|
||||
{ XK_KP_7, SK_KP7 },
|
||||
{ XK_KP_8, SK_KP8 },
|
||||
{ XK_KP_9, SK_KP9 },
|
||||
{ XK_0, SK_0 },
|
||||
{ XK_1, SK_1 },
|
||||
{ XK_2, SK_2 },
|
||||
{ XK_3, SK_3 },
|
||||
{ XK_4, SK_4 },
|
||||
{ XK_5, SK_5 },
|
||||
{ XK_6, SK_6 },
|
||||
{ XK_7, SK_7 },
|
||||
{ XK_8, SK_8 },
|
||||
{ XK_9, SK_9 },
|
||||
{ XK_F1, SK_F1 },
|
||||
{ XK_F2, SK_F2 },
|
||||
{ XK_F3, SK_F3 },
|
||||
{ XK_F4, SK_F4 },
|
||||
{ XK_F5, SK_F5 },
|
||||
{ XK_F6, SK_F6 },
|
||||
{ XK_F7, SK_F7 },
|
||||
{ XK_F8, SK_F8 },
|
||||
{ XK_F9, SK_F9 },
|
||||
{ XK_F10, SK_F10 },
|
||||
{ XK_F11, SK_F11 },
|
||||
{ XK_F12, SK_F12 },
|
||||
{ XK_a, SK_a },
|
||||
{ XK_b, SK_b },
|
||||
{ XK_c, SK_c },
|
||||
{ XK_d, SK_d },
|
||||
{ XK_e, SK_e },
|
||||
{ XK_f, SK_f },
|
||||
{ XK_g, SK_g },
|
||||
{ XK_h, SK_h },
|
||||
{ XK_i, SK_i },
|
||||
{ XK_j, SK_j },
|
||||
{ XK_k, SK_k },
|
||||
{ XK_l, SK_l },
|
||||
{ XK_m, SK_m },
|
||||
{ XK_n, SK_n },
|
||||
{ XK_o, SK_o },
|
||||
{ XK_p, SK_p },
|
||||
{ XK_q, SK_q },
|
||||
{ XK_r, SK_r },
|
||||
{ XK_s, SK_s },
|
||||
{ XK_t, SK_t },
|
||||
{ XK_u, SK_u },
|
||||
{ XK_v, SK_v },
|
||||
{ XK_w, SK_w },
|
||||
{ XK_x, SK_x },
|
||||
{ XK_y, SK_y },
|
||||
{ XK_z, SK_z },
|
||||
};
|
||||
|
||||
static void init_lut(void)
|
||||
{
|
||||
memset(keysym_lut, 0, sizeof(keysym_lut));
|
||||
for (unsigned i = 0; i < sizeof(lut_binds) / sizeof(lut_binds[0]); i++)
|
||||
keysym_lut[lut_binds[i].sdl] = lut_binds[i].x;
|
||||
keysym_lut[lut_binds[i].sk] = lut_binds[i].x;
|
||||
}
|
||||
|
||||
static void* x_input_init(void)
|
||||
|
||||
+21
@@ -66,6 +66,25 @@ extern "C" {
|
||||
#define SNES_MEMORY_OAM 103
|
||||
#define SNES_MEMORY_CGRAM 104
|
||||
|
||||
// SSNES extension. Not required to be implemented for a working implementation.
|
||||
#define SNES_ENVIRONMENT_GET_FULLPATH 0 // const char **
|
||||
#define SNES_ENVIRONMENT_SET_GEOMETRY 1 // const struct snes_geometry *
|
||||
#define SNES_ENVIRONMENT_SET_PITCH 2 // const unsigned *
|
||||
|
||||
struct snes_geometry
|
||||
{
|
||||
unsigned base_width; // Nominal video width of system.
|
||||
unsigned base_height; // Nominal video height of system.
|
||||
unsigned max_width; // Maximum possible width of system.
|
||||
unsigned max_height; // Maximum possible height of system.
|
||||
};
|
||||
|
||||
typedef bool (*snes_environment_t)(unsigned cmd, void *data);
|
||||
|
||||
// Must be called before calling snes_init().
|
||||
void snes_set_environment(snes_environment_t);
|
||||
////
|
||||
|
||||
typedef void (*snes_video_refresh_t)(const uint16_t *data, unsigned width, unsigned height);
|
||||
typedef void (*snes_audio_sample_t)(uint16_t left, uint16_t right);
|
||||
typedef void (*snes_input_poll_t)(void);
|
||||
@@ -74,6 +93,8 @@ typedef int16_t (*snes_input_state_t)(bool port, unsigned device, unsigned index
|
||||
unsigned snes_library_revision_major(void);
|
||||
unsigned snes_library_revision_minor(void);
|
||||
|
||||
const char *snes_library_id(void);
|
||||
|
||||
void snes_set_video_refresh(snes_video_refresh_t);
|
||||
void snes_set_audio_sample(snes_audio_sample_t);
|
||||
void snes_set_input_poll(snes_input_poll_t);
|
||||
|
||||
@@ -78,7 +78,7 @@ uint32_t crc32_adjust(uint32_t crc32, uint8_t input)
|
||||
uint32_t crc32_calculate(const uint8_t *data, unsigned length)
|
||||
{
|
||||
uint32_t crc32 = ~0;
|
||||
for(unsigned i = 0; i < length; i++)
|
||||
for (unsigned i = 0; i < length; i++)
|
||||
crc32 = crc32_adjust(crc32, data[i]);
|
||||
return ~crc32;
|
||||
}
|
||||
|
||||
@@ -15,25 +15,14 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "netplay.h"
|
||||
#include "general.h"
|
||||
#include "autosave.h"
|
||||
#include "dynamic.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0501
|
||||
#endif
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <ws2tcpip.h>
|
||||
// Woohoo, Winsock has headers from the STONE AGE! :D
|
||||
#define close(x) closesocket(x)
|
||||
#define CONST_CAST (const char*)
|
||||
#define NONCONST_CAST (char*)
|
||||
#else
|
||||
#include <sys/select.h>
|
||||
#include <sys/types.h>
|
||||
@@ -43,11 +32,29 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#include "netplay.h"
|
||||
#include "general.h"
|
||||
#include "autosave.h"
|
||||
#include "dynamic.h"
|
||||
#include "message.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
// Woohoo, Winsock has headers from the STONE AGE! :D
|
||||
#define close(x) closesocket(x)
|
||||
#define CONST_CAST (const char*)
|
||||
#define NONCONST_CAST (char*)
|
||||
#else
|
||||
#define CONST_CAST
|
||||
#define NONCONST_CAST
|
||||
#endif
|
||||
|
||||
|
||||
#define PREV_PTR(x) ((x) == 0 ? handle->buffer_size - 1 : (x) - 1)
|
||||
#define NEXT_PTR(x) ((x + 1) % handle->buffer_size)
|
||||
|
||||
@@ -88,11 +95,19 @@ struct netplay
|
||||
uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; // To compat UDP packet loss we also send old data along with the packets.
|
||||
uint32_t frame_count;
|
||||
uint32_t read_frame_count;
|
||||
uint32_t other_frame_count;
|
||||
struct addrinfo *addr;
|
||||
struct sockaddr_storage their_addr;
|
||||
bool has_client_addr;
|
||||
};
|
||||
|
||||
static void warn_hangup(void)
|
||||
{
|
||||
SSNES_WARN("Netplay has disconnected. Will continue without connection ...\n");
|
||||
if (g_extern.msg_queue)
|
||||
msg_queue_push(g_extern.msg_queue, "Netplay has disconnected. Will continue without connection.", 0, 480);
|
||||
}
|
||||
|
||||
void input_poll_net(void)
|
||||
{
|
||||
if (!netplay_should_skip(g_extern.netplay) && netplay_can_poll(g_extern.netplay))
|
||||
@@ -261,9 +276,27 @@ bool netplay_can_poll(netplay_t *handle)
|
||||
return handle->can_poll;
|
||||
}
|
||||
|
||||
// Not really a hash, but should be enough to differentiate implementations from each other.
|
||||
// Subtle differences in the implementation will not be possible to spot.
|
||||
// The alternative would have been checking serialization sizes, but it was troublesome for cross platform compat.
|
||||
static uint32_t implementation_magic_value(void)
|
||||
{
|
||||
uint32_t res = 0;
|
||||
res |= (psnes_library_revision_major() & 0xf) << 0;
|
||||
res |= (psnes_library_revision_minor() & 0xf) << 4;
|
||||
|
||||
// Shouldn't really use this, but oh well :) It'll do the job.
|
||||
const char *lib = psnes_library_id();
|
||||
size_t len = strlen(lib);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
res ^= lib[i] << (i & 0xf);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool send_info(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(psnes_serialize_size()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
|
||||
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(implementation_magic_value()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
|
||||
if (send(handle->fd, CONST_CAST header, sizeof(header), 0) != sizeof(header))
|
||||
return false;
|
||||
|
||||
@@ -398,7 +431,7 @@ static bool send_chunk(netplay_t *handle)
|
||||
{
|
||||
if (sendto(handle->udp_fd, CONST_CAST handle->packet_buffer, sizeof(handle->packet_buffer), 0, addr, sizeof(struct sockaddr)) != sizeof(handle->packet_buffer))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
warn_hangup();
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
@@ -438,7 +471,7 @@ static int poll_input(netplay_t *handle, bool block)
|
||||
|
||||
if (block && !send_chunk(handle))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
warn_hangup();
|
||||
handle->has_connection = false;
|
||||
return -1;
|
||||
}
|
||||
@@ -480,7 +513,7 @@ static bool get_self_input_state(netplay_t *handle)
|
||||
|
||||
if (!send_chunk(handle))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
warn_hangup();
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
@@ -541,7 +574,7 @@ bool netplay_poll(netplay_t *handle)
|
||||
if (!get_self_input_state(handle))
|
||||
return false;
|
||||
|
||||
// We skip reading the first frame so the host has a change to grab our host info so we don't block forever :')
|
||||
// We skip reading the first frame so the host has a chance to grab our host info so we don't block forever :')
|
||||
if (handle->frame_count == 0)
|
||||
{
|
||||
handle->buffer[0].used_real = true;
|
||||
@@ -552,20 +585,22 @@ bool netplay_poll(netplay_t *handle)
|
||||
return true;
|
||||
}
|
||||
|
||||
//fprintf(stderr, "Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
|
||||
//fprintf(stderr, "Before poll: Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
|
||||
/*
|
||||
if (handle->buffer_size > 1)
|
||||
{
|
||||
assert(handle->other_ptr != handle->self_ptr);
|
||||
assert(handle->read_ptr != handle->self_ptr);
|
||||
}
|
||||
assert(handle->other_ptr == handle->read_ptr);
|
||||
*/
|
||||
|
||||
// We might have reached the end of the buffer, where we simply have to block.
|
||||
int res = poll_input(handle, handle->other_ptr == NEXT_PTR(handle->self_ptr));
|
||||
int res = poll_input(handle, handle->other_ptr == handle->self_ptr);
|
||||
if (res == -1)
|
||||
{
|
||||
handle->has_connection = false;
|
||||
SSNES_WARN("Netplay connection timed out. Will continue without netplay.\n");
|
||||
warn_hangup();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -577,26 +612,28 @@ bool netplay_poll(netplay_t *handle)
|
||||
uint32_t buffer[UDP_FRAME_PACKETS * 2];
|
||||
if (!receive_data(handle, buffer, sizeof(buffer)))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
warn_hangup();
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
parse_packet(handle, buffer, UDP_FRAME_PACKETS);
|
||||
|
||||
} while ((handle->read_frame_count <= handle->frame_count) &&
|
||||
poll_input(handle, (handle->other_ptr == NEXT_PTR(handle->self_ptr)) &&
|
||||
poll_input(handle, (handle->other_ptr == handle->self_ptr) &&
|
||||
(first_read == handle->read_frame_count)) == 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cannot allow this. Should not happen though.
|
||||
if (NEXT_PTR(handle->self_ptr) == handle->other_ptr)
|
||||
if (handle->self_ptr == handle->other_ptr)
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
warn_hangup();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//fprintf(stderr, "After poll: Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
|
||||
|
||||
if (handle->read_ptr != handle->self_ptr)
|
||||
simulate_input(handle);
|
||||
else
|
||||
@@ -666,36 +703,38 @@ void netplay_post_frame(netplay_t *handle)
|
||||
handle->frame_count++;
|
||||
|
||||
// Nothing to do...
|
||||
if (handle->other_ptr == handle->read_ptr)
|
||||
if (handle->other_frame_count == handle->read_frame_count)
|
||||
return;
|
||||
|
||||
// Skip ahead if we predicted correctly. Skip until our simulation failed.
|
||||
while (handle->other_ptr != handle->read_ptr)
|
||||
while (handle->other_frame_count < handle->read_frame_count)
|
||||
{
|
||||
struct delta_frame *ptr = &handle->buffer[handle->other_ptr];
|
||||
if ((ptr->simulated_input_state != ptr->real_input_state) && !ptr->used_real)
|
||||
break;
|
||||
handle->other_ptr = NEXT_PTR(handle->other_ptr);
|
||||
handle->other_frame_count++;
|
||||
}
|
||||
|
||||
if (handle->other_ptr != handle->read_ptr)
|
||||
if (handle->other_frame_count < handle->read_frame_count)
|
||||
{
|
||||
// Replay frames
|
||||
handle->is_replay = true;
|
||||
handle->tmp_ptr = handle->other_ptr;
|
||||
psnes_unserialize(handle->buffer[handle->other_ptr].state, handle->state_size);
|
||||
while (handle->tmp_ptr != handle->self_ptr)
|
||||
bool first = true;
|
||||
while (first || (handle->tmp_ptr != handle->self_ptr))
|
||||
{
|
||||
psnes_serialize(handle->buffer[handle->tmp_ptr].state, handle->state_size);
|
||||
lock_autosave();
|
||||
psnes_run();
|
||||
unlock_autosave();
|
||||
handle->tmp_ptr = NEXT_PTR(handle->tmp_ptr);
|
||||
first = false;
|
||||
}
|
||||
handle->other_ptr = handle->read_ptr;
|
||||
handle->other_frame_count = handle->read_frame_count;
|
||||
handle->is_replay = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+29
-10
@@ -3,6 +3,15 @@
|
||||
check_switch_c C99 -std=gnu99
|
||||
check_critical C99 "Cannot find C99 compatible compiler."
|
||||
|
||||
# There are still broken 64-bit Linux distros out there. :)
|
||||
if [ -d /usr/lib64 ]; then
|
||||
add_library_dirs /usr/lib64
|
||||
fi
|
||||
|
||||
if [ -d /opt/local/lib ]; then
|
||||
add_library_dirs /opt/local/lib
|
||||
fi
|
||||
|
||||
if [ "$OS" = BSD ]; then
|
||||
DYLIB=-lc
|
||||
else
|
||||
@@ -49,14 +58,11 @@ check_pkgconf PULSE libpulse
|
||||
check_lib COREAUDIO "-framework AudioUnit" AudioUnitInitialize
|
||||
|
||||
check_pkgconf SDL sdl 1.2.10
|
||||
check_critical SDL "Cannot find SDL 1.2 library."
|
||||
check_pkgconf SDL_NEW sdl 1.3
|
||||
if [ $HAVE_SDL_NEW = yes ]; then
|
||||
echo "SSNES is not compatible with SDL 1.3 API, please use a recent 1.2 release."
|
||||
exit 1
|
||||
fi
|
||||
check_critical SDL "Cannot find SDL library."
|
||||
|
||||
# On some distros, -lCg doesn't link against -lstdc++ it seems ...
|
||||
check_lib_cxx CG -lCg cgCreateContext
|
||||
|
||||
check_lib CG -lCg cgCreateContext
|
||||
check_pkgconf XML libxml-2.0
|
||||
check_pkgconf SDL_IMAGE SDL_image
|
||||
|
||||
@@ -69,12 +75,25 @@ if [ $HAVE_FFMPEG != no ]; then
|
||||
( [ $HAVE_FFMPEG = auto ] && ( [ $HAVE_AVCODEC = no ] || [ $HAVE_AVFORMAT = no ] || [ $HAVE_AVUTIL = no ] || [ $HAVE_SWSCALE = no ] ) && HAVE_FFMPEG=no ) || HAVE_FFMPEG=yes
|
||||
fi
|
||||
|
||||
check_pkgconf SRC samplerate
|
||||
if [ $HAVE_FFMPEG = yes ]; then
|
||||
check_lib FFMPEG_ALLOC_CONTEXT3 "$AVCODEC_LIBS" avcodec_alloc_context3
|
||||
check_lib FFMPEG_AVCODEC_OPEN2 "$AVCODEC_LIBS" avcodec_open2
|
||||
check_lib FFMPEG_AVIO_OPEN "$AVFORMAT_LIBS" avio_open
|
||||
check_lib FFMPEG_AVFORMAT_WRITE_HEADER "$AVFORMAT_LIBS" avformat_write_header
|
||||
check_lib FFMPEG_AVFORMAT_NEW_STREAM "$AVFORMAT_LIBS" avformat_new_stream
|
||||
fi
|
||||
|
||||
check_lib DYNAMIC $DYLIB dlopen
|
||||
|
||||
check_pkgconf FREETYPE freetype2
|
||||
check_pkgconf XVIDEO xv
|
||||
check_pkgconf X11 x11
|
||||
check_pkgconf XEXT xext
|
||||
if [ "$HAVE_X11" = "yes" ] && [ "$HAVE_XEXT" = "yes" ]; then
|
||||
check_pkgconf XVIDEO xv
|
||||
else
|
||||
echo "X11 or Xext not present. Skipping XVideo."
|
||||
HAVE_XVIDEO=no
|
||||
fi
|
||||
|
||||
check_lib STRL -lc strlcpy
|
||||
|
||||
@@ -83,7 +102,7 @@ check_pkgconf PYTHON python3
|
||||
add_define_make OS $OS
|
||||
|
||||
# Creates config.mk and config.h.
|
||||
VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL PYTHON"
|
||||
VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE CONFIGFILE FREETYPE XVIDEO X11 XEXT NETPLAY FBO STRL PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM X264RGB"
|
||||
create_config_make config.mk $VARS
|
||||
create_config_header config.h $VARS
|
||||
|
||||
+3
-3
@@ -1,18 +1,18 @@
|
||||
. qb/qb.params.sh
|
||||
|
||||
PACKAGE_NAME=ssnes
|
||||
PACKAGE_VERSION=0.7
|
||||
PACKAGE_VERSION=0.9.1
|
||||
|
||||
# Adds a command line opt to ./configure --help
|
||||
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
|
||||
# $2: Comment
|
||||
# $3: Default arg. auto implies that HAVE_ALSA will be set according to library checks later on.
|
||||
add_command_line_enable DYNAMIC "Enable dynamic loading of libsnes library." no
|
||||
add_command_line_enable DYNAMIC "Enable dynamic loading of libsnes library" no
|
||||
add_command_line_string LIBSNES "libsnes library used" "-lsnes"
|
||||
add_command_line_enable FFMPEG "Enable FFmpeg recording support" no
|
||||
add_command_line_enable X264RGB "Enable lossless X264 RGB recording" no
|
||||
add_command_line_enable DYLIB "Enable dynamic loading support" auto
|
||||
add_command_line_enable NETPLAY "Enable netplay support" auto
|
||||
add_command_line_enable SRC "Enable libsamplerate support" no
|
||||
add_command_line_enable CONFIGFILE "Disable support for config file" yes
|
||||
add_command_line_enable CG "Enable Cg shader support" auto
|
||||
add_command_line_enable XML "Enable bSNES-style XML shader support" auto
|
||||
|
||||
+205
-103
@@ -13,11 +13,16 @@
|
||||
#include <stdbool.h>
|
||||
#include "ffemu.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "SDL_thread.h"
|
||||
#include "thread.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "../config.h"
|
||||
#endif
|
||||
|
||||
struct video_info
|
||||
{
|
||||
AVCodecContext *codec;
|
||||
AVCodec *encoder;
|
||||
|
||||
AVFrame *conv_frame;
|
||||
uint8_t *conv_frame_buf;
|
||||
@@ -27,6 +32,7 @@ struct video_info
|
||||
size_t outbuf_size;
|
||||
|
||||
int fmt;
|
||||
int pix_fmt;
|
||||
int pix_size;
|
||||
|
||||
AVFormatContext *format;
|
||||
@@ -37,6 +43,7 @@ struct video_info
|
||||
struct audio_info
|
||||
{
|
||||
AVCodecContext *codec;
|
||||
AVCodec *encoder;
|
||||
|
||||
int16_t *buffer;
|
||||
size_t frames_in_buffer;
|
||||
@@ -62,13 +69,14 @@ struct ffemu
|
||||
|
||||
struct ffemu_params params;
|
||||
|
||||
SDL_cond *cond;
|
||||
SDL_mutex *cond_lock;
|
||||
SDL_mutex *lock;
|
||||
scond_t *cond;
|
||||
slock_t *cond_lock;
|
||||
slock_t *lock;
|
||||
fifo_buffer_t *audio_fifo;
|
||||
fifo_buffer_t *video_fifo;
|
||||
fifo_buffer_t *attr_fifo;
|
||||
SDL_Thread *thread;
|
||||
sthread_t *thread;
|
||||
|
||||
volatile bool alive;
|
||||
volatile bool can_sleep;
|
||||
};
|
||||
@@ -79,15 +87,29 @@ static bool init_audio(struct audio_info *audio, struct ffemu_params *param)
|
||||
if (!codec)
|
||||
return false;
|
||||
|
||||
audio->encoder = codec;
|
||||
|
||||
// FFmpeg just loves to deprecate stuff :)
|
||||
#ifdef HAVE_FFMPEG_ALLOC_CONTEXT3
|
||||
audio->codec = avcodec_alloc_context3(codec);
|
||||
#else
|
||||
audio->codec = avcodec_alloc_context();
|
||||
avcodec_get_context_defaults(audio->codec);
|
||||
#endif
|
||||
|
||||
audio->codec->sample_rate = param->samplerate;
|
||||
audio->codec->time_base = (AVRational) { 1, param->samplerate };
|
||||
audio->codec->channels = param->channels;
|
||||
audio->codec->sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
|
||||
#ifdef HAVE_FFMPEG_AVCODEC_OPEN2
|
||||
if (avcodec_open2(audio->codec, codec, NULL) != 0)
|
||||
#else
|
||||
if (avcodec_open(audio->codec, codec) != 0)
|
||||
#endif
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
audio->buffer = av_malloc(audio->codec->frame_size * param->channels * sizeof(int16_t));
|
||||
if (!audio->buffer)
|
||||
@@ -101,12 +123,18 @@ static bool init_audio(struct audio_info *audio, struct ffemu_params *param)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init_video(struct video_info *video, struct ffemu_params *param)
|
||||
static bool init_video(struct video_info *video, const struct ffemu_params *param)
|
||||
{
|
||||
#ifdef HAVE_X264RGB
|
||||
AVCodec *codec = avcodec_find_encoder(CODEC_ID_H264);
|
||||
#else
|
||||
AVCodec *codec = avcodec_find_encoder(CODEC_ID_FFV1);
|
||||
#endif
|
||||
if (!codec)
|
||||
return false;
|
||||
|
||||
video->encoder = codec;
|
||||
|
||||
#if AV_HAVE_BIGENDIAN
|
||||
video->fmt = PIX_FMT_RGB555BE;
|
||||
#else
|
||||
@@ -119,25 +147,56 @@ static bool init_video(struct video_info *video, struct ffemu_params *param)
|
||||
video->pix_size = sizeof(uint32_t);
|
||||
}
|
||||
|
||||
#ifdef HAVE_X264RGB
|
||||
video->pix_fmt = PIX_FMT_BGR24;
|
||||
#else
|
||||
video->pix_fmt = PIX_FMT_RGB32;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FFMPEG_ALLOC_CONTEXT3
|
||||
video->codec = avcodec_alloc_context3(codec);
|
||||
#else
|
||||
video->codec = avcodec_alloc_context();
|
||||
avcodec_get_context_defaults(video->codec);
|
||||
#endif
|
||||
|
||||
video->codec->width = param->out_width;
|
||||
video->codec->height = param->out_height;
|
||||
video->codec->time_base = (AVRational) {param->fps.den, param->fps.num};
|
||||
video->codec->pix_fmt = PIX_FMT_RGB32;
|
||||
video->codec->sample_aspect_ratio = av_d2q(param->aspect_ratio * param->out_height / param->out_width, 255);
|
||||
video->codec->pix_fmt = video->pix_fmt;
|
||||
|
||||
AVDictionary *opts = NULL;
|
||||
|
||||
#ifdef HAVE_X264RGB
|
||||
video->codec->thread_count = 3;
|
||||
av_dict_set(&opts, "crf", "0", 0);
|
||||
#else
|
||||
video->codec->thread_count = 2;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FFMPEG_AVCODEC_OPEN2
|
||||
if (avcodec_open2(video->codec, codec, &opts) != 0)
|
||||
#else
|
||||
if (avcodec_open(video->codec, codec) != 0)
|
||||
#endif
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_FFMPEG_AVCODEC_OPEN2
|
||||
if (opts)
|
||||
av_dict_free(&opts);
|
||||
#endif
|
||||
|
||||
// Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be.
|
||||
video->outbuf_size = 1 << 23;
|
||||
video->outbuf = av_malloc(video->outbuf_size);
|
||||
|
||||
// Just to make sure we can handle the biggest frames. Seemed to crash with just 256 * 224.
|
||||
int size = avpicture_get_size(PIX_FMT_RGB32, param->fb_width, param->fb_height);
|
||||
size_t size = avpicture_get_size(video->pix_fmt, param->out_width, param->out_height);
|
||||
video->conv_frame_buf = av_malloc(size);
|
||||
video->conv_frame = avcodec_alloc_frame();
|
||||
avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, PIX_FMT_RGB32, param->fb_width, param->fb_height);
|
||||
avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, video->pix_fmt, param->out_width, param->out_height);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -147,18 +206,34 @@ static bool init_muxer(ffemu_t *handle)
|
||||
AVFormatContext *ctx = avformat_alloc_context();
|
||||
av_strlcpy(ctx->filename, handle->params.filename, sizeof(ctx->filename));
|
||||
ctx->oformat = av_guess_format(NULL, ctx->filename, NULL);
|
||||
#ifdef AVIO_FLAG_WRITE
|
||||
if (avio_open(&ctx->pb, ctx->filename, AVIO_FLAG_WRITE) < 0)
|
||||
|
||||
// FFmpeg sure likes to make things difficult.
|
||||
#if defined(AVIO_FLAG_WRITE)
|
||||
#define FFMPEG_FLAG_RW AVIO_FLAG_WRITE
|
||||
#elif defined(AVIO_WRONLY)
|
||||
#define FFMPEG_FLAG_RW AVIO_WRONLY
|
||||
#elif defined(URL_WRONLY)
|
||||
#define FFMPEG_FLAG_RW URL_WRONLY
|
||||
#else
|
||||
if (url_fopen(&ctx->pb, ctx->filename, URL_WRONLY) < 0)
|
||||
#define FFMPEG_FLAG_RW 2 // Seems to be consistent, but you never know.
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FFMPEG_AVIO_OPEN
|
||||
if (avio_open(&ctx->pb, ctx->filename, FFMPEG_FLAG_RW) < 0)
|
||||
#else
|
||||
if (url_fopen(&ctx->pb, ctx->filename, FFMPEG_FLAG_RW) < 0)
|
||||
#endif
|
||||
{
|
||||
av_free(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_FFMPEG_AVFORMAT_NEW_STREAM
|
||||
AVStream *stream = avformat_new_stream(ctx, handle->video.encoder);
|
||||
#else
|
||||
int stream_cnt = 0;
|
||||
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
||||
#endif
|
||||
stream->codec = handle->video.codec;
|
||||
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
@@ -166,29 +241,43 @@ static bool init_muxer(ffemu_t *handle)
|
||||
handle->muxer.vstream = stream;
|
||||
handle->muxer.vstream->sample_aspect_ratio = handle->video.codec->sample_aspect_ratio;
|
||||
|
||||
#ifdef HAVE_FFMPEG_AVFORMAT_NEW_STREAM
|
||||
stream = avformat_new_stream(ctx, handle->audio.encoder);
|
||||
#else
|
||||
stream = av_new_stream(ctx, stream_cnt++);
|
||||
#endif
|
||||
stream->codec = handle->audio.codec;
|
||||
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->audio.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.astream = stream;
|
||||
|
||||
if (av_write_header(ctx) < 0)
|
||||
#ifdef HAVE_X264RGB // Avoids a warning at end about non-monotonically increasing DTS values. It seems to be harmless to disable this.
|
||||
ctx->oformat->flags |= AVFMT_TS_NONSTRICT;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FFMPEG_AVFORMAT_WRITE_HEADER
|
||||
if (avformat_write_header(ctx, NULL) < 0)
|
||||
#else
|
||||
if (av_write_header(ctx) != 0)
|
||||
#endif
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->muxer.ctx = ctx;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MAX_FRAMES 64
|
||||
#define MAX_FRAMES 32
|
||||
|
||||
static int SDLCALL ffemu_thread(void *data);
|
||||
static void ffemu_thread(void *data);
|
||||
|
||||
static bool init_thread(ffemu_t *handle)
|
||||
{
|
||||
assert(handle->lock = SDL_CreateMutex());
|
||||
assert(handle->cond_lock = SDL_CreateMutex());
|
||||
assert(handle->cond = SDL_CreateCond());
|
||||
assert(handle->lock = slock_new());
|
||||
assert(handle->cond_lock = slock_new());
|
||||
assert(handle->cond = scond_new());
|
||||
assert(handle->audio_fifo = fifo_new(32000 * sizeof(int16_t) * handle->params.channels * MAX_FRAMES / 60));
|
||||
assert(handle->attr_fifo = fifo_new(sizeof(struct ffemu_video_data) * MAX_FRAMES));
|
||||
assert(handle->video_fifo = fifo_new(handle->params.fb_width * handle->params.fb_height *
|
||||
@@ -196,7 +285,7 @@ static bool init_thread(ffemu_t *handle)
|
||||
|
||||
handle->alive = true;
|
||||
handle->can_sleep = true;
|
||||
assert(handle->thread = SDL_CreateThread(ffemu_thread, handle));
|
||||
assert(handle->thread = sthread_create(ffemu_thread, handle));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -205,17 +294,17 @@ static void deinit_thread(ffemu_t *handle)
|
||||
{
|
||||
if (handle->thread)
|
||||
{
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
slock_lock(handle->cond_lock);
|
||||
handle->alive = false;
|
||||
handle->can_sleep = false;
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
slock_unlock(handle->cond_lock);
|
||||
|
||||
SDL_CondSignal(handle->cond);
|
||||
SDL_WaitThread(handle->thread, NULL);
|
||||
scond_signal(handle->cond);
|
||||
sthread_join(handle->thread);
|
||||
|
||||
SDL_DestroyMutex(handle->lock);
|
||||
SDL_DestroyMutex(handle->cond_lock);
|
||||
SDL_DestroyCond(handle->cond);
|
||||
slock_free(handle->lock);
|
||||
slock_free(handle->cond_lock);
|
||||
scond_free(handle->cond);
|
||||
|
||||
handle->thread = NULL;
|
||||
}
|
||||
@@ -244,7 +333,6 @@ static void deinit_thread_buf(ffemu_t *handle)
|
||||
|
||||
ffemu_t *ffemu_new(const struct ffemu_params *params)
|
||||
{
|
||||
avcodec_init();
|
||||
av_register_all();
|
||||
|
||||
ffemu_t *handle = calloc(1, sizeof(*handle));
|
||||
@@ -311,9 +399,9 @@ int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
SDL_mutexP(handle->lock);
|
||||
slock_lock(handle->lock);
|
||||
unsigned avail = fifo_write_avail(handle->attr_fifo);
|
||||
SDL_mutexV(handle->lock);
|
||||
slock_unlock(handle->lock);
|
||||
|
||||
if (!handle->alive)
|
||||
return -1;
|
||||
@@ -321,24 +409,33 @@ int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
if (avail >= sizeof(*data))
|
||||
break;
|
||||
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
slock_lock(handle->cond_lock);
|
||||
if (handle->can_sleep)
|
||||
{
|
||||
handle->can_sleep = false;
|
||||
SDL_CondWait(handle->cond, handle->cond_lock);
|
||||
scond_wait(handle->cond, handle->cond_lock);
|
||||
handle->can_sleep = true;
|
||||
}
|
||||
else
|
||||
SDL_CondSignal(handle->cond);
|
||||
scond_signal(handle->cond);
|
||||
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
slock_unlock(handle->cond_lock);
|
||||
}
|
||||
|
||||
SDL_mutexP(handle->lock);
|
||||
fifo_write(handle->attr_fifo, data, sizeof(*data));
|
||||
fifo_write(handle->video_fifo, data->data, data->pitch * data->height);
|
||||
SDL_mutexV(handle->lock);
|
||||
SDL_CondSignal(handle->cond);
|
||||
slock_lock(handle->lock);
|
||||
|
||||
// Tightly pack our frame to conserve memory. libsnes tends to use a very large pitch.
|
||||
struct ffemu_video_data attr_data = *data;
|
||||
attr_data.pitch = attr_data.width * handle->video.pix_size;
|
||||
|
||||
fifo_write(handle->attr_fifo, &attr_data, sizeof(attr_data));
|
||||
|
||||
unsigned offset = 0;
|
||||
for (unsigned y = 0; y < data->height; y++, offset += data->pitch)
|
||||
fifo_write(handle->video_fifo, (const uint8_t*)data->data + offset, attr_data.pitch);
|
||||
|
||||
slock_unlock(handle->lock);
|
||||
scond_signal(handle->cond);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -347,9 +444,9 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
SDL_mutexP(handle->lock);
|
||||
slock_lock(handle->lock);
|
||||
unsigned avail = fifo_write_avail(handle->audio_fifo);
|
||||
SDL_mutexV(handle->lock);
|
||||
slock_unlock(handle->lock);
|
||||
|
||||
if (!handle->alive)
|
||||
return -1;
|
||||
@@ -357,23 +454,23 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
if (avail >= data->frames * handle->params.channels * sizeof(int16_t))
|
||||
break;
|
||||
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
slock_lock(handle->cond_lock);
|
||||
if (handle->can_sleep)
|
||||
{
|
||||
handle->can_sleep = false;
|
||||
SDL_CondWait(handle->cond, handle->cond_lock);
|
||||
scond_wait(handle->cond, handle->cond_lock);
|
||||
handle->can_sleep = true;
|
||||
}
|
||||
else
|
||||
SDL_CondSignal(handle->cond);
|
||||
scond_signal(handle->cond);
|
||||
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
slock_unlock(handle->cond_lock);
|
||||
}
|
||||
|
||||
SDL_mutexP(handle->lock);
|
||||
slock_lock(handle->lock);
|
||||
fifo_write(handle->audio_fifo, data->data, data->frames * handle->params.channels * sizeof(int16_t));
|
||||
SDL_mutexV(handle->lock);
|
||||
SDL_CondSignal(handle->cond);
|
||||
slock_unlock(handle->lock);
|
||||
scond_signal(handle->cond);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -381,16 +478,15 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
{
|
||||
handle->video.sws_ctx = sws_getCachedContext(handle->video.sws_ctx, data->width, data->height, handle->video.fmt,
|
||||
handle->params.out_width, handle->params.out_height, PIX_FMT_RGB32, SWS_POINT,
|
||||
handle->params.out_width, handle->params.out_height, handle->video.pix_fmt, SWS_POINT,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
int linesize = data->pitch;
|
||||
|
||||
sws_scale(handle->video.sws_ctx, (const uint8_t* const*)&data->data, &linesize, 0,
|
||||
handle->video.codec->height, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
data->height, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
|
||||
handle->video.conv_frame->pts = handle->video.frame_cnt;
|
||||
handle->video.conv_frame->display_picture_number = handle->video.frame_cnt;
|
||||
|
||||
int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf,
|
||||
handle->video.outbuf_size, handle->video.conv_frame);
|
||||
@@ -427,7 +523,7 @@ static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_dat
|
||||
while (written_frames < data->frames)
|
||||
{
|
||||
size_t can_write = handle->audio.codec->frame_size - handle->audio.frames_in_buffer;
|
||||
size_t write_frames = data->frames > can_write ? can_write : data->frames;
|
||||
size_t write_frames = data->frames - written_frames > can_write ? can_write : data->frames - written_frames;
|
||||
|
||||
memcpy(handle->audio.buffer + handle->audio.frames_in_buffer * handle->params.channels,
|
||||
data->data + written_frames * handle->params.channels,
|
||||
@@ -448,10 +544,8 @@ static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_dat
|
||||
return -1;
|
||||
|
||||
pkt.size = out_size;
|
||||
if (handle->audio.codec->coded_frame && handle->audio.codec->coded_frame->pts != AV_NOPTS_VALUE)
|
||||
pkt.pts = av_rescale_q(handle->audio.codec->coded_frame->pts, handle->audio.codec->time_base, handle->muxer.astream->time_base);
|
||||
else
|
||||
pkt.pts = av_rescale_q(handle->audio.frame_cnt, handle->audio.codec->time_base, handle->muxer.astream->time_base);
|
||||
|
||||
pkt.pts = av_rescale_q(handle->audio.codec->coded_frame->pts, handle->audio.codec->time_base, handle->muxer.astream->time_base);
|
||||
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
handle->audio.frames_in_buffer = 0;
|
||||
@@ -471,6 +565,35 @@ int ffemu_finalize(ffemu_t *handle)
|
||||
{
|
||||
deinit_thread(handle);
|
||||
|
||||
// Push out audio still in queue.
|
||||
size_t audio_buf_size = 512 * handle->params.channels * sizeof(int16_t);
|
||||
int16_t *audio_buf = av_malloc(audio_buf_size);
|
||||
if (audio_buf)
|
||||
{
|
||||
while (fifo_read_avail(handle->audio_fifo) >= audio_buf_size)
|
||||
{
|
||||
fifo_read(handle->audio_fifo, audio_buf, sizeof(audio_buf_size));
|
||||
|
||||
struct ffemu_audio_data aud = {
|
||||
.frames = 512,
|
||||
.data = audio_buf
|
||||
};
|
||||
|
||||
ffemu_push_audio_thread(handle, &aud);
|
||||
}
|
||||
|
||||
size_t avail = fifo_read_avail(handle->audio_fifo);
|
||||
fifo_read(handle->audio_fifo, audio_buf, avail);
|
||||
struct ffemu_audio_data aud = {
|
||||
.frames = avail / (sizeof(int16_t) * handle->params.channels),
|
||||
.data = audio_buf
|
||||
};
|
||||
|
||||
ffemu_push_audio_thread(handle, &aud);
|
||||
|
||||
av_free(audio_buf);
|
||||
}
|
||||
|
||||
// Push out frames still stuck in queue.
|
||||
void *video_buf = av_malloc(2 * handle->params.fb_width * handle->params.fb_height * handle->video.pix_size);
|
||||
if (video_buf)
|
||||
@@ -486,24 +609,6 @@ int ffemu_finalize(ffemu_t *handle)
|
||||
av_free(video_buf);
|
||||
}
|
||||
|
||||
size_t audio_buf_size = 128 * handle->params.channels * sizeof(int16_t);
|
||||
int16_t *audio_buf = av_malloc(audio_buf_size);
|
||||
if (audio_buf)
|
||||
{
|
||||
while (fifo_read_avail(handle->audio_fifo) >= audio_buf_size)
|
||||
{
|
||||
fifo_read(handle->audio_fifo, audio_buf, sizeof(audio_buf_size));
|
||||
|
||||
struct ffemu_audio_data aud = {
|
||||
.frames = 128,
|
||||
.data = audio_buf
|
||||
};
|
||||
|
||||
ffemu_push_audio_thread(handle, &aud);
|
||||
}
|
||||
av_free(audio_buf);
|
||||
}
|
||||
|
||||
deinit_thread_buf(handle);
|
||||
|
||||
// Push out delayed frames. (MPEG codecs)
|
||||
@@ -513,24 +618,22 @@ int ffemu_finalize(ffemu_t *handle)
|
||||
pkt.data = handle->video.outbuf;
|
||||
|
||||
int out_size = 0;
|
||||
do
|
||||
for (;;)
|
||||
{
|
||||
out_size = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, NULL);
|
||||
if (out_size <= 0)
|
||||
break;
|
||||
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
||||
|
||||
if (handle->video.codec->coded_frame->key_frame)
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
|
||||
pkt.size = out_size;
|
||||
|
||||
if (pkt.size > 0)
|
||||
{
|
||||
int err = av_interleaved_write_frame(handle->muxer.ctx, &pkt);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
} while (out_size > 0);
|
||||
int err = av_interleaved_write_frame(handle->muxer.ctx, &pkt);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Write final data.
|
||||
av_write_trailer(handle->muxer.ctx);
|
||||
@@ -538,7 +641,7 @@ int ffemu_finalize(ffemu_t *handle)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int SDLCALL ffemu_thread(void *data)
|
||||
static void ffemu_thread(void *data)
|
||||
{
|
||||
ffemu_t *ff = data;
|
||||
|
||||
@@ -546,47 +649,47 @@ static int SDLCALL ffemu_thread(void *data)
|
||||
void *video_buf = av_malloc(2 * ff->params.fb_width * ff->params.fb_height * ff->video.pix_size);
|
||||
assert(video_buf);
|
||||
|
||||
size_t audio_buf_size = 128 * ff->params.channels * sizeof(int16_t);
|
||||
size_t audio_buf_size = 512 * ff->params.channels * sizeof(int16_t);
|
||||
int16_t *audio_buf = av_malloc(audio_buf_size);
|
||||
assert(audio_buf);
|
||||
|
||||
struct ffemu_video_data attr_buf;
|
||||
|
||||
while (ff->alive)
|
||||
{
|
||||
struct ffemu_video_data attr_buf;
|
||||
|
||||
bool avail_video = false;
|
||||
bool avail_audio = false;
|
||||
|
||||
SDL_mutexP(ff->lock);
|
||||
slock_lock(ff->lock);
|
||||
if (fifo_read_avail(ff->attr_fifo) >= sizeof(attr_buf))
|
||||
avail_video = true;
|
||||
|
||||
if (fifo_read_avail(ff->audio_fifo) >= audio_buf_size)
|
||||
avail_audio = true;
|
||||
SDL_mutexV(ff->lock);
|
||||
slock_unlock(ff->lock);
|
||||
|
||||
if (!avail_video && !avail_audio)
|
||||
{
|
||||
SDL_mutexP(ff->cond_lock);
|
||||
slock_lock(ff->cond_lock);
|
||||
if (ff->can_sleep)
|
||||
{
|
||||
ff->can_sleep = false;
|
||||
SDL_CondWait(ff->cond, ff->cond_lock);
|
||||
scond_wait(ff->cond, ff->cond_lock);
|
||||
ff->can_sleep = true;
|
||||
}
|
||||
else
|
||||
SDL_CondSignal(ff->cond);
|
||||
scond_signal(ff->cond);
|
||||
|
||||
SDL_mutexV(ff->cond_lock);
|
||||
slock_unlock(ff->cond_lock);
|
||||
}
|
||||
|
||||
if (avail_video)
|
||||
{
|
||||
SDL_mutexP(ff->lock);
|
||||
slock_lock(ff->lock);
|
||||
fifo_read(ff->attr_fifo, &attr_buf, sizeof(attr_buf));
|
||||
fifo_read(ff->video_fifo, video_buf, attr_buf.height * attr_buf.pitch);
|
||||
SDL_mutexV(ff->lock);
|
||||
SDL_CondSignal(ff->cond);
|
||||
slock_unlock(ff->lock);
|
||||
scond_signal(ff->cond);
|
||||
|
||||
attr_buf.data = video_buf;
|
||||
ffemu_push_video_thread(ff, &attr_buf);
|
||||
@@ -594,13 +697,13 @@ static int SDLCALL ffemu_thread(void *data)
|
||||
|
||||
if (avail_audio)
|
||||
{
|
||||
SDL_mutexP(ff->lock);
|
||||
slock_lock(ff->lock);
|
||||
fifo_read(ff->audio_fifo, audio_buf, audio_buf_size);
|
||||
SDL_mutexV(ff->lock);
|
||||
SDL_CondSignal(ff->cond);
|
||||
slock_unlock(ff->lock);
|
||||
scond_signal(ff->cond);
|
||||
|
||||
struct ffemu_audio_data aud = {
|
||||
.frames = 128,
|
||||
.frames = 512,
|
||||
.data = audio_buf
|
||||
};
|
||||
|
||||
@@ -610,6 +713,5 @@ static int SDLCALL ffemu_thread(void *data)
|
||||
|
||||
av_free(video_buf);
|
||||
av_free(audio_buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -22,14 +22,11 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
//#include <stdio.h>
|
||||
|
||||
struct state_manager
|
||||
{
|
||||
uint64_t *buffer;
|
||||
size_t buf_size;
|
||||
uint32_t *tmp_state;
|
||||
uint32_t *scratch_buf;
|
||||
size_t top_ptr;
|
||||
size_t bottom_ptr;
|
||||
size_t state_size;
|
||||
@@ -126,7 +123,6 @@ static void reassign_bottom(state_manager_t *state)
|
||||
|
||||
static void generate_delta(state_manager_t *state, const void *data)
|
||||
{
|
||||
//unsigned patch_size = 0;
|
||||
bool crossed = false;
|
||||
const uint32_t *old_state = state->tmp_state;
|
||||
const uint32_t *new_state = data;
|
||||
@@ -139,11 +135,12 @@ static void generate_delta(state_manager_t *state, const void *data)
|
||||
{
|
||||
uint64_t xor = old_state[i] ^ new_state[i];
|
||||
|
||||
// If the data differs (xor != 0), we push that xor on the stack with index and xor. This can be reversed by reapplying the xor.
|
||||
// This, if states don't really differ much, we'll save lots of space :) Hopefully this will work really well with save states.
|
||||
// If the data differs (xor != 0), we push that xor on the stack with index and xor.
|
||||
// This can be reversed by reapplying the xor.
|
||||
// This, if states don't really differ much, we'll save lots of space :)
|
||||
// Hopefully this will work really well with save states.
|
||||
if (xor)
|
||||
{
|
||||
//patch_size++;
|
||||
state->buffer[state->top_ptr] = (i << 32) | xor;
|
||||
state->top_ptr = (state->top_ptr + 1) % state->buf_size;
|
||||
|
||||
@@ -154,8 +151,6 @@ static void generate_delta(state_manager_t *state, const void *data)
|
||||
|
||||
if (crossed)
|
||||
reassign_bottom(state);
|
||||
|
||||
//fprintf(stderr, "DELTA SIZE: %u, ORIG SIZE: %u\n", (unsigned)patch_size << 3, (unsigned)state->state_size << 2);
|
||||
}
|
||||
|
||||
bool state_manager_push(state_manager_t *state, const void *data)
|
||||
|
||||
+6
-6
@@ -69,12 +69,12 @@ static void dump_content(FILE *file, const uint16_t *frame, unsigned width, unsi
|
||||
for (unsigned i = 0; i < width; i++)
|
||||
{
|
||||
uint16_t pixel = *src++;
|
||||
uint8_t b = (pixel & 0x1f) << 3;
|
||||
uint8_t g = (pixel & 0x03e0) >> 2;
|
||||
uint8_t r = (pixel & 0x7c00) >> 7;
|
||||
*dst++ = b;
|
||||
*dst++ = g;
|
||||
*dst++ = r;
|
||||
uint8_t b = (pixel >> 0) & 0x1f;
|
||||
uint8_t g = (pixel >> 5) & 0x1f;
|
||||
uint8_t r = (pixel >> 10) & 0x1f;
|
||||
*dst++ = (b << 3) | (b >> 2);
|
||||
*dst++ = (g << 3) | (g >> 2);
|
||||
*dst++ = (r << 3) | (r >> 2);
|
||||
}
|
||||
|
||||
fwrite(line, 1, sizeof(line), file);
|
||||
|
||||
+143
-106
@@ -17,9 +17,11 @@
|
||||
|
||||
#include "general.h"
|
||||
#include "conf/config_file.h"
|
||||
#include "input/keysym.h"
|
||||
#include <assert.h>
|
||||
#include "strl.h"
|
||||
#include "config.def.h"
|
||||
#include "file.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -28,6 +30,7 @@
|
||||
#include <ctype.h>
|
||||
|
||||
struct settings g_settings;
|
||||
struct global g_extern;
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
static void read_keybinds(config_file_t *conf);
|
||||
@@ -126,13 +129,17 @@ static void set_defaults(void)
|
||||
g_settings.video.smooth = video_smooth;
|
||||
g_settings.video.force_aspect = force_aspect;
|
||||
g_settings.video.crop_overscan = crop_overscan;
|
||||
g_settings.video.aspect_ratio = SNES_ASPECT_RATIO;
|
||||
g_settings.video.aspect_ratio = -1.0f; // Automatic
|
||||
g_settings.video.shader_type = SSNES_SHADER_AUTO;
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
g_settings.video.font_size = font_size;
|
||||
g_settings.video.msg_pos_x = message_pos_offset_x;
|
||||
g_settings.video.msg_pos_y = message_pos_offset_y;
|
||||
|
||||
g_settings.video.msg_color_r = ((message_color >> 16) & 0xff) / 255.0f;
|
||||
g_settings.video.msg_color_g = ((message_color >> 8) & 0xff) / 255.0f;
|
||||
g_settings.video.msg_color_b = ((message_color >> 0) & 0xff) / 255.0f;
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_CG) || defined(HAVE_XML)
|
||||
@@ -154,23 +161,22 @@ static void set_defaults(void)
|
||||
g_settings.audio.latency = out_latency;
|
||||
g_settings.audio.sync = audio_sync;
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
g_settings.audio.src_quality = SAMPLERATE_QUALITY;
|
||||
#endif
|
||||
|
||||
g_settings.rewind_enable = rewind_enable;
|
||||
g_settings.rewind_buffer_size = rewind_buffer_size;
|
||||
g_settings.rewind_granularity = rewind_granularity;
|
||||
g_settings.pause_nonactive = pause_nonactive;
|
||||
g_settings.autosave_interval = autosave_interval;
|
||||
|
||||
g_settings.block_sram_overwrite = block_sram_overwrite;
|
||||
g_settings.savestate_auto_index = savestate_auto_index;
|
||||
|
||||
assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1));
|
||||
assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_rest));
|
||||
memcpy(g_settings.input.binds[0], snes_keybinds_1, sizeof(snes_keybinds_1));
|
||||
for (unsigned i = 1; i < 5; i++)
|
||||
memcpy(g_settings.input.binds[i], snes_keybinds_rest, sizeof(snes_keybinds_rest));
|
||||
|
||||
g_settings.input.axis_threshold = AXIS_THRESHOLD;
|
||||
g_settings.input.axis_threshold = axis_threshold;
|
||||
g_settings.input.netplay_client_swap_input = netplay_client_swap_input;
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
g_settings.input.joypad_map[i] = i;
|
||||
@@ -183,7 +189,6 @@ static void parse_config_file(void);
|
||||
|
||||
void parse_config(void)
|
||||
{
|
||||
memset(&g_settings, 0, sizeof(struct settings));
|
||||
set_defaults();
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
@@ -203,7 +208,7 @@ static config_file_t *open_default_config_file(void)
|
||||
const char *appdata = getenv("APPDATA");
|
||||
if (appdata)
|
||||
{
|
||||
char conf_path[strlen(appdata) + strlen("/ssnes.cfg ")];
|
||||
char conf_path[MAXPATHLEN];
|
||||
strlcpy(conf_path, appdata, sizeof(conf_path));
|
||||
strlcat(conf_path, "/ssnes.cfg", sizeof(conf_path));
|
||||
conf = config_file_new(conf_path);
|
||||
@@ -213,7 +218,7 @@ static config_file_t *open_default_config_file(void)
|
||||
const char *home = getenv("HOME");
|
||||
if (home)
|
||||
{
|
||||
char conf_path[strlen(home) + strlen("/.ssnes.cfg ")];
|
||||
char conf_path[MAXPATHLEN];
|
||||
strlcpy(conf_path, home, sizeof(conf_path));
|
||||
strlcat(conf_path, "/.ssnes.cfg", sizeof(conf_path));
|
||||
conf = config_file_new(conf_path);
|
||||
@@ -228,14 +233,14 @@ static config_file_t *open_default_config_file(void)
|
||||
const char *home = getenv("HOME");
|
||||
if (xdg)
|
||||
{
|
||||
char conf_path[strlen(xdg) + strlen("/ssnes/ssnes.cfg ")];
|
||||
char conf_path[MAXPATHLEN];
|
||||
strlcpy(conf_path, xdg, sizeof(conf_path));
|
||||
strlcat(conf_path, "/ssnes/ssnes.cfg", sizeof(conf_path));
|
||||
conf = config_file_new(conf_path);
|
||||
}
|
||||
else if (home)
|
||||
{
|
||||
char conf_path[strlen(home) + strlen("/.ssnes.cfg ")];
|
||||
char conf_path[MAXPATHLEN];
|
||||
strlcpy(conf_path, home, sizeof(conf_path));
|
||||
strlcat(conf_path, "/.ssnes.cfg", sizeof(conf_path));
|
||||
conf = config_file_new(conf_path);
|
||||
@@ -259,13 +264,8 @@ static config_file_t *open_default_config_file(void)
|
||||
#define CONFIG_GET_DOUBLE(var, key) if (config_get_double(conf, key, &tmp_double)) \
|
||||
g_settings.var = tmp_double
|
||||
|
||||
#define CONFIG_GET_STRING(var, key) do { \
|
||||
if (config_get_string(conf, key, &tmp_str)) \
|
||||
{ \
|
||||
strlcpy(g_settings.var, tmp_str, sizeof(g_settings.var)); \
|
||||
free(tmp_str); \
|
||||
} \
|
||||
} while(0)
|
||||
#define CONFIG_GET_STRING(var, key) \
|
||||
config_get_array(conf, key, g_settings.var, sizeof(g_settings.var))
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
static void parse_config_file(void)
|
||||
@@ -288,12 +288,16 @@ static void parse_config_file(void)
|
||||
return;
|
||||
|
||||
if (g_extern.verbose)
|
||||
config_file_dump(conf, stderr);
|
||||
{
|
||||
fprintf(stderr, "=== Config ===\n");
|
||||
config_file_dump_all(conf, stderr);
|
||||
fprintf(stderr, "=== Config end ===\n");
|
||||
}
|
||||
|
||||
int tmp_int;
|
||||
double tmp_double;
|
||||
bool tmp_bool;
|
||||
char *tmp_str;
|
||||
char tmp_str[MAXPATHLEN];
|
||||
|
||||
CONFIG_GET_DOUBLE(video.xscale, "video_xscale");
|
||||
CONFIG_GET_DOUBLE(video.yscale, "video_yscale");
|
||||
@@ -326,6 +330,14 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_INT(video.font_size, "video_font_size");
|
||||
CONFIG_GET_DOUBLE(video.msg_pos_x, "video_message_pos_x");
|
||||
CONFIG_GET_DOUBLE(video.msg_pos_y, "video_message_pos_y");
|
||||
|
||||
unsigned msg_color;
|
||||
if (config_get_hex(conf, "video_message_color", &msg_color))
|
||||
{
|
||||
g_settings.video.msg_color_r = ((msg_color >> 16) & 0xff) / 255.0f;
|
||||
g_settings.video.msg_color_g = ((msg_color >> 8) & 0xff) / 255.0f;
|
||||
g_settings.video.msg_color_b = ((msg_color >> 0) & 0xff) / 255.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
CONFIG_GET_BOOL(video.hires_record, "video_hires_record");
|
||||
@@ -338,7 +350,7 @@ static void parse_config_file(void)
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_CG) || defined(HAVE_XML)
|
||||
if (config_get_string(conf, "video_shader_type", &tmp_str))
|
||||
if (config_get_array(conf, "video_shader_type", tmp_str, sizeof(tmp_str)))
|
||||
{
|
||||
if (strcmp("cg", tmp_str) == 0)
|
||||
g_settings.video.shader_type = SSNES_SHADER_CG;
|
||||
@@ -348,8 +360,6 @@ static void parse_config_file(void)
|
||||
g_settings.video.shader_type = SSNES_SHADER_AUTO;
|
||||
else if (strcmp("none", tmp_str) == 0)
|
||||
g_settings.video.shader_type = SSNES_SHADER_NONE;
|
||||
|
||||
free(tmp_str);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -374,23 +384,18 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_INT(audio.latency, "audio_latency");
|
||||
CONFIG_GET_BOOL(audio.sync, "audio_sync");
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
if (config_get_int(conf, "audio_src_quality", &tmp_int))
|
||||
{
|
||||
int quals[] = { SRC_ZERO_ORDER_HOLD, SRC_LINEAR, SRC_SINC_FASTEST,
|
||||
SRC_SINC_MEDIUM_QUALITY, SRC_SINC_BEST_QUALITY };
|
||||
|
||||
if (tmp_int > 0 && tmp_int < 6)
|
||||
g_settings.audio.src_quality = quals[tmp_int];
|
||||
}
|
||||
#endif
|
||||
|
||||
CONFIG_GET_STRING(video.driver, "video_driver");
|
||||
CONFIG_GET_STRING(audio.driver, "audio_driver");
|
||||
CONFIG_GET_STRING(audio.dsp_plugin, "audio_dsp_plugin");
|
||||
CONFIG_GET_STRING(input.driver, "input_driver");
|
||||
CONFIG_GET_STRING(libsnes, "libsnes_path");
|
||||
|
||||
CONFIG_GET_STRING(screenshot_directory, "screenshot_directory");
|
||||
if (*g_settings.screenshot_directory && !path_is_directory(g_settings.screenshot_directory))
|
||||
{
|
||||
SSNES_WARN("screenshot_directory is not an existing directory, ignoring ...\n");
|
||||
*g_settings.screenshot_directory = '\0';
|
||||
}
|
||||
|
||||
CONFIG_GET_BOOL(rewind_enable, "rewind_enable");
|
||||
|
||||
@@ -403,6 +408,31 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_INT(autosave_interval, "autosave_interval");
|
||||
|
||||
CONFIG_GET_STRING(cheat_database, "cheat_database_path");
|
||||
CONFIG_GET_STRING(cheat_settings_path, "cheat_settings_path");
|
||||
|
||||
CONFIG_GET_BOOL(block_sram_overwrite, "block_sram_overwrite");
|
||||
CONFIG_GET_BOOL(savestate_auto_index, "savestate_auto_index");
|
||||
|
||||
if (!g_extern.has_set_save_path && config_get_array(conf, "savefile_directory", tmp_str, sizeof(tmp_str)))
|
||||
{
|
||||
if (path_is_directory(tmp_str))
|
||||
{
|
||||
strlcpy(g_extern.savefile_name_srm, tmp_str, sizeof(g_extern.savefile_name_srm));
|
||||
fill_pathname_dir(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm));
|
||||
}
|
||||
else
|
||||
SSNES_WARN("savefile_directory is not a directory, ignoring ...!\n");
|
||||
}
|
||||
if (!g_extern.has_set_state_path && config_get_array(conf, "savestate_directory", tmp_str, sizeof(tmp_str)))
|
||||
{
|
||||
if (path_is_directory(tmp_str))
|
||||
{
|
||||
strlcpy(g_extern.savestate_name, tmp_str, sizeof(g_extern.savestate_name));
|
||||
fill_pathname_dir(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name));
|
||||
}
|
||||
else
|
||||
SSNES_WARN("savestate_directory is not a directory, ignoring ...\n");
|
||||
}
|
||||
|
||||
read_keybinds(conf);
|
||||
|
||||
@@ -447,6 +477,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(rewind, SSNES_REWIND)
|
||||
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
|
||||
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
@@ -482,6 +513,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(rewind, SSNES_REWIND)
|
||||
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
|
||||
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
@@ -517,6 +549,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(rewind, SSNES_REWIND)
|
||||
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
|
||||
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
@@ -552,6 +585,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(rewind, SSNES_REWIND)
|
||||
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
|
||||
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
@@ -587,6 +621,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(rewind, SSNES_REWIND)
|
||||
DECLARE_BIND(movie_record_toggle, SSNES_MOVIE_RECORD_TOGGLE)
|
||||
DECLARE_BIND(pause_toggle, SSNES_PAUSE_TOGGLE)
|
||||
DECLARE_BIND(frame_advance, SSNES_FRAMEADVANCE)
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
@@ -606,70 +641,72 @@ struct key_map
|
||||
|
||||
// Edit: Not portable to different input systems atm. Might move this map into the driver itself or something.
|
||||
// However, this should map nicely over to other systems aswell since the definition are mostly the same anyways.
|
||||
static const struct key_map sdlk_map[] = {
|
||||
{ "left", SDLK_LEFT },
|
||||
{ "right", SDLK_RIGHT },
|
||||
{ "up", SDLK_UP },
|
||||
{ "down", SDLK_DOWN },
|
||||
{ "enter", SDLK_RETURN },
|
||||
{ "kp_enter", SDLK_KP_ENTER },
|
||||
{ "tab", SDLK_TAB },
|
||||
{ "insert", SDLK_INSERT },
|
||||
{ "del", SDLK_DELETE },
|
||||
{ "end", SDLK_END },
|
||||
{ "home", SDLK_HOME },
|
||||
{ "rshift", SDLK_RSHIFT },
|
||||
{ "shift", SDLK_LSHIFT },
|
||||
{ "ctrl", SDLK_LCTRL },
|
||||
{ "alt", SDLK_LALT },
|
||||
{ "space", SDLK_SPACE },
|
||||
{ "escape", SDLK_ESCAPE },
|
||||
{ "add", SDLK_KP_PLUS },
|
||||
{ "subtract", SDLK_KP_MINUS },
|
||||
{ "f1", SDLK_F1 },
|
||||
{ "f2", SDLK_F2 },
|
||||
{ "f3", SDLK_F3 },
|
||||
{ "f4", SDLK_F4 },
|
||||
{ "f5", SDLK_F5 },
|
||||
{ "f6", SDLK_F6 },
|
||||
{ "f7", SDLK_F7 },
|
||||
{ "f8", SDLK_F8 },
|
||||
{ "f9", SDLK_F9 },
|
||||
{ "f10", SDLK_F10 },
|
||||
{ "f11", SDLK_F11 },
|
||||
{ "f12", SDLK_F12 },
|
||||
{ "num0", SDLK_0 },
|
||||
{ "num1", SDLK_1 },
|
||||
{ "num2", SDLK_2 },
|
||||
{ "num3", SDLK_3 },
|
||||
{ "num4", SDLK_4 },
|
||||
{ "num5", SDLK_5 },
|
||||
{ "num6", SDLK_6 },
|
||||
{ "num7", SDLK_7 },
|
||||
{ "num8", SDLK_8 },
|
||||
{ "num9", SDLK_9 },
|
||||
{ "pageup", SDLK_PAGEUP },
|
||||
{ "pagedown", SDLK_PAGEDOWN },
|
||||
{ "keypad0", SDLK_KP0 },
|
||||
{ "keypad1", SDLK_KP1 },
|
||||
{ "keypad2", SDLK_KP2 },
|
||||
{ "keypad3", SDLK_KP3 },
|
||||
{ "keypad4", SDLK_KP4 },
|
||||
{ "keypad5", SDLK_KP5 },
|
||||
{ "keypad6", SDLK_KP6 },
|
||||
{ "keypad7", SDLK_KP7 },
|
||||
{ "keypad8", SDLK_KP8 },
|
||||
{ "keypad9", SDLK_KP9 },
|
||||
{ "period", SDLK_PERIOD },
|
||||
{ "capslock", SDLK_CAPSLOCK },
|
||||
{ "numlock", SDLK_NUMLOCK },
|
||||
{ "backspace", SDLK_BACKSPACE },
|
||||
{ "multiply", SDLK_KP_MULTIPLY },
|
||||
{ "divide", SDLK_KP_DIVIDE },
|
||||
{ "print_screen", SDLK_PRINT },
|
||||
{ "scroll_lock", SDLK_SCROLLOCK },
|
||||
{ "tilde", SDLK_BACKQUOTE },
|
||||
{ "nul", SDLK_UNKNOWN },
|
||||
static const struct key_map sk_map[] = {
|
||||
{ "left", SK_LEFT },
|
||||
{ "right", SK_RIGHT },
|
||||
{ "up", SK_UP },
|
||||
{ "down", SK_DOWN },
|
||||
{ "enter", SK_RETURN },
|
||||
{ "kp_enter", SK_KP_ENTER },
|
||||
{ "tab", SK_TAB },
|
||||
{ "insert", SK_INSERT },
|
||||
{ "del", SK_DELETE },
|
||||
{ "end", SK_END },
|
||||
{ "home", SK_HOME },
|
||||
{ "rshift", SK_RSHIFT },
|
||||
{ "shift", SK_LSHIFT },
|
||||
{ "ctrl", SK_LCTRL },
|
||||
{ "alt", SK_LALT },
|
||||
{ "space", SK_SPACE },
|
||||
{ "escape", SK_ESCAPE },
|
||||
{ "add", SK_KP_PLUS },
|
||||
{ "subtract", SK_KP_MINUS },
|
||||
{ "f1", SK_F1 },
|
||||
{ "f2", SK_F2 },
|
||||
{ "f3", SK_F3 },
|
||||
{ "f4", SK_F4 },
|
||||
{ "f5", SK_F5 },
|
||||
{ "f6", SK_F6 },
|
||||
{ "f7", SK_F7 },
|
||||
{ "f8", SK_F8 },
|
||||
{ "f9", SK_F9 },
|
||||
{ "f10", SK_F10 },
|
||||
{ "f11", SK_F11 },
|
||||
{ "f12", SK_F12 },
|
||||
{ "num0", SK_0 },
|
||||
{ "num1", SK_1 },
|
||||
{ "num2", SK_2 },
|
||||
{ "num3", SK_3 },
|
||||
{ "num4", SK_4 },
|
||||
{ "num5", SK_5 },
|
||||
{ "num6", SK_6 },
|
||||
{ "num7", SK_7 },
|
||||
{ "num8", SK_8 },
|
||||
{ "num9", SK_9 },
|
||||
{ "pageup", SK_PAGEUP },
|
||||
{ "pagedown", SK_PAGEDOWN },
|
||||
{ "keypad0", SK_KP0 },
|
||||
{ "keypad1", SK_KP1 },
|
||||
{ "keypad2", SK_KP2 },
|
||||
{ "keypad3", SK_KP3 },
|
||||
{ "keypad4", SK_KP4 },
|
||||
{ "keypad5", SK_KP5 },
|
||||
{ "keypad6", SK_KP6 },
|
||||
{ "keypad7", SK_KP7 },
|
||||
{ "keypad8", SK_KP8 },
|
||||
{ "keypad9", SK_KP9 },
|
||||
{ "period", SK_PERIOD },
|
||||
{ "capslock", SK_CAPSLOCK },
|
||||
{ "numlock", SK_NUMLOCK },
|
||||
{ "backspace", SK_BACKSPACE },
|
||||
{ "multiply", SK_KP_MULTIPLY },
|
||||
{ "divide", SK_KP_DIVIDE },
|
||||
{ "print_screen", SK_PRINT },
|
||||
{ "scroll_lock", SK_SCROLLOCK },
|
||||
{ "tilde", SK_BACKQUOTE },
|
||||
{ "backquote", SK_BACKQUOTE },
|
||||
{ "pause", SK_PAUSE },
|
||||
{ "nul", SK_UNKNOWN },
|
||||
};
|
||||
|
||||
static struct snes_keybind *find_snes_bind(unsigned port, int id)
|
||||
@@ -684,23 +721,23 @@ static struct snes_keybind *find_snes_bind(unsigned port, int id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int find_sdlk_bind(const char *str)
|
||||
static int find_sk_bind(const char *str)
|
||||
{
|
||||
for (int i = 0; i < sizeof(sdlk_map)/sizeof(struct key_map); i++)
|
||||
for (int i = 0; i < sizeof(sk_map)/sizeof(struct key_map); i++)
|
||||
{
|
||||
if (strcasecmp(sdlk_map[i].str, str) == 0)
|
||||
return sdlk_map[i].key;
|
||||
if (strcasecmp(sk_map[i].str, str) == 0)
|
||||
return sk_map[i].key;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int find_sdlk_key(const char *str)
|
||||
static int find_sk_key(const char *str)
|
||||
{
|
||||
// If the bind is a normal key-press ...
|
||||
if (strlen(str) == 1 && isalpha(*str))
|
||||
return (int)SDLK_a + (tolower(*str) - (int)'a');
|
||||
return (int)SK_a + (tolower(*str) - (int)'a');
|
||||
else // Check if we have a special mapping for it.
|
||||
return find_sdlk_bind(str);
|
||||
return find_sk_bind(str);
|
||||
}
|
||||
|
||||
// Yes, this function needs a good refactor :)
|
||||
@@ -721,7 +758,7 @@ static void read_keybinds(config_file_t *conf)
|
||||
// Check keybind
|
||||
if (bind_maps[j][i].key && config_get_string(conf, bind_maps[j][i].key, &tmp_key))
|
||||
{
|
||||
int key = find_sdlk_key(tmp_key);
|
||||
int key = find_sk_key(tmp_key);
|
||||
|
||||
if (key >= 0)
|
||||
bind->key = key;
|
||||
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
#!/bin/sh
|
||||
###############
|
||||
# Build script which builds and packages SSNES for MinGW 32/64-bit.
|
||||
# Preferably build on Linux with a cross chain ... :D
|
||||
##########
|
||||
|
||||
####
|
||||
## Tweak these to suit your environment.
|
||||
## Not defining the variable will avoid building that target.
|
||||
## Set MINGW32_BASE and/or MINGW64_BASE to set toolchain prefix:
|
||||
## E.g.: i486-mingw32-gcc would get prefix "i486-mingw32".
|
||||
|
||||
BUILD_32BIT=yes
|
||||
BUILD_64BIT=yes
|
||||
BUILD_PHOENIX_GUI=yes
|
||||
|
||||
if [ ! -z "$NOBUILD_32BIT" ]; then
|
||||
BUILD_32BIT=no
|
||||
fi
|
||||
if [ ! -z "$NOBUILD_64BIT" ]; then
|
||||
BUILD_64BIT=no
|
||||
fi
|
||||
if [ ! -z "$NOBUILD_PHOENIX_GUI" ]; then
|
||||
BUILD_PHOENIX_GUI=no
|
||||
fi
|
||||
|
||||
########
|
||||
|
||||
die()
|
||||
{
|
||||
echo "$@"
|
||||
exit 1
|
||||
}
|
||||
|
||||
message()
|
||||
{
|
||||
echo ""
|
||||
echo "================================"
|
||||
echo "$@"
|
||||
echo "================================"
|
||||
echo ""
|
||||
}
|
||||
|
||||
if [ ! -f "`which zip`" ]; then
|
||||
echo "Cannot find 'zip'. Cannot package, quitting ..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "`which unzip`" ]; then
|
||||
echo "Cannot find 'unzip'. Cannot unpack, quitting ..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "`which wget`" ]; then
|
||||
echo "Cannot find 'wget'. Cannot unpack, quitting ..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
do_phoenix_build()
|
||||
{
|
||||
message "Build Phoenix GUI"
|
||||
### Build Phoenix GUI
|
||||
if [ ! -d "Phoenix" ]; then
|
||||
git clone git://github.com/Themaister/SSNES-Phoenix.git Phoenix
|
||||
cd Phoenix
|
||||
else
|
||||
cd Phoenix
|
||||
git pull origin master
|
||||
fi
|
||||
|
||||
make -f Makefile.win clean || die "Failed to clean ..."
|
||||
make -f Makefile.win CC="$C_COMPILER" CXX="$CXX_COMPILER" WINDRES="$WINDRES" -j4 all || die "Failed to build ..."
|
||||
touch ssnes-phoenix.cfg
|
||||
cp ssnes-phoenix.cfg ssnes-phoenix.exe ../ || die "Failed to copy ..."
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
do_build()
|
||||
{
|
||||
SSNES_DIR="$1"
|
||||
LIBZIPNAME="$2"
|
||||
BUILDTYPE="$3"
|
||||
|
||||
if [ ! -d "$SSNES_DIR" ]; then
|
||||
git clone git://github.com/Themaister/SSNES.git "$SSNES_DIR"
|
||||
cd "$SSNES_DIR"
|
||||
else
|
||||
cd "$SSNES_DIR"
|
||||
git pull origin master
|
||||
fi
|
||||
|
||||
if [ ! -f "$LIBZIPNAME" ]; then
|
||||
make -f Makefile.win libs_${BUILDTYPE} || die "Failed to extract"
|
||||
fi
|
||||
|
||||
make -f Makefile.win clean || die "Failed to clean ..."
|
||||
make -f Makefile.win CC="$C_COMPILER" CXX="$CXX_COMPILER" -j4 all SLIM=1 || die "Failed to build ..."
|
||||
make -f Makefile.win CC="$C_COMPILER" CXX="$CXX_COMPILER" dist_${BUILDTYPE} SLIM=1 || die "Failed to dist ..."
|
||||
if [ -z "`find . | grep "ssnes-win"`" ]; then
|
||||
die "Did not find build ..."
|
||||
fi
|
||||
|
||||
if [ "$BUILD_PHOENIX_GUI" = "yes" ]; then
|
||||
do_phoenix_build
|
||||
fi
|
||||
|
||||
ZIP_BASE="`find . | grep "ssnes-win" | head -n1`"
|
||||
ZIP_SLIM="`echo $ZIP_BASE | sed -e 's|\.zip|-slim.zip|'`"
|
||||
ZIP_FULL="`echo $ZIP_BASE | sed -e 's|\.zip|-full.zip|'`"
|
||||
|
||||
if [ "$BUILD_PHOENIX_GUI" = "yes" ]; then
|
||||
zip "$ZIP_BASE" ssnes-phoenix.exe ssnes-phoenix.cfg
|
||||
fi
|
||||
mv -v "$ZIP_BASE" "../$ZIP_SLIM" || die "Failed to move final build ..."
|
||||
|
||||
make -f Makefile.win clean || die "Failed to clean ..."
|
||||
make -f Makefile.win CC="$C_COMPILER" CXX="$CXX_COMPILER" -j4 all || die "Failed to build ..."
|
||||
make -f Makefile.win CC="$C_COMPILER" CXX="$CXX_COMPILER" dist_${BUILDTYPE} || die "Failed to dist ..."
|
||||
|
||||
if [ "$BUILD_PHOENIX_GUI" = "yes" ]; then
|
||||
zip "$ZIP_BASE" ssnes-phoenix.exe ssnes-phoenix.cfg
|
||||
fi
|
||||
mv -v "$ZIP_BASE" "../$ZIP_FULL" || die "Failed to move final build ..."
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
if [ "$BUILD_32BIT" = yes ]; then
|
||||
message "Building for 32-bit!"
|
||||
C_COMPILER=${MINGW32_BASE}-gcc
|
||||
CXX_COMPILER=${MINGW32_BASE}-g++
|
||||
WINDRES=${MINGW32_BASE}-windres
|
||||
do_build "SSNES-w32" "SSNES-win32-libs.zip" "x86"
|
||||
fi
|
||||
|
||||
if [ "$BUILD_64BIT" = yes ]; then
|
||||
message "Building for 64-bit!"
|
||||
C_COMPILER=${MINGW64_BASE}-gcc
|
||||
CXX_COMPILER=${MINGW64_BASE}-g++
|
||||
WINDRES=${MINGW64_BASE}-windres
|
||||
do_build "SSNES-w64" "SSNES-win64-libs.zip" "x86_64"
|
||||
fi
|
||||
|
||||
message "Built successfully! :)"
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
##### Config file for SSNES
|
||||
|
||||
# Save all save files (*.srm) to this directory. This includes related files like .bsv, .rtc, .psrm, etc ...
|
||||
# This will be overridden by explicit command line options.
|
||||
# savefile_directory =
|
||||
|
||||
# Save all save states (*.state) to this directory.
|
||||
# This will be overridden by explicit command line options.
|
||||
# savestate_directory =
|
||||
|
||||
## If enabled, load libsnes from a dynamic location.
|
||||
# libsnes_path = "/path/to/libsnes.so"
|
||||
|
||||
@@ -11,7 +19,8 @@
|
||||
# Path to external video driver using the SSNES driver API.
|
||||
# video_external_driver =
|
||||
|
||||
# Windowed xscale and yscale (Real x res: 224 * xscale * aspect_ratio, real y scale: 224 * xscale)
|
||||
# Windowed xscale and yscale
|
||||
# (Real x res: base_size * xscale * aspect_ratio, real y res: base_size * yscale)
|
||||
# video_xscale = 3.0
|
||||
# video_yscale = 3.0
|
||||
|
||||
@@ -83,7 +92,11 @@
|
||||
# Offset for where messages will be placed on screen. Values are in range 0.0 to 1.0 for both x and y values.
|
||||
# [0.0, 0.0] maps to the lower left corner of the screen.
|
||||
# video_message_pos_x = 0.05
|
||||
# video_message_pox_y = 0.05
|
||||
# video_message_pos_y = 0.05
|
||||
|
||||
# Color for message. The value is treated as a hexadecimal value.
|
||||
# It is a regular RGB hex number, i.e. red is "ff0000".
|
||||
# video_message_color = ffffff
|
||||
|
||||
#### Audio
|
||||
|
||||
@@ -120,8 +133,6 @@
|
||||
# Desired audio latency in milliseconds. Might not be honored if driver can't provide given latency.
|
||||
# audio_latency = 64
|
||||
|
||||
# libsamplerate quality. Valid values are from 1 to 5. These values map to zero_order_hold, linear, sinc_fastest, sinc_medium and sinc_best respectively.
|
||||
# audio_src_quality =
|
||||
|
||||
### Input
|
||||
|
||||
@@ -263,6 +274,9 @@
|
||||
# Toggle between paused and non-paused state
|
||||
# input_pause_toggle = p
|
||||
|
||||
# Frame advance when game is paused
|
||||
# input_frame_advance = k
|
||||
|
||||
# Reset the emulated SNES.
|
||||
# input_reset = h
|
||||
|
||||
@@ -283,6 +297,11 @@
|
||||
# Path to XML cheat database (as used by bSNES).
|
||||
# cheat_database_path =
|
||||
|
||||
# Path to XML cheat config, a file which keeps track of which
|
||||
# cheat settings are used for individual games.
|
||||
# If the file does not exist, it will be created.
|
||||
# cheat_settings_path =
|
||||
|
||||
# Cheats.
|
||||
# input_cheat_index_plus = y
|
||||
# input_cheat_index_minus = t
|
||||
@@ -299,3 +318,14 @@
|
||||
|
||||
# Records video after CPU video filter.
|
||||
# video_post_filter_record = false
|
||||
|
||||
# Block SRAM from being overwritten when loading save states.
|
||||
# Might potentially lead to buggy games.
|
||||
# block_sram_overwrite = false
|
||||
|
||||
# When saving a savestate, save state index is automatically increased before
|
||||
# it is saved.
|
||||
# Also, when loading a ROM, the index will be set to the highest existing index.
|
||||
# There is no upper bound on the index.
|
||||
# savestate_auto_index = false
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES 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 Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES 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 SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "thread.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
#ifdef __MACH__
|
||||
#include <mach/clock.h>
|
||||
#include <mach/mach.h>
|
||||
#endif
|
||||
|
||||
struct thread_data
|
||||
{
|
||||
void (*func)(void*);
|
||||
void *userdata;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
struct sthread
|
||||
{
|
||||
HANDLE thread;
|
||||
};
|
||||
|
||||
static DWORD CALLBACK thread_wrap(void *data_)
|
||||
{
|
||||
struct thread_data *data = data_;
|
||||
data->func(data->userdata);
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sthread_t *sthread_create(void (*thread_func)(void*), void *userdata)
|
||||
{
|
||||
sthread_t *thread = calloc(1, sizeof(*thread));
|
||||
if (!thread)
|
||||
return NULL;
|
||||
|
||||
struct thread_data *data = calloc(1, sizeof(*data));
|
||||
if (!data)
|
||||
{
|
||||
free(thread);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data->func = thread_func;
|
||||
data->userdata = userdata;
|
||||
|
||||
thread->thread = CreateThread(NULL, 0, thread_wrap, data, 0, NULL);
|
||||
if (!thread->thread)
|
||||
{
|
||||
free(data);
|
||||
free(thread);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
void sthread_join(sthread_t *thread)
|
||||
{
|
||||
WaitForSingleObject(thread->thread, INFINITE);
|
||||
CloseHandle(thread->thread);
|
||||
free(thread);
|
||||
}
|
||||
|
||||
struct slock
|
||||
{
|
||||
CRITICAL_SECTION lock;
|
||||
};
|
||||
|
||||
slock_t *slock_new(void)
|
||||
{
|
||||
slock_t *lock = calloc(1, sizeof(*lock));
|
||||
if (!lock)
|
||||
return NULL;
|
||||
|
||||
InitializeCriticalSection(&lock->lock);
|
||||
return lock;
|
||||
}
|
||||
|
||||
void slock_free(slock_t *lock)
|
||||
{
|
||||
DeleteCriticalSection(&lock->lock);
|
||||
free(lock);
|
||||
}
|
||||
|
||||
void slock_lock(slock_t *lock)
|
||||
{
|
||||
EnterCriticalSection(&lock->lock);
|
||||
}
|
||||
|
||||
void slock_unlock(slock_t *lock)
|
||||
{
|
||||
LeaveCriticalSection(&lock->lock);
|
||||
}
|
||||
|
||||
struct scond
|
||||
{
|
||||
HANDLE event;
|
||||
};
|
||||
|
||||
scond_t *scond_new(void)
|
||||
{
|
||||
scond_t *cond = calloc(1, sizeof(*cond));
|
||||
if (!cond)
|
||||
return NULL;
|
||||
|
||||
cond->event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (!cond->event)
|
||||
{
|
||||
free(cond);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return cond;
|
||||
}
|
||||
|
||||
void scond_wait(scond_t *cond, slock_t *lock)
|
||||
{
|
||||
WaitForSingleObject(cond->event, 0);
|
||||
slock_unlock(lock);
|
||||
|
||||
WaitForSingleObject(cond->event, INFINITE);
|
||||
|
||||
slock_lock(lock);
|
||||
}
|
||||
|
||||
bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms)
|
||||
{
|
||||
WaitForSingleObject(cond->event, 0);
|
||||
slock_unlock(lock);
|
||||
|
||||
DWORD res = WaitForSingleObject(cond->event, timeout_ms);
|
||||
|
||||
slock_lock(lock);
|
||||
return res == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
void scond_signal(scond_t *cond)
|
||||
{
|
||||
SetEvent(cond->event);
|
||||
}
|
||||
|
||||
void scond_free(scond_t *cond)
|
||||
{
|
||||
CloseHandle(cond->event);
|
||||
free(cond);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
struct sthread
|
||||
{
|
||||
pthread_t id;
|
||||
};
|
||||
|
||||
static void *thread_wrap(void *data_)
|
||||
{
|
||||
struct thread_data *data = data_;
|
||||
data->func(data->userdata);
|
||||
free(data);
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
sthread_t *sthread_create(void (*thread_func)(void*), void *userdata)
|
||||
{
|
||||
sthread_t *thr = calloc(1, sizeof(*thr));
|
||||
if (!thr)
|
||||
return NULL;
|
||||
|
||||
struct thread_data *data = calloc(1, sizeof(*data));
|
||||
if (!data)
|
||||
{
|
||||
free(thr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data->func = thread_func;
|
||||
data->userdata = userdata;
|
||||
|
||||
if (pthread_create(&thr->id, NULL, thread_wrap, data) < 0)
|
||||
{
|
||||
free(data);
|
||||
free(thr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return thr;
|
||||
}
|
||||
|
||||
void sthread_join(sthread_t *thread)
|
||||
{
|
||||
pthread_join(thread->id, NULL);
|
||||
free(thread);
|
||||
}
|
||||
|
||||
struct slock
|
||||
{
|
||||
pthread_mutex_t lock;
|
||||
};
|
||||
|
||||
slock_t *slock_new(void)
|
||||
{
|
||||
slock_t *lock = calloc(1, sizeof(*lock));
|
||||
if (!lock)
|
||||
return NULL;
|
||||
|
||||
if (pthread_mutex_init(&lock->lock, NULL) < 0)
|
||||
{
|
||||
free(lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
void slock_free(slock_t *lock)
|
||||
{
|
||||
pthread_mutex_destroy(&lock->lock);
|
||||
free(lock);
|
||||
}
|
||||
|
||||
void slock_lock(slock_t *lock)
|
||||
{
|
||||
pthread_mutex_lock(&lock->lock);
|
||||
}
|
||||
|
||||
void slock_unlock(slock_t *lock)
|
||||
{
|
||||
pthread_mutex_unlock(&lock->lock);
|
||||
}
|
||||
|
||||
struct scond
|
||||
{
|
||||
pthread_cond_t cond;
|
||||
};
|
||||
|
||||
scond_t *scond_new(void)
|
||||
{
|
||||
scond_t *cond = calloc(1, sizeof(*cond));
|
||||
if (!cond)
|
||||
return NULL;
|
||||
|
||||
if (pthread_cond_init(&cond->cond, NULL) < 0)
|
||||
{
|
||||
free(cond);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return cond;
|
||||
}
|
||||
|
||||
void scond_free(scond_t *cond)
|
||||
{
|
||||
pthread_cond_destroy(&cond->cond);
|
||||
free(cond);
|
||||
}
|
||||
|
||||
void scond_wait(scond_t *cond, slock_t *lock)
|
||||
{
|
||||
pthread_cond_wait(&cond->cond, &lock->lock);
|
||||
}
|
||||
|
||||
bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms)
|
||||
{
|
||||
struct timespec now;
|
||||
|
||||
#ifdef __MACH__ // OSX doesn't have clock_gettime ... :(
|
||||
clock_serv_t cclock;
|
||||
mach_timespec_t mts;
|
||||
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
|
||||
clock_get_time(cclock, &mts);
|
||||
mach_port_deallocate(mach_task_self(), cclock);
|
||||
now.tv_sec = mts.tv_sec;
|
||||
now.tv_nsec = mts.tv_nsec;
|
||||
#else
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
#endif
|
||||
|
||||
now.tv_sec += timeout_ms / 1000;
|
||||
now.tv_nsec += timeout_ms * 1000000L;
|
||||
|
||||
now.tv_sec += now.tv_nsec / 1000000000L;
|
||||
now.tv_nsec = now.tv_nsec % 1000000000L;
|
||||
|
||||
int ret = pthread_cond_timedwait(&cond->cond, &lock->lock, &now);
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
void scond_signal(scond_t *cond)
|
||||
{
|
||||
pthread_cond_signal(&cond->cond);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES 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 Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES 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 SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef THREAD_H__
|
||||
#define THREAD_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Implements the bare minimum needed for SSNES. :)
|
||||
|
||||
typedef struct sthread sthread_t;
|
||||
|
||||
// Threading
|
||||
sthread_t *sthread_create(void (*thread_func)(void*), void *userdata);
|
||||
void sthread_join(sthread_t *thread);
|
||||
|
||||
// Mutexes
|
||||
typedef struct slock slock_t;
|
||||
|
||||
slock_t *slock_new(void);
|
||||
void slock_free(slock_t *lock);
|
||||
|
||||
void slock_lock(slock_t *lock);
|
||||
void slock_unlock(slock_t *lock);
|
||||
|
||||
// Condition variables.
|
||||
typedef struct scond scond_t;
|
||||
|
||||
scond_t *scond_new(void);
|
||||
void scond_free(scond_t *cond);
|
||||
|
||||
void scond_wait(scond_t *cond, slock_t *lock);
|
||||
bool scond_wait_timeout(scond_t *cond, slock_t *lock, unsigned timeout_ms);
|
||||
void scond_signal(scond_t *cond);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -92,6 +92,7 @@ static struct bind binds[] = {
|
||||
MISC_BIND("Rewind", rewind)
|
||||
MISC_BIND("Movie recording toggle", movie_record_toggle)
|
||||
MISC_BIND("Pause", pause_toggle)
|
||||
MISC_BIND("Frame advance", frame_advance)
|
||||
MISC_BIND("Reset", reset)
|
||||
MISC_BIND("Next shader", shader_next)
|
||||
MISC_BIND("Previous shader", shader_prev)
|
||||
@@ -235,7 +236,7 @@ static void parse_input(int argc, char *argv[])
|
||||
};
|
||||
|
||||
int option_index = 0;
|
||||
for(;;)
|
||||
for (;;)
|
||||
{
|
||||
int c = getopt_long(argc, argv, optstring, opts, &option_index);
|
||||
if (c == -1)
|
||||
|
||||
Reference in new issue
Block a user