mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
Compare commits
257
Commits
v0.5-final
...
v0.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
d2b06a92c5 | ||
|
|
3901934d97 | ||
|
|
a76b25aff9 | ||
|
|
343852af26 | ||
|
|
4395f124ca | ||
|
|
b16ff3024c | ||
|
|
bc23b421e1 | ||
|
|
d9cb85a8d5 | ||
|
|
7d1cc23d16 | ||
|
|
4496ed3c13 | ||
|
|
3f5b6a450e | ||
|
|
9e24474047 | ||
|
|
7c17ede1ef | ||
|
|
5d9805c3e5 | ||
|
|
b17617bed5 | ||
|
|
a405708f1c | ||
|
|
594e66704d | ||
|
|
50579aaeea | ||
|
|
38eb855b48 | ||
|
|
12cc85c8a5 | ||
|
|
5b12595c85 | ||
|
|
3753f0bfca | ||
|
|
14ae5e397a | ||
|
|
657b4ce8c7 | ||
|
|
777e6a5334 | ||
|
|
a2ec78320b | ||
|
|
1fb53627ac | ||
|
|
fc2a39cd58 | ||
|
|
7425e1f879 | ||
|
|
6a260a57dd | ||
|
|
dadd794ece | ||
|
|
706a3738b5 | ||
|
|
9aef33aabd | ||
|
|
ac4219bcde | ||
|
|
b337856f80 | ||
|
|
079a0100c0 | ||
|
|
014c3c90e0 | ||
|
|
4759e3411f | ||
|
|
4a05e50636 | ||
|
|
72c070f338 | ||
|
|
776291a137 | ||
|
|
f3cdb95b04 | ||
|
|
033860ceb9 | ||
|
|
ab601ee3dc | ||
|
|
783ab7acd5 | ||
|
|
19a46b1f8b | ||
|
|
508cfdbda7 | ||
|
|
d81b9ee0a7 | ||
|
|
36c6c21194 | ||
|
|
1631e940f6 | ||
|
|
2eeb1fa6d1 | ||
|
|
26ac00ad76 | ||
|
|
bafc4f596f | ||
|
|
eee89dddb1 | ||
|
|
089b4fbba0 | ||
|
|
80727cef5a | ||
|
|
346a9f2b4e | ||
|
|
363d8ca5b4 | ||
|
|
06a574225d | ||
|
|
fa777b8356 | ||
|
|
7cf01318cb | ||
|
|
7c538274b4 | ||
|
|
72fb694e97 | ||
|
|
59c92dddc3 | ||
|
|
3f60fdd106 | ||
|
|
d221c480e6 | ||
|
|
59d9ff0d29 | ||
|
|
d279ea79e8 | ||
|
|
77cf9423cc | ||
|
|
60b94bbe43 | ||
|
|
b6272f9653 | ||
|
|
25fce1a6c6 | ||
|
|
59229f99c5 | ||
|
|
db3e1ae053 | ||
|
|
ec1401e657 | ||
|
|
f85fd50fa1 | ||
|
|
c39b0ab09b | ||
|
|
4b5ed0fa41 | ||
|
|
75d1155a23 | ||
|
|
753d2057bb | ||
|
|
b06d814d9c | ||
|
|
eb085a8068 | ||
|
|
45904ccd73 | ||
|
|
3b0606bbd6 | ||
|
|
0f1a76f932 | ||
|
|
d3e0140f01 | ||
|
|
ea7dded915 | ||
|
|
f187f3db29 | ||
|
|
3099a79bac | ||
|
|
7cdea39c95 | ||
|
|
95f6a5116a | ||
|
|
da94877155 | ||
|
|
b1feed9d2b | ||
|
|
a3816bffa1 | ||
|
|
612270ec9b | ||
|
|
54bc0a8d96 | ||
|
|
f761811189 | ||
|
|
0e964c5cc2 | ||
|
|
c34a944fcf | ||
|
|
3feade6b6e | ||
|
|
f79eafe449 | ||
|
|
111b159373 | ||
|
|
d744167865 | ||
|
|
a2fea86c92 | ||
|
|
5385806e74 | ||
|
|
99e85684db | ||
|
|
45727bf9f5 | ||
|
|
2a0abb0618 | ||
|
|
192e3303a1 | ||
|
|
b6051add6a | ||
|
|
24d0cda5fd | ||
|
|
d0ba59f8b5 | ||
|
|
99a1784aaf | ||
|
|
b6ff98cf18 | ||
|
|
2dcccc7cda | ||
|
|
d03855c655 | ||
|
|
69227bc1ca | ||
|
|
851debf1e4 | ||
|
|
8c1db1c407 | ||
|
|
576894e0e8 | ||
|
|
631c3ca393 | ||
|
|
fd70a1c875 | ||
|
|
30cb2e4c07 | ||
|
|
7d87cc2d24 | ||
|
|
0342dcb169 | ||
|
|
f339560550 | ||
|
|
11b58d8c69 | ||
|
|
3717f7442c | ||
|
|
e02d9f41e8 | ||
|
|
f62120861a | ||
|
|
884e2881a0 | ||
|
|
a605628dff | ||
|
|
4d96fbba6a | ||
|
|
9bfeb88e24 | ||
|
|
712f463022 | ||
|
|
713e8624a0 | ||
|
|
5c8a549125 | ||
|
|
c3e7a35b62 | ||
|
|
5bc9ca216e | ||
|
|
838a85a719 | ||
|
|
b880039541 | ||
|
|
c646166fe1 | ||
|
|
44c52ca4f8 | ||
|
|
9c9eee5d01 | ||
|
|
7bbaa6ee31 | ||
|
|
f24aed9358 | ||
|
|
02344f9048 | ||
|
|
200d2b598b | ||
|
|
3213bb13ea | ||
|
|
53fd0b3751 | ||
|
|
9b6e6a1215 | ||
|
|
e5a9d78b70 | ||
|
|
f22e964cc0 | ||
|
|
9fb827d238 | ||
|
|
24f802023a | ||
|
|
ef2b755e17 | ||
|
|
7cc8c01a1e | ||
|
|
ca509c5b9f | ||
|
|
12a39e160b | ||
|
|
7c5f36be61 | ||
|
|
aec44b093e | ||
|
|
ee83c31183 | ||
|
|
f2ad94f1c3 | ||
|
|
3aed471d57 | ||
|
|
3ee19ec75f | ||
|
|
b0d1a98cd1 | ||
|
|
94ff67ee15 | ||
|
|
8d2a7e4593 | ||
|
|
4e7a003497 | ||
|
|
380384654c | ||
|
|
2144ef84fd | ||
|
|
83d913b6d2 | ||
|
|
2782c9da91 | ||
|
|
2f539cd326 | ||
|
|
f4af518fb4 | ||
|
|
eebe0ae5d0 | ||
|
|
f9c5c7c8a5 | ||
|
|
8bc3d3ac5c | ||
|
|
6a3386ad1a | ||
|
|
5dced40c98 | ||
|
|
3f58932c2b | ||
|
|
373b9dcc37 | ||
|
|
993bdfb75f | ||
|
|
70000607a2 | ||
|
|
1b64e4b902 |
No files matched your search
@@ -3,3 +3,6 @@ Hans-Kristian Arntzen - <[email protected]>
|
||||
|
||||
Devin J. Pohly - <[email protected]>
|
||||
- Joypad axis support
|
||||
|
||||
Chris Moeller - <[email protected]>
|
||||
- CoreAudio audio driver
|
||||
@@ -2,20 +2,27 @@ 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
|
||||
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o strl.o
|
||||
HEADERS = $(wildcard */*.h) $(wildcard *.h)
|
||||
|
||||
LIBS = -lm
|
||||
DEFINES = -DHAVE_CONFIG_H
|
||||
|
||||
ifneq ($(findstring Darwin,$(shell uname -a)),)
|
||||
ifneq ($(findstring Darwin,$(OS)),)
|
||||
OSX := 1
|
||||
LIBS += -framework AppKit
|
||||
else
|
||||
OSX := 0
|
||||
endif
|
||||
|
||||
BSD_LOCAL_INC =
|
||||
DYLIB_LIB = -ldl
|
||||
ifneq ($(findstring BSD,$(OS)),)
|
||||
BSD_LOCAL_INC = -I/usr/local/include
|
||||
DYLIB_LIB = -lc
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS)
|
||||
@@ -33,19 +40,29 @@ endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
LIBS += -lrsound
|
||||
LIBS += $(RSOUND_LIBS)
|
||||
DEFINES += $(RSOUND_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_OSS), 1)
|
||||
OBJ += audio/oss.o
|
||||
endif
|
||||
ifeq ($(HAVE_OSS_BSD), 1)
|
||||
OBJ += audio/oss.o
|
||||
endif
|
||||
ifeq ($(HAVE_OSS_LIB), 1)
|
||||
LIBS += -lossaudio
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_ALSA), 1)
|
||||
OBJ += audio/alsa.o
|
||||
LIBS += -lasound
|
||||
LIBS += $(ALSA_LIBS)
|
||||
DEFINES += $(ALSA_CFLAGS)
|
||||
endif
|
||||
ifeq ($(HAVE_ROAR), 1)
|
||||
OBJ += audio/roar.o
|
||||
LIBS += -lroar
|
||||
LIBS += $(ROAR_LIBS)
|
||||
DEFINES += $(ROAR_CFLAGS)
|
||||
endif
|
||||
ifeq ($(HAVE_AL), 1)
|
||||
OBJ += audio/openal.o
|
||||
@@ -66,9 +83,14 @@ ifeq ($(HAVE_PULSE), 1)
|
||||
DEFINES += $(PULSE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_COREAUDIO), 1)
|
||||
OBJ += audio/coreaudio.o
|
||||
LIBS += -framework CoreServices -framework CoreAudio -framework AudioUnit
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
DEFINES += $(SDL_CFLAGS)
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
DEFINES += $(SDL_CFLAGS) $(BSD_LOCAL_INC)
|
||||
LIBS += $(SDL_LIBS)
|
||||
ifeq ($(OSX),1)
|
||||
LIBS += -framework OpenGL
|
||||
@@ -77,13 +99,10 @@ else
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FBO), 1)
|
||||
DEFINES += -DHAVE_FBO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XVIDEO), 1)
|
||||
OBJ += gfx/xvideo.o input/x11_input.o
|
||||
LIBS += -lXv -lX11
|
||||
LIBS += $(XVIDEO_LIBS)
|
||||
DEFINES += $(XVIDEO_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CG), 1)
|
||||
@@ -92,14 +111,14 @@ ifeq ($(HAVE_CG), 1)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o gfx/image.o sha256.o cheats.o
|
||||
OBJ += gfx/shader_glsl.o gfx/image.o gfx/snes_state.o sha256.o cheats.o
|
||||
LIBS += $(XML_LIBS)
|
||||
DEFINES += $(XML_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
OBJ += gfx/ext.o audio/ext.o
|
||||
LIBS += -ldl
|
||||
LIBS += $(DYLIB_LIB)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FREETYPE), 1)
|
||||
@@ -108,19 +127,19 @@ ifeq ($(HAVE_FREETYPE), 1)
|
||||
DEFINES += $(FREETYPE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_IMLIB), 1)
|
||||
LIBS += $(IMLIB_LIBS)
|
||||
DEFINES += $(IMLIB_CFLAGS)
|
||||
ifeq ($(HAVE_SDL_IMAGE), 1)
|
||||
LIBS += $(SDL_IMAGE_LIBS)
|
||||
DEFINES += $(SDL_IMAGE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FFMPEG), 1)
|
||||
OBJ += record/ffemu.o
|
||||
LIBS += $(AVCODEC_LIBS) $(AVCORE_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS)
|
||||
DEFINES += $(AVCODEC_CFLAGS) $(AVCORE_CFLAGS) $(AVFORMAT_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS)
|
||||
LIBS += $(AVCODEC_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS)
|
||||
DEFINES += $(AVCODEC_CFLAGS) $(AVFORMAT_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYNAMIC), 1)
|
||||
LIBS += -ldl
|
||||
LIBS += $(DYLIB_LIB)
|
||||
else
|
||||
LIBS += $(libsnes)
|
||||
endif
|
||||
@@ -129,11 +148,17 @@ ifeq ($(HAVE_STRL), 1)
|
||||
DEFINES += -DHAVE_STRL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_PYTHON), 1)
|
||||
DEFINES += $(PYTHON_CFLAGS) -Wno-unused-parameter
|
||||
LIBS += $(PYTHON_LIBS)
|
||||
OBJ += gfx/py_state/py_state.o
|
||||
endif
|
||||
|
||||
ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
CFLAGS += -Wall -O3 -g -I.
|
||||
CFLAGS += -Wall -O3 -g -I. -pedantic
|
||||
ifneq ($(findstring icc,$(CC)),)
|
||||
CFLAGS += -std=c99
|
||||
else
|
||||
@@ -147,11 +172,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)
|
||||
@@ -181,6 +206,7 @@ clean:
|
||||
rm -f audio/*.o
|
||||
rm -f conf/*.o
|
||||
rm -f gfx/*.o
|
||||
rm -f gfx/py_state/*.o
|
||||
rm -f record/*.o
|
||||
rm -f input/*.o
|
||||
rm -f tools/*.o
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
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 bps.o ups.o strl.o screenshot.o audio/hermite.o
|
||||
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o strl.o
|
||||
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
|
||||
HAVE_DINPUT = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_DSOUND = 1
|
||||
HAVE_SDL = 1
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
|
||||
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 += -lSDL -lopengl32
|
||||
DEFINES += -ISDL -DHAVE_SDL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL_IMAGE), 1)
|
||||
LIBS += -lSDL_image
|
||||
DEFINES += -DHAVE_SDL_IMAGE
|
||||
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 audio/xaudio-c/xaudio-c.o
|
||||
DEFINES += -DHAVE_XAUDIO
|
||||
LIBS += -lole32
|
||||
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 += -Ilibxml2 -DHAVE_XML
|
||||
LIBS += -lxml2 -liconv
|
||||
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 += -lfreetype -lz
|
||||
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
|
||||
|
||||
ifeq ($(HAVE_FFMPEG), 1)
|
||||
LIBS += -lavformat -lavcodec -lavutil -lswscale -lws2_32 -lz
|
||||
DEFINES += -DHAVE_FFMPEG -Iffmpeg
|
||||
OBJ += record/ffemu.o
|
||||
endif
|
||||
|
||||
ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
CFLAGS += -Wall -O3 -std=gnu99 -I.
|
||||
CXXFLAGS += -Wall -O3 -pedantic -I.
|
||||
|
||||
all: $(TARGET) $(JTARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(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) -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 input/*.o
|
||||
rm -f $(TARGET)
|
||||
rm -f ssnes-joyconfig.exe
|
||||
rm -f tools/*.o
|
||||
|
||||
dist_x86: all
|
||||
zip -r ssnes-win32-0.8.zip $(TARGET) $(JTARGET) ssnes.cfg snes.dll SDL.dll
|
||||
|
||||
dist_x86_64: all
|
||||
zip -r ssnes-win64-0.8.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
|
||||
|
||||
libs_x86_64:
|
||||
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_x86 dist_x86_64 libs_x86 libs_x86_64
|
||||
-136
@@ -1,136 +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_SRC = 0
|
||||
HAVE_SDL = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_RSOUND = 1
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
HAVE_FBO = 1
|
||||
HAVE_CG = 0
|
||||
libsnes ?= -lsnes
|
||||
|
||||
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
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o audio/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_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
DEFINES += -DHAVE_RSOUND
|
||||
LIBS += -lrsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o gfx/image.o sha256.o cheats.o
|
||||
DEFINES += $(XML_CFLAGS) -DHAVE_XML
|
||||
LIBS += -lxml2
|
||||
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
|
||||
|
||||
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-win32-0.5.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll README.win32.txt $(JTARGET)
|
||||
|
||||
libs:
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-libs.zip --no-check-certificate
|
||||
unzip SSNES-win32-libs.zip
|
||||
|
||||
.PHONY: all install uninstall clean dist libs
|
||||
-137
@@ -1,137 +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_SRC = 0
|
||||
HAVE_SDL = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_RSOUND = 0
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
HAVE_FBO = 1
|
||||
HAVE_CG = 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 audio/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_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
DEFINES += -DHAVE_RSOUND
|
||||
LIBS += -lrsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o gfx/image.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
|
||||
|
||||
|
||||
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.5.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
-1
@@ -18,7 +18,7 @@
|
||||
|
||||
#include "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <asoundlib.h>
|
||||
#include "general.h"
|
||||
|
||||
#define TRY_ALSA(x) if ( x < 0 ) { \
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
* Copyright (C) 2011 - Chris Moeller
|
||||
*
|
||||
* 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 "driver.h"
|
||||
#include "general.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioUnit/AUComponent.h>
|
||||
|
||||
typedef struct coreaudio
|
||||
{
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
|
||||
ComponentInstance dev;
|
||||
bool dev_alive;
|
||||
|
||||
fifo_buffer_t *buffer;
|
||||
bool nonblock;
|
||||
} coreaudio_t;
|
||||
|
||||
static void coreaudio_free(void *data)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
if (dev->dev_alive)
|
||||
{
|
||||
AudioOutputUnitStop(dev->dev);
|
||||
CloseComponent(dev->dev);
|
||||
}
|
||||
|
||||
if (dev->buffer)
|
||||
fifo_free(dev->buffer);
|
||||
|
||||
pthread_mutex_destroy(&dev->lock);
|
||||
pthread_cond_destroy(&dev->cond);
|
||||
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static OSStatus audio_cb(void *userdata, AudioUnitRenderActionFlags *action_flags,
|
||||
const AudioTimeStamp *time_stamp, UInt32 bus_number,
|
||||
UInt32 number_frames, AudioBufferList *io_data)
|
||||
{
|
||||
coreaudio_t *dev = userdata;
|
||||
(void)time_stamp;
|
||||
(void)bus_number;
|
||||
(void)number_frames;
|
||||
|
||||
if (!io_data)
|
||||
return noErr;
|
||||
if (io_data->mNumberBuffers != 1)
|
||||
return noErr;
|
||||
|
||||
unsigned write_avail = io_data->mBuffers[0].mDataByteSize;
|
||||
void *outbuf = io_data->mBuffers[0].mData;
|
||||
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
if (fifo_read_avail(dev->buffer) < write_avail)
|
||||
{
|
||||
*action_flags = kAudioUnitRenderAction_OutputIsSilence;
|
||||
memset(outbuf, 0, write_avail); // Seems to be needed.
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
pthread_cond_signal(&dev->cond); // Technically possible to deadlock without.
|
||||
return noErr;
|
||||
}
|
||||
|
||||
fifo_read(dev->buffer, outbuf, write_avail);
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
pthread_cond_signal(&dev->cond);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static void* coreaudio_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
(void)device;
|
||||
|
||||
coreaudio_t *dev = calloc(1, sizeof(*dev));
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
CFRunLoopRef run_loop = NULL;
|
||||
AudioObjectPropertyAddress addr = {
|
||||
kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
pthread_mutex_init(&dev->lock, NULL);
|
||||
pthread_cond_init(&dev->cond, NULL);
|
||||
AudioObjectSetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL,
|
||||
sizeof(CFRunLoopRef), &run_loop);
|
||||
|
||||
ComponentDescription desc = {
|
||||
.componentType = kAudioUnitType_Output,
|
||||
.componentSubType = kAudioUnitSubType_HALOutput,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple,
|
||||
};
|
||||
|
||||
Component comp = FindNextComponent(NULL, &desc);
|
||||
if (comp == NULL)
|
||||
goto error;
|
||||
|
||||
OSStatus res = OpenAComponent(comp, &dev->dev);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
dev->dev_alive = true;
|
||||
|
||||
AudioStreamBasicDescription stream_desc = {
|
||||
.mSampleRate = rate,
|
||||
.mBitsPerChannel = sizeof(float) * CHAR_BIT,
|
||||
.mChannelsPerFrame = 2,
|
||||
.mBytesPerPacket = 2 * sizeof(float),
|
||||
.mBytesPerFrame = 2 * sizeof(float),
|
||||
.mFramesPerPacket = 1,
|
||||
.mFormatID = kAudioFormatLinearPCM,
|
||||
.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | (is_little_endian() ? 0 : kAudioFormatFlagIsBigEndian),
|
||||
};
|
||||
|
||||
res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &stream_desc, sizeof(stream_desc));
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
AudioStreamBasicDescription real_desc;
|
||||
UInt32 i_size = sizeof(real_desc);
|
||||
res = AudioUnitGetProperty(dev->dev, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &real_desc, &i_size);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
SSNES_LOG("[CoreAudio]: Using output sample rate of %.1f Hz\n", (float)real_desc.mSampleRate);
|
||||
g_settings.audio.out_rate = real_desc.mSampleRate;
|
||||
|
||||
if (real_desc.mChannelsPerFrame != stream_desc.mChannelsPerFrame)
|
||||
goto error;
|
||||
if (real_desc.mBitsPerChannel != stream_desc.mBitsPerChannel)
|
||||
goto error;
|
||||
if (real_desc.mFormatFlags != stream_desc.mFormatFlags)
|
||||
goto error;
|
||||
if (real_desc.mFormatID != stream_desc.mFormatID)
|
||||
goto error;
|
||||
|
||||
AudioChannelLayout layout = {
|
||||
.mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap,
|
||||
.mChannelBitmap = (1 << 2) - 1,
|
||||
};
|
||||
|
||||
res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_AudioChannelLayout,
|
||||
kAudioUnitScope_Input, 0, &layout, sizeof(layout));
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
AURenderCallbackStruct cb = {
|
||||
.inputProc = audio_cb,
|
||||
.inputProcRefCon = dev,
|
||||
};
|
||||
|
||||
res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &cb, sizeof(cb));
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
res = AudioUnitInitialize(dev->dev);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
size_t fifo_size = (latency * g_settings.audio.out_rate) / 1000;
|
||||
fifo_size *= 2 * sizeof(float);
|
||||
|
||||
dev->buffer = fifo_new(fifo_size);
|
||||
if (!dev->buffer)
|
||||
goto error;
|
||||
|
||||
SSNES_LOG("[CoreAudio]: Using buffer size of %u bytes: (latency = %u ms)\n", (unsigned)fifo_size, latency);
|
||||
|
||||
res = AudioOutputUnitStart(dev->dev);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
return dev;
|
||||
|
||||
error:
|
||||
SSNES_ERR("[CoreAudio]: Failed to initialize driver ...\n");
|
||||
coreaudio_free(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t coreaudio_write(void* data, const void* buf_, size_t size)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
|
||||
const uint8_t *buf = buf_;
|
||||
size_t written = 0;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
|
||||
size_t write_avail = fifo_write_avail(dev->buffer);
|
||||
if (write_avail > size)
|
||||
write_avail = size;
|
||||
|
||||
fifo_write(dev->buffer, buf, write_avail);
|
||||
buf += write_avail;
|
||||
written += write_avail;
|
||||
size -= write_avail;
|
||||
|
||||
if (dev->nonblock)
|
||||
{
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
break;
|
||||
}
|
||||
|
||||
if (write_avail == 0)
|
||||
pthread_cond_wait(&dev->cond, &dev->lock);
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
static bool coreaudio_stop(void *data)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
return AudioOutputUnitStop(dev->dev) == noErr;
|
||||
}
|
||||
|
||||
static void coreaudio_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
dev->nonblock = state;
|
||||
}
|
||||
|
||||
static bool coreaudio_start(void *data)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
return AudioOutputUnitStart(dev->dev) == noErr;
|
||||
}
|
||||
|
||||
static bool coreaudio_use_float(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
const audio_driver_t audio_coreaudio = {
|
||||
.init = coreaudio_init,
|
||||
.write = coreaudio_write,
|
||||
.stop = coreaudio_stop,
|
||||
.start = coreaudio_start,
|
||||
.set_nonblock_state = coreaudio_set_nonblock_state,
|
||||
.free = coreaudio_free,
|
||||
.use_float = coreaudio_use_float,
|
||||
.ident = "coreaudio"
|
||||
};
|
||||
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
/* 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 "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dsound.h>
|
||||
#include "fifo_buffer.h"
|
||||
#include "general.h"
|
||||
|
||||
typedef struct dsound
|
||||
{
|
||||
LPDIRECTSOUND ds;
|
||||
LPDIRECTSOUNDBUFFER dsb;
|
||||
HANDLE event;
|
||||
bool nonblock;
|
||||
|
||||
fifo_buffer_t *buffer;
|
||||
CRITICAL_SECTION crit;
|
||||
|
||||
volatile bool thread_alive;
|
||||
HANDLE thread;
|
||||
unsigned buffer_size;
|
||||
} dsound_t;
|
||||
|
||||
static inline unsigned write_avail(unsigned read_ptr, unsigned write_ptr, unsigned buffer_size)
|
||||
{
|
||||
return (read_ptr + buffer_size - write_ptr) % buffer_size;
|
||||
}
|
||||
|
||||
static inline void get_positions(dsound_t *ds, DWORD *read_ptr, DWORD *write_ptr)
|
||||
{
|
||||
IDirectSoundBuffer_GetCurrentPosition(ds->dsb, read_ptr, write_ptr);
|
||||
}
|
||||
|
||||
#define CHUNK_SIZE 256
|
||||
|
||||
struct audio_lock
|
||||
{
|
||||
void *chunk1;
|
||||
DWORD size1;
|
||||
void *chunk2;
|
||||
DWORD size2;
|
||||
};
|
||||
|
||||
static inline bool grab_region(dsound_t *ds, DWORD write_ptr, struct audio_lock *region)
|
||||
{
|
||||
HRESULT res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE, ®ion->chunk1, ®ion->size1, ®ion->chunk2, ®ion->size2, 0);
|
||||
if (res == DSERR_BUFFERLOST)
|
||||
{
|
||||
res = IDirectSoundBuffer_Restore(ds->dsb);
|
||||
if (res != DS_OK)
|
||||
return false;
|
||||
|
||||
res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE, ®ion->chunk1, ®ion->size1, ®ion->chunk2, ®ion->size2, 0);
|
||||
if (res != DS_OK)
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *err;
|
||||
switch (res)
|
||||
{
|
||||
case DSERR_BUFFERLOST:
|
||||
err = "DSERR_BUFFERLOST";
|
||||
break;
|
||||
case DSERR_INVALIDCALL:
|
||||
err = "DSERR_INVALIDCALL";
|
||||
break;
|
||||
case DSERR_INVALIDPARAM:
|
||||
err = "DSERR_INVALIDPARAM";
|
||||
break;
|
||||
case DSERR_PRIOLEVELNEEDED:
|
||||
err = "DSERR_PRIOLEVELNEEDED";
|
||||
break;
|
||||
|
||||
default:
|
||||
err = NULL;
|
||||
}
|
||||
|
||||
if (err)
|
||||
{
|
||||
SSNES_WARN("[DirectSound error]: %s\n", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void release_region(dsound_t *ds, const struct audio_lock *region)
|
||||
{
|
||||
IDirectSoundBuffer_Unlock(ds->dsb, region->chunk1, region->size1, region->chunk2, region->size2);
|
||||
}
|
||||
|
||||
static DWORD CALLBACK dsound_thread(PVOID data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||
|
||||
DWORD write_ptr;
|
||||
get_positions(ds, NULL, &write_ptr);
|
||||
write_ptr = (write_ptr + ds->buffer_size / 2) % ds->buffer_size;
|
||||
|
||||
while (ds->thread_alive)
|
||||
{
|
||||
DWORD read_ptr;
|
||||
get_positions(ds, &read_ptr, NULL);
|
||||
|
||||
DWORD avail = write_avail(read_ptr, write_ptr, ds->buffer_size);
|
||||
|
||||
EnterCriticalSection(&ds->crit);
|
||||
DWORD fifo_avail = fifo_read_avail(ds->buffer);
|
||||
LeaveCriticalSection(&ds->crit);
|
||||
|
||||
// No space to write, or we don't have data in our fifo, but we can wait some time before it underruns ...
|
||||
if (avail < CHUNK_SIZE || ((fifo_avail < CHUNK_SIZE) && (avail < ds->buffer_size / 2)))
|
||||
{
|
||||
Sleep(1);
|
||||
// We could opt for using the notification interface,
|
||||
// but it is not guaranteed to work, so use high priority sleeping patterns. :(
|
||||
}
|
||||
else if (fifo_avail < CHUNK_SIZE) // Got space to write, but nothing in FIFO (underrun), fill block with silence.
|
||||
{
|
||||
struct audio_lock region;
|
||||
if (!grab_region(ds, write_ptr, ®ion))
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
SetEvent(ds->event);
|
||||
break;
|
||||
}
|
||||
|
||||
memset(region.chunk1, 0, region.size1);
|
||||
memset(region.chunk2, 0, region.size2);
|
||||
|
||||
release_region(ds, ®ion);
|
||||
write_ptr = (write_ptr + region.size1 + region.size2) % ds->buffer_size;
|
||||
}
|
||||
else // All is good. Pull from it and notify FIFO :D
|
||||
{
|
||||
struct audio_lock region;
|
||||
if (!grab_region(ds, write_ptr, ®ion))
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
SetEvent(ds->event);
|
||||
break;
|
||||
}
|
||||
|
||||
EnterCriticalSection(&ds->crit);
|
||||
if (region.chunk1)
|
||||
fifo_read(ds->buffer, region.chunk1, region.size1);
|
||||
if (region.chunk2)
|
||||
fifo_read(ds->buffer, region.chunk2, region.size2);
|
||||
LeaveCriticalSection(&ds->crit);
|
||||
|
||||
release_region(ds, ®ion);
|
||||
write_ptr = (write_ptr + region.size1 + region.size2) % ds->buffer_size;
|
||||
|
||||
SetEvent(ds->event);
|
||||
}
|
||||
}
|
||||
|
||||
ExitThread(0);
|
||||
}
|
||||
|
||||
static void dsound_stop_thread(dsound_t *ds)
|
||||
{
|
||||
if (ds->thread)
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
WaitForSingleObject(ds->thread, INFINITE);
|
||||
CloseHandle(ds->thread);
|
||||
ds->thread = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool dsound_start_thread(dsound_t *ds)
|
||||
{
|
||||
if (!ds->thread)
|
||||
{
|
||||
ds->thread_alive = true;
|
||||
ds->thread = CreateThread(NULL, 0, dsound_thread, ds, 0, NULL);
|
||||
if (ds->thread == NULL)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dsound_clear_buffer(dsound_t *ds)
|
||||
{
|
||||
IDirectSoundBuffer_SetCurrentPosition(ds->dsb, 0);
|
||||
void *ptr;
|
||||
DWORD size;
|
||||
|
||||
if (IDirectSoundBuffer_Lock(ds->dsb, 0, 0, &ptr, &size, NULL, NULL, DSBLOCK_ENTIREBUFFER) == DS_OK)
|
||||
{
|
||||
memset(ptr, 0, size);
|
||||
IDirectSoundBuffer_Unlock(ds->dsb, ptr, size, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsound_free(void *data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
if (ds)
|
||||
{
|
||||
if (ds->thread)
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
WaitForSingleObject(ds->thread, INFINITE);
|
||||
CloseHandle(ds->thread);
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&ds->crit);
|
||||
|
||||
if (ds->dsb)
|
||||
{
|
||||
IDirectSoundBuffer_Stop(ds->dsb);
|
||||
IDirectSoundBuffer_Release(ds->dsb);
|
||||
}
|
||||
|
||||
if (ds)
|
||||
IDirectSound_Release(ds->ds);
|
||||
|
||||
if (ds->event)
|
||||
CloseHandle(ds->event);
|
||||
|
||||
if (ds->buffer)
|
||||
fifo_free(ds->buffer);
|
||||
|
||||
free(ds);
|
||||
}
|
||||
}
|
||||
|
||||
static void* dsound_init(const char *device, unsigned rate, unsigned latency)
|
||||
{
|
||||
dsound_t *ds = calloc(1, sizeof(*ds));
|
||||
if (!ds)
|
||||
goto error;
|
||||
|
||||
InitializeCriticalSection(&ds->crit);
|
||||
|
||||
if (DirectSoundCreate(NULL, &ds->ds, NULL) != DS_OK)
|
||||
goto error;
|
||||
|
||||
if (IDirectSound_SetCooperativeLevel(ds->ds, GetDesktopWindow(), DSSCL_PRIORITY) != DS_OK)
|
||||
goto error;
|
||||
|
||||
WAVEFORMATEX wfx = {
|
||||
.wFormatTag = WAVE_FORMAT_PCM,
|
||||
.nChannels = 2,
|
||||
.nSamplesPerSec = rate,
|
||||
.wBitsPerSample = 16,
|
||||
.nBlockAlign = 2 * sizeof(int16_t),
|
||||
.nAvgBytesPerSec = rate * 2 * sizeof(int16_t),
|
||||
};
|
||||
|
||||
ds->buffer_size = (latency * wfx.nAvgBytesPerSec) / 1000;
|
||||
ds->buffer_size /= CHUNK_SIZE;
|
||||
ds->buffer_size *= CHUNK_SIZE;
|
||||
if (ds->buffer_size < 4 * CHUNK_SIZE)
|
||||
ds->buffer_size = 4 * CHUNK_SIZE;
|
||||
|
||||
SSNES_LOG("[DirectSound]: Setting buffer size of %u bytes\n", ds->buffer_size);
|
||||
SSNES_LOG("[DirectSound]: Latency = %u ms\n", (unsigned)((1000 * ds->buffer_size) / wfx.nAvgBytesPerSec));
|
||||
|
||||
DSBUFFERDESC bufdesc = {
|
||||
.dwSize = sizeof(DSBUFFERDESC),
|
||||
.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS,
|
||||
.dwBufferBytes = ds->buffer_size,
|
||||
.lpwfxFormat = &wfx,
|
||||
};
|
||||
|
||||
ds->event = CreateEvent(NULL, false, false, NULL);
|
||||
if (!ds->event)
|
||||
goto error;
|
||||
|
||||
ds->buffer = fifo_new(4 * 1024);
|
||||
if (!ds->buffer)
|
||||
goto error;
|
||||
|
||||
if (IDirectSound_CreateSoundBuffer(ds->ds, &bufdesc, &ds->dsb, 0) != DS_OK)
|
||||
goto error;
|
||||
|
||||
IDirectSoundBuffer_SetCurrentPosition(ds->dsb, 0);
|
||||
|
||||
dsound_clear_buffer(ds);
|
||||
|
||||
if (IDirectSoundBuffer_Play(ds->dsb, 0, 0, DSBPLAY_LOOPING) != DS_OK)
|
||||
goto error;
|
||||
|
||||
if (!dsound_start_thread(ds))
|
||||
goto error;
|
||||
|
||||
return ds;
|
||||
|
||||
error:
|
||||
SSNES_ERR("[DirectSound] Error occured in init!\n");
|
||||
dsound_free(ds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool dsound_stop(void *data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
dsound_stop_thread(ds);
|
||||
return IDirectSoundBuffer_Stop(ds->dsb) == DS_OK;
|
||||
}
|
||||
|
||||
static bool dsound_start(void *data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
dsound_clear_buffer(ds);
|
||||
|
||||
if (!dsound_start_thread(ds))
|
||||
return false;
|
||||
|
||||
return IDirectSoundBuffer_Play(ds->dsb, 0, 0, DSBPLAY_LOOPING) == DS_OK;
|
||||
}
|
||||
|
||||
static void dsound_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
ds->nonblock = state;
|
||||
}
|
||||
|
||||
static ssize_t dsound_write(void *data, const void *buf_, size_t size)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
const uint8_t *buf = buf_;
|
||||
|
||||
if (!ds->thread_alive)
|
||||
return -1;
|
||||
|
||||
size_t written = 0;
|
||||
while (size > 0)
|
||||
{
|
||||
EnterCriticalSection(&ds->crit);
|
||||
size_t avail = fifo_write_avail(ds->buffer);
|
||||
if (avail > size)
|
||||
avail = size;
|
||||
|
||||
fifo_write(ds->buffer, buf, avail);
|
||||
LeaveCriticalSection(&ds->crit);
|
||||
|
||||
buf += avail;
|
||||
size -= avail;
|
||||
written += avail;
|
||||
|
||||
if (ds->nonblock || !ds->thread_alive)
|
||||
break;
|
||||
|
||||
if (avail == 0)
|
||||
WaitForSingleObject(ds->event, INFINITE);
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
const audio_driver_t audio_dsound = {
|
||||
.init = dsound_init,
|
||||
.write = dsound_write,
|
||||
.stop = dsound_stop,
|
||||
.start = dsound_start,
|
||||
.set_nonblock_state = dsound_set_nonblock_state,
|
||||
.free = dsound_free,
|
||||
.ident = "dsound"
|
||||
};
|
||||
|
||||
+8
-2
@@ -64,7 +64,7 @@ static void* audio_ext_init(const char *device, unsigned rate, unsigned latency)
|
||||
goto error;
|
||||
}
|
||||
|
||||
const ssnes_audio_driver_t* (*plugin_load)(void) = dylib_proc(ext->lib, "ssnes_audio_driver_init");
|
||||
const ssnes_audio_driver_t* (*plugin_load)(void) = (const ssnes_audio_driver_t* (*)(void))dylib_proc(ext->lib, "ssnes_audio_driver_init");
|
||||
|
||||
if (!plugin_load)
|
||||
{
|
||||
@@ -83,7 +83,7 @@ static void* audio_ext_init(const char *device, unsigned rate, unsigned latency)
|
||||
|
||||
if (ext->driver->api_version != SSNES_AUDIO_API_VERSION)
|
||||
{
|
||||
SSNES_ERR("API mismatch in external video plugin. SSNES: %d, Plugin: %d ...\n", SSNES_AUDIO_API_VERSION, ext->driver->api_version);
|
||||
SSNES_ERR("API mismatch in external audio plugin. SSNES: %d, Plugin: %d ...\n", SSNES_AUDIO_API_VERSION, ext->driver->api_version);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,12 @@ static void* audio_ext_init(const char *device, unsigned rate, unsigned latency)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ext->driver->sample_rate)
|
||||
g_settings.audio.out_rate = ext->driver->sample_rate(ext->handle);
|
||||
|
||||
if (!g_settings.audio.sync)
|
||||
ext->driver->set_nonblock_state(ext->handle, SSNES_TRUE);
|
||||
|
||||
return ext;
|
||||
|
||||
error:
|
||||
|
||||
+2
-2
@@ -4,10 +4,10 @@ OBJ = ssnes_iir_filter.o
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(CC) -o $@ -shared $(OBJ)
|
||||
$(CC) -o $@ -shared $(OBJ) -lm
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< -std=c99 -fPIC -O3 -march=native $(CFLAGS)
|
||||
$(CC) -c -o $@ $< -g -std=c99 -fPIC -O3 -march=native $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
TARGET = iir_filter
|
||||
OBJ = ssnes_iir_filter.o
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(CC) -o $@ $(OBJ) -lm
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< -g -std=c99 -O3 -march=native $(CFLAGS) -DTEST -DLOG
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
rm -f $(OBJ)
|
||||
|
||||
.PHONY: clean
|
||||
@@ -38,7 +38,7 @@ extern "C" {
|
||||
#define SSNES_ERROR 0
|
||||
#endif
|
||||
|
||||
#define SSNES_AUDIO_API_VERSION 1
|
||||
#define SSNES_AUDIO_API_VERSION 2
|
||||
|
||||
typedef struct ssnes_audio_driver_info
|
||||
{
|
||||
@@ -103,6 +103,13 @@ typedef struct ssnes_audio_driver
|
||||
// [-1.0, 1.0].
|
||||
int (*use_float)(void *data);
|
||||
|
||||
// The driver might be forced to use a certain output frequency
|
||||
// (i.e. Jack), and thus to avoid double resampling, the driver
|
||||
// can request SSNES to resample to a different sample rate.
|
||||
// This function can be set to NULL if the driver does not
|
||||
// desire to override the sample rate.
|
||||
unsigned (*sample_rate)(void *data);
|
||||
|
||||
// Human readable identification string for the driver.
|
||||
const char *ident;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ extern "C" {
|
||||
#define SSNES_TRUE 1
|
||||
#endif
|
||||
|
||||
#define SSNES_DSP_API_VERSION 2
|
||||
#define SSNES_DSP_API_VERSION 3
|
||||
|
||||
typedef struct ssnes_dsp_info
|
||||
{
|
||||
@@ -94,7 +94,7 @@ typedef struct ssnes_dsp_plugin
|
||||
void (*process)(void *data, ssnes_dsp_output_t *output,
|
||||
const ssnes_dsp_input_t *input);
|
||||
|
||||
// Frees the handle.
|
||||
// Frees the handle.
|
||||
void (*free)(void *data);
|
||||
|
||||
// API version used to compile the plugin.
|
||||
@@ -110,6 +110,11 @@ typedef struct ssnes_dsp_plugin
|
||||
|
||||
// Human readable identification string.
|
||||
const char *ident;
|
||||
|
||||
// Called every frame, allows creating a GUI main loop in the main thread.
|
||||
// GUI events can be processed here in a non-blocking fashion.
|
||||
// Can be set to NULL to ignore it.
|
||||
void (*events)(void *data);
|
||||
} ssnes_dsp_plugin_t;
|
||||
|
||||
// Called by SSNES at startup to get the callback struct.
|
||||
|
||||
+181
-16
@@ -3,8 +3,10 @@
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <complex.h>
|
||||
|
||||
// Simple IIR filter implementation, optimized for SSE3.
|
||||
// Simple IIR/EQ filter implementation, optimized for SSE3.
|
||||
|
||||
#ifdef __SSE3__ // Build with -march=native or -msse3 to let this be detected! D:
|
||||
#define USE_SSE3
|
||||
@@ -22,20 +24,22 @@
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
// Log filter coeffs for debugging
|
||||
//#define LOG
|
||||
|
||||
#define min(x, y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265
|
||||
#endif
|
||||
|
||||
// Taps needs to be power-of-two.
|
||||
#define TAPS (128)
|
||||
#define TAPS (2048)
|
||||
|
||||
// FIR coefficients, up to TAPS taps.
|
||||
// Basic passthrough.
|
||||
static const float fir_coeff[] = {
|
||||
1.0
|
||||
};
|
||||
|
||||
// IIR coefficients, up to TAPS taps.
|
||||
static const float iir_coeff[] = {
|
||||
};
|
||||
static const float lp_freq = 5000.0;
|
||||
static const float hp_freq = 500.0;
|
||||
static const float bp_freq = 300.0;
|
||||
static const unsigned bp_q = 500;
|
||||
|
||||
#ifdef USE_SSE3
|
||||
typedef union
|
||||
@@ -58,6 +62,151 @@ typedef struct
|
||||
unsigned buf_ptr;
|
||||
} dsp_state;
|
||||
|
||||
// Convolve polynoms.
|
||||
static void conv(float * restrict a, const float * restrict b, unsigned a_size, unsigned b_size)
|
||||
{
|
||||
float output[a_size + b_size - 1];
|
||||
|
||||
// Lead-in
|
||||
for (int i = 0; i < b_size - 1; i++)
|
||||
{
|
||||
output[i] = 0.0;
|
||||
for (int j = 0; j < b_size; j++)
|
||||
{
|
||||
if ((i - j) >= 0)
|
||||
output[i] += a[i - j] * b[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Mid-section
|
||||
for (unsigned i = b_size - 1; i < a_size; i++)
|
||||
{
|
||||
output[i] = 0.0;
|
||||
for (unsigned j = 0; j < b_size; j++)
|
||||
output[i] += a[i - j] * b[j];
|
||||
}
|
||||
|
||||
// Lead-out
|
||||
for (unsigned i = a_size; i < a_size + b_size - 1; i++)
|
||||
{
|
||||
output[i] = 0.0;
|
||||
for (unsigned j = 0; j < b_size; j++)
|
||||
{
|
||||
if ((i - j) < a_size)
|
||||
output[i] += a[i - j] * b[j];
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(a, output, sizeof(output));
|
||||
}
|
||||
|
||||
#ifdef LOG
|
||||
static void print_coeff(const char *key, const float *coeff, unsigned elem)
|
||||
{
|
||||
for (unsigned i = 0; i < elem; i++)
|
||||
fprintf(stderr, "%4s [%3u] = %9.5f\n", key, i, coeff[i]);
|
||||
}
|
||||
|
||||
static void print_filter_coeffs(const float *fir, const float *iir, unsigned elem)
|
||||
{
|
||||
print_coeff("FIR", fir, elem);
|
||||
print_coeff("IIR", iir, elem);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Simulate plain 1-pole RC circuit.
|
||||
// dVc/dt = (Vin - Vc) / RC =>
|
||||
// y[n] = y[n - 1] * (1 - Ts/RC) + x[n] * Ts/RC
|
||||
// RCw = 1 => RC = 1/(2*pi*fc)
|
||||
static void generate_low_pass(float fir_out[1], float iir_out[2], float freq, float input_rate)
|
||||
{
|
||||
float ts = 1.0 / input_rate;
|
||||
float t = 1.0 / (2 * M_PI * freq);
|
||||
fir_out[0] = ts / t;
|
||||
iir_out[0] = 1.0;
|
||||
iir_out[1] = -(1.0 - ts / t);
|
||||
|
||||
#ifdef LOG
|
||||
print_coeff("FLOW", fir_out, 1);
|
||||
print_coeff("ILOW", iir_out, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
// 1-pole RC circuit (Vo taken over R).
|
||||
static void generate_high_pass(float fir_out[2], float iir_out[2], float freq, float input_rate)
|
||||
{
|
||||
float ts = 1.0 / input_rate;
|
||||
float t = 1.0 / (2 * M_PI * freq);
|
||||
fir_out[0] = 1.0;
|
||||
fir_out[1] = -1.0;
|
||||
|
||||
iir_out[0] = 1.0;
|
||||
iir_out[1] = -(1.0 - ts / t);
|
||||
|
||||
#ifdef LOG
|
||||
print_coeff("FHI", fir_out, 2);
|
||||
print_coeff("IHI", iir_out, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void generate_band_pass(float *fir_out, unsigned num_fir, float freq, float input_rate)
|
||||
{
|
||||
float w_norm = 2.0 * M_PI * freq / input_rate;
|
||||
for (unsigned i = 0; i < num_fir; i++)
|
||||
fir_out[i] = cosf(i * w_norm);
|
||||
|
||||
complex float amp = 0.0;
|
||||
for (unsigned i = 0; i < num_fir; i++)
|
||||
amp += cosf(i * w_norm) * cexp(-I * w_norm * i);
|
||||
|
||||
float abs = cabs(amp);
|
||||
for (unsigned i = 0; i < num_fir; i++)
|
||||
fir_out[i] /= abs;
|
||||
}
|
||||
|
||||
// Generic low-pass. Differential equation.
|
||||
static void generate_filter_coeffs(float * restrict fir_out, float * restrict iir_out, float input_rate)
|
||||
{
|
||||
fir_out[0] = 1.0;
|
||||
iir_out[0] = 1.0;
|
||||
|
||||
unsigned fir_length = 1;
|
||||
unsigned iir_length = 1;
|
||||
|
||||
/*
|
||||
// Low pass
|
||||
float lp_fir[1];
|
||||
float lp_iir[2];
|
||||
generate_low_pass(lp_fir, lp_iir, lp_freq, input_rate);
|
||||
conv(fir_out, lp_fir, fir_length, 1);
|
||||
conv(iir_out, lp_iir, iir_length, 2);
|
||||
fir_length += 0;
|
||||
iir_length += 1;
|
||||
|
||||
// High pass
|
||||
float hp_fir[2];
|
||||
float hp_iir[2];
|
||||
generate_high_pass(hp_fir, hp_iir, hp_freq, input_rate);
|
||||
conv(fir_out, hp_fir, fir_length, 2);
|
||||
conv(iir_out, hp_iir, iir_length, 2);
|
||||
fir_length += 1;
|
||||
iir_length += 1;
|
||||
*/
|
||||
|
||||
// Band pass
|
||||
float bp_fir[bp_q];
|
||||
generate_band_pass(bp_fir, bp_q, bp_freq, input_rate);
|
||||
conv(fir_out, bp_fir, fir_length, bp_q);
|
||||
fir_length += bp_q - 1;
|
||||
iir_length += 0;
|
||||
|
||||
// Adjust from Z rational polynomial model to discrete domain.
|
||||
// We don't care about first param as it's implicitly 1.
|
||||
for (unsigned i = 0; i < TAPS; i++)
|
||||
iir_out[i] = -1.0 * iir_out[i + 1];
|
||||
}
|
||||
|
||||
|
||||
static void* dsp_init(const ssnes_dsp_info_t *info)
|
||||
{
|
||||
(void)info;
|
||||
@@ -65,11 +214,24 @@ static void* dsp_init(const ssnes_dsp_info_t *info)
|
||||
if (!state)
|
||||
return NULL;
|
||||
|
||||
memcpy(&state->fir_coeff, fir_coeff, min(sizeof(fir_coeff), TAPS * sizeof(float)));
|
||||
memcpy(&state->iir_coeff, iir_coeff, min(sizeof(iir_coeff), TAPS * sizeof(float)));
|
||||
fprintf(stderr, "Input rate = %.2f\n", (float)info->input_rate);
|
||||
|
||||
#ifdef USE_SSE3
|
||||
memcpy(state->fir_coeff.f + TAPS, fir_coeff, min(sizeof(fir_coeff), 4 * sizeof(float)));
|
||||
memcpy(state->iir_coeff.f + TAPS, iir_coeff, min(sizeof(iir_coeff), 4 * sizeof(float)));
|
||||
generate_filter_coeffs(state->fir_coeff.f, state->iir_coeff.f, info->input_rate);
|
||||
#ifdef LOG
|
||||
print_filter_coeffs(state->fir_coeff.f, state->iir_coeff.f, 3);
|
||||
#endif
|
||||
#else
|
||||
generate_filter_coeffs(state->fir_coeff, state->iir_coeff, info->input_rate);
|
||||
#ifdef LOG
|
||||
print_filter_coeffs(state->fir_coeff, state->iir_coeff, 3);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef USE_SSE3
|
||||
memcpy(state->fir_coeff.f + TAPS, state->fir_coeff.f, 4 * sizeof(float));
|
||||
memcpy(state->iir_coeff.f + TAPS, state->iir_coeff.f, 4 * sizeof(float));
|
||||
#endif
|
||||
|
||||
return state;
|
||||
@@ -216,7 +378,10 @@ int main(void)
|
||||
const ssnes_dsp_plugin_t *plug = ssnes_dsp_plugin_init();
|
||||
assert(plug);
|
||||
|
||||
ssnes_dsp_info_t info;
|
||||
ssnes_dsp_info_t info = {
|
||||
.input_rate = 44100,
|
||||
.output_rate = 44100
|
||||
};
|
||||
void *handle = plug->init(&info); // Info isn't used.
|
||||
assert(handle);
|
||||
|
||||
|
||||
+17
-18
@@ -127,9 +127,12 @@ static size_t find_buffersize(jack_t *jd, int latency)
|
||||
static void* __jack_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
jack_t *jd = calloc(1, sizeof(jack_t));
|
||||
if ( jd == NULL )
|
||||
if (!jd)
|
||||
return NULL;
|
||||
|
||||
pthread_cond_init(&jd->cond, NULL);
|
||||
pthread_mutex_init(&jd->cond_lock, NULL);
|
||||
|
||||
const char **jports = NULL;
|
||||
|
||||
jd->client = jack_client_open("SSNES", JackNullOption, NULL);
|
||||
@@ -148,7 +151,6 @@ static void* __jack_init(const char* device, unsigned rate, unsigned latency)
|
||||
SSNES_ERR("Failed to register ports.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
char *dest_ports[2];
|
||||
jports = jack_get_ports(jd->client, NULL, NULL, JackPortIsPhysical | JackPortIsInput);
|
||||
@@ -158,6 +160,18 @@ static void* __jack_init(const char* device, unsigned rate, unsigned latency)
|
||||
goto error;
|
||||
}
|
||||
|
||||
size_t bufsize = find_buffersize(jd, latency);
|
||||
SSNES_LOG("JACK: Internal buffer size: %d frames.\n", (int)(bufsize / sizeof(jack_default_audio_sample_t)));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
jd->buffer[i] = jack_ringbuffer_create(bufsize);
|
||||
if (jd->buffer[i] == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to create buffers.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
int parsed = parse_ports(dest_ports, jports);
|
||||
|
||||
if (jack_activate(jd->client) < 0)
|
||||
@@ -178,22 +192,7 @@ static void* __jack_init(const char* device, unsigned rate, unsigned latency)
|
||||
for (int i = 0; i < parsed; i++)
|
||||
free(dest_ports[i]);
|
||||
|
||||
size_t bufsize = find_buffersize(jd, latency);
|
||||
|
||||
SSNES_LOG("JACK: Internal buffer size: %d frames.\n", (int)(bufsize / sizeof(jack_default_audio_sample_t)));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
jd->buffer[i] = jack_ringbuffer_create(bufsize);
|
||||
if (jd->buffer[i] == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to create buffers.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_cond_init(&jd->cond, NULL);
|
||||
pthread_mutex_init(&jd->cond_lock, NULL);
|
||||
|
||||
|
||||
jack_free(jports);
|
||||
return jd;
|
||||
|
||||
|
||||
+29
-17
@@ -15,28 +15,44 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "driver.h"
|
||||
#include "general.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_OSS_BSD
|
||||
#include <soundcard.h>
|
||||
#else
|
||||
#include <sys/soundcard.h>
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef HAVE_OSS_BSD
|
||||
#define DEFAULT_OSS_DEV "/dev/audio"
|
||||
#else
|
||||
#define DEFAULT_OSS_DEV "/dev/dsp"
|
||||
#endif
|
||||
|
||||
static void* __oss_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
int *fd = calloc(1, sizeof(int));
|
||||
if ( fd == NULL )
|
||||
if (fd == NULL)
|
||||
return NULL;
|
||||
|
||||
const char *oss_device = "/dev/dsp";
|
||||
const char *oss_device = DEFAULT_OSS_DEV;
|
||||
|
||||
if ( device != NULL )
|
||||
if (device != NULL)
|
||||
oss_device = device;
|
||||
|
||||
if ( (*fd = open(oss_device, O_WRONLY)) < 0 )
|
||||
if ((*fd = open(oss_device, O_WRONLY)) < 0)
|
||||
{
|
||||
free(fd);
|
||||
return NULL;
|
||||
@@ -45,7 +61,7 @@ static void* __oss_init(const char* device, unsigned rate, unsigned latency)
|
||||
int frags = (latency * rate * 4)/(1000 * (1 << 9));
|
||||
int frag = (frags << 16) | 9;
|
||||
|
||||
if ( ioctl(*fd, SNDCTL_DSP_SETFRAGMENT, &frag) < 0 )
|
||||
if (ioctl(*fd, SNDCTL_DSP_SETFRAGMENT, &frag) < 0)
|
||||
{
|
||||
close(*fd);
|
||||
free(fd);
|
||||
@@ -53,23 +69,24 @@ static void* __oss_init(const char* device, unsigned rate, unsigned latency)
|
||||
}
|
||||
|
||||
int channels = 2;
|
||||
int format = AFMT_S16_LE;
|
||||
int format = is_little_endian() ?
|
||||
AFMT_S16_LE : AFMT_S16_BE;
|
||||
|
||||
if ( ioctl(*fd, SNDCTL_DSP_CHANNELS, &channels) < 0 )
|
||||
if (ioctl(*fd, SNDCTL_DSP_CHANNELS, &channels) < 0)
|
||||
{
|
||||
close(*fd);
|
||||
free(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ( ioctl(*fd, SNDCTL_DSP_SETFMT, &format) < 0 )
|
||||
if (ioctl(*fd, SNDCTL_DSP_SETFMT, &format) < 0)
|
||||
{
|
||||
close(*fd);
|
||||
free(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ( ioctl(*fd, SNDCTL_DSP_SPEED, &rate) < 0 )
|
||||
if (ioctl(*fd, SNDCTL_DSP_SPEED, &rate) < 0)
|
||||
{
|
||||
close(*fd);
|
||||
free(fd);
|
||||
@@ -83,13 +100,13 @@ static ssize_t __oss_write(void* data, const void* buf, size_t size)
|
||||
{
|
||||
int *fd = data;
|
||||
|
||||
if ( size == 0 )
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
ssize_t ret;
|
||||
if ( (ret = write(*fd, buf, size)) <= 0 )
|
||||
if ((ret = write(*fd, buf, size)) <= 0)
|
||||
{
|
||||
if ( (fcntl(*fd, F_GETFL) & O_NONBLOCK) && errno == EAGAIN )
|
||||
if ((fcntl(*fd, F_GETFL) & O_NONBLOCK) && errno == EAGAIN)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
@@ -139,9 +156,4 @@ const audio_driver_t audio_oss = {
|
||||
.free = __oss_free,
|
||||
.ident = "oss"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,18 +59,6 @@ static void __pulse_free(void *data)
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t is_little_endian(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint16_t x;
|
||||
uint8_t y[2];
|
||||
} u;
|
||||
|
||||
u.x = 1;
|
||||
return u.y[0];
|
||||
}
|
||||
|
||||
static void context_state_cb(pa_context *c, void *data)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
|
||||
+10
-10
@@ -19,7 +19,7 @@
|
||||
#include "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include <rsound.h>
|
||||
#include "buffer.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include <stdbool.h>
|
||||
#include "SDL.h"
|
||||
|
||||
@@ -29,7 +29,7 @@ typedef struct rsd
|
||||
bool nonblock;
|
||||
volatile bool has_error;
|
||||
|
||||
rsound_fifo_buffer_t *buffer;
|
||||
fifo_buffer_t *buffer;
|
||||
|
||||
SDL_mutex *cond_lock;
|
||||
SDL_cond *cond;
|
||||
@@ -39,9 +39,9 @@ static ssize_t audio_cb(void *data, size_t bytes, void *userdata)
|
||||
{
|
||||
rsd_t *rsd = userdata;
|
||||
|
||||
size_t avail = rsnd_fifo_read_avail(rsd->buffer);
|
||||
size_t avail = fifo_read_avail(rsd->buffer);
|
||||
size_t write_size = bytes > avail ? avail : bytes;
|
||||
rsnd_fifo_read(rsd->buffer, data, write_size);
|
||||
fifo_read(rsd->buffer, data, write_size);
|
||||
SDL_CondSignal(rsd->cond);
|
||||
|
||||
return write_size;
|
||||
@@ -71,7 +71,7 @@ static void* __rsd_init(const char* device, unsigned rate, unsigned latency)
|
||||
rsd->cond_lock = SDL_CreateMutex();
|
||||
rsd->cond = SDL_CreateCond();
|
||||
|
||||
rsd->buffer = rsnd_fifo_new(1024 * 4);
|
||||
rsd->buffer = fifo_new(1024 * 4);
|
||||
|
||||
int channels = 2;
|
||||
int format = RSD_S16_NE;
|
||||
@@ -108,9 +108,9 @@ static ssize_t __rsd_write(void* data, const void* buf, size_t size)
|
||||
if (rsd->nonblock)
|
||||
{
|
||||
rsd_callback_lock(rsd->rd);
|
||||
size_t avail = rsnd_fifo_write_avail(rsd->buffer);
|
||||
size_t avail = fifo_write_avail(rsd->buffer);
|
||||
size_t write_amt = avail > size ? size : avail;
|
||||
rsnd_fifo_write(rsd->buffer, buf, write_amt);
|
||||
fifo_write(rsd->buffer, buf, write_amt);
|
||||
rsd_callback_unlock(rsd->rd);
|
||||
return write_amt;
|
||||
}
|
||||
@@ -120,7 +120,7 @@ static ssize_t __rsd_write(void* data, const void* buf, size_t size)
|
||||
while (written < size && !rsd->has_error)
|
||||
{
|
||||
rsd_callback_lock(rsd->rd);
|
||||
size_t avail = rsnd_fifo_write_avail(rsd->buffer);
|
||||
size_t avail = fifo_write_avail(rsd->buffer);
|
||||
|
||||
if (avail == 0)
|
||||
{
|
||||
@@ -135,7 +135,7 @@ static ssize_t __rsd_write(void* data, const void* buf, size_t size)
|
||||
else
|
||||
{
|
||||
size_t write_amt = size - written > avail ? avail : size - written;
|
||||
rsnd_fifo_write(rsd->buffer, (const char*)buf + written, write_amt);
|
||||
fifo_write(rsd->buffer, (const char*)buf + written, write_amt);
|
||||
rsd_callback_unlock(rsd->rd);
|
||||
written += write_amt;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ static void __rsd_free(void *data)
|
||||
rsd_stop(rsd->rd);
|
||||
rsd_free(rsd->rd);
|
||||
|
||||
rsnd_fifo_free(rsd->buffer);
|
||||
fifo_free(rsd->buffer);
|
||||
SDL_DestroyMutex(rsd->cond_lock);
|
||||
SDL_DestroyCond(rsd->cond);
|
||||
|
||||
|
||||
+11
-16
@@ -27,7 +27,7 @@
|
||||
#include "SDL_audio.h"
|
||||
#include "SDL_thread.h"
|
||||
#include "general.h"
|
||||
#include "buffer.h"
|
||||
#include "fifo_buffer.h"
|
||||
|
||||
typedef struct sdl_audio
|
||||
{
|
||||
@@ -35,16 +35,16 @@ typedef struct sdl_audio
|
||||
|
||||
SDL_mutex *lock;
|
||||
SDL_cond *cond;
|
||||
rsound_fifo_buffer_t *buffer;
|
||||
fifo_buffer_t *buffer;
|
||||
} sdl_audio_t;
|
||||
|
||||
static void sdl_audio_cb(void *data, Uint8 *stream, int len)
|
||||
{
|
||||
sdl_audio_t *sdl = data;
|
||||
|
||||
size_t avail = rsnd_fifo_read_avail(sdl->buffer);
|
||||
size_t avail = fifo_read_avail(sdl->buffer);
|
||||
size_t write_size = len > avail ? avail : len;
|
||||
rsnd_fifo_read(sdl->buffer, stream, write_size);
|
||||
fifo_read(sdl->buffer, stream, write_size);
|
||||
SDL_CondSignal(sdl->cond);
|
||||
|
||||
// If underrun, fill rest with silence.
|
||||
@@ -98,10 +98,10 @@ static void* sdl_audio_init(const char* device, unsigned rate, unsigned latency)
|
||||
// Create a buffer twice as big as needed and prefill the buffer.
|
||||
size_t bufsize = out.samples * 4 * sizeof(int16_t);
|
||||
void *tmp = calloc(1, bufsize);
|
||||
sdl->buffer = rsnd_fifo_new(bufsize);
|
||||
sdl->buffer = fifo_new(bufsize);
|
||||
if (tmp)
|
||||
{
|
||||
rsnd_fifo_write(sdl->buffer, tmp, bufsize);
|
||||
fifo_write(sdl->buffer, tmp, bufsize);
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
@@ -117,9 +117,9 @@ static ssize_t sdl_audio_write(void* data, const void* buf, size_t size)
|
||||
if (sdl->nonblock)
|
||||
{
|
||||
SDL_LockAudio();
|
||||
size_t avail = rsnd_fifo_write_avail(sdl->buffer);
|
||||
size_t avail = fifo_write_avail(sdl->buffer);
|
||||
size_t write_amt = avail > size ? size : avail;
|
||||
rsnd_fifo_write(sdl->buffer, buf, write_amt);
|
||||
fifo_write(sdl->buffer, buf, write_amt);
|
||||
SDL_UnlockAudio();
|
||||
ret = write_amt;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ static ssize_t sdl_audio_write(void* data, const void* buf, size_t size)
|
||||
while (written < size)
|
||||
{
|
||||
SDL_LockAudio();
|
||||
size_t avail = rsnd_fifo_write_avail(sdl->buffer);
|
||||
size_t avail = fifo_write_avail(sdl->buffer);
|
||||
|
||||
if (avail == 0)
|
||||
{
|
||||
@@ -141,7 +141,7 @@ static ssize_t sdl_audio_write(void* data, const void* buf, size_t size)
|
||||
else
|
||||
{
|
||||
size_t write_amt = size - written > avail ? avail : size - written;
|
||||
rsnd_fifo_write(sdl->buffer, (const char*)buf + written, write_amt);
|
||||
fifo_write(sdl->buffer, (const char*)buf + written, write_amt);
|
||||
SDL_UnlockAudio();
|
||||
written += write_amt;
|
||||
}
|
||||
@@ -180,7 +180,7 @@ static void sdl_audio_free(void *data)
|
||||
sdl_audio_t *sdl = data;
|
||||
if (sdl)
|
||||
{
|
||||
rsnd_fifo_free(sdl->buffer);
|
||||
fifo_free(sdl->buffer);
|
||||
SDL_DestroyMutex(sdl->lock);
|
||||
SDL_DestroyCond(sdl->cond);
|
||||
}
|
||||
@@ -196,9 +196,4 @@ const audio_driver_t audio_sdl = {
|
||||
.free = sdl_audio_free,
|
||||
.ident = "sdl"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+21
-57
@@ -15,73 +15,31 @@
|
||||
* 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.
|
||||
|
||||
xa_t *xa = calloc(1, sizeof(xa_t));
|
||||
if (xa == NULL)
|
||||
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");
|
||||
@@ -96,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)
|
||||
@@ -121,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 = {
|
||||
@@ -139,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"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+10
-1
@@ -42,6 +42,9 @@ struct autosave
|
||||
static int autosave_thread(void *data)
|
||||
{
|
||||
autosave_t *save = data;
|
||||
|
||||
bool first_log = true;
|
||||
|
||||
while (!save->quit)
|
||||
{
|
||||
autosave_lock(save);
|
||||
@@ -52,7 +55,13 @@ static int autosave_thread(void *data)
|
||||
FILE *file = fopen(save->path, "wb");
|
||||
if (file)
|
||||
{
|
||||
SSNES_LOG("Autosaving SRAM to \"%s\"\n", save->path);
|
||||
// Avoid spamming down stderr ... :)
|
||||
if (first_log)
|
||||
{
|
||||
SSNES_LOG("Autosaving SRAM to \"%s\", will continue to autosave every %u seconds ...\n", save->path, save->interval);
|
||||
first_log = false;
|
||||
}
|
||||
|
||||
bool failed = false;
|
||||
failed |= fwrite(save->buffer, 1, save->bufsize, file) != save->bufsize;
|
||||
failed |= fflush(file) != 0;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/tree.h>
|
||||
@@ -51,7 +50,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,58 +61,74 @@ 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;
|
||||
}
|
||||
|
||||
cheat_manager_t* cheat_manager_new(const char *path)
|
||||
@@ -125,8 +141,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;
|
||||
@@ -185,7 +201,17 @@ 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;
|
||||
}
|
||||
|
||||
xmlFreeDoc(doc);
|
||||
xmlFreeParserCtxt(ctx);
|
||||
|
||||
+34
-1
@@ -21,6 +21,7 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include "strl.h"
|
||||
|
||||
struct entry_list
|
||||
{
|
||||
@@ -223,6 +224,22 @@ bool config_get_int(config_file_t *conf, const char *key, int *in)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool config_get_hex(config_file_t *conf, const char *key, unsigned *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
*in = strtol(list->value, NULL, 16);
|
||||
return true;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool config_get_char(config_file_t *conf, const char *key, char *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
@@ -231,7 +248,7 @@ bool config_get_char(config_file_t *conf, const char *key, char *in)
|
||||
{
|
||||
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;
|
||||
@@ -257,6 +274,22 @@ 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 != NULL)
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct config_file config_file_t;
|
||||
|
||||
@@ -42,10 +43,14 @@ void config_file_free(config_file_t *conf);
|
||||
bool config_get_double(config_file_t *conf, const char *entry, double *in);
|
||||
// Extracts an int from config file.
|
||||
bool config_get_int(config_file_t *conf, const char *entry, int *in);
|
||||
// Extracts an int from config file. (Hexadecimal)
|
||||
bool config_get_hex(config_file_t *conf, const char *entry, unsigned *in);
|
||||
// Extracts a single char. If value consists of several chars, this is an error.
|
||||
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);
|
||||
|
||||
|
||||
+29
-14
@@ -57,6 +57,8 @@
|
||||
#define AUDIO_XAUDIO 9
|
||||
#define AUDIO_PULSE 10
|
||||
#define AUDIO_EXT 15
|
||||
#define AUDIO_DSOUND 16
|
||||
#define AUDIO_COREAUDIO 17
|
||||
////////////////////////
|
||||
#define INPUT_SDL 7
|
||||
#define INPUT_X 12
|
||||
@@ -82,8 +84,12 @@
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_OSS
|
||||
#elif defined(HAVE_JACK)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_JACK
|
||||
#elif defined(HAVE_COREAUDIO)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_COREAUDIO
|
||||
#elif defined(HAVE_AL)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_AL
|
||||
#elif defined(HAVE_DSOUND)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_DSOUND
|
||||
#elif defined(HAVE_SDL)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_SDL
|
||||
#elif defined(HAVE_XAUDIO)
|
||||
@@ -123,6 +129,9 @@ static const unsigned fullscreen_y = 0;
|
||||
// Force 16-bit colors.
|
||||
static const bool force_16bit = false;
|
||||
|
||||
// Forcibly disable composition. Only valid on Windows Vista/7 for now.
|
||||
static const bool disable_composition = false;
|
||||
|
||||
// Video VSYNC (recommended)
|
||||
static const bool vsync = true;
|
||||
|
||||
@@ -148,6 +157,12 @@ static const float fbo_scale_x = 2.0;
|
||||
static const float fbo_scale_y = 2.0;
|
||||
static const bool second_pass_smooth = true;
|
||||
|
||||
// Record video assuming game runs hi-res.
|
||||
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)
|
||||
|
||||
////////////////
|
||||
@@ -198,7 +213,7 @@ static const bool pause_nonactive = true;
|
||||
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 = false;
|
||||
static const bool netplay_client_swap_input = true;
|
||||
|
||||
|
||||
////////////////////
|
||||
@@ -228,6 +243,7 @@ static const struct snes_keybind snes_keybinds_1[] = {
|
||||
{ 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 },
|
||||
@@ -252,19 +268,18 @@ static const struct snes_keybind snes_keybinds_1[] = {
|
||||
|
||||
// Player 2-5
|
||||
static const struct snes_keybind snes_keybinds_rest[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, NO_BTN, AXIS_NONE },
|
||||
{ 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 },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
|
||||
+16
-4
@@ -14,13 +14,19 @@ static const bool _sdl_supp = true;
|
||||
static const bool _sdl_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XVIDEO
|
||||
static const bool _xvideo_supp = true;
|
||||
#else
|
||||
static const bool _xvideo_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ALSA
|
||||
static const bool _alsa_supp = true;
|
||||
#else
|
||||
static const bool _alsa_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_OSS
|
||||
#if defined(HAVE_OSS) || defined(HAVE_OSS_BSD)
|
||||
static const bool _oss_supp = true;
|
||||
#else
|
||||
static const bool _oss_supp = false;
|
||||
@@ -80,10 +86,10 @@ static const bool _xml_supp = true;
|
||||
static const bool _xml_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_IMLIB
|
||||
static const bool _imlib_supp = true;
|
||||
#ifdef HAVE_SDL_IMAGE
|
||||
static const bool _sdl_image_supp = true;
|
||||
#else
|
||||
static const bool _imlib_supp = false;
|
||||
static const bool _sdl_image_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
@@ -128,4 +134,10 @@ static const bool _netplay_supp = true;
|
||||
static const bool _netplay_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
static const bool _python_supp = true;
|
||||
#else
|
||||
static const bool _python_supp = false;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+27
-2
@@ -32,19 +32,30 @@ Prints help text.
|
||||
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
|
||||
\fB--fullscreen, -f\fR
|
||||
Always starts SSNES in fullscreen. Disregards settings in configuration file.
|
||||
|
||||
.TP
|
||||
\fB--config PATH, -c PATH\fR
|
||||
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.
|
||||
@@ -80,6 +91,9 @@ When using Sufami, save ram paths will be inferred from the Sufami BIOS path, no
|
||||
\fB--mouse PORT, -m PORT\fR
|
||||
Connects a Super Nintendo Mouse into port number PORT of the emulated SNES. Possible values for PORT are 1 and 2.
|
||||
|
||||
\fB--nodevice PORT, -N PORT\fR
|
||||
Disconnects an input device from port number PORT of the emulated SNES. Possible values for PORT are 1 and 2. This may be needed for some odd games to run properly.
|
||||
|
||||
.TP
|
||||
\fB--scope, -p\fR
|
||||
Connects a Super Scope into port 2 of the SNES. It is controlled with your mouse.
|
||||
@@ -98,7 +112,8 @@ 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--bsvplay PATH, -P PATH\fR
|
||||
@@ -131,6 +146,16 @@ 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.
|
||||
If this flag is not specified, SSNES will look for a .xml file with same basename as ROM specified.
|
||||
|
||||
.TP
|
||||
\fB-D, --detach\fR
|
||||
Detach from the current console. This is currently only relevant for Microsoft Windows.
|
||||
|
||||
@@ -32,12 +32,15 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_ALSA
|
||||
&audio_alsa,
|
||||
#endif
|
||||
#ifdef HAVE_OSS
|
||||
#if defined(HAVE_OSS) || defined(HAVE_OSS_BSD)
|
||||
&audio_oss,
|
||||
#endif
|
||||
#ifdef HAVE_RSOUND
|
||||
&audio_rsound,
|
||||
#endif
|
||||
#ifdef HAVE_COREAUDIO
|
||||
&audio_coreaudio,
|
||||
#endif
|
||||
#ifdef HAVE_AL
|
||||
&audio_openal,
|
||||
#endif
|
||||
@@ -53,6 +56,9 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_XAUDIO
|
||||
&audio_xa,
|
||||
#endif
|
||||
#ifdef HAVE_DSOUND
|
||||
&audio_dsound,
|
||||
#endif
|
||||
#ifdef HAVE_PULSE
|
||||
&audio_pulse,
|
||||
#endif
|
||||
@@ -132,7 +138,7 @@ static void find_input_driver(void)
|
||||
SSNES_ERR("Couldn't find any input driver named \"%s\"\n", g_settings.input.driver);
|
||||
fprintf(stderr, "Available input drivers are:\n");
|
||||
for (int i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++)
|
||||
fprintf(stderr, "\t%s\n", video_drivers[i]->ident);
|
||||
fprintf(stderr, "\t%s\n", input_drivers[i]->ident);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
@@ -161,7 +167,8 @@ static void init_dsp_plugin(void)
|
||||
return;
|
||||
}
|
||||
|
||||
const ssnes_dsp_plugin_t* (*SSNES_API_CALLTYPE plugin_init)(void) = dylib_proc(g_extern.audio_data.dsp_lib, "ssnes_dsp_plugin_init");
|
||||
const ssnes_dsp_plugin_t* (*SSNES_API_CALLTYPE plugin_init)(void) =
|
||||
(const ssnes_dsp_plugin_t *(SSNES_API_CALLTYPE*)(void))dylib_proc(g_extern.audio_data.dsp_lib, "ssnes_dsp_plugin_init");
|
||||
if (!plugin_init)
|
||||
{
|
||||
SSNES_ERR("Failed to find symbol \"ssnes_dsp_plugin_init\" in DSP plugin.\n");
|
||||
@@ -183,7 +190,7 @@ static void init_dsp_plugin(void)
|
||||
|
||||
SSNES_LOG("Loaded DSP plugin: \"%s\"\n", g_extern.audio_data.dsp_plugin->ident ? g_extern.audio_data.dsp_plugin->ident : "Unknown");
|
||||
|
||||
ssnes_dsp_info_t info = {
|
||||
const ssnes_dsp_info_t info = {
|
||||
.input_rate = g_settings.audio.in_rate,
|
||||
.output_rate = g_settings.audio.out_rate
|
||||
};
|
||||
@@ -310,8 +317,12 @@ static void init_filter(void)
|
||||
return;
|
||||
}
|
||||
|
||||
g_extern.filter.psize = dylib_proc(g_extern.filter.lib, "filter_size");
|
||||
g_extern.filter.prender = dylib_proc(g_extern.filter.lib, "filter_render");
|
||||
g_extern.filter.psize =
|
||||
(void (*)(unsigned*, unsigned*))dylib_proc(g_extern.filter.lib, "filter_size");
|
||||
g_extern.filter.prender =
|
||||
(void (*)(uint32_t*, uint32_t*,
|
||||
unsigned, const uint16_t*,
|
||||
unsigned, unsigned, unsigned))dylib_proc(g_extern.filter.lib, "filter_render");
|
||||
if (!g_extern.filter.psize || !g_extern.filter.prender)
|
||||
{
|
||||
SSNES_ERR("Failed to find functions in filter...\n");
|
||||
@@ -338,7 +349,7 @@ static void init_filter(void)
|
||||
g_extern.filter.colormap = malloc(32768 * sizeof(uint32_t));
|
||||
assert(g_extern.filter.colormap);
|
||||
|
||||
// Set up conversion map from 16-bit XBGR1555 to 32-bit RGBA.
|
||||
// Set up conversion map from 16-bit XRGB1555 to 32-bit ARGB.
|
||||
for (int i = 0; i < 32768; i++)
|
||||
{
|
||||
unsigned r = (i >> 10) & 31;
|
||||
@@ -348,7 +359,7 @@ static void init_filter(void)
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g << 3) | (g >> 2);
|
||||
b = (b << 3) | (b >> 2);
|
||||
g_extern.filter.colormap[i] = (r << 24) | (g << 16) | (b << 8);
|
||||
g_extern.filter.colormap[i] = (r << 16) | (g << 8) | (b << 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -447,7 +458,7 @@ void init_video_input(void)
|
||||
|
||||
if ( driver.video_data == NULL )
|
||||
{
|
||||
SSNES_ERR("Cannot open video driver... Exiting ...\n");
|
||||
SSNES_ERR("Cannot open video driver ... Exiting ...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -474,12 +485,12 @@ void init_video_input(void)
|
||||
|
||||
void uninit_video_input(void)
|
||||
{
|
||||
if ( driver.video_data && driver.video )
|
||||
driver.video->free(driver.video_data);
|
||||
|
||||
if ( driver.input_data != driver.video_data && driver.input )
|
||||
driver.input->free(driver.input_data);
|
||||
|
||||
if ( driver.video_data && driver.video )
|
||||
driver.video->free(driver.video_data);
|
||||
|
||||
deinit_filter();
|
||||
|
||||
#ifdef HAVE_XML
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
enum
|
||||
{
|
||||
SSNES_FAST_FORWARD_KEY = 0x666, // Hurr, durr
|
||||
SSNES_FAST_FORWARD_HOLD_KEY,
|
||||
SSNES_LOAD_STATE_KEY,
|
||||
SSNES_SAVE_STATE_KEY,
|
||||
SSNES_FULLSCREEN_TOGGLE_KEY,
|
||||
@@ -157,6 +158,8 @@ extern const audio_driver_t audio_sdl;
|
||||
extern const audio_driver_t audio_xa;
|
||||
extern const audio_driver_t audio_pulse;
|
||||
extern const audio_driver_t audio_ext;
|
||||
extern const audio_driver_t audio_dsound;
|
||||
extern const audio_driver_t audio_coreaudio;
|
||||
extern const video_driver_t video_gl;
|
||||
extern const video_driver_t video_xvideo;
|
||||
extern const video_driver_t video_sdl;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -34,13 +35,13 @@
|
||||
#ifdef HAVE_DYNAMIC
|
||||
#define DLSYM(lib, x) dylib_proc(lib, #x)
|
||||
|
||||
#define SYM(x) do { \
|
||||
p##x = DLSYM(lib_handle, x); \
|
||||
#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)
|
||||
|
||||
#define OPT_SYM(x) do { \
|
||||
p##x = DLSYM(lib_handle, x); \
|
||||
#define OPT_SYM(type, x) do { \
|
||||
p##x = (type)DLSYM(lib_handle, x); \
|
||||
} while(0)
|
||||
|
||||
static dylib_t lib_handle = NULL;
|
||||
@@ -106,33 +107,38 @@ static void load_dynamic(void)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SYM(snes_init);
|
||||
SYM(snes_set_video_refresh);
|
||||
SYM(snes_set_audio_sample);
|
||||
SYM(snes_set_input_poll);
|
||||
SYM(snes_set_input_state);
|
||||
OPT_SYM(snes_library_id);
|
||||
SYM(snes_library_revision_minor);
|
||||
SYM(snes_library_revision_major);
|
||||
SYM(snes_cheat_reset);
|
||||
SYM(snes_cheat_set);
|
||||
SYM(snes_reset);
|
||||
SYM(snes_run);
|
||||
SYM(snes_get_region);
|
||||
SYM(snes_load_cartridge_normal);
|
||||
SYM(snes_load_cartridge_super_game_boy);
|
||||
SYM(snes_load_cartridge_bsx);
|
||||
SYM(snes_load_cartridge_bsx_slotted);
|
||||
SYM(snes_load_cartridge_sufami_turbo);
|
||||
SYM(snes_set_controller_port_device);
|
||||
SYM(snes_serialize_size);
|
||||
SYM(snes_serialize);
|
||||
SYM(snes_unserialize);
|
||||
SYM(snes_set_cartridge_basename);
|
||||
SYM(snes_get_memory_data);
|
||||
SYM(snes_get_memory_size);
|
||||
SYM(snes_unload_cartridge);
|
||||
SYM(snes_term);
|
||||
SYM(void (*)(void), snes_init);
|
||||
SYM(void (*)(snes_video_refresh_t), snes_set_video_refresh);
|
||||
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(unsigned (*)(void), snes_library_revision_minor);
|
||||
SYM(unsigned (*)(void), snes_library_revision_major);
|
||||
SYM(void (*)(void), snes_cheat_reset);
|
||||
SYM(void (*)(unsigned, bool, const char*), snes_cheat_set);
|
||||
SYM(void (*)(void), snes_reset);
|
||||
SYM(void (*)(void), snes_run);
|
||||
SYM(bool (*)(void), snes_get_region);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned), snes_load_cartridge_normal);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned), snes_load_cartridge_super_game_boy);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned), snes_load_cartridge_bsx);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned), snes_load_cartridge_bsx_slotted);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned), snes_load_cartridge_sufami_turbo);
|
||||
SYM(void (*)(bool, unsigned), snes_set_controller_port_device);
|
||||
SYM(unsigned (*)(void), snes_serialize_size);
|
||||
SYM(bool (*)(uint8_t*, unsigned), snes_serialize);
|
||||
SYM(bool (*)(const uint8_t*, unsigned), snes_unserialize);
|
||||
SYM(void (*)(const char*), snes_set_cartridge_basename);
|
||||
SYM(uint8_t *(*)(unsigned), snes_get_memory_data);
|
||||
SYM(unsigned (*)(unsigned), snes_get_memory_size);
|
||||
SYM(void (*)(void), snes_unload_cartridge);
|
||||
SYM(void (*)(void), snes_term);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -174,7 +180,30 @@ 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 ...
|
||||
assert(sizeof(void*) == sizeof(void (*)(void)));
|
||||
|
||||
#ifdef HAVE_DYNAMIC
|
||||
|
||||
#ifndef _WIN32 // Try to verify that -lsnes was not linked in from other modules
|
||||
// since loading it dynamically and with -l will fail hard.
|
||||
void *lib = dlopen(NULL, RTLD_LAZY);
|
||||
if (lib)
|
||||
{
|
||||
void *sym = dlsym(lib, "snes_init");
|
||||
if (sym)
|
||||
{
|
||||
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");
|
||||
dlclose(lib);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dlclose(lib);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (strlen(g_settings.libsnes) > 0)
|
||||
load_dynamic();
|
||||
else
|
||||
@@ -205,12 +234,15 @@ dylib_t dylib_load(const char *path)
|
||||
#endif
|
||||
}
|
||||
|
||||
void* dylib_proc(dylib_t lib, const char *proc)
|
||||
function_t dylib_proc(dylib_t lib, const char *proc)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
void *sym = (void*)GetProcAddress(lib, proc);
|
||||
function_t sym = (function_t)GetProcAddress(lib, proc);
|
||||
#else
|
||||
void *sym = dlsym(lib, proc);
|
||||
// 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)
|
||||
|
||||
@@ -25,10 +25,11 @@ void init_dlsym(void);
|
||||
void uninit_dlsym(void);
|
||||
|
||||
typedef void *dylib_t;
|
||||
typedef void (*function_t)(void);
|
||||
|
||||
dylib_t dylib_load(const char *path);
|
||||
void dylib_close(dylib_t lib);
|
||||
void* dylib_proc(dylib_t lib, const char *proc);
|
||||
function_t dylib_proc(dylib_t lib, const char *proc);
|
||||
|
||||
extern void (*psnes_init)(void);
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "buffer.h"
|
||||
#include "fifo_buffer.h"
|
||||
|
||||
struct rsound_fifo_buffer
|
||||
struct fifo_buffer
|
||||
{
|
||||
char *buffer;
|
||||
size_t bufsize;
|
||||
@@ -23,9 +23,9 @@ struct rsound_fifo_buffer
|
||||
size_t end;
|
||||
};
|
||||
|
||||
rsound_fifo_buffer_t* rsnd_fifo_new(size_t size)
|
||||
fifo_buffer_t* fifo_new(size_t size)
|
||||
{
|
||||
rsound_fifo_buffer_t *buf = calloc(1, sizeof(*buf));
|
||||
fifo_buffer_t *buf = calloc(1, sizeof(*buf));
|
||||
if (buf == NULL)
|
||||
return NULL;
|
||||
|
||||
@@ -40,7 +40,7 @@ rsound_fifo_buffer_t* rsnd_fifo_new(size_t size)
|
||||
return buf;
|
||||
}
|
||||
|
||||
void rsnd_fifo_free(rsound_fifo_buffer_t* buffer)
|
||||
void fifo_free(fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
@@ -49,7 +49,7 @@ void rsnd_fifo_free(rsound_fifo_buffer_t* buffer)
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer)
|
||||
size_t fifo_read_avail(fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
@@ -61,7 +61,7 @@ size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer)
|
||||
return end - first;
|
||||
}
|
||||
|
||||
size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer)
|
||||
size_t fifo_write_avail(fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
@@ -74,12 +74,12 @@ size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer)
|
||||
return (buffer->bufsize - 1) - (end - first);
|
||||
}
|
||||
|
||||
void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size)
|
||||
void fifo_write(fifo_buffer_t* buffer, const void* in_buf, size_t size)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
assert(in_buf);
|
||||
assert(rsnd_fifo_write_avail(buffer) >= size);
|
||||
assert(fifo_write_avail(buffer) >= size);
|
||||
|
||||
size_t first_write = size;
|
||||
size_t rest_write = 0;
|
||||
@@ -97,12 +97,12 @@ void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t si
|
||||
}
|
||||
|
||||
|
||||
void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, 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(rsnd_fifo_read_avail(buffer) >= size);
|
||||
assert(fifo_read_avail(buffer) >= size);
|
||||
|
||||
size_t first_read = size;
|
||||
size_t rest_read = 0;
|
||||
@@ -21,16 +21,16 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef RSD_FIFO_BUF_TYPEDEF
|
||||
#define RSD_FIFO_BUF_TYPEDEF
|
||||
typedef struct rsound_fifo_buffer rsound_fifo_buffer_t;
|
||||
#ifndef FIFO_BUF_TYPEDEF
|
||||
#define FIFO_BUF_TYPEDEF
|
||||
typedef struct fifo_buffer fifo_buffer_t;
|
||||
#endif
|
||||
|
||||
rsound_fifo_buffer_t* rsnd_fifo_new(size_t size);
|
||||
void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size);
|
||||
void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size);
|
||||
void rsnd_fifo_free(rsound_fifo_buffer_t* buffer);
|
||||
size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer);
|
||||
size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer);
|
||||
fifo_buffer_t* fifo_new(size_t size);
|
||||
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);
|
||||
void fifo_free(fifo_buffer_t* buffer);
|
||||
size_t fifo_read_avail(fifo_buffer_t* buffer);
|
||||
size_t fifo_write_avail(fifo_buffer_t* buffer);
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
|
||||
@@ -51,7 +56,7 @@ ssize_t read_file(const char *path, void **buf)
|
||||
long len = ftell(file);
|
||||
ssize_t rc = 0;
|
||||
rewind(file);
|
||||
rom_buf = malloc(len);
|
||||
rom_buf = malloc(len + 1);
|
||||
if (!rom_buf)
|
||||
{
|
||||
SSNES_ERR("Couldn't allocate memory!\n");
|
||||
@@ -62,6 +67,9 @@ ssize_t read_file(const char *path, void **buf)
|
||||
SSNES_WARN("Didn't read whole file.\n");
|
||||
|
||||
*buf = rom_buf;
|
||||
// Allow for easy reading of strings to be safe.
|
||||
// Will only work with sane character formatting (Unix).
|
||||
((char*)rom_buf)[len] = '\0';
|
||||
fclose(file);
|
||||
return rc;
|
||||
|
||||
@@ -73,6 +81,154 @@ error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Reads file content as one string.
|
||||
bool read_file_string(const char *path, char **buf)
|
||||
{
|
||||
*buf = NULL;
|
||||
FILE *file = fopen(path, "r");
|
||||
if (!file)
|
||||
goto error;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long len = ftell(file) + 2; // Takes account of being able to read in EOF and '\0' at end.
|
||||
rewind(file);
|
||||
|
||||
*buf = calloc(len, sizeof(char));
|
||||
if (!*buf)
|
||||
goto error;
|
||||
|
||||
char *ptr = *buf;
|
||||
|
||||
while (ptr && !feof(file))
|
||||
{
|
||||
size_t bufsize = (size_t)(((ptrdiff_t)*buf + (ptrdiff_t)len) - (ptrdiff_t)ptr);
|
||||
fgets(ptr, bufsize, file);
|
||||
|
||||
ptr = strchr(ptr, '\0');
|
||||
}
|
||||
|
||||
ptr = strchr(ptr, EOF);
|
||||
if (ptr) *ptr = '\0';
|
||||
|
||||
fclose(file);
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (file)
|
||||
fclose(file);
|
||||
if (*buf)
|
||||
free(*buf);
|
||||
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)
|
||||
{
|
||||
@@ -140,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)
|
||||
{
|
||||
@@ -178,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);
|
||||
@@ -196,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();
|
||||
@@ -225,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;
|
||||
@@ -245,7 +432,7 @@ bool load_state(const char* path)
|
||||
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);
|
||||
@@ -261,13 +448,34 @@ void load_ram_file(const char* path, int type)
|
||||
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)
|
||||
{
|
||||
char *xml_buf = NULL;
|
||||
if (*path)
|
||||
{
|
||||
if (!read_file_string(path, &xml_buf))
|
||||
SSNES_LOG("Did not find XML memory map in \"%s\"\n", path);
|
||||
else
|
||||
SSNES_LOG("Found XML memory map in \"%s\"\n", path);
|
||||
}
|
||||
|
||||
return xml_buf;
|
||||
}
|
||||
|
||||
static bool load_sgb_rom(void)
|
||||
@@ -291,14 +499,19 @@ static bool load_sgb_rom(void)
|
||||
goto error;
|
||||
}
|
||||
|
||||
char *xml_buf = load_xml_map(g_extern.xml_name);
|
||||
|
||||
if (!psnes_load_cartridge_super_game_boy(
|
||||
NULL, rom_buf, rom_len,
|
||||
xml_buf, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load SGB/GameBoy rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (xml_buf)
|
||||
free(xml_buf);
|
||||
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
if (extra_rom)
|
||||
@@ -338,10 +551,12 @@ static bool load_bsx_rom(bool slotted)
|
||||
goto error;
|
||||
}
|
||||
|
||||
char *xml_buf = load_xml_map(g_extern.xml_name);
|
||||
|
||||
if (slotted)
|
||||
{
|
||||
if (!psnes_load_cartridge_bsx_slotted(
|
||||
NULL, rom_buf, rom_len,
|
||||
xml_buf, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load BSX slotted rom.\n");
|
||||
@@ -360,6 +575,9 @@ static bool load_bsx_rom(bool slotted)
|
||||
}
|
||||
}
|
||||
|
||||
if (xml_buf)
|
||||
free(xml_buf);
|
||||
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
if (extra_rom)
|
||||
@@ -407,8 +625,10 @@ static bool load_sufami_rom(void)
|
||||
}
|
||||
}
|
||||
|
||||
char *xml_buf = load_xml_map(g_extern.xml_name);
|
||||
|
||||
if (!psnes_load_cartridge_sufami_turbo(
|
||||
NULL, rom_buf, rom_len,
|
||||
xml_buf, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf[0], extra_rom_len[0],
|
||||
NULL, extra_rom_buf[1], extra_rom_len[1]))
|
||||
{
|
||||
@@ -416,6 +636,8 @@ static bool load_sufami_rom(void)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (xml_buf)
|
||||
free(xml_buf);
|
||||
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
@@ -457,13 +679,18 @@ static bool load_normal_rom(void)
|
||||
|
||||
SSNES_LOG("ROM size: %d bytes\n", (int)rom_len);
|
||||
|
||||
if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len))
|
||||
char *xml_buf = load_xml_map(g_extern.xml_name);
|
||||
|
||||
if (!psnes_load_cartridge_normal(xml_buf, rom_buf, rom_len))
|
||||
{
|
||||
SSNES_ERR("ROM file is not valid!\n");
|
||||
free(rom_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xml_buf)
|
||||
free(xml_buf);
|
||||
|
||||
free(rom_buf);
|
||||
return true;
|
||||
}
|
||||
@@ -509,8 +736,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;
|
||||
@@ -519,18 +744,18 @@ 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;
|
||||
if (strlcat(utf8_buf, ext, sizeof(utf8_buf)) >= sizeof(utf8_buf))
|
||||
goto error;
|
||||
|
||||
wchar_t wchar_buf[MAX_PATH + 1];
|
||||
char utf8_buf[MAX_PATH + 3];
|
||||
|
||||
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)
|
||||
@@ -538,7 +763,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)
|
||||
@@ -559,8 +783,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, MAX_PATH, NULL, NULL) == 0)
|
||||
continue;
|
||||
if (ext && !strstr(utf8_buf, ext))
|
||||
continue;
|
||||
#else
|
||||
@@ -568,19 +792,17 @@ 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;
|
||||
|
||||
strcpy(dir_list[cur_ptr], dir);
|
||||
strlcpy(dir_list[cur_ptr], dir, MAXPATHLEN);
|
||||
strlcat(dir_list[cur_ptr], "/", MAXPATHLEN);
|
||||
#ifdef _WIN32
|
||||
dir_list[cur_ptr][path_len] = '\\';
|
||||
strcpy(&dir_list[cur_ptr][path_len + 1], utf8_buf);
|
||||
strlcat(dir_list[cur_ptr], utf8_buf, MAXPATHLEN);
|
||||
#else
|
||||
dir_list[cur_ptr][path_len] = '/';
|
||||
strcpy(&dir_list[cur_ptr][path_len + 1], entry->d_name);
|
||||
strlcat(dir_list[cur_ptr], entry->d_name, MAXPATHLEN);
|
||||
#endif
|
||||
dir_list[cur_ptr][final_off - 1] = '\0';
|
||||
|
||||
cur_ptr++;
|
||||
if (cur_ptr + 1 == cur_size) // Need to reserve for NULL.
|
||||
@@ -596,7 +818,6 @@ char** dir_list_new(const char *dir, const char *ext)
|
||||
}
|
||||
#ifdef _WIN32
|
||||
while (FindNextFileW(hFind, &ffd) != 0);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -629,3 +850,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[strlen(in_path) + 1];
|
||||
strlcpy(tmp_path, in_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);
|
||||
}
|
||||
@@ -28,11 +28,11 @@
|
||||
|
||||
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);
|
||||
|
||||
@@ -40,4 +40,16 @@ bool init_rom_file(enum ssnes_game_type type);
|
||||
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
|
||||
@@ -42,9 +42,20 @@
|
||||
#include "audio/hermite.h"
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/param.h> // MAXPATHLEN
|
||||
#endif
|
||||
|
||||
#ifndef MAXPATHLEN
|
||||
#ifdef PATH_MAX
|
||||
#define MAXPATHLEN PATH_MAX
|
||||
#else
|
||||
#define MAXPATHLEN 512
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAX_PLAYERS 5
|
||||
#define MAX_BINDS 33 // Needs to be increased every time there are new binds added.
|
||||
#define MAX_BINDS 34 // Needs to be increased every time there are new binds added.
|
||||
#define SSNES_NO_JOYPAD 0xFFFF
|
||||
|
||||
enum ssnes_shader_type
|
||||
@@ -71,26 +82,30 @@ 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;
|
||||
|
||||
bool force_16bit;
|
||||
bool disable_composition;
|
||||
|
||||
char external_driver[256];
|
||||
bool hires_record;
|
||||
bool post_filter_record;
|
||||
|
||||
char external_driver[MAXPATHLEN];
|
||||
} video;
|
||||
|
||||
struct
|
||||
@@ -100,13 +115,13 @@ 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
|
||||
@@ -118,10 +133,10 @@ struct settings
|
||||
bool netplay_client_swap_input;
|
||||
} input;
|
||||
|
||||
char libsnes[256];
|
||||
char cheat_database[256];
|
||||
char libsnes[MAXPATHLEN];
|
||||
char cheat_database[MAXPATHLEN];
|
||||
|
||||
char screenshot_directory[256];
|
||||
char screenshot_directory[MAXPATHLEN];
|
||||
|
||||
bool rewind_enable;
|
||||
unsigned rewind_buffer_size;
|
||||
@@ -147,35 +162,42 @@ struct global
|
||||
bool verbose;
|
||||
bool audio_active;
|
||||
bool video_active;
|
||||
bool force_fullscreen;
|
||||
|
||||
bool has_mouse[2];
|
||||
bool has_scope[2];
|
||||
bool has_justifier;
|
||||
bool has_justifiers;
|
||||
bool has_multitap;
|
||||
bool disconnect_device[2];
|
||||
|
||||
FILE *rom_file;
|
||||
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 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;
|
||||
|
||||
@@ -226,7 +248,7 @@ 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;
|
||||
|
||||
@@ -238,7 +260,7 @@ struct global
|
||||
|
||||
// Netplay.
|
||||
netplay_t *netplay;
|
||||
char netplay_server[256];
|
||||
char netplay_server[MAXPATHLEN];
|
||||
bool netplay_enable;
|
||||
bool netplay_is_client;
|
||||
unsigned netplay_sync_frames;
|
||||
@@ -247,7 +269,7 @@ struct global
|
||||
// FFmpeg record.
|
||||
#ifdef HAVE_FFMPEG
|
||||
ffemu_t *rec;
|
||||
char record_path[256];
|
||||
char record_path[MAXPATHLEN];
|
||||
bool recording;
|
||||
#endif
|
||||
|
||||
@@ -274,19 +296,19 @@ void parse_config(void);
|
||||
extern struct settings g_settings;
|
||||
extern struct global g_extern;
|
||||
|
||||
#define SSNES_LOG(msg, args...) do { \
|
||||
#define SSNES_LOG(...) do { \
|
||||
if (g_extern.verbose) \
|
||||
fprintf(stderr, "SSNES: " msg, ##args); \
|
||||
fprintf(stderr, "SSNES: " __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
#define SSNES_ERR(msg, args...) do { \
|
||||
fprintf(stderr, "SSNES [ERROR] :: " msg, ##args); \
|
||||
#define SSNES_ERR(...) do { \
|
||||
fprintf(stderr, "SSNES [ERROR] :: " __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
#define SSNES_WARN(msg, args...) do { \
|
||||
fprintf(stderr, "SSNES [WARN] :: " msg, ##args); \
|
||||
#define SSNES_WARN(...) do { \
|
||||
fprintf(stderr, "SSNES [WARN] :: " __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
@@ -302,6 +324,18 @@ static inline uint32_t next_pow2(uint32_t v)
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline uint8_t is_little_endian(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint16_t x;
|
||||
uint8_t y[2];
|
||||
} u;
|
||||
|
||||
u.x = 1;
|
||||
return u.y[0];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -224,6 +224,15 @@ static bool setup_video(ext_t *ext, const video_info_t *video, const input_drive
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *cg_shader = NULL;
|
||||
const char *xml_shader = NULL;
|
||||
enum ssnes_shader_type type = g_settings.video.shader_type;
|
||||
if ((type == SSNES_SHADER_CG || type == SSNES_SHADER_AUTO) && *g_settings.video.cg_shader_path)
|
||||
cg_shader = g_settings.video.cg_shader_path;
|
||||
else if ((type == SSNES_SHADER_BSNES || type == SSNES_SHADER_AUTO) && *g_settings.video.bsnes_shader_path)
|
||||
xml_shader = g_settings.video.bsnes_shader_path;
|
||||
|
||||
|
||||
ssnes_video_info_t info = {
|
||||
.width = video->width,
|
||||
.height = video->height,
|
||||
@@ -234,8 +243,8 @@ static bool setup_video(ext_t *ext, const video_info_t *video, const input_drive
|
||||
.smooth = video->smooth,
|
||||
.input_scale = video->input_scale,
|
||||
.color_format = video->rgb32 ? SSNES_COLOR_FORMAT_ARGB8888 : SSNES_COLOR_FORMAT_XRGB1555,
|
||||
.xml_shader = g_settings.video.bsnes_shader_path,
|
||||
.cg_shader = g_settings.video.cg_shader_path,
|
||||
.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
|
||||
};
|
||||
@@ -273,7 +282,8 @@ static void* video_ext_init(const video_info_t *video, const input_driver_t **in
|
||||
goto error;
|
||||
}
|
||||
|
||||
const ssnes_video_driver_t* (*video_init)(void) = dylib_proc(g_lib, "ssnes_video_init");
|
||||
const ssnes_video_driver_t* (*video_init)(void) =
|
||||
(const ssnes_video_driver_t *(*)(void))dylib_proc(g_lib, "ssnes_video_init");
|
||||
if (!video_init)
|
||||
{
|
||||
SSNES_ERR("Couldn't find function ssnes_video_init in library ...\n");
|
||||
|
||||
@@ -129,8 +129,8 @@ typedef struct ssnes_video_info
|
||||
#define SSNES_HAT_RIGHT_MASK (1 << 12)
|
||||
#define SSNES_HAT_MAP(x, hat) ((x & ((1 << 12) - 1)) | hat)
|
||||
|
||||
#define SSNES_HAT_MASK (HAT_UP_MASK | HAT_DOWN_MASK | \
|
||||
HAT_LEFT_MASK | HAT_RIGHT_MASK)
|
||||
#define SSNES_HAT_MASK (SSNES_HAT_UP_MASK | SSNES_HAT_DOWN_MASK | \
|
||||
SSNES_HAT_LEFT_MASK | SSNES_HAT_RIGHT_MASK)
|
||||
|
||||
// Test this on the joykey. If true, we want to test for a joypad hat
|
||||
// rather than a button.
|
||||
|
||||
+82
-6
@@ -25,21 +25,26 @@ static float tv_to_fps(const struct timeval *tv, const struct timeval *new_tv, i
|
||||
return frames/time;
|
||||
}
|
||||
|
||||
static unsigned gl_frames = 0;
|
||||
|
||||
void gfx_window_title_reset(void)
|
||||
{
|
||||
gl_frames = 0;
|
||||
}
|
||||
|
||||
bool gfx_window_title(char *buf, size_t size)
|
||||
{
|
||||
static int frames = 0;
|
||||
static struct timeval tv;
|
||||
struct timeval new_tv;
|
||||
bool ret = false;
|
||||
|
||||
if (frames == 0)
|
||||
if (gl_frames == 0)
|
||||
{
|
||||
gettimeofday(&tv, NULL);
|
||||
snprintf(buf, size, "%s", g_extern.title_buf);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if ((frames % 180) == 0 && frames > 0)
|
||||
else if ((gl_frames % 180) == 0)
|
||||
{
|
||||
gettimeofday(&new_tv, NULL);
|
||||
struct timeval tmp_tv = tv;
|
||||
@@ -47,12 +52,83 @@ bool gfx_window_title(char *buf, size_t size)
|
||||
|
||||
float fps = tv_to_fps(&tmp_tv, &new_tv, 180);
|
||||
|
||||
snprintf(buf, size, "%s || FPS: %6.1f || Frames: %d", g_extern.title_buf, fps, frames);
|
||||
snprintf(buf, size, "%s || FPS: %6.1f || Frames: %d", g_extern.title_buf, fps, gl_frames);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
frames++;
|
||||
gl_frames++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include "dynamic.h"
|
||||
// We only load this library once, so we let it be unloaded at application shutdown,
|
||||
// since unloading it early seems to cause issues on some systems.
|
||||
|
||||
static dylib_t dwmlib = NULL;
|
||||
|
||||
static void gfx_dwm_shutdown(void)
|
||||
{
|
||||
if (dwmlib)
|
||||
dylib_close(dwmlib);
|
||||
}
|
||||
|
||||
void gfx_set_dwm(void)
|
||||
{
|
||||
static bool inited = false;
|
||||
if (inited)
|
||||
return;
|
||||
inited = true;
|
||||
|
||||
dwmlib = dylib_load("dwmapi.dll");
|
||||
if (!dwmlib)
|
||||
{
|
||||
SSNES_LOG("Did not find dwmapi.dll");
|
||||
return;
|
||||
}
|
||||
atexit(gfx_dwm_shutdown);
|
||||
|
||||
HRESULT (WINAPI *mmcss)(BOOL) = (HRESULT (WINAPI*)(BOOL))dylib_proc(dwmlib, "DwmEnableMMCSS");
|
||||
if (mmcss)
|
||||
{
|
||||
SSNES_LOG("Setting multimedia scheduling for DWM.\n");
|
||||
mmcss(TRUE);
|
||||
}
|
||||
|
||||
if (!g_settings.video.disable_composition)
|
||||
return;
|
||||
|
||||
HRESULT (WINAPI *composition_enable)(UINT) = (HRESULT (WINAPI*)(UINT))dylib_proc(dwmlib, "DwmEnableComposition");
|
||||
if (!composition_enable)
|
||||
{
|
||||
SSNES_ERR("Did not find DwmEnableComposition ...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
HRESULT ret = composition_enable(0);
|
||||
if (FAILED(ret))
|
||||
SSNES_ERR("Failed to set composition state ...\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#include "SDL_syswm.h"
|
||||
#include "SDL.h"
|
||||
|
||||
#if !defined(__APPLE__) && !defined(_WIN32)
|
||||
void gfx_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
|
||||
@@ -22,5 +22,14 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
bool gfx_window_title(char *buf, size_t size);
|
||||
void gfx_window_title_reset(void);
|
||||
|
||||
#ifdef _WIN32
|
||||
void gfx_set_dwm(void);
|
||||
#endif
|
||||
|
||||
#if !defined(__APPLE__) && !defined(_WIN32)
|
||||
void gfx_get_window_size(unsigned *width, unsigned *height);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -52,13 +52,23 @@
|
||||
#include "fonts.h"
|
||||
#endif
|
||||
|
||||
static const GLfloat vertexes[] = {
|
||||
// Used for the last pass when rendering to the back buffer.
|
||||
static const GLfloat vertexes_flipped[] = {
|
||||
0, 0,
|
||||
0, 1,
|
||||
1, 1,
|
||||
1, 0
|
||||
};
|
||||
|
||||
// Used when rendering to an FBO.
|
||||
// Texture coords have to be aligned with vertex coordinates.
|
||||
static const GLfloat vertexes[] = {
|
||||
0, 1,
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1
|
||||
};
|
||||
|
||||
static const GLfloat tex_coords[] = {
|
||||
0, 1,
|
||||
0, 0,
|
||||
@@ -66,12 +76,6 @@ static const GLfloat tex_coords[] = {
|
||||
1, 1
|
||||
};
|
||||
|
||||
static const GLfloat fbo_tex_coords[] = {
|
||||
0, 0,
|
||||
0, 1,
|
||||
1, 1,
|
||||
1, 0
|
||||
};
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
#ifdef _WIN32
|
||||
@@ -81,7 +85,7 @@ static PFNGLFRAMEBUFFERTEXTURE2DPROC pglFramebufferTexture2D = NULL;
|
||||
static PFNGLCHECKFRAMEBUFFERSTATUSPROC pglCheckFramebufferStatus = NULL;
|
||||
static PFNGLDELETEFRAMEBUFFERSPROC pglDeleteFramebuffers = NULL;
|
||||
|
||||
#define LOAD_SYM(sym) if (!p##sym) p##sym = ((void*)SDL_GL_GetProcAddress(#sym))
|
||||
#define LOAD_SYM(sym) if (!p##sym) { SDL_SYM_WRAP(p##sym, #sym) }
|
||||
static bool load_fbo_proc(void)
|
||||
{
|
||||
LOAD_SYM(glGenFramebuffers);
|
||||
@@ -103,31 +107,31 @@ static bool load_fbo_proc(void) { return true; }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_XML) && defined(_WIN32)
|
||||
#if (defined(HAVE_XML) || defined(HAVE_CG)) && defined(_WIN32)
|
||||
PFNGLCLIENTACTIVETEXTUREPROC pglClientActiveTexture = NULL;
|
||||
PFNGLACTIVETEXTUREPROC pglActiveTexture = NULL;
|
||||
static inline bool load_gl_proc(void)
|
||||
{
|
||||
LOAD_SYM(glClientActiveTexture);
|
||||
LOAD_SYM(glActiveTexture);
|
||||
|
||||
return pglClientActiveTexture && pglActiveTexture;
|
||||
}
|
||||
#else
|
||||
#define pglClientActiveTexture glClientActiveTexture
|
||||
#define pglActiveTexture glActiveTexture
|
||||
static inline bool load_gl_proc(void) { return true; }
|
||||
#endif
|
||||
|
||||
|
||||
#define MAX_SHADERS 16
|
||||
|
||||
typedef struct gl
|
||||
{
|
||||
bool vsync;
|
||||
GLuint texture;
|
||||
GLuint texture[2];
|
||||
unsigned tex_index; // For use with PREV.
|
||||
struct gl_tex_info prev_info;
|
||||
GLuint tex_filter;
|
||||
|
||||
void *empty_buf;
|
||||
|
||||
unsigned frame_count;
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
@@ -143,6 +147,7 @@ typedef struct gl
|
||||
|
||||
bool should_resize;
|
||||
bool quitting;
|
||||
bool fullscreen;
|
||||
bool keep_aspect;
|
||||
|
||||
unsigned full_x, full_y;
|
||||
@@ -151,15 +156,15 @@ typedef struct gl
|
||||
unsigned win_height;
|
||||
unsigned vp_width, vp_out_width;
|
||||
unsigned vp_height, vp_out_height;
|
||||
unsigned last_width;
|
||||
unsigned last_height;
|
||||
unsigned last_width[2];
|
||||
unsigned last_height[2];
|
||||
unsigned tex_w, tex_h;
|
||||
GLfloat tex_coords[8];
|
||||
#ifdef HAVE_FBO
|
||||
GLfloat fbo_tex_coords[8];
|
||||
#endif
|
||||
|
||||
GLenum texture_type; // XBGR1555 or RGBA
|
||||
GLenum texture_type; // XBGR1555 or ARGB
|
||||
GLenum texture_fmt;
|
||||
unsigned base_size; // 2 or 4
|
||||
|
||||
@@ -251,14 +256,23 @@ static void gl_shader_set_proj_matrix(void)
|
||||
static void gl_shader_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count)
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt)
|
||||
{
|
||||
#ifdef HAVE_CG
|
||||
gl_cg_set_params(width, height, tex_width, tex_height, out_width, out_height, frame_count);
|
||||
gl_cg_set_params(width, height,
|
||||
tex_width, tex_height,
|
||||
out_width, out_height,
|
||||
frame_count, info, prev_info, fbo_info, fbo_info_cnt);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
gl_glsl_set_params(width, height, tex_width, tex_height, out_width, out_height, frame_count);
|
||||
gl_glsl_set_params(width, height,
|
||||
tex_width, tex_height,
|
||||
out_width, out_height,
|
||||
frame_count, info, prev_info, fbo_info, fbo_info_cnt);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -328,7 +342,7 @@ static inline void gl_init_font(gl_t *gl, const char *font_path, unsigned font_s
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
}
|
||||
else
|
||||
SSNES_WARN("Couldn't init font renderer with font \"%s\"...\n", font_path);
|
||||
@@ -485,7 +499,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);
|
||||
}
|
||||
|
||||
@@ -642,20 +656,86 @@ static void gl_render_msg(gl_t *gl, const char *msg)
|
||||
|
||||
// Go back to old rendering path.
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
glDisable(GL_BLEND);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void set_lut_texture_coords(const GLfloat *coords)
|
||||
{
|
||||
#if defined(HAVE_XML) || defined(HAVE_CG)
|
||||
// For texture images.
|
||||
pglClientActiveTexture(GL_TEXTURE1);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), coords);
|
||||
pglClientActiveTexture(GL_TEXTURE0);
|
||||
#else
|
||||
(void)coords;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void set_texture_coords(GLfloat *coords, GLfloat xamt, GLfloat yamt)
|
||||
{
|
||||
coords[1] = yamt;
|
||||
coords[4] = xamt;
|
||||
coords[6] = xamt;
|
||||
coords[7] = yamt;
|
||||
}
|
||||
|
||||
static void check_window(gl_t *gl)
|
||||
{
|
||||
SDL_Event event;
|
||||
|
||||
// Search for events we care about ...
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
gl->quitting = true;
|
||||
break;
|
||||
|
||||
case SDL_VIDEORESIZE:
|
||||
gl->should_resize = true;
|
||||
gl->win_width = event.resize.w;
|
||||
gl->win_height = event.resize.h;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(__APPLE__) && !defined(_WIN32)
|
||||
// Hack to workaround limitations in tiling WMs ...
|
||||
if (!gl->should_resize && !gl->fullscreen)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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++;
|
||||
|
||||
#if defined(HAVE_XML) || defined(HAVE_CG)
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
// Render to texture in first pass.
|
||||
if (gl->fbo_inited)
|
||||
@@ -711,7 +791,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
last_max_height = gl->fbo_rect[i].max_img_height;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
pglBindFramebuffer(GL_FRAMEBUFFER, gl->fbo[0]);
|
||||
gl->render_to_tex = true;
|
||||
set_viewport(gl, gl->fbo_rect[0].img_width, gl->fbo_rect[0].img_height, true);
|
||||
@@ -744,7 +824,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);
|
||||
@@ -758,7 +838,7 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
}
|
||||
|
||||
// Go back to what we're supposed to do, render to FBO #0 :D
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
pglBindFramebuffer(GL_FRAMEBUFFER, gl->fbo[0]);
|
||||
set_viewport(gl, gl->fbo_rect[0].img_width, gl->fbo_rect[0].img_height, true);
|
||||
}
|
||||
@@ -767,39 +847,56 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
#endif
|
||||
}
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl->vp_width, gl->vp_height, gl->frame_count);
|
||||
|
||||
if (width != gl->last_width || height != gl->last_height) // Res change. need to clear out texture.
|
||||
if ((width != gl->last_width[gl->tex_index] || height != gl->last_height[gl->tex_index]) && gl->empty_buf) // Res change. need to clear out texture.
|
||||
{
|
||||
gl->last_width = width;
|
||||
gl->last_height = height;
|
||||
gl->last_width[gl->tex_index] = width;
|
||||
gl->last_height[gl->tex_index] = height;
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(pitch));
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->tex_w);
|
||||
|
||||
// Can we pass NULL here, hmm?
|
||||
void *tmp = calloc(1, gl->tex_w * gl->tex_h * gl->base_size);
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0, 0, 0, gl->tex_w, gl->tex_h, gl->texture_type,
|
||||
gl->texture_fmt, tmp);
|
||||
free(tmp);
|
||||
gl->texture_fmt, gl->empty_buf);
|
||||
|
||||
GLfloat x = (GLfloat)width / gl->tex_w;
|
||||
GLfloat y = (GLfloat)height / gl->tex_h;
|
||||
GLfloat xamt = (GLfloat)width / gl->tex_w;
|
||||
GLfloat yamt = (GLfloat)height / gl->tex_h;
|
||||
|
||||
gl->tex_coords[1] = y;
|
||||
gl->tex_coords[4] = x;
|
||||
gl->tex_coords[6] = x;
|
||||
gl->tex_coords[7] = y;
|
||||
|
||||
//SSNES_LOG("Setting last rect: %ux%u\n", width, height);
|
||||
set_texture_coords(gl->tex_coords, xamt, yamt);
|
||||
}
|
||||
#if defined(HAVE_XML) || defined(HAVE_CG)
|
||||
else if (width != gl->last_width[1 - gl->tex_index] || height != gl->last_height[1 - gl->tex_index])
|
||||
{
|
||||
GLfloat xamt = (GLfloat)width / gl->tex_w;
|
||||
GLfloat yamt = (GLfloat)height / gl->tex_h;
|
||||
set_texture_coords(gl->tex_coords, xamt, yamt);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
// Need to preserve the "flipped" state when in FBO too to have
|
||||
// consistent texture coordinates.
|
||||
if (gl->render_to_tex)
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes);
|
||||
#endif
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch / gl->base_size);
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0, 0, 0, width, height, gl->texture_type,
|
||||
gl->texture_fmt, frame);
|
||||
|
||||
struct gl_tex_info tex_info = {
|
||||
.tex = gl->texture[gl->tex_index],
|
||||
.input_size = {width, height},
|
||||
.tex_size = {gl->tex_w, gl->tex_h}
|
||||
};
|
||||
struct gl_tex_info fbo_tex_info[MAX_SHADERS];
|
||||
unsigned fbo_tex_info_cnt = 0;
|
||||
memcpy(tex_info.coord, gl->tex_coords, sizeof(gl->tex_coords));
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl->vp_width, gl->vp_height, gl->frame_count,
|
||||
&tex_info, &gl->prev_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
@@ -811,20 +908,26 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
// It's kinda handy ... :)
|
||||
const struct gl_fbo_rect *prev_rect;
|
||||
const struct gl_fbo_rect *rect;
|
||||
struct gl_tex_info *fbo_info;
|
||||
|
||||
// Calculate viewports, texture coordinates etc, and render all passes from FBOs, to another FBO.
|
||||
for (int i = 1; i < gl->fbo_pass; i++)
|
||||
{
|
||||
prev_rect = &gl->fbo_rect[i - 1];
|
||||
rect = &gl->fbo_rect[i];
|
||||
fbo_info = &fbo_tex_info[i - 1];
|
||||
|
||||
GLfloat xamt = (GLfloat)prev_rect->img_width / prev_rect->width;
|
||||
GLfloat yamt = (GLfloat)prev_rect->img_height / prev_rect->height;
|
||||
|
||||
gl->fbo_tex_coords[3] = yamt;
|
||||
gl->fbo_tex_coords[4] = xamt;
|
||||
gl->fbo_tex_coords[5] = yamt;
|
||||
gl->fbo_tex_coords[6] = xamt;
|
||||
set_texture_coords(gl->fbo_tex_coords, xamt, yamt);
|
||||
|
||||
fbo_info->tex = gl->fbo_texture[i - 1];
|
||||
fbo_info->input_size[0] = prev_rect->img_width;
|
||||
fbo_info->input_size[1] = prev_rect->img_height;
|
||||
fbo_info->tex_size[0] = prev_rect->width;
|
||||
fbo_info->tex_size[1] = prev_rect->height;
|
||||
memcpy(fbo_info->coord, gl->fbo_tex_coords, sizeof(gl->fbo_tex_coords));
|
||||
|
||||
pglBindFramebuffer(GL_FRAMEBUFFER, gl->fbo[i]);
|
||||
gl_shader_use(i + 1);
|
||||
@@ -834,8 +937,14 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
|
||||
// Render to FBO with certain size.
|
||||
set_viewport(gl, rect->img_width, rect->img_height, true);
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height, prev_rect->width, prev_rect->height, gl->vp_width, gl->vp_height, gl->frame_count);
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height,
|
||||
prev_rect->width, prev_rect->height,
|
||||
gl->vp_width, gl->vp_height, gl->frame_count,
|
||||
&tex_info, &gl->prev_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
fbo_tex_info_cnt++;
|
||||
}
|
||||
|
||||
// Render our last FBO texture directly to screen.
|
||||
@@ -843,26 +952,34 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
GLfloat xamt = (GLfloat)prev_rect->img_width / prev_rect->width;
|
||||
GLfloat yamt = (GLfloat)prev_rect->img_height / prev_rect->height;
|
||||
|
||||
gl->fbo_tex_coords[3] = yamt;
|
||||
gl->fbo_tex_coords[4] = xamt;
|
||||
gl->fbo_tex_coords[5] = yamt;
|
||||
gl->fbo_tex_coords[6] = xamt;
|
||||
set_texture_coords(gl->fbo_tex_coords, xamt, yamt);
|
||||
|
||||
// Render our FBO texture to back buffer.
|
||||
pglBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
gl_shader_use(gl->fbo_pass + 1);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->fbo_texture[gl->fbo_pass - 1]);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
gl->render_to_tex = false;
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height, prev_rect->width, prev_rect->height, gl->vp_width, gl->vp_height, gl->frame_count);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->fbo_texture[gl->fbo_pass - 1]);
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height,
|
||||
prev_rect->width, prev_rect->height,
|
||||
gl->vp_width, gl->vp_height, gl->frame_count,
|
||||
&tex_info, &gl->prev_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_XML) || defined(HAVE_CG)
|
||||
memcpy(&gl->prev_info, &tex_info, sizeof(tex_info));
|
||||
gl->tex_index = 1 - gl->tex_index;
|
||||
#endif
|
||||
|
||||
if (msg)
|
||||
gl_render_msg(gl, msg);
|
||||
|
||||
@@ -882,7 +999,7 @@ static void gl_free(void *data)
|
||||
gl_shader_deinit();
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glDeleteTextures(1, &gl->texture);
|
||||
glDeleteTextures(2, gl->texture);
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
if (gl->fbo_inited)
|
||||
@@ -893,23 +1010,42 @@ static void gl_free(void *data)
|
||||
#endif
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
if (gl->empty_buf)
|
||||
free(gl->empty_buf);
|
||||
|
||||
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");
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN32)
|
||||
static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL;
|
||||
if (!wgl_swap_interval) wgl_swap_interval = (BOOL (APIENTRY*)(int)) SDL_GL_GetProcAddress("wglSwapIntervalEXT");
|
||||
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) glx_swap_interval = (int (*)(int))SDL_GL_GetProcAddress("glXSwapIntervalSGI");
|
||||
if (!glx_swap_interval) glx_swap_interval = (int (*)(int))SDL_GL_GetProcAddress("glXSwapIntervalMESA");
|
||||
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
|
||||
}
|
||||
@@ -917,6 +1053,10 @@ static void gl_set_nonblock_state(void *data, bool state)
|
||||
|
||||
static void* gl_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
gfx_set_dwm();
|
||||
#endif
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
return NULL;
|
||||
|
||||
@@ -932,20 +1072,16 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
if (!SDL_SetVideoMode(video->width, video->height, g_settings.video.force_16bit ? 16 : 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
|
||||
return NULL;
|
||||
|
||||
gfx_window_title_reset();
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
SDL_WM_SetCaption(buf, NULL);
|
||||
|
||||
// Remove that ugly mouse :D
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
int attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr);
|
||||
if (attr <= 0 && video->vsync)
|
||||
SSNES_WARN("GL VSync has not been enabled!\n");
|
||||
attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &attr);
|
||||
if (attr <= 0)
|
||||
SSNES_WARN("GL double buffer has not been enabled!\n");
|
||||
|
||||
#if defined(HAVE_XML) && defined(_WIN32)
|
||||
#if (defined(HAVE_XML) || defined(HAVE_CG)) && defined(_WIN32)
|
||||
// Win32 GL lib doesn't have some functions needed for XML shaders.
|
||||
// Need to load dynamically :(
|
||||
if (!load_gl_proc())
|
||||
@@ -962,6 +1098,20 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gl->vsync = video->vsync;
|
||||
gl->fullscreen = video->fullscreen;
|
||||
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->full_x = full_x;
|
||||
gl->full_y = full_y;
|
||||
|
||||
@@ -986,15 +1136,16 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SSNES_LOG("GL: Loaded %u programs(s).\n", gl_shader_num());
|
||||
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->vsync = video->vsync;
|
||||
|
||||
gl->keep_aspect = video->force_aspect;
|
||||
|
||||
// Apparently need to set viewport for passes when we aren't using FBOs.
|
||||
gl_shader_use(0);
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
gl_shader_use(1);
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
|
||||
@@ -1004,8 +1155,8 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
else
|
||||
gl->tex_filter = video->smooth ? GL_LINEAR : GL_NEAREST;
|
||||
|
||||
gl->texture_type = video->rgb32 ? GL_RGBA : GL_BGRA;
|
||||
gl->texture_fmt = video->rgb32 ? GL_UNSIGNED_INT_8_8_8_8 : GL_UNSIGNED_SHORT_1_5_5_5_REV;
|
||||
gl->texture_type = GL_BGRA;
|
||||
gl->texture_fmt = video->rgb32 ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_SHORT_1_5_5_5_REV;
|
||||
gl->base_size = video->rgb32 ? sizeof(uint32_t) : sizeof(uint16_t);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
@@ -1014,56 +1165,64 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
glColor4f(1, 1, 1, 1);
|
||||
glClearColor(0, 0, 0, 1);
|
||||
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
SDL_WM_SetCaption(buf, NULL);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
glGenTextures(1, &gl->texture);
|
||||
glGenTextures(2, gl->texture);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[i]);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl->tex_filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl->tex_filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl->tex_filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl->tex_filter);
|
||||
}
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
|
||||
|
||||
memcpy(gl->tex_coords, tex_coords, sizeof(tex_coords));
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
|
||||
|
||||
#ifdef HAVE_XML
|
||||
// For texture images.
|
||||
pglClientActiveTexture(GL_TEXTURE1);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), tex_coords);
|
||||
pglClientActiveTexture(GL_TEXTURE0);
|
||||
#endif
|
||||
set_lut_texture_coords(tex_coords);
|
||||
|
||||
gl->tex_w = 256 * video->input_scale;
|
||||
gl->tex_h = 256 * video->input_scale;
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, gl->tex_w, gl->tex_h, 0, gl->texture_type,
|
||||
gl->texture_fmt, NULL);
|
||||
// Empty buffer that we use to clear out the texture with on res change.
|
||||
gl->empty_buf = calloc(gl->tex_w * gl->tex_h, gl->base_size);
|
||||
|
||||
gl->last_width = gl->tex_w;
|
||||
gl->last_height = gl->tex_h;
|
||||
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);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
gl->last_width[i] = gl->tex_w;
|
||||
gl->last_height[i] = gl->tex_h;
|
||||
}
|
||||
|
||||
gl->prev_info.tex = gl->texture[1 - gl->tex_index];
|
||||
gl->prev_info.input_size[0] = gl->tex_w;
|
||||
gl->prev_info.tex_size[0] = gl->tex_w;
|
||||
gl->prev_info.input_size[1] = gl->tex_h;
|
||||
gl->prev_info.tex_size[1] = gl->tex_h;
|
||||
memcpy(gl->prev_info.coord, tex_coords, sizeof(tex_coords));
|
||||
|
||||
// Hook up SDL input driver to get SDL_QUIT events and RESIZE.
|
||||
sdl_input_t *sdl_input = input_sdl.init();
|
||||
if (sdl_input)
|
||||
{
|
||||
sdl_input->quitting = &gl->quitting;
|
||||
sdl_input->should_resize = &gl->should_resize;
|
||||
sdl_input->new_width = &gl->win_width;
|
||||
sdl_input->new_height = &gl->win_height;
|
||||
*input = &input_sdl;
|
||||
*input_data = sdl_input;
|
||||
}
|
||||
@@ -1071,7 +1230,7 @@ static void* gl_init(const video_info_t *video, const input_driver_t **input, vo
|
||||
*input = NULL;
|
||||
|
||||
gl_init_font(gl, g_settings.video.font_path, g_settings.video.font_size);
|
||||
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
@@ -1098,7 +1257,13 @@ static bool gl_focus(void *data)
|
||||
static bool gl_xml_shader(void *data, const char *path)
|
||||
{
|
||||
gl_t *gl = data;
|
||||
gl_shader_deinit();
|
||||
|
||||
//if (!gl_check_error())
|
||||
// SSNES_WARN("Error happened before deinit!\n");
|
||||
|
||||
|
||||
//if (!gl_check_error())
|
||||
// SSNES_WARN("Error happened in deinit!\n");
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
if (gl->fbo_inited)
|
||||
@@ -1111,13 +1276,15 @@ static bool gl_xml_shader(void *data, const char *path)
|
||||
gl->render_to_tex = false;
|
||||
gl->fbo_pass = 0;
|
||||
|
||||
if (!gl_check_error())
|
||||
SSNES_WARN("Failed to deinit FBO properly!\n");
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
|
||||
}
|
||||
#endif
|
||||
|
||||
gl_shader_deinit();
|
||||
|
||||
//if (!gl_check_error())
|
||||
// SSNES_WARN("Failed to deinit rendering path properly!\n");
|
||||
|
||||
if (!gl_glsl_init(path))
|
||||
return false;
|
||||
|
||||
@@ -1125,6 +1292,8 @@ static bool gl_xml_shader(void *data, const char *path)
|
||||
gl_init_fbo(gl, gl->tex_w, gl->tex_h);
|
||||
|
||||
// Apparently need to set viewport for passes when we aren't using FBOs.
|
||||
gl_shader_use(0);
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
gl_shader_use(1);
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
|
||||
|
||||
+16
-1
@@ -98,8 +98,23 @@ struct gl_fbo_scale
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct gl_tex_info
|
||||
{
|
||||
GLuint tex;
|
||||
GLfloat input_size[2];
|
||||
GLfloat tex_size[2];
|
||||
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(_WIN32)
|
||||
#if (defined(HAVE_XML) || defined(HAVE_CG)) && defined(_WIN32)
|
||||
extern PFNGLCLIENTACTIVETEXTUREPROC pglClientActiveTexture;
|
||||
extern PFNGLACTIVETEXTUREPROC pglActiveTexture;
|
||||
#else
|
||||
|
||||
+61
-20
@@ -27,35 +27,76 @@
|
||||
#include <stddef.h>
|
||||
#include "general.h"
|
||||
|
||||
#ifdef HAVE_IMLIB
|
||||
#ifdef HAVE_SDL_IMAGE
|
||||
|
||||
#include <Imlib2.h>
|
||||
#include "SDL_image.h"
|
||||
bool texture_image_load(const char *path, struct texture_image *out_img)
|
||||
{
|
||||
Imlib_Image img;
|
||||
img = imlib_load_image(path);
|
||||
SDL_Surface *img = IMG_Load(path);
|
||||
if (!img)
|
||||
return false;
|
||||
|
||||
imlib_context_set_image(img);
|
||||
|
||||
out_img->width = imlib_image_get_width();
|
||||
out_img->height = imlib_image_get_height();
|
||||
out_img->width = img->w;
|
||||
out_img->height = img->h;
|
||||
|
||||
size_t size = out_img->width * out_img->height * sizeof(uint32_t);
|
||||
out_img->pixels = malloc(size);
|
||||
if (!out_img->pixels)
|
||||
{
|
||||
imlib_free_image();
|
||||
SDL_FreeSurface(img);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t *read = imlib_image_get_data_for_reading_only();
|
||||
// Convert ARGB -> RGBA.
|
||||
for (unsigned i = 0; i < size / sizeof(uint32_t); i++)
|
||||
out_img->pixels[i] = (read[i] >> 24) | (read[i] << 8);
|
||||
const SDL_PixelFormat *fmt = img->format;
|
||||
|
||||
SSNES_LOG("SDL_image: %dx%d @ %d bpp\n", img->w, img->h, img->format->BitsPerPixel);
|
||||
if (img->format->BitsPerPixel == 32)
|
||||
{
|
||||
for (unsigned y = 0; y < img->h; y++)
|
||||
{
|
||||
uint32_t *dst = out_img->pixels + y * img->w;
|
||||
const uint32_t *src = (const uint32_t*)img->pixels + y * img->pitch / sizeof(uint32_t);
|
||||
|
||||
for (unsigned x = 0; x < img->w; x++)
|
||||
{
|
||||
uint32_t r = (src[x] & fmt->Rmask) >> fmt->Rshift;
|
||||
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] = (b << 24) | (g << 16) | (r << 8) | a;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (img->format->BitsPerPixel == 24)
|
||||
{
|
||||
for (unsigned y = 0; y < img->h; y++)
|
||||
{
|
||||
uint32_t *dst = out_img->pixels + y * img->w;
|
||||
const uint8_t *src = (const uint8_t*)img->pixels + y * img->pitch;
|
||||
|
||||
for (unsigned x = 0; x < img->w; x++)
|
||||
{
|
||||
// Correct?
|
||||
uint32_t color = 0;
|
||||
color |= src[3 * x + 0] << 0;
|
||||
color |= src[3 * x + 1] << 8;
|
||||
color |= src[3 * x + 2] << 16;
|
||||
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] = (b << 24) | (g << 16) | (r << 8) | 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_ERR("8-bit and 16-bit image support are not implemented.\n");
|
||||
SDL_FreeSurface(img);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_FreeSurface(img);
|
||||
|
||||
imlib_free_image();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -107,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
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
/* 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 <Python.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "dynamic.h"
|
||||
#include "libsnes.hpp"
|
||||
#include "py_state.h"
|
||||
#include "general.h"
|
||||
#include "strl.h"
|
||||
|
||||
#define PY_READ_FUNC_DECL(RAMTYPE) py_read_##RAMTYPE
|
||||
#define PY_READ_FUNC(RAMTYPE) \
|
||||
static PyObject* PY_READ_FUNC_DECL(RAMTYPE) (PyObject *self, PyObject *args) \
|
||||
{ \
|
||||
(void)self; \
|
||||
\
|
||||
const uint8_t *data = psnes_get_memory_data(SNES_MEMORY_##RAMTYPE); \
|
||||
if (!data) \
|
||||
{ \
|
||||
Py_INCREF(Py_None); \
|
||||
return Py_None; \
|
||||
} \
|
||||
unsigned max = psnes_get_memory_size(SNES_MEMORY_##RAMTYPE); \
|
||||
\
|
||||
unsigned addr; \
|
||||
if (!PyArg_ParseTuple(args, "I", &addr)) \
|
||||
return NULL; \
|
||||
\
|
||||
if (addr >= max || addr < 0) \
|
||||
{ \
|
||||
Py_INCREF(Py_None); \
|
||||
return Py_None; \
|
||||
} \
|
||||
\
|
||||
return PyLong_FromLong((long)data[addr]); \
|
||||
}
|
||||
|
||||
PY_READ_FUNC(WRAM)
|
||||
PY_READ_FUNC(VRAM)
|
||||
PY_READ_FUNC(APURAM)
|
||||
PY_READ_FUNC(CGRAM)
|
||||
PY_READ_FUNC(OAM)
|
||||
|
||||
static PyObject* py_read_input(PyObject *self, PyObject *args)
|
||||
{
|
||||
(void)self;
|
||||
|
||||
if (!driver.input_data)
|
||||
return PyBool_FromLong(0);
|
||||
|
||||
unsigned player;
|
||||
unsigned key;
|
||||
if (!PyArg_ParseTuple(args, "II", &player, &key))
|
||||
return NULL;
|
||||
|
||||
if (player > MAX_PLAYERS || player < 1)
|
||||
return NULL;
|
||||
|
||||
const struct snes_keybind *binds[MAX_PLAYERS];
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
binds[i] = g_settings.input.binds[i];
|
||||
|
||||
int16_t res = driver.input->input_state(driver.input_data,
|
||||
binds, player > 1,
|
||||
player > 2 ? SNES_DEVICE_MULTITAP : SNES_DEVICE_JOYPAD,
|
||||
player > 2 ? player - 2 : 0,
|
||||
key);
|
||||
|
||||
return PyBool_FromLong((long)res);
|
||||
}
|
||||
|
||||
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." },
|
||||
{ "read_apuram", PY_READ_FUNC_DECL(APURAM), METH_VARARGS, "Read APURAM from SNES." },
|
||||
{ "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." },
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
static PyModuleDef SNESModule = {
|
||||
PyModuleDef_HEAD_INIT, "snes", NULL, -1, SNESMethods,
|
||||
NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
static PyObject* PyInit_SNES(void)
|
||||
{
|
||||
PyObject *mod = PyModule_Create(&SNESModule);
|
||||
if (!mod)
|
||||
return NULL;
|
||||
|
||||
py_set_attrs(mod);
|
||||
return mod;
|
||||
}
|
||||
|
||||
struct py_state
|
||||
{
|
||||
PyObject *main;
|
||||
PyObject *dict;
|
||||
PyObject *inst;
|
||||
|
||||
bool warned_ret;
|
||||
bool warned_type;
|
||||
};
|
||||
|
||||
static char *dupe_newline(const char *str)
|
||||
{
|
||||
if (!str)
|
||||
return NULL;
|
||||
|
||||
unsigned size = strlen(str) + 2;
|
||||
char *ret = malloc(size);
|
||||
if (!ret)
|
||||
return NULL;
|
||||
|
||||
strlcpy(ret, str, size);
|
||||
ret[size - 2] = '\n';
|
||||
ret[size - 1] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Need to make sure that first-line indentation is 0. :(
|
||||
static char* align_program(const char *program)
|
||||
{
|
||||
char *prog = strdup(program);
|
||||
if (!prog)
|
||||
return NULL;
|
||||
|
||||
size_t prog_size = strlen(program) + 1;
|
||||
char *new_prog = calloc(1, prog_size);
|
||||
if (!new_prog)
|
||||
return NULL;
|
||||
|
||||
char *line = dupe_newline(strtok(prog, "\n"));
|
||||
if (!line)
|
||||
{
|
||||
free(prog);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned skip_chars = 0;
|
||||
while (isblank(line[skip_chars]) && line[skip_chars])
|
||||
skip_chars++;
|
||||
|
||||
while (line)
|
||||
{
|
||||
unsigned length = strlen(line);
|
||||
unsigned skip_len = skip_chars > length ? length : skip_chars;
|
||||
|
||||
strlcat(new_prog, line + skip_len, prog_size);
|
||||
|
||||
free(line);
|
||||
line = dupe_newline(strtok(NULL, "\n"));
|
||||
}
|
||||
|
||||
free(prog);
|
||||
|
||||
return new_prog;
|
||||
}
|
||||
|
||||
py_state_t *py_state_new(const char *script, bool is_file, const char *pyclass)
|
||||
{
|
||||
PyImport_AppendInittab("snes", &PyInit_SNES);
|
||||
Py_Initialize();
|
||||
|
||||
py_state_t *handle = calloc(1, sizeof(*handle));
|
||||
|
||||
handle->main = PyImport_AddModule("__main__");
|
||||
if (!handle->main)
|
||||
goto error;
|
||||
|
||||
if (is_file)
|
||||
{
|
||||
FILE *file = fopen(script, "r");
|
||||
if (!file)
|
||||
goto error;
|
||||
PyRun_SimpleFile(file, script);
|
||||
fclose(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
char *script_ = align_program(script);
|
||||
if (script_)
|
||||
{
|
||||
PyRun_SimpleString(script_);
|
||||
free(script_);
|
||||
}
|
||||
}
|
||||
|
||||
handle->dict = PyModule_GetDict(handle->main);
|
||||
if (!handle->dict)
|
||||
goto error;
|
||||
|
||||
PyObject *hook = PyDict_GetItemString(handle->dict, pyclass);
|
||||
if (!hook)
|
||||
goto error;
|
||||
|
||||
handle->inst = PyObject_CallFunction(hook, NULL);
|
||||
if (!handle->inst)
|
||||
goto error;
|
||||
|
||||
return handle;
|
||||
|
||||
error:
|
||||
PyErr_Print();
|
||||
py_state_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void py_state_free(py_state_t *handle)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
if (handle->main)
|
||||
Py_DECREF(handle->main);
|
||||
if (handle->dict)
|
||||
Py_DECREF(handle->dict);
|
||||
if (handle->inst)
|
||||
Py_DECREF(handle->inst);
|
||||
|
||||
free(handle);
|
||||
}
|
||||
|
||||
Py_Finalize();
|
||||
}
|
||||
|
||||
int 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);
|
||||
if (!ret)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
Py_DECREF(ret);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/* 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_PY_STATE_H
|
||||
#define __SSNES_PY_STATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
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,
|
||||
const char *id, unsigned frame_count);
|
||||
|
||||
#endif
|
||||
@@ -92,6 +92,9 @@ static void sdl_init_font(sdl_video_t *vid, const char *font_path, unsigned font
|
||||
static void sdl_render_msg_15(sdl_video_t *vid, SDL_Surface *buffer, const char *msg, unsigned width, unsigned height, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (!vid->font)
|
||||
return;
|
||||
|
||||
struct font_output_list out;
|
||||
font_renderer_msg(vid->font, msg, &out);
|
||||
struct font_output *head = out.head;
|
||||
@@ -150,6 +153,9 @@ static void sdl_render_msg_15(sdl_video_t *vid, SDL_Surface *buffer, const char
|
||||
static void sdl_render_msg_32(sdl_video_t *vid, SDL_Surface *buffer, const char *msg, unsigned width, unsigned height, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (!vid->font)
|
||||
return;
|
||||
|
||||
struct font_output_list out;
|
||||
font_renderer_msg(vid->font, msg, &out);
|
||||
struct font_output *head = out.head;
|
||||
@@ -207,6 +213,10 @@ static void sdl_render_msg_32(sdl_video_t *vid, SDL_Surface *buffer, const char
|
||||
|
||||
static void* sdl_gfx_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
gfx_set_dwm();
|
||||
#endif
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
sdl_video_t *vid = calloc(1, sizeof(*vid));
|
||||
@@ -264,7 +274,6 @@ static void* sdl_gfx_init(const video_info_t *video, const input_driver_t **inpu
|
||||
sdl_input_t *sdl_input = input_sdl.init();
|
||||
if (sdl_input)
|
||||
{
|
||||
sdl_input->quitting = &vid->quitting;
|
||||
*input = &input_sdl;
|
||||
*input_data = sdl_input;
|
||||
}
|
||||
@@ -287,7 +296,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 == 24 && fmt->Gshift == 16 && fmt->Bshift == 8)
|
||||
if (fmt->Rshift == 16 && fmt->Gshift == 8 && fmt->Bshift == 0)
|
||||
{
|
||||
SSNES_LOG("SDL: 32-bit format matches. Fast blit.\n");
|
||||
vid->convert_32_func = convert_32bit_32bit_direct;
|
||||
@@ -307,9 +316,9 @@ error:
|
||||
|
||||
static inline uint16_t conv_pixel_32_15(uint32_t pix, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
uint16_t r = ((pix & 0xf8000000) >> 27) << fmt->Rshift;
|
||||
uint16_t g = ((pix & 0x00f80000) >> 19) << fmt->Gshift;
|
||||
uint16_t b = ((pix & 0x0000f800) >> 11) << fmt->Bshift;
|
||||
uint16_t r = ((pix & 0x00f80000) >> 19) << fmt->Rshift;
|
||||
uint16_t g = ((pix & 0x0000f800) >> 11) << fmt->Gshift;
|
||||
uint16_t b = ((pix & 0x000000f8) >> 3) << fmt->Bshift;
|
||||
return r | g | b;
|
||||
}
|
||||
|
||||
@@ -393,17 +402,35 @@ static void convert_32bit_32bit_shift(uint32_t *out, unsigned outpitch, const ui
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint32_t color = src[x];
|
||||
uint32_t r = ((color >> 24) & 0xff) << fmt->Rshift;
|
||||
uint32_t g = ((color >> 16) & 0xff) << fmt->Gshift;
|
||||
uint32_t b = ((color >> 8) & 0xff) << fmt->Bshift;
|
||||
uint32_t r = ((color >> 16) & 0xff) << fmt->Rshift;
|
||||
uint32_t g = ((color >> 8) & 0xff) << fmt->Gshift;
|
||||
uint32_t b = ((color >> 0) & 0xff) << fmt->Bshift;
|
||||
dest[x] = r | g | b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_window(sdl_video_t *vid)
|
||||
{
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
vid->quitting = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
+869
-75
File diff suppressed because it is too large.
Load diff
+4
-1
@@ -31,7 +31,10 @@ void gl_cg_set_proj_matrix(void);
|
||||
void gl_cg_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count);
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt);
|
||||
|
||||
void gl_cg_use(unsigned index);
|
||||
|
||||
|
||||
+462
-38
@@ -20,6 +20,8 @@
|
||||
#include "general.h"
|
||||
#include "shader_glsl.h"
|
||||
#include "strl.h"
|
||||
#include "snes_state.h"
|
||||
#include "dynamic.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl.h>
|
||||
@@ -61,6 +63,9 @@
|
||||
#define pglGetProgramInfoLog glGetProgramInfoLog
|
||||
#define pglDeleteProgram glDeleteProgram
|
||||
#define pglGetAttachedShaders glGetAttachedShaders
|
||||
#define pglGetAttribLocation glGetAttribLocation
|
||||
#define pglEnableVertexAttribArray glEnableVertexAttribArray
|
||||
#define pglVertexAttribPointer glVertexAttribPointer
|
||||
#else
|
||||
static PFNGLCREATEPROGRAMPROC pglCreateProgram = NULL;
|
||||
static PFNGLUSEPROGRAMPROC pglUseProgram = NULL;
|
||||
@@ -81,10 +86,14 @@ static PFNGLGETPROGRAMIVPROC pglGetProgramiv = NULL;
|
||||
static PFNGLGETPROGRAMINFOLOGPROC pglGetProgramInfoLog = NULL;
|
||||
static PFNGLDELETEPROGRAMPROC pglDeleteProgram = NULL;
|
||||
static PFNGLGETATTACHEDSHADERSPROC pglGetAttachedShaders = NULL;
|
||||
static PFNGLGETATTRIBLOCATIONPROC pglGetAttribLocation = NULL;
|
||||
static PFNGLENABLEVERTEXATTRIBARRAYPROC pglEnableVertexAttribArray = NULL;
|
||||
static PFNGLVERTEXATTRIBPOINTERPROC pglVertexAttribPointer = NULL;
|
||||
#endif
|
||||
|
||||
#define MAX_PROGRAMS 16
|
||||
#define MAX_TEXTURES 8
|
||||
#define MAX_VARIABLES 256
|
||||
|
||||
enum filter_type
|
||||
{
|
||||
@@ -104,6 +113,13 @@ static GLuint gl_teximage[MAX_TEXTURES];
|
||||
static unsigned gl_teximage_cnt = 0;
|
||||
static char gl_teximage_uniforms[MAX_TEXTURES][64];
|
||||
|
||||
static snes_tracker_t *gl_snes_tracker = NULL;
|
||||
static struct snes_tracker_uniform_info gl_tracker_info[MAX_VARIABLES];
|
||||
static unsigned gl_tracker_info_cnt = 0;
|
||||
static char gl_tracker_script[256];
|
||||
static char gl_tracker_script_class[64];
|
||||
static xmlChar *gl_script_program = NULL;
|
||||
|
||||
struct shader_program
|
||||
{
|
||||
char *vertex;
|
||||
@@ -340,7 +356,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);
|
||||
@@ -365,6 +381,208 @@ error:
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
static bool get_script(const char *path, xmlNodePtr ptr)
|
||||
{
|
||||
if (*gl_tracker_script || gl_script_program)
|
||||
{
|
||||
SSNES_ERR("Script already imported.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
xmlChar *script_class = xmlGetProp(ptr, (const xmlChar*)"class");
|
||||
if (script_class)
|
||||
{
|
||||
strlcpy(gl_tracker_script_class, (const char*)script_class, sizeof(gl_tracker_script_class));
|
||||
xmlFree(script_class);
|
||||
}
|
||||
|
||||
xmlChar *language = xmlGetProp(ptr, (const xmlChar*)"language");
|
||||
if (!language || strcmp((const char*)language, "python") != 0)
|
||||
{
|
||||
SSNES_ERR("Script language is not Python!\n");
|
||||
if (language)
|
||||
xmlFree(language);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (language)
|
||||
xmlFree(language);
|
||||
|
||||
xmlChar *src = xmlGetProp(ptr, (const xmlChar*)"src");
|
||||
if (src)
|
||||
{
|
||||
strlcpy(gl_tracker_script, path, sizeof(gl_tracker_script));
|
||||
char *dir_ptr = strrchr(gl_tracker_script, '/');
|
||||
if (!dir_ptr) dir_ptr = strrchr(gl_tracker_script, '\\');
|
||||
if (dir_ptr) dir_ptr[1] = '\0';
|
||||
strlcat(gl_tracker_script, (const char*)src, sizeof(gl_tracker_script));
|
||||
|
||||
xmlFree(src);
|
||||
}
|
||||
else
|
||||
{
|
||||
xmlChar *script = xmlNodeGetContent(ptr);
|
||||
if (!script)
|
||||
{
|
||||
SSNES_ERR("No content in script!\n");
|
||||
return false;
|
||||
}
|
||||
gl_script_program = script;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool get_import_value(xmlNodePtr ptr)
|
||||
{
|
||||
if (gl_tracker_info_cnt >= MAX_VARIABLES)
|
||||
{
|
||||
SSNES_ERR("Too many import variables ...\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
xmlChar *id = xmlGetProp(ptr, (const xmlChar*)"id");
|
||||
xmlChar *semantic = xmlGetProp(ptr, (const xmlChar*)"semantic");
|
||||
xmlChar *wram = xmlGetProp(ptr, (const xmlChar*)"wram");
|
||||
xmlChar *input = xmlGetProp(ptr, (const xmlChar*)"input_slot");
|
||||
xmlChar *apuram = xmlGetProp(ptr, (const xmlChar*)"apuram");
|
||||
xmlChar *vram = xmlGetProp(ptr, (const xmlChar*)"vram");
|
||||
xmlChar *oam = xmlGetProp(ptr, (const xmlChar*)"oam");
|
||||
xmlChar *cgram = xmlGetProp(ptr, (const xmlChar*)"cgram");
|
||||
xmlChar *bitmask = xmlGetProp(ptr, (const xmlChar*)"mask");
|
||||
|
||||
if (!semantic || !id)
|
||||
{
|
||||
SSNES_ERR("No semantic or ID for import value!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
enum snes_tracker_type tracker_type;
|
||||
|
||||
if (strcmp((const char*)semantic, "capture") == 0)
|
||||
tracker_type = SSNES_STATE_CAPTURE;
|
||||
else if (strcmp((const char*)semantic, "capture_previous") == 0)
|
||||
tracker_type = SSNES_STATE_CAPTURE_PREV;
|
||||
else if (strcmp((const char*)semantic, "transition") == 0)
|
||||
tracker_type = SSNES_STATE_TRANSITION;
|
||||
else if (strcmp((const char*)semantic, "transition_count") == 0)
|
||||
tracker_type = SSNES_STATE_TRANSITION_COUNT;
|
||||
else if (strcmp((const char*)semantic, "transition_previous") == 0)
|
||||
tracker_type = SSNES_STATE_TRANSITION_PREV;
|
||||
#ifdef HAVE_PYTHON
|
||||
else if (strcmp((const char*)semantic, "python") == 0)
|
||||
tracker_type = SSNES_STATE_PYTHON;
|
||||
#endif
|
||||
else
|
||||
{
|
||||
SSNES_ERR("Invalid semantic for import value.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
enum snes_ram_type ram_type = SSNES_STATE_WRAM;
|
||||
uint32_t addr = 0;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (tracker_type != SSNES_STATE_PYTHON)
|
||||
{
|
||||
#endif
|
||||
if (input)
|
||||
{
|
||||
unsigned slot = strtoul((const char*)input, NULL, 0);
|
||||
switch (slot)
|
||||
{
|
||||
case 1:
|
||||
ram_type = SSNES_STATE_INPUT_SLOT1;
|
||||
break;
|
||||
case 2:
|
||||
ram_type = SSNES_STATE_INPUT_SLOT2;
|
||||
break;
|
||||
|
||||
default:
|
||||
SSNES_ERR("Invalid input slot for import!\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else if (wram) { addr = strtoul((const char*)wram, NULL, 16); ram_type = SSNES_STATE_WRAM; }
|
||||
else if (apuram) { addr = strtoul((const char*)apuram, NULL, 16); ram_type = SSNES_STATE_APURAM; }
|
||||
else if (vram) { addr = strtoul((const char*)vram, NULL, 16); ram_type = SSNES_STATE_VRAM; }
|
||||
else if (oam) { addr = strtoul((const char*)oam, NULL, 16); ram_type = SSNES_STATE_OAM; }
|
||||
else if (cgram) { addr = strtoul((const char*)cgram, NULL, 16); ram_type = SSNES_STATE_CGRAM; }
|
||||
else
|
||||
{
|
||||
SSNES_ERR("No RAM address specificed for import value.\n");
|
||||
goto error;
|
||||
}
|
||||
#ifdef HAVE_PYTHON
|
||||
}
|
||||
#endif
|
||||
|
||||
int memtype = 0;
|
||||
switch (ram_type)
|
||||
{
|
||||
case SSNES_STATE_WRAM:
|
||||
memtype = SNES_MEMORY_WRAM;
|
||||
break;
|
||||
case SSNES_STATE_APURAM:
|
||||
memtype = SNES_MEMORY_APURAM;
|
||||
break;
|
||||
case SSNES_STATE_VRAM:
|
||||
memtype = SNES_MEMORY_VRAM;
|
||||
break;
|
||||
case SSNES_STATE_OAM:
|
||||
memtype = SNES_MEMORY_OAM;
|
||||
break;
|
||||
case SSNES_STATE_CGRAM:
|
||||
memtype = SNES_MEMORY_CGRAM;
|
||||
break;
|
||||
|
||||
default:
|
||||
memtype = SNES_MEMORY_WRAM;
|
||||
}
|
||||
|
||||
if (addr >= psnes_get_memory_size(memtype))
|
||||
{
|
||||
SSNES_ERR("Address out of bounds.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
unsigned mask_value = 0;
|
||||
if (bitmask)
|
||||
mask_value = strtoul((const char*)bitmask, NULL, 16);
|
||||
|
||||
strlcpy(gl_tracker_info[gl_tracker_info_cnt].id, (const char*)id, sizeof(gl_tracker_info[0].id));
|
||||
gl_tracker_info[gl_tracker_info_cnt].addr = addr;
|
||||
gl_tracker_info[gl_tracker_info_cnt].type = tracker_type;
|
||||
gl_tracker_info[gl_tracker_info_cnt].ram_type = ram_type;
|
||||
gl_tracker_info[gl_tracker_info_cnt].mask = mask_value;
|
||||
gl_tracker_info_cnt++;
|
||||
|
||||
if (id) xmlFree(id);
|
||||
if (semantic) xmlFree(semantic);
|
||||
if (wram) xmlFree(wram);
|
||||
if (input) xmlFree(input);
|
||||
if (apuram) xmlFree(apuram);
|
||||
if (vram) xmlFree(vram);
|
||||
if (oam) xmlFree(oam);
|
||||
if (cgram) xmlFree(cgram);
|
||||
if (bitmask) xmlFree(bitmask);
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (id) xmlFree(id);
|
||||
if (semantic) xmlFree(semantic);
|
||||
if (wram) xmlFree(wram);
|
||||
if (input) xmlFree(input);
|
||||
if (apuram) xmlFree(apuram);
|
||||
if (vram) xmlFree(vram);
|
||||
if (oam) xmlFree(oam);
|
||||
if (cgram) xmlFree(cgram);
|
||||
if (bitmask) xmlFree(bitmask);
|
||||
return false;
|
||||
}
|
||||
|
||||
static unsigned get_xml_shaders(const char *path, struct shader_program *prog, size_t size)
|
||||
{
|
||||
LIBXML_TEST_VERSION;
|
||||
@@ -454,6 +672,24 @@ static unsigned get_xml_shaders(const char *path, struct shader_program *prog, s
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else if (strcmp((const char*)cur->name, "import") == 0)
|
||||
{
|
||||
if (!get_import_value(cur))
|
||||
{
|
||||
SSNES_ERR("Import value is invalid.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_PYTHON
|
||||
else if (strcmp((const char*)cur->name, "script") == 0)
|
||||
{
|
||||
if (!get_script(path, cur))
|
||||
{
|
||||
SSNES_ERR("Script is invalid.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (num == 0)
|
||||
@@ -476,8 +712,8 @@ error:
|
||||
|
||||
static void print_shader_log(GLuint obj)
|
||||
{
|
||||
int info_len = 0;
|
||||
int max_len;
|
||||
GLint info_len = 0;
|
||||
GLint max_len;
|
||||
|
||||
pglGetShaderiv(obj, GL_INFO_LOG_LENGTH, &max_len);
|
||||
|
||||
@@ -490,8 +726,8 @@ static void print_shader_log(GLuint obj)
|
||||
|
||||
static void print_linker_log(GLuint obj)
|
||||
{
|
||||
int info_len = 0;
|
||||
int max_len;
|
||||
GLint info_len = 0;
|
||||
GLint max_len;
|
||||
|
||||
pglGetProgramiv(obj, GL_INFO_LOG_LENGTH, &max_len);
|
||||
|
||||
@@ -502,13 +738,42 @@ static void print_linker_log(GLuint obj)
|
||||
SSNES_LOG("Linker log: %s\n", info_log);
|
||||
}
|
||||
|
||||
static bool compile_shader(GLuint shader, const char *program)
|
||||
{
|
||||
pglShaderSource(shader, 1, &program, 0);
|
||||
pglCompileShader(shader);
|
||||
|
||||
GLint status;
|
||||
pglGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
print_shader_log(shader);
|
||||
|
||||
return status == GL_TRUE;
|
||||
}
|
||||
|
||||
static bool link_program(GLuint prog)
|
||||
{
|
||||
pglLinkProgram(prog);
|
||||
|
||||
GLint status;
|
||||
pglGetProgramiv(prog, GL_LINK_STATUS, &status);
|
||||
print_linker_log(prog);
|
||||
|
||||
if (status == GL_TRUE)
|
||||
{
|
||||
pglUseProgram(prog);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool compile_programs(GLuint *gl_prog, struct shader_program *progs, size_t num)
|
||||
{
|
||||
for (unsigned i = 0; i < num; i++)
|
||||
{
|
||||
gl_prog[i] = pglCreateProgram();
|
||||
|
||||
if (!gl_check_error() || gl_prog[i] == 0)
|
||||
if (gl_prog[i] == 0)
|
||||
{
|
||||
SSNES_ERR("Failed to create GL program #%u.\n", i);
|
||||
return false;
|
||||
@@ -518,61 +783,49 @@ static bool compile_programs(GLuint *gl_prog, struct shader_program *progs, size
|
||||
{
|
||||
SSNES_LOG("Found GLSL vertex shader.\n");
|
||||
GLuint shader = pglCreateShader(GL_VERTEX_SHADER);
|
||||
pglShaderSource(shader, 1, (const char**)&progs[i].vertex, 0);
|
||||
pglCompileShader(shader);
|
||||
print_shader_log(shader);
|
||||
if (!compile_shader(shader, progs[i].vertex))
|
||||
{
|
||||
SSNES_ERR("Failed to compile vertex shader #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
pglAttachShader(gl_prog[i], shader);
|
||||
free(progs[i].vertex);
|
||||
}
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SSNES_ERR("Failed to compile vertex shader #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (progs[i].fragment)
|
||||
{
|
||||
SSNES_LOG("Found GLSL fragment shader.\n");
|
||||
GLuint shader = pglCreateShader(GL_FRAGMENT_SHADER);
|
||||
pglShaderSource(shader, 1, (const char**)&progs[i].fragment, 0);
|
||||
pglCompileShader(shader);
|
||||
print_shader_log(shader);
|
||||
if (!compile_shader(shader, progs[i].fragment))
|
||||
{
|
||||
SSNES_ERR("Failed to compile fragment shader #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
pglAttachShader(gl_prog[i], shader);
|
||||
free(progs[i].fragment);
|
||||
}
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SSNES_ERR("Failed to compile fragment shader #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (progs[i].vertex || progs[i].fragment)
|
||||
{
|
||||
SSNES_LOG("Linking GLSL program.\n");
|
||||
pglLinkProgram(gl_prog[i]);
|
||||
pglUseProgram(gl_prog[i]);
|
||||
print_linker_log(gl_prog[i]);
|
||||
if (!link_program(gl_prog[i]))
|
||||
{
|
||||
SSNES_ERR("Failed to link program #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
GLint location = pglGetUniformLocation(gl_prog[i], "rubyTexture");
|
||||
pglUniform1i(location, 0);
|
||||
pglUseProgram(0);
|
||||
}
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SSNES_ERR("Failed to link program #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define LOAD_GL_SYM(SYM) if (!(pgl##SYM)) pgl##SYM = SDL_GL_GetProcAddress("gl" #SYM)
|
||||
#define LOAD_GL_SYM(SYM) if (!pgl##SYM) { SDL_SYM_WRAP(pgl##SYM, "gl" #SYM) }
|
||||
|
||||
bool gl_glsl_init(const char *path)
|
||||
{
|
||||
@@ -597,6 +850,9 @@ bool gl_glsl_init(const char *path)
|
||||
LOAD_GL_SYM(GetProgramInfoLog);
|
||||
LOAD_GL_SYM(DeleteProgram);
|
||||
LOAD_GL_SYM(GetAttachedShaders);
|
||||
LOAD_GL_SYM(GetAttribLocation);
|
||||
LOAD_GL_SYM(EnableVertexAttribArray);
|
||||
LOAD_GL_SYM(VertexAttribPointer);
|
||||
#endif
|
||||
|
||||
SSNES_LOG("Checking GLSL shader support ...\n");
|
||||
@@ -608,7 +864,9 @@ bool gl_glsl_init(const char *path)
|
||||
&& pglDetachShader && pglLinkProgram && pglGetUniformLocation
|
||||
&& pglUniform1i && pglUniform2fv && pglUniform4fv
|
||||
&& pglGetShaderiv && pglGetShaderInfoLog && pglGetProgramiv && pglGetProgramInfoLog
|
||||
&& pglDeleteProgram && pglGetAttachedShaders;
|
||||
&& pglDeleteProgram && pglGetAttachedShaders
|
||||
&& pglGetAttribLocation && pglEnableVertexAttribArray
|
||||
&& pglVertexAttribPointer;
|
||||
#endif
|
||||
|
||||
if (!shader_support)
|
||||
@@ -657,8 +915,37 @@ bool gl_glsl_init(const char *path)
|
||||
}
|
||||
}
|
||||
|
||||
if (!gl_check_error())
|
||||
return false;
|
||||
//if (!gl_check_error())
|
||||
// SSNES_WARN("Detected GL error!\n");
|
||||
|
||||
if (gl_tracker_info_cnt > 0)
|
||||
{
|
||||
struct snes_tracker_info info = {
|
||||
.wram = psnes_get_memory_data(SNES_MEMORY_WRAM),
|
||||
.vram = psnes_get_memory_data(SNES_MEMORY_VRAM),
|
||||
.cgram = psnes_get_memory_data(SNES_MEMORY_CGRAM),
|
||||
.apuram = psnes_get_memory_data(SNES_MEMORY_APURAM),
|
||||
.oam = psnes_get_memory_data(SNES_MEMORY_OAM),
|
||||
.info = gl_tracker_info,
|
||||
.info_elem = gl_tracker_info_cnt,
|
||||
};
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (*gl_tracker_script)
|
||||
info.script = gl_tracker_script;
|
||||
else if (gl_script_program)
|
||||
info.script = (const char*)gl_script_program;
|
||||
else
|
||||
info.script = NULL;
|
||||
|
||||
info.script_class = *gl_tracker_script_class ? gl_tracker_script_class : NULL;
|
||||
info.script_is_file = *gl_tracker_script;
|
||||
#endif
|
||||
|
||||
gl_snes_tracker = snes_tracker_init(&info);
|
||||
if (!gl_snes_tracker)
|
||||
SSNES_WARN("Failed to init SNES tracker!\n");
|
||||
}
|
||||
|
||||
glsl_enable = true;
|
||||
gl_num_programs = num_progs;
|
||||
@@ -696,13 +983,38 @@ void gl_glsl_deinit(void)
|
||||
memset(gl_program, 0, sizeof(gl_program));
|
||||
glsl_enable = false;
|
||||
active_index = 0;
|
||||
|
||||
gl_tracker_info_cnt = 0;
|
||||
memset(gl_tracker_info, 0, sizeof(gl_tracker_info));
|
||||
memset(gl_tracker_script, 0, sizeof(gl_tracker_script));
|
||||
memset(gl_tracker_script_class, 0, sizeof(gl_tracker_script_class));
|
||||
if (gl_script_program)
|
||||
{
|
||||
xmlFree(gl_script_program);
|
||||
gl_script_program = NULL;
|
||||
}
|
||||
if (gl_snes_tracker)
|
||||
{
|
||||
snes_tracker_free(gl_snes_tracker);
|
||||
gl_snes_tracker = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count)
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt)
|
||||
{
|
||||
// We enforce a certain layout for our various texture types in the texunits.
|
||||
// Unit 0: Regular SNES frame (rubyTexture).
|
||||
// Unit 1-A: LUT textures.
|
||||
// Unit A+1: Previous texture.
|
||||
// Unit A+2: Original texture.
|
||||
// Unit A+3-B: FBO textures.
|
||||
|
||||
if (glsl_enable && gl_program[active_index] > 0)
|
||||
{
|
||||
GLint location;
|
||||
@@ -727,6 +1039,118 @@ void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
location = pglGetUniformLocation(gl_program[active_index], gl_teximage_uniforms[i]);
|
||||
pglUniform1i(location, i + 1);
|
||||
}
|
||||
|
||||
// Set previous texture.
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 1);
|
||||
glBindTexture(GL_TEXTURE_2D, prev_info->tex);
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyPrevTexture");
|
||||
pglUniform1i(location, gl_teximage_cnt + 1);
|
||||
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyPrevTextureSize");
|
||||
pglUniform2fv(location, 1, prev_info->tex_size);
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyPrevInputSize");
|
||||
pglUniform2fv(location, 1, prev_info->input_size);
|
||||
|
||||
// Pass texture coordinates.
|
||||
location = pglGetAttribLocation(gl_program[active_index], "rubyPrevTexCoord");
|
||||
if (location >= 0)
|
||||
{
|
||||
pglEnableVertexAttribArray(location);
|
||||
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, prev_info->coord);
|
||||
}
|
||||
|
||||
// Set original texture unless we're in first pass (pointless).
|
||||
if (active_index > 1)
|
||||
{
|
||||
// Bind original texture.
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 2);
|
||||
glBindTexture(GL_TEXTURE_2D, info->tex);
|
||||
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigTexture");
|
||||
pglUniform1i(location, gl_teximage_cnt + 2);
|
||||
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigTextureSize");
|
||||
pglUniform2fv(location, 1, info->tex_size);
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigInputSize");
|
||||
pglUniform2fv(location, 1, info->input_size);
|
||||
|
||||
// Pass texture coordinates.
|
||||
location = pglGetAttribLocation(gl_program[active_index], "rubyOrigTexCoord");
|
||||
if (location >= 0)
|
||||
{
|
||||
pglEnableVertexAttribArray(location);
|
||||
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, info->coord);
|
||||
}
|
||||
|
||||
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 3;
|
||||
|
||||
// Bind new texture in the chain.
|
||||
if (fbo_info_cnt > 0)
|
||||
{
|
||||
pglActiveTexture(base_tex + fbo_info_cnt - 1);
|
||||
glBindTexture(GL_TEXTURE_2D, fbo_info[fbo_info_cnt - 1].tex);
|
||||
}
|
||||
|
||||
// Bind FBO textures.
|
||||
for (unsigned i = 0; i < fbo_info_cnt; i++)
|
||||
{
|
||||
char attrib_buf[64];
|
||||
|
||||
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uTexture", i + 1);
|
||||
location = pglGetUniformLocation(gl_program[active_index], attrib_buf);
|
||||
pglUniform1i(location, base_tex + i - GL_TEXTURE0);
|
||||
|
||||
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uTextureSize", i + 1);
|
||||
location = pglGetUniformLocation(gl_program[active_index], attrib_buf);
|
||||
pglUniform2fv(location, 1, fbo_info[i].tex_size);
|
||||
|
||||
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uInputSize", i + 1);
|
||||
location = pglGetUniformLocation(gl_program[active_index], attrib_buf);
|
||||
pglUniform2fv(location, 1, fbo_info[i].input_size);
|
||||
|
||||
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uTexCoord", i + 1);
|
||||
location = pglGetAttribLocation(gl_program[active_index], attrib_buf);
|
||||
if (location >= 0)
|
||||
{
|
||||
pglEnableVertexAttribArray(location);
|
||||
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, fbo_info[i].coord);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// First pass, so unbind everything to avoid collitions.
|
||||
// Unbind ORIG.
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 2);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 3;
|
||||
// Unbind any lurking FBO passes.
|
||||
// Rendering to a texture that is bound to a texture unit
|
||||
// sounds very shaky ... ;)
|
||||
for (unsigned i = 0; i < gl_num_programs; i++)
|
||||
{
|
||||
pglActiveTexture(GL_TEXTURE0 + base_tex + i);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pglActiveTexture(GL_TEXTURE0);
|
||||
|
||||
if (gl_snes_tracker)
|
||||
{
|
||||
static struct snes_tracker_uniform info[MAX_VARIABLES];
|
||||
static unsigned cnt = 0;
|
||||
|
||||
if (active_index == 1)
|
||||
cnt = snes_get_uniform(gl_snes_tracker, info, MAX_VARIABLES, frame_count);
|
||||
|
||||
for (unsigned i = 0; i < cnt; i++)
|
||||
{
|
||||
location = pglGetUniformLocation(gl_program[active_index], info[i].id);
|
||||
pglUniform1i(location, info[i].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -31,7 +31,10 @@ void gl_glsl_set_proj_matrix(void);
|
||||
void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_counter);
|
||||
unsigned frame_counter,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt);
|
||||
|
||||
void gl_glsl_use(unsigned index);
|
||||
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
/* 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 "snes_state.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "strl.h"
|
||||
#include "general.h"
|
||||
#include <libsnes.hpp>
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
#include "py_state/py_state.h"
|
||||
#endif
|
||||
|
||||
struct snes_tracker_internal
|
||||
{
|
||||
char id[64];
|
||||
|
||||
bool is_input;
|
||||
const uint16_t *input_ptr;
|
||||
const uint8_t *ptr;
|
||||
#ifdef HAVE_PYTHON
|
||||
py_state_t *py;
|
||||
#endif
|
||||
|
||||
uint32_t addr;
|
||||
unsigned mask;
|
||||
|
||||
enum snes_tracker_type type;
|
||||
|
||||
uint32_t prev[2];
|
||||
int frame_count;
|
||||
int frame_count_prev;
|
||||
uint32_t old_value;
|
||||
int transition_count;
|
||||
};
|
||||
|
||||
struct snes_tracker
|
||||
{
|
||||
struct snes_tracker_internal *info;
|
||||
unsigned info_elem;
|
||||
|
||||
uint16_t input_state[2];
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
py_state_t *py;
|
||||
#endif
|
||||
};
|
||||
|
||||
snes_tracker_t* snes_tracker_init(const struct snes_tracker_info *info)
|
||||
{
|
||||
snes_tracker_t *tracker = calloc(1, sizeof(*tracker));
|
||||
if (!tracker)
|
||||
return NULL;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (info->script)
|
||||
{
|
||||
tracker->py = py_state_new(info->script, info->script_is_file, info->script_class ? info->script_class : "GameAware");
|
||||
if (!tracker->py)
|
||||
{
|
||||
free(tracker);
|
||||
SSNES_ERR("Failed to init Python script.\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
tracker->info = calloc(info->info_elem, sizeof(struct snes_tracker_internal));
|
||||
tracker->info_elem = info->info_elem;
|
||||
|
||||
for (unsigned i = 0; i < info->info_elem; i++)
|
||||
{
|
||||
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;
|
||||
|
||||
#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);
|
||||
|
||||
switch (info->info[i].ram_type)
|
||||
{
|
||||
case SSNES_STATE_WRAM:
|
||||
tracker->info[i].ptr = info->wram;
|
||||
break;
|
||||
case SSNES_STATE_APURAM:
|
||||
tracker->info[i].ptr = info->apuram;
|
||||
break;
|
||||
case SSNES_STATE_OAM:
|
||||
tracker->info[i].ptr = info->oam;
|
||||
break;
|
||||
case SSNES_STATE_CGRAM:
|
||||
tracker->info[i].ptr = info->cgram;
|
||||
break;
|
||||
case SSNES_STATE_VRAM:
|
||||
tracker->info[i].ptr = info->vram;
|
||||
break;
|
||||
case SSNES_STATE_INPUT_SLOT1:
|
||||
tracker->info[i].input_ptr = &tracker->input_state[0];
|
||||
tracker->info[i].is_input = true;
|
||||
break;
|
||||
case SSNES_STATE_INPUT_SLOT2:
|
||||
tracker->info[i].input_ptr = &tracker->input_state[1];
|
||||
tracker->info[i].is_input = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
tracker->info[i].ptr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
void snes_tracker_free(snes_tracker_t *tracker)
|
||||
{
|
||||
free(tracker->info);
|
||||
#ifdef HAVE_PYTHON
|
||||
py_state_free(tracker->py);
|
||||
#endif
|
||||
free(tracker);
|
||||
}
|
||||
|
||||
#define fetch() ((info->is_input ? *info->input_ptr : info->ptr[info->addr]) & info->mask)
|
||||
|
||||
static void update_element(
|
||||
struct snes_tracker_uniform *uniform,
|
||||
struct snes_tracker_internal *info,
|
||||
unsigned frame_count)
|
||||
{
|
||||
uniform->id = info->id;
|
||||
|
||||
switch (info->type)
|
||||
{
|
||||
case SSNES_STATE_CAPTURE:
|
||||
uniform->value = fetch();
|
||||
break;
|
||||
|
||||
case SSNES_STATE_CAPTURE_PREV:
|
||||
if (info->prev[0] != fetch())
|
||||
{
|
||||
info->prev[1] = info->prev[0];
|
||||
info->prev[0] = fetch();
|
||||
}
|
||||
uniform->value = info->prev[1];
|
||||
break;
|
||||
|
||||
case SSNES_STATE_TRANSITION:
|
||||
if (info->old_value != fetch())
|
||||
{
|
||||
info->old_value = fetch();
|
||||
info->frame_count = frame_count;
|
||||
}
|
||||
uniform->value = info->frame_count;
|
||||
break;
|
||||
|
||||
case SSNES_STATE_TRANSITION_COUNT:
|
||||
if (info->old_value != fetch())
|
||||
{
|
||||
info->old_value = fetch();
|
||||
info->transition_count++;
|
||||
}
|
||||
uniform->value = info->transition_count;
|
||||
break;
|
||||
|
||||
case SSNES_STATE_TRANSITION_PREV:
|
||||
if (info->old_value != fetch())
|
||||
{
|
||||
info->old_value = fetch();
|
||||
info->frame_count_prev = info->frame_count;
|
||||
info->frame_count = frame_count;
|
||||
}
|
||||
uniform->value = info->frame_count_prev;
|
||||
break;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
case SSNES_STATE_PYTHON:
|
||||
uniform->value = py_state_get(info->py, info->id, frame_count);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef fetch
|
||||
|
||||
// Updates 16-bit input in same format as SNES itself.
|
||||
static void update_input(snes_tracker_t *tracker)
|
||||
{
|
||||
if (!driver.input_data)
|
||||
return;
|
||||
|
||||
static const unsigned buttons[] = {
|
||||
SNES_DEVICE_ID_JOYPAD_R,
|
||||
SNES_DEVICE_ID_JOYPAD_L,
|
||||
SNES_DEVICE_ID_JOYPAD_X,
|
||||
SNES_DEVICE_ID_JOYPAD_A,
|
||||
SNES_DEVICE_ID_JOYPAD_RIGHT,
|
||||
SNES_DEVICE_ID_JOYPAD_LEFT,
|
||||
SNES_DEVICE_ID_JOYPAD_DOWN,
|
||||
SNES_DEVICE_ID_JOYPAD_UP,
|
||||
SNES_DEVICE_ID_JOYPAD_START,
|
||||
SNES_DEVICE_ID_JOYPAD_SELECT,
|
||||
SNES_DEVICE_ID_JOYPAD_Y,
|
||||
SNES_DEVICE_ID_JOYPAD_B
|
||||
};
|
||||
|
||||
static const struct snes_keybind *binds[MAX_PLAYERS] = {
|
||||
g_settings.input.binds[0],
|
||||
g_settings.input.binds[1],
|
||||
g_settings.input.binds[2],
|
||||
g_settings.input.binds[3],
|
||||
g_settings.input.binds[4]
|
||||
};
|
||||
|
||||
uint16_t state[2] = {0};
|
||||
for (unsigned i = 4; i < 16; i++)
|
||||
{
|
||||
state[0] |= (driver.input->input_state(driver.input_data, binds, SNES_PORT_1, SNES_DEVICE_JOYPAD, 0, buttons[i - 4]) ? 1 : 0) << i;
|
||||
state[1] |= (driver.input->input_state(driver.input_data, binds, SNES_PORT_2, SNES_DEVICE_JOYPAD, 0, buttons[i - 4]) ? 1 : 0) << i;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
tracker->input_state[i] = state[i];
|
||||
}
|
||||
|
||||
unsigned snes_get_uniform(snes_tracker_t *tracker, struct snes_tracker_uniform *uniforms, unsigned elem, unsigned frame_count)
|
||||
{
|
||||
unsigned elems = tracker->info_elem < elem ? tracker->info_elem : elem;
|
||||
|
||||
update_input(tracker);
|
||||
|
||||
for (unsigned i = 0; i < elems; i++)
|
||||
update_element(&uniforms[i], &tracker->info[i], frame_count);
|
||||
|
||||
return elems;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/* 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_SNES_STATE_H
|
||||
#define __SSNES_SNES_STATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
enum snes_tracker_type
|
||||
{
|
||||
SSNES_STATE_CAPTURE,
|
||||
SSNES_STATE_CAPTURE_PREV,
|
||||
SSNES_STATE_TRANSITION,
|
||||
SSNES_STATE_TRANSITION_COUNT,
|
||||
SSNES_STATE_TRANSITION_PREV,
|
||||
#ifdef HAVE_PYTHON
|
||||
SSNES_STATE_PYTHON
|
||||
#endif
|
||||
};
|
||||
|
||||
enum snes_ram_type
|
||||
{
|
||||
SSNES_STATE_WRAM,
|
||||
SSNES_STATE_APURAM,
|
||||
SSNES_STATE_OAM,
|
||||
SSNES_STATE_CGRAM,
|
||||
SSNES_STATE_VRAM,
|
||||
SSNES_STATE_INPUT_SLOT1,
|
||||
SSNES_STATE_INPUT_SLOT2
|
||||
};
|
||||
|
||||
struct snes_tracker_uniform_info
|
||||
{
|
||||
char id[64];
|
||||
uint32_t addr;
|
||||
enum snes_tracker_type type;
|
||||
enum snes_ram_type ram_type;
|
||||
unsigned mask;
|
||||
};
|
||||
|
||||
struct snes_tracker_info
|
||||
{
|
||||
const uint8_t *wram;
|
||||
const uint8_t *vram;
|
||||
const uint8_t *cgram;
|
||||
const uint8_t *oam;
|
||||
const uint8_t *apuram;
|
||||
|
||||
const struct snes_tracker_uniform_info *info;
|
||||
unsigned info_elem;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
const char *script;
|
||||
const char *script_class;
|
||||
bool script_is_file;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct snes_tracker_uniform
|
||||
{
|
||||
const char *id;
|
||||
int value;
|
||||
};
|
||||
|
||||
typedef struct snes_tracker snes_tracker_t;
|
||||
|
||||
snes_tracker_t* snes_tracker_init(const struct snes_tracker_info *info);
|
||||
void snes_tracker_free(snes_tracker_t *tracker);
|
||||
|
||||
unsigned snes_get_uniform(snes_tracker_t *tracker, struct snes_tracker_uniform *uniforms, unsigned elem, unsigned frame_count);
|
||||
|
||||
#endif
|
||||
+2
-2
@@ -249,7 +249,7 @@ static void render32_yuy2(xv_t *xv, const void *input_, unsigned width, unsigned
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint32_t p = *input++;
|
||||
p = ((p >> 17) & 0x7c00) | ((p >> 14) & 0x03e0) | ((p >> 11) & 0x1f); // RGBA -> RGB15
|
||||
p = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x1f); // ARGB -> RGB15
|
||||
|
||||
uint8_t y0 = xv->ytable[p];
|
||||
uint8_t u = xv->utable[p];
|
||||
@@ -278,7 +278,7 @@ static void render32_uyvy(xv_t *xv, const void *input_, unsigned width, unsigned
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint32_t p = *input++;
|
||||
p = ((p >> 17) & 0x7c00) | ((p >> 14) & 0x03e0) | ((p >> 11) & 0x1f); // RGBA -> RGB15
|
||||
p = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x1f); // ARGB -> RGB15
|
||||
|
||||
uint8_t y0 = xv->ytable[p];
|
||||
uint8_t u = xv->utable[p];
|
||||
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
/* 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 "ssnes_dinput.h"
|
||||
#include "SDL.h"
|
||||
#include "SDL_syswm.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
void sdl_dinput_free(sdl_dinput_t *di)
|
||||
{
|
||||
if (di)
|
||||
{
|
||||
for (unsigned i = 0; i < MAX_PLAYERS; i++)
|
||||
{
|
||||
if (di->joypad[i])
|
||||
{
|
||||
IDirectInputDevice8_Unacquire(di->joypad[i]);
|
||||
IDirectInputDevice8_Release(di->joypad[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (di->ctx)
|
||||
IDirectInput8_Release(di->ctx);
|
||||
|
||||
free(di);
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL CALLBACK enum_axes_cb(const DIDEVICEOBJECTINSTANCE *inst, void *p)
|
||||
{
|
||||
LPDIRECTINPUTDEVICE8 joypad = p;
|
||||
|
||||
DIPROPRANGE range;
|
||||
memset(&range, 0, sizeof(range));
|
||||
range.diph.dwSize = sizeof(DIPROPRANGE);
|
||||
range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
||||
range.diph.dwHow = DIPH_BYID;
|
||||
range.diph.dwObj = inst->dwType;
|
||||
range.lMin = -32768;
|
||||
range.lMax = 32767;
|
||||
IDirectInputDevice8_SetProperty(joypad, DIPROP_RANGE, &range.diph);
|
||||
|
||||
return DIENUM_CONTINUE;
|
||||
}
|
||||
|
||||
static BOOL CALLBACK enum_joypad_cb(const DIDEVICEINSTANCE *inst, void *p)
|
||||
{
|
||||
sdl_dinput_t *di = p;
|
||||
|
||||
unsigned active = 0;
|
||||
unsigned n;
|
||||
for (n = 0; n < MAX_PLAYERS; n++)
|
||||
{
|
||||
if (!di->joypad[n] && g_settings.input.joypad_map[n] == di->joypad_cnt)
|
||||
break;
|
||||
|
||||
if (di->joypad[n])
|
||||
active++;
|
||||
}
|
||||
|
||||
if (active == MAX_PLAYERS) return DIENUM_STOP;
|
||||
|
||||
di->joypad_cnt++;
|
||||
if (n == MAX_PLAYERS)
|
||||
return DIENUM_CONTINUE;
|
||||
|
||||
if (FAILED(IDirectInput8_CreateDevice(di->ctx, &inst->guidInstance, &di->joypad[n], NULL)))
|
||||
return DIENUM_CONTINUE;
|
||||
|
||||
IDirectInputDevice8_SetDataFormat(di->joypad[n], &c_dfDIJoystick2);
|
||||
IDirectInputDevice8_SetCooperativeLevel(di->joypad[n], di->hWnd,
|
||||
DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
|
||||
|
||||
IDirectInputDevice8_EnumObjects(di->joypad[n], enum_axes_cb,
|
||||
di->joypad[n], DIDFT_ABSAXIS);
|
||||
|
||||
return DIENUM_CONTINUE;
|
||||
}
|
||||
|
||||
sdl_dinput_t* sdl_dinput_init(void)
|
||||
{
|
||||
sdl_dinput_t *di = calloc(1, sizeof(*di));
|
||||
if (!di)
|
||||
return NULL;
|
||||
|
||||
CoInitialize(NULL);
|
||||
|
||||
SDL_SysWMinfo info;
|
||||
if (!SDL_GetWMInfo(&info))
|
||||
{
|
||||
SSNES_ERR("Failed to get SysWM info.\n");
|
||||
goto error;
|
||||
}
|
||||
di->hWnd = info.window;
|
||||
|
||||
if (FAILED(DirectInput8Create(
|
||||
GetModuleHandle(NULL), DIRECTINPUT_VERSION, &IID_IDirectInput8,
|
||||
(void**)&di->ctx, NULL)))
|
||||
{
|
||||
SSNES_ERR("Failed to init DirectInput.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
SSNES_LOG("Enumerating DInput devices ...\n");
|
||||
IDirectInput8_EnumDevices(di->ctx, DI8DEVCLASS_GAMECTRL, enum_joypad_cb, di, DIEDFL_ATTACHEDONLY);
|
||||
SSNES_LOG("Done enumerating DInput devices ...\n");
|
||||
|
||||
return di;
|
||||
|
||||
error:
|
||||
sdl_dinput_free(di);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool dinput_joykey_pressed(sdl_dinput_t *di, unsigned port_num, uint16_t joykey)
|
||||
{
|
||||
if (joykey == NO_BTN)
|
||||
return false;
|
||||
|
||||
// Check hat.
|
||||
if (GET_HAT_DIR(joykey))
|
||||
{
|
||||
unsigned hat = GET_HAT(joykey);
|
||||
|
||||
unsigned elems = sizeof(di->joy_state[0].rgdwPOV) / sizeof(di->joy_state[0].rgdwPOV[0]);
|
||||
if (hat >= elems)
|
||||
return false;
|
||||
|
||||
unsigned pov = di->joy_state[port_num].rgdwPOV[hat];
|
||||
|
||||
// Magic numbers I'm not sure where originate from.
|
||||
if (pov < 36000)
|
||||
{
|
||||
switch (GET_HAT_DIR(joykey))
|
||||
{
|
||||
case HAT_UP_MASK:
|
||||
return (pov >= 31500) || (pov <= 4500);
|
||||
case HAT_RIGHT_MASK:
|
||||
return (pov >= 4500) && (pov <= 13500);
|
||||
case HAT_DOWN_MASK:
|
||||
return (pov >= 13500) && (pov <= 22500);
|
||||
case HAT_LEFT_MASK:
|
||||
return (pov >= 22500) && (pov <= 31500);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned elems = sizeof(di->joy_state[0].rgbButtons) / sizeof(di->joy_state[0].rgbButtons[0]);
|
||||
|
||||
if (joykey < elems)
|
||||
return di->joy_state[port_num].rgbButtons[joykey];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool dinput_joyaxis_pressed(sdl_dinput_t *di, unsigned port_num, uint32_t joyaxis)
|
||||
{
|
||||
if (joyaxis == AXIS_NONE)
|
||||
return false;
|
||||
|
||||
int min = -32678 * g_settings.input.axis_threshold;
|
||||
int max = 32677 * g_settings.input.axis_threshold;
|
||||
|
||||
switch (AXIS_NEG_GET(joyaxis))
|
||||
{
|
||||
case 0:
|
||||
return di->joy_state[port_num].lX <= min;
|
||||
case 1:
|
||||
return di->joy_state[port_num].lY <= min;
|
||||
case 2:
|
||||
return di->joy_state[port_num].lZ <= min;
|
||||
case 3:
|
||||
return di->joy_state[port_num].lRx <= min;
|
||||
case 4:
|
||||
return di->joy_state[port_num].lRy <= min;
|
||||
case 5:
|
||||
return di->joy_state[port_num].lRz <= min;
|
||||
}
|
||||
|
||||
switch (AXIS_POS_GET(joyaxis))
|
||||
{
|
||||
case 0:
|
||||
return di->joy_state[port_num].lX >= max;
|
||||
case 1:
|
||||
return di->joy_state[port_num].lY >= max;
|
||||
case 2:
|
||||
return di->joy_state[port_num].lZ >= max;
|
||||
case 3:
|
||||
return di->joy_state[port_num].lRx >= max;
|
||||
case 4:
|
||||
return di->joy_state[port_num].lRy >= max;
|
||||
case 5:
|
||||
return di->joy_state[port_num].lRz >= max;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sdl_dinput_pressed(sdl_dinput_t *di, unsigned port_num, const struct snes_keybind *key)
|
||||
{
|
||||
if (di->joypad[port_num] == NULL)
|
||||
return false;
|
||||
if (dinput_joykey_pressed(di, port_num, key->joykey))
|
||||
return true;
|
||||
if (dinput_joyaxis_pressed(di, port_num, key->joyaxis))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sdl_dinput_poll(sdl_dinput_t *di)
|
||||
{
|
||||
memset(di->joy_state, 0, sizeof(di->joy_state));
|
||||
for (unsigned i = 0; i < MAX_PLAYERS; i++)
|
||||
{
|
||||
if (di->joypad[i])
|
||||
{
|
||||
if (FAILED(IDirectInputDevice8_Poll(di->joypad[i])))
|
||||
{
|
||||
IDirectInputDevice8_Acquire(di->joypad[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
IDirectInputDevice8_GetDeviceState(di->joypad[i], sizeof(DIJOYSTATE2), &di->joy_state[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+59
-58
@@ -25,12 +25,22 @@
|
||||
#include <libsnes.hpp>
|
||||
#include "ssnes_sdl_input.h"
|
||||
|
||||
|
||||
static void* sdl_input_init(void)
|
||||
{
|
||||
sdl_input_t *sdl = calloc(1, sizeof(*sdl));
|
||||
if (!sdl)
|
||||
return NULL;
|
||||
|
||||
#ifdef HAVE_DINPUT
|
||||
sdl->di = sdl_dinput_init();
|
||||
if (!sdl->di)
|
||||
{
|
||||
free(sdl);
|
||||
SSNES_ERR("Failed to init SDL/DInput.\n");
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
|
||||
return NULL;
|
||||
|
||||
@@ -60,9 +70,9 @@ static void* sdl_input_init(void)
|
||||
sdl->num_hats[i] = SDL_JoystickNumHats(sdl->joysticks[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
sdl->use_keyboard = true;
|
||||
|
||||
return sdl;
|
||||
}
|
||||
|
||||
@@ -78,8 +88,12 @@ static bool sdl_key_pressed(int key)
|
||||
return keymap[key];
|
||||
}
|
||||
|
||||
#ifndef HAVE_DINPUT
|
||||
static bool sdl_joykey_pressed(sdl_input_t *sdl, int port_num, uint16_t joykey)
|
||||
{
|
||||
if (joykey == NO_BTN)
|
||||
return false;
|
||||
|
||||
// Check hat.
|
||||
if (GET_HAT_DIR(joykey))
|
||||
{
|
||||
@@ -120,37 +134,43 @@ static bool sdl_joykey_pressed(sdl_input_t *sdl, int port_num, uint16_t joykey)
|
||||
|
||||
static bool sdl_axis_pressed(sdl_input_t *sdl, int port_num, uint32_t joyaxis)
|
||||
{
|
||||
if (joyaxis != AXIS_NONE)
|
||||
if (joyaxis == AXIS_NONE)
|
||||
return false;
|
||||
|
||||
if (AXIS_NEG_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
if (AXIS_NEG_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled < -g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
if (AXIS_POS_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled > g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled < -g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
if (AXIS_POS_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled > g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key)
|
||||
static bool sdl_is_pressed(sdl_input_t *sdl, unsigned port_num, const struct snes_keybind *key)
|
||||
{
|
||||
if (sdl->use_keyboard && sdl_key_pressed(key->key))
|
||||
return true;
|
||||
|
||||
#ifdef HAVE_DINPUT
|
||||
return sdl_dinput_pressed(sdl->di, port_num, key);
|
||||
#else
|
||||
if (sdl->joysticks[port_num] == NULL)
|
||||
return false;
|
||||
if (sdl_joykey_pressed(sdl, port_num, key->joykey))
|
||||
return true;
|
||||
if (sdl_axis_pressed(sdl, port_num, key->joyaxis))
|
||||
return true;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -159,7 +179,7 @@ static bool sdl_bind_button_pressed(void *data, int key)
|
||||
{
|
||||
// Only let player 1 use special binds called from main loop.
|
||||
const struct snes_keybind *binds = g_settings.input.binds[0];
|
||||
for (int i = 0; binds[i].id != -1; i++)
|
||||
for (unsigned i = 0; binds[i].id != -1; i++)
|
||||
{
|
||||
if (binds[i].id == key)
|
||||
return sdl_is_pressed(data, 0, &binds[i]);
|
||||
@@ -168,23 +188,21 @@ static bool sdl_bind_button_pressed(void *data, int key)
|
||||
}
|
||||
|
||||
static int16_t sdl_joypad_device_state(sdl_input_t *sdl, const struct snes_keybind **binds,
|
||||
int port_num, unsigned device, unsigned index, unsigned id)
|
||||
unsigned port_num, int id)
|
||||
{
|
||||
const struct snes_keybind *snes_keybinds = binds[port_num];
|
||||
|
||||
for (int i = 0; snes_keybinds[i].id != -1; i++)
|
||||
for (unsigned i = 0; snes_keybinds[i].id != -1; i++)
|
||||
{
|
||||
if (snes_keybinds[i].id == (int)id)
|
||||
return sdl_is_pressed(sdl, port_num, &snes_keybinds[i]);
|
||||
if (snes_keybinds[i].id == id)
|
||||
return sdl_is_pressed(sdl, port_num, &snes_keybinds[i]) ? 1 : 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int16_t sdl_mouse_device_state(sdl_input_t *sdl, bool port, unsigned id)
|
||||
static int16_t sdl_mouse_device_state(sdl_input_t *sdl, unsigned id)
|
||||
{
|
||||
// Might implement support for joypad mapping later.
|
||||
(void)port;
|
||||
switch (id)
|
||||
{
|
||||
case SNES_DEVICE_ID_MOUSE_LEFT:
|
||||
@@ -248,11 +266,11 @@ static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bo
|
||||
switch (device)
|
||||
{
|
||||
case SNES_DEVICE_JOYPAD:
|
||||
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_1) ? 0 : 1, device, index, id);
|
||||
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_1) ? 0 : 1, id);
|
||||
case SNES_DEVICE_MULTITAP:
|
||||
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_2) ? 1 + index : 0, device, index, id);
|
||||
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_2) ? 1 + index : 0, id);
|
||||
case SNES_DEVICE_MOUSE:
|
||||
return sdl_mouse_device_state(data, port, id);
|
||||
return sdl_mouse_device_state(data, id);
|
||||
case SNES_DEVICE_SUPER_SCOPE:
|
||||
return sdl_scope_device_state(data, id);
|
||||
case SNES_DEVICE_JUSTIFIER:
|
||||
@@ -273,14 +291,19 @@ static void sdl_input_free(void *data)
|
||||
while (SDL_PollEvent(&event));
|
||||
|
||||
sdl_input_t *sdl = data;
|
||||
|
||||
#ifdef HAVE_DINPUT
|
||||
sdl_dinput_free(sdl->di);
|
||||
#else
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
{
|
||||
if (sdl->joysticks[i])
|
||||
SDL_JoystickClose(sdl->joysticks[i]);
|
||||
}
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
#endif
|
||||
|
||||
free(data);
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,37 +321,15 @@ static void sdl_poll_mouse(sdl_input_t *sdl)
|
||||
static void sdl_input_poll(void *data)
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
SDL_Event event;
|
||||
SDL_JoystickUpdate();
|
||||
sdl_poll_mouse(data);
|
||||
|
||||
sdl_input_t *sdl = data;
|
||||
// Search for events...
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
if (sdl->quitting)
|
||||
{
|
||||
*sdl->quitting = true;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_VIDEORESIZE:
|
||||
if (sdl->should_resize)
|
||||
{
|
||||
*sdl->new_width = event.resize.w;
|
||||
*sdl->new_height = event.resize.h;
|
||||
*sdl->should_resize = true;
|
||||
}
|
||||
break;
|
||||
#ifdef HAVE_DINPUT
|
||||
sdl_dinput_poll(sdl->di);
|
||||
#else
|
||||
SDL_JoystickUpdate();
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
sdl_poll_mouse(sdl);
|
||||
}
|
||||
|
||||
const input_driver_t input_sdl = {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* 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_DINPUT_H
|
||||
#define __SSNES_DINPUT_H
|
||||
|
||||
#undef DIRECTINPUT_VERSION
|
||||
#define DIRECTINPUT_VERSION 0x0800
|
||||
|
||||
#include <dinput.h>
|
||||
#include <stdbool.h>
|
||||
#include "general.h"
|
||||
|
||||
// Piggyback joypad driver for SDL.
|
||||
|
||||
typedef struct sdl_dinput
|
||||
{
|
||||
HWND hWnd;
|
||||
LPDIRECTINPUT8 ctx;
|
||||
LPDIRECTINPUTDEVICE8 joypad[MAX_PLAYERS];
|
||||
unsigned joypad_cnt;
|
||||
DIJOYSTATE2 joy_state[MAX_PLAYERS];
|
||||
} sdl_dinput_t;
|
||||
|
||||
sdl_dinput_t *sdl_dinput_init(void);
|
||||
void sdl_dinput_free(sdl_dinput_t *di);
|
||||
|
||||
bool sdl_dinput_pressed(sdl_dinput_t *di, unsigned port_num,
|
||||
const struct snes_keybind *key);
|
||||
|
||||
void sdl_dinput_poll(sdl_dinput_t *di);
|
||||
|
||||
|
||||
#endif
|
||||
+10
-5
@@ -20,20 +20,25 @@
|
||||
|
||||
#include "SDL.h"
|
||||
#include "general.h"
|
||||
|
||||
#ifdef HAVE_DINPUT
|
||||
#include "ssnes_dinput.h"
|
||||
#endif
|
||||
|
||||
typedef struct sdl_input
|
||||
{
|
||||
#ifdef HAVE_DINPUT
|
||||
sdl_dinput_t *di;
|
||||
#else
|
||||
SDL_Joystick *joysticks[MAX_PLAYERS];
|
||||
unsigned num_axes[MAX_PLAYERS];
|
||||
unsigned num_buttons[MAX_PLAYERS];
|
||||
unsigned num_hats[MAX_PLAYERS];
|
||||
unsigned num_joysticks;
|
||||
#endif
|
||||
|
||||
bool use_keyboard;
|
||||
|
||||
// A video driver could pre-init with the SDL driver and have it handle resizing events...
|
||||
bool *quitting;
|
||||
bool *should_resize;
|
||||
unsigned *new_width;
|
||||
unsigned *new_height;
|
||||
int16_t mouse_x, mouse_y;
|
||||
int16_t mouse_l, mouse_r, mouse_m;
|
||||
} sdl_input_t;
|
||||
|
||||
@@ -107,18 +107,6 @@ struct bsv_movie
|
||||
#define CRC_INDEX 2
|
||||
#define STATE_SIZE_INDEX 3
|
||||
|
||||
static inline uint8_t is_little_endian(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint16_t u16;
|
||||
uint8_t u8[2];
|
||||
} u;
|
||||
|
||||
u.u16 = 1;
|
||||
return u.u8[0];
|
||||
}
|
||||
|
||||
// Convert to big-endian if needed
|
||||
static inline uint32_t swap_if_big32(uint32_t val)
|
||||
{
|
||||
|
||||
@@ -25,10 +25,12 @@
|
||||
#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)
|
||||
@@ -96,10 +98,7 @@ struct netplay
|
||||
void input_poll_net(void)
|
||||
{
|
||||
if (!netplay_should_skip(g_extern.netplay) && netplay_can_poll(g_extern.netplay))
|
||||
{
|
||||
netplay_callbacks(g_extern.netplay)->poll_cb();
|
||||
netplay_poll(g_extern.netplay);
|
||||
}
|
||||
}
|
||||
|
||||
void video_frame_net(const uint16_t *data, unsigned width, unsigned height)
|
||||
@@ -243,7 +242,7 @@ static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
|
||||
{
|
||||
WSACleanup();
|
||||
return false;
|
||||
@@ -470,7 +469,7 @@ static bool get_self_input_state(netplay_t *handle)
|
||||
if (handle->frame_count > 0) // First frame we always give zero input since relying on input from first frame screws up when we use -F 0.
|
||||
{
|
||||
snes_input_state_t cb = handle->cbs.state_cb;
|
||||
for (int i = 0; i <= 11; i++)
|
||||
for (unsigned i = 0; i <= 11; i++)
|
||||
{
|
||||
int16_t tmp = cb(g_settings.input.netplay_client_swap_input ? 0 : !handle->port, SNES_DEVICE_JOYPAD, 0, i);
|
||||
state |= tmp ? 1 << i : 0;
|
||||
@@ -659,6 +658,8 @@ void netplay_pre_frame(netplay_t *handle)
|
||||
{
|
||||
psnes_serialize(handle->buffer[handle->self_ptr].state, handle->state_size);
|
||||
handle->can_poll = true;
|
||||
|
||||
input_poll_net();
|
||||
}
|
||||
|
||||
// Here we check if we have new input and replay from recorded input.
|
||||
|
||||
@@ -34,7 +34,6 @@ struct snes_callbacks
|
||||
{
|
||||
snes_video_refresh_t frame_cb;
|
||||
snes_audio_sample_t sample_cb;
|
||||
snes_input_poll_t poll_cb;
|
||||
snes_input_state_t state_cb;
|
||||
};
|
||||
|
||||
|
||||
File renamed without changes.
+41
-12
@@ -3,6 +3,21 @@
|
||||
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
|
||||
DYLIB=-ldl
|
||||
fi
|
||||
|
||||
if [ "$HAVE_DYNAMIC" = "yes" ] && [ "$HAVE_CONFIGFILE" = "no" ]; then
|
||||
echo "Cannot have dynamic loading of libsnes and no configfile support."
|
||||
echo "Dynamic loading requires config file support."
|
||||
@@ -10,16 +25,18 @@ if [ "$HAVE_DYNAMIC" = "yes" ] && [ "$HAVE_CONFIGFILE" = "no" ]; then
|
||||
fi
|
||||
|
||||
if [ $HAVE_DYNAMIC != yes ]; then
|
||||
check_lib_cxx SNES $LIBSNES snes_init -ldl
|
||||
check_lib_cxx SNES $LIBSNES snes_init $DYLIB
|
||||
check_critical SNES "Cannot find libsnes."
|
||||
add_define_make libsnes $LIBSNES
|
||||
fi
|
||||
|
||||
check_lib DYLIB -ldl dlopen
|
||||
check_lib DYLIB $DYLIB dlopen
|
||||
check_lib NETPLAY -lc socket
|
||||
|
||||
check_lib ALSA -lasound snd_pcm_open
|
||||
check_pkgconf ALSA alsa
|
||||
check_header OSS sys/soundcard.h
|
||||
check_header OSS_BSD soundcard.h
|
||||
check_lib OSS_LIB -lossaudio
|
||||
|
||||
if [ "$OS" = "Darwin" ]; then
|
||||
check_lib AL "-framework OpenAL" alcOpenDevice
|
||||
@@ -34,38 +51,50 @@ else
|
||||
fi
|
||||
|
||||
check_pkgconf RSOUND rsound 1.1
|
||||
check_lib ROAR -lroar roar_vs_new
|
||||
check_pkgconf ROAR libroar
|
||||
check_pkgconf JACK jack 0.120.1
|
||||
check_pkgconf PULSE libpulse
|
||||
|
||||
check_lib COREAUDIO "-framework AudioUnit" AudioUnitInitialize
|
||||
|
||||
check_pkgconf SDL sdl 1.2.10
|
||||
check_critical SDL "Cannot find SDL library."
|
||||
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
|
||||
|
||||
# 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 IMLIB imlib2
|
||||
check_pkgconf SDL_IMAGE SDL_image
|
||||
|
||||
if [ $HAVE_FFMPEG != no ]; then
|
||||
check_pkgconf AVCODEC libavcodec
|
||||
check_pkgconf AVFORMAT libavformat
|
||||
check_pkgconf AVCORE libavcore
|
||||
check_pkgconf AVUTIL libavutil
|
||||
check_pkgconf SWSCALE libswscale
|
||||
|
||||
( [ $HAVE_FFMPEG = auto ] && ( [ $HAVE_AVCODEC = no ] || [ $HAVE_AVFORMAT = no ] || [ $HAVE_AVCORE = no ] || [ $HAVE_AVUTIL = no ] || [ $HAVE_SWSCALE = no ] ) && HAVE_FFMPEG=no ) || HAVE_FFMPEG=yes
|
||||
( [ $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
|
||||
|
||||
check_lib DYNAMIC -ldl dlopen
|
||||
check_lib DYNAMIC $DYLIB dlopen
|
||||
|
||||
check_pkgconf FREETYPE freetype2
|
||||
check_lib XVIDEO -lXv XvShmCreateImage
|
||||
check_pkgconf XVIDEO xv
|
||||
|
||||
check_lib STRL -lc strlcpy
|
||||
|
||||
check_pkgconf PYTHON python3
|
||||
|
||||
add_define_make OS $OS
|
||||
|
||||
# Creates config.mk and config.h.
|
||||
VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL DYLIB CG XML IMLIB DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL"
|
||||
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"
|
||||
create_config_make config.mk $VARS
|
||||
create_config_header config.h $VARS
|
||||
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
. qb/qb.params.sh
|
||||
|
||||
PACKAGE_NAME=ssnes
|
||||
PACKAGE_VERSION=0.5
|
||||
PACKAGE_VERSION=0.8
|
||||
|
||||
# Adds a command line opt to ./configure --help
|
||||
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
|
||||
@@ -23,7 +23,9 @@ add_command_line_enable RSOUND "Enable RSound support" auto
|
||||
add_command_line_enable ROAR "Enable RoarAudio support" auto
|
||||
add_command_line_enable AL "Enable OpenAL support" auto
|
||||
add_command_line_enable JACK "Enable JACK support" auto
|
||||
add_command_line_enable COREAUDIO "Enable CoreAudio support" auto
|
||||
add_command_line_enable PULSE "Enable PulseAudio support" auto
|
||||
add_command_line_enable FREETYPE "Enable FreeType support" auto
|
||||
add_command_line_enable XVIDEO "Enable XVideo support" auto
|
||||
add_command_line_enable IMLIB "Enable imlib2 support" auto
|
||||
add_command_line_enable SDL_IMAGE "Enable SDL_image support" auto
|
||||
add_command_line_enable PYTHON "Enable Python 3 support for shaders" no
|
||||
+13
-12
@@ -1,24 +1,25 @@
|
||||
. qb/conf.comp.sh
|
||||
. qb/config.comp.sh
|
||||
|
||||
TEMP_C=.tmp.c
|
||||
TEMP_CXX=.tmp.cxx
|
||||
TEMP_EXE=.tmp
|
||||
|
||||
echo -n "Checking operating system ... "
|
||||
ECHOBUF="Checking operating system ... "
|
||||
OS="Win32" # whatever ;D
|
||||
unamestr="`uname -a`"
|
||||
if [ ! -z "`echo $unamestr | grep -i Linux`" ]; then
|
||||
if [ ! -z "`echo "$unamestr" | grep -i Linux`" ]; then
|
||||
OS="Linux"
|
||||
elif [ ! -z "`echo $unamestr | grep -i Darwin`" ]; then
|
||||
elif [ ! -z "`echo "$unamestr" | grep -i Darwin`" ]; then
|
||||
OS="Darwin"
|
||||
elif [ ! -z "`echo $unamestr | grep -i BSD`" ]; then
|
||||
elif [ ! -z "`echo "$unamestr" | grep -i BSD`" ]; then
|
||||
OS="BSD"
|
||||
elif [ ! -z "`echo $unamestr | grep -i NT`" ]; then
|
||||
elif [ ! -z "`echo "$unamestr" | grep -i MINGW32`" ]; then
|
||||
OS="MinGW"
|
||||
elif [ ! -z "`echo "$unamestr" | grep -i NT`" ]; then
|
||||
OS="Cygwin"
|
||||
fi
|
||||
|
||||
echo $OS
|
||||
|
||||
echo $ECHOBUF $OS
|
||||
|
||||
# Checking for working C compiler
|
||||
if [ "$USE_LANG_C" = yes ]; then
|
||||
@@ -31,12 +32,12 @@ if [ "$USE_LANG_C" = yes ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -n "Checking if $CC is a suitable compiler ... "
|
||||
ECHOBUF="Checking if $CC is a suitable compiler ..."
|
||||
answer=no
|
||||
echo "#include <stdio.h>" > $TEMP_C
|
||||
echo "int main(void) { puts(\"Hai world!\"); return 0; }" >> $TEMP_C
|
||||
$CC -o $TEMP_EXE $TEMP_C 2>/dev/null >/dev/null && answer=yes
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_C $TEMP_EXE
|
||||
|
||||
@@ -54,12 +55,12 @@ if [ "$USE_LANG_CXX" = "yes" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -n "Checking if $CXX is a suitable compiler ... "
|
||||
ECHOBUF="Checking if $CXX is a suitable compiler ..."
|
||||
answer=no
|
||||
echo "#include <iostream>" > $TEMP_CXX
|
||||
echo "int main() { std::cout << \"Hai guise\" << std::endl; return 0; }" >> $TEMP_CXX
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX 2>/dev/null >/dev/null && answer=yes
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_CXX $TEMP_EXE
|
||||
|
||||
|
||||
+61
-23
@@ -41,9 +41,13 @@ check_lib()
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
echo -n "Checking function $3 in $2 ... "
|
||||
echo "void $3(void); int main(void) { $3(); return 0; }" > $TEMP_C
|
||||
|
||||
if [ -z "$3" ]; then
|
||||
ECHOBUF="Checking existence of $2 ..."
|
||||
echo "int main(void) { return 0; }" > $TEMP_C
|
||||
else
|
||||
ECHOBUF="Checking function $3 in $2 ..."
|
||||
echo "void $3(void); int main(void) { $3(); return 0; }" > $TEMP_C
|
||||
fi
|
||||
|
||||
eval HAVE_$1=no
|
||||
answer=no
|
||||
@@ -52,9 +56,9 @@ check_lib()
|
||||
|
||||
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $CFLAGS $LDFLAGS $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_C $TEMP_EXE
|
||||
rm -f $TEMP_C $TEMP_EXE
|
||||
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
|
||||
echo "Forced to build with library $2, but cannot locate. Exiting ..."
|
||||
exit 1
|
||||
@@ -67,8 +71,13 @@ check_lib_cxx()
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
echo -n "Checking function $3 in $2 ... "
|
||||
echo "extern \"C\" { void $3(void); } int main() { $3(); }" > $TEMP_CXX
|
||||
if [ -z "$3" ]; then
|
||||
ECHOBUF="Checking existence of $2 ..."
|
||||
echo "int main() { return 0; }" > $TEMP_CXX
|
||||
else
|
||||
ECHOBUF="Checking function $3 in $2 ..."
|
||||
echo "extern \"C\" { void $3(void); } int main() { $3(); }" > $TEMP_CXX
|
||||
fi
|
||||
|
||||
eval HAVE_$1=no
|
||||
answer=no
|
||||
@@ -77,25 +86,55 @@ check_lib_cxx()
|
||||
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $CFLAGS $LDFLAGS $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_CXX $TEMP_EXE
|
||||
rm -f $TEMP_CXX $TEMP_EXE
|
||||
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
|
||||
echo "Forced to build with library $2, but cannot locate. Exiting ..."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_code_c()
|
||||
{
|
||||
tmpval="HAVE_$1"
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
ECHOBUF="Checking C code snippet \"$3\" ..."
|
||||
eval HAVE_$1=no
|
||||
answer=no
|
||||
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $2 $CFLAGS $LDFLAGS 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $ECHOBUF $answer
|
||||
rm -f $TEMP_C $TEMP_EXE
|
||||
}
|
||||
|
||||
check_code_cxx()
|
||||
{
|
||||
tmpval="HAVE_$1"
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
ECHOBUF="Checking C++ code snippet \"$3\" ..."
|
||||
eval HAVE_$1=no
|
||||
answer=no
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $2 $CXXFLAGS $LDFLAGS 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $ECHOBUF $answer
|
||||
rm -f $TEMP_CXX $TEMP_EXE
|
||||
}
|
||||
|
||||
locate_pkg_conf()
|
||||
{
|
||||
echo -n "Checking for pkg-config ... "
|
||||
ECHOBUF="Checking for pkg-config ... "
|
||||
PKG_CONF_PATH="`which pkg-config | grep ^/ | head -n1`"
|
||||
if [ -z $PKG_CONF_PATH ]; then
|
||||
echo "not found"
|
||||
echo "Cannot locate pkg-config. Exiting ..."
|
||||
exit 1
|
||||
fi
|
||||
echo "$PKG_CONF_PATH"
|
||||
echo $ECHOBUF $PKG_CONF_PATH
|
||||
}
|
||||
|
||||
check_pkgconf()
|
||||
@@ -106,16 +145,16 @@ check_pkgconf()
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
echo -n "Checking presence of package $2"
|
||||
ECHOBUF="Checking presence of package $2"
|
||||
eval HAVE_$1=no
|
||||
eval $1_CFLAGS=""
|
||||
eval $1_LIBS=""
|
||||
answer=no
|
||||
minver=0.0
|
||||
[ ! -z $3 ] && minver=$3 && echo -n " with minimum version $minver"
|
||||
echo -n " ... "
|
||||
[ ! -z $3 ] && minver=$3 && ECHOBUF="$ECHOBUF with minimum version $minver"
|
||||
ECHOBUF="$ECHOBUF ... "
|
||||
pkg-config --atleast-version=$minver "$2" && eval HAVE_$1=yes && eval $1_CFLAGS='"`pkg-config $2 --cflags`"' && eval $1_LIBS='"`pkg-config $2 --libs`"' && answer=yes
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
PKG_CONF_USED="$PKG_CONF_USED $1"
|
||||
|
||||
@@ -131,7 +170,7 @@ check_header()
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
echo -n "Checking presence of header file $2 ... "
|
||||
ECHOBUF="Checking presence of header file $2 ..."
|
||||
echo "#include<$2>" > $TEMP_C
|
||||
echo "int main(void) { return 0; }" >> $TEMP_C
|
||||
eval HAVE_$1=no
|
||||
@@ -139,7 +178,7 @@ check_header()
|
||||
|
||||
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_C $TEMP_EXE
|
||||
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
|
||||
@@ -150,7 +189,7 @@ check_header()
|
||||
|
||||
check_switch_c()
|
||||
{
|
||||
echo -n "Checking for availability of switch $2 in $CC ... "
|
||||
ECHOBUF="Checking for availability of switch $2 in $CC ..."
|
||||
if [ -z "$CC" ]; then
|
||||
echo "No C compiler, cannot check ..."
|
||||
exit 1
|
||||
@@ -160,14 +199,14 @@ check_switch_c()
|
||||
answer=no
|
||||
$CC -o $TEMP_EXE $TEMP_C $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_C $TEMP_EXE
|
||||
}
|
||||
|
||||
check_switch_cxx()
|
||||
{
|
||||
echo -n "Checking for availability of switch $2 in $CXX ... "
|
||||
ECHOBUF="Checking for availability of switch $2 in $CXX ... "
|
||||
if [ -z "$CXX" ]; then
|
||||
echo "No C++ compiler, cannot check ..."
|
||||
exit 1
|
||||
@@ -177,7 +216,7 @@ check_switch_cxx()
|
||||
answer=no
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_CXX $TEMP_EXE
|
||||
}
|
||||
@@ -207,7 +246,7 @@ create_config_header()
|
||||
|
||||
echo "Creating config header: $outfile"
|
||||
|
||||
name="`echo __$outfile | sed 's|[\./]|_|g' | tr '[a-z]' '[A-Z]'`"
|
||||
name="`echo QB_${outfile}__ | sed 's|[\./]|_|g' | tr '[a-z]' '[A-Z]'`"
|
||||
echo "#ifndef $name" > "$outfile"
|
||||
echo "#define $name" >> "$outfile"
|
||||
echo "" >> "$outfile"
|
||||
@@ -251,7 +290,6 @@ output_define_make()
|
||||
|
||||
create_config_make()
|
||||
{
|
||||
|
||||
outfile="$1"
|
||||
shift
|
||||
|
||||
|
||||
+12
-9
@@ -64,8 +64,7 @@ print_sub_opt()
|
||||
lowertext="`echo $arg1 | tr '[A-Z]' '[a-z]'`"
|
||||
|
||||
if [ "$arg3" = "auto" ]; then
|
||||
echo -n "--enable-$lowertext: "
|
||||
echo $arg2
|
||||
echo "--enable-$lowertext: $arg2"
|
||||
echo "--disable-$lowertext"
|
||||
elif [ "$arg3" = "yes" ]; then
|
||||
echo "--disable-$lowertext: $arg2"
|
||||
@@ -103,8 +102,9 @@ parse_input()
|
||||
;;
|
||||
|
||||
--enable-*)
|
||||
enable=`echo $1 | sed 's|^--enable-||'`
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $enable`" ]; then
|
||||
tmp="$1"
|
||||
enable="${tmp#--enable-}"
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i -- $enable`" ]; then
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
@@ -112,8 +112,9 @@ parse_input()
|
||||
;;
|
||||
|
||||
--disable-*)
|
||||
disable=`echo $1 | sed 's|^--disable-||'`
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $disable`" ]; then
|
||||
tmp="$1"
|
||||
disable="${tmp#--disable-}"
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i -- $disable`" ]; then
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
@@ -121,9 +122,11 @@ parse_input()
|
||||
;;
|
||||
|
||||
--with-*)
|
||||
arg="`echo $1 | sed 's|^--with-\S\S*=||'`"
|
||||
with=`echo $1 | sed 's|^--with-\(\S\S*\)=.*$|\1|'`
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_STRINGS | grep -i $with`" ]; then
|
||||
tmp="$1"
|
||||
arg="${tmp#--with-*=}"
|
||||
tmp="${tmp#--with-}"
|
||||
with="${tmp%%=*}"
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_STRINGS | grep -i -- $with`" ]; then
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+366
-164
@@ -2,18 +2,21 @@
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/avstring.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/avconfig.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "ffemu.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "SDL_thread.h"
|
||||
|
||||
struct video_info
|
||||
{
|
||||
bool enabled;
|
||||
AVCodecContext *codec;
|
||||
|
||||
AVFrame *conv_frame;
|
||||
@@ -23,13 +26,16 @@ struct video_info
|
||||
uint8_t *outbuf;
|
||||
size_t outbuf_size;
|
||||
|
||||
int fmt;
|
||||
int pix_size;
|
||||
|
||||
AVFormatContext *format;
|
||||
|
||||
struct SwsContext *sws_ctx;
|
||||
} video;
|
||||
|
||||
struct audio_info
|
||||
{
|
||||
bool enabled;
|
||||
AVCodecContext *codec;
|
||||
|
||||
int16_t *buffer;
|
||||
@@ -55,163 +61,189 @@ struct ffemu
|
||||
struct muxer_info muxer;
|
||||
|
||||
struct ffemu_params params;
|
||||
|
||||
SDL_cond *cond;
|
||||
SDL_mutex *cond_lock;
|
||||
SDL_mutex *lock;
|
||||
fifo_buffer_t *audio_fifo;
|
||||
fifo_buffer_t *video_fifo;
|
||||
fifo_buffer_t *attr_fifo;
|
||||
SDL_Thread *thread;
|
||||
volatile bool alive;
|
||||
volatile bool can_sleep;
|
||||
};
|
||||
|
||||
// Currently hardcoded atm. :)
|
||||
static int map_audio_codec(ffemu_audio_codec codec)
|
||||
static bool init_audio(struct audio_info *audio, struct ffemu_params *param)
|
||||
{
|
||||
(void)codec;
|
||||
return CODEC_ID_VORBIS;
|
||||
}
|
||||
|
||||
// Currently hardcoded atm. :)
|
||||
static int map_video_codec(ffemu_video_codec codec)
|
||||
{
|
||||
(void)codec;
|
||||
return CODEC_ID_H264;
|
||||
}
|
||||
|
||||
static int init_audio(struct audio_info *audio, struct ffemu_params *param)
|
||||
{
|
||||
AVCodec *codec = avcodec_find_encoder(map_audio_codec(param->acodec));
|
||||
AVCodec *codec = avcodec_find_encoder(CODEC_ID_FLAC);
|
||||
if (!codec)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
audio->codec = avcodec_alloc_context();
|
||||
audio->codec = avcodec_alloc_context3(codec);
|
||||
|
||||
avcodec_get_context_defaults(audio->codec);
|
||||
|
||||
// Hardcode this atm.
|
||||
audio->codec->global_quality = 100000;
|
||||
audio->codec->flags |= CODEC_FLAG_QSCALE;
|
||||
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;
|
||||
if (avcodec_open(audio->codec, codec) != 0)
|
||||
return -1;
|
||||
|
||||
if (avcodec_open2(audio->codec, codec, NULL) != 0)
|
||||
return false;
|
||||
|
||||
audio->buffer = av_malloc(audio->codec->frame_size * param->channels * sizeof(int16_t));
|
||||
if (!audio->buffer)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
audio->outbuf_size = 50000;
|
||||
audio->outbuf_size = 2000000;
|
||||
audio->outbuf = av_malloc(audio->outbuf_size);
|
||||
if (!audio->outbuf)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hardcode.
|
||||
static void init_x264_param(AVCodecContext *c)
|
||||
static bool init_video(struct video_info *video, struct ffemu_params *param)
|
||||
{
|
||||
c->coder_type = 1; // coder = 1
|
||||
c->flags|=CODEC_FLAG_LOOP_FILTER; // flags=+loop
|
||||
c->me_cmp|= 1; // cmp=+chroma, where CHROMA = 1
|
||||
c->partitions|=X264_PART_I8X8+X264_PART_I4X4+X264_PART_P8X8+X264_PART_B8X8; // partitions=+parti8x8+parti4x4+partp8x8+partb8x8
|
||||
c->me_method=ME_HEX; // me_method=hex
|
||||
c->me_subpel_quality = 7; // subq=7
|
||||
c->me_range = 16; // me_range=16
|
||||
c->gop_size = 250; // g=250
|
||||
c->keyint_min = 25; // keyint_min=25
|
||||
c->scenechange_threshold = 40; // sc_threshold=40
|
||||
c->i_quant_factor = 0.71; // i_qfactor=0.71
|
||||
c->b_frame_strategy = 1; // b_strategy=1
|
||||
c->qcompress = 0.6; // qcomp=0.6
|
||||
c->qmin = 10; // qmin=10
|
||||
c->qmax = 51; // qmax=51
|
||||
c->max_qdiff = 4; // qdiff=4
|
||||
c->max_b_frames = 3; // bf=3
|
||||
c->refs = 3; // refs=3
|
||||
c->directpred = 1; // directpred=1
|
||||
c->trellis = 1; // trellis=1
|
||||
c->flags2|=CODEC_FLAG2_BPYRAMID+CODEC_FLAG2_MIXED_REFS+CODEC_FLAG2_WPRED+CODEC_FLAG2_8X8DCT+CODEC_FLAG2_FASTPSKIP; // flags2=+bpyramid+mixed_refs+wpred+dct8x8+fastpskip
|
||||
c->weighted_p_pred = 2; // wpredp=2
|
||||
|
||||
// libx264-main.ffpreset preset
|
||||
c->flags2|=CODEC_FLAG2_8X8DCT;
|
||||
c->flags2^=CODEC_FLAG2_8X8DCT;
|
||||
}
|
||||
|
||||
static int init_video(struct video_info *video, struct ffemu_params *param)
|
||||
{
|
||||
AVCodec *codec = avcodec_find_encoder(map_video_codec(param->vcodec));
|
||||
AVCodec *codec = avcodec_find_encoder(CODEC_ID_FFV1);
|
||||
if (!codec)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
video->codec = avcodec_alloc_context();
|
||||
#if AV_HAVE_BIGENDIAN
|
||||
video->fmt = PIX_FMT_RGB555BE;
|
||||
#else
|
||||
video->fmt = PIX_FMT_RGB555LE;
|
||||
#endif
|
||||
video->pix_size = sizeof(uint16_t);
|
||||
if (param->rgb32)
|
||||
{
|
||||
video->fmt = PIX_FMT_RGB32;
|
||||
video->pix_size = sizeof(uint32_t);
|
||||
}
|
||||
|
||||
video->codec = avcodec_alloc_context3(codec);
|
||||
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->crf = 25;
|
||||
// Might have to change this later to account for RGB codecs.
|
||||
video->codec->pix_fmt = PIX_FMT_YUV420P;
|
||||
///// Is this element in all recent ffmpeg versions?
|
||||
video->codec->thread_count = 4;
|
||||
/////
|
||||
video->codec->pix_fmt = PIX_FMT_RGB32;
|
||||
video->codec->sample_aspect_ratio = av_d2q(param->aspect_ratio * param->out_height / param->out_width, 255);
|
||||
init_x264_param(video->codec);
|
||||
video->codec->thread_count = 2;
|
||||
|
||||
if (avcodec_open(video->codec, codec) != 0)
|
||||
return -1;
|
||||
if (avcodec_open2(video->codec, codec, NULL) != 0)
|
||||
return false;
|
||||
|
||||
// Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be.
|
||||
video->outbuf_size = 1000000;
|
||||
video->outbuf_size = 1 << 23;
|
||||
video->outbuf = av_malloc(video->outbuf_size);
|
||||
|
||||
int size = avpicture_get_size(PIX_FMT_YUV420P, param->out_width, param->out_height);
|
||||
// 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);
|
||||
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_YUV420P, param->out_width, param->out_height);
|
||||
avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, PIX_FMT_RGB32, param->fb_width, param->fb_height);
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int init_muxer(ffemu_t *handle)
|
||||
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)
|
||||
#else
|
||||
if (url_fopen(&ctx->pb, ctx->filename, URL_WRONLY) < 0)
|
||||
#endif
|
||||
{
|
||||
av_free(ctx);
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
int stream_cnt = 0;
|
||||
if (handle->video.enabled)
|
||||
{
|
||||
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->video.codec;
|
||||
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->video.codec;
|
||||
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->video.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.vstream = stream;
|
||||
handle->muxer.vstream->sample_aspect_ratio = handle->video.codec->sample_aspect_ratio;
|
||||
}
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->video.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.vstream = stream;
|
||||
handle->muxer.vstream->sample_aspect_ratio = handle->video.codec->sample_aspect_ratio;
|
||||
|
||||
if (handle->audio.enabled)
|
||||
{
|
||||
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->audio.codec;
|
||||
stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->audio.codec;
|
||||
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->audio.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.astream = stream;
|
||||
}
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->audio.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.astream = stream;
|
||||
|
||||
if (av_write_header(ctx) < 0)
|
||||
return -1;
|
||||
if (avformat_write_header(ctx, NULL) < 0)
|
||||
return false;
|
||||
|
||||
handle->muxer.ctx = ctx;
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MAX_FRAMES 64
|
||||
|
||||
static int SDLCALL 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->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 *
|
||||
handle->video.pix_size * MAX_FRAMES));
|
||||
|
||||
handle->alive = true;
|
||||
handle->can_sleep = true;
|
||||
assert(handle->thread = SDL_CreateThread(ffemu_thread, handle));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void deinit_thread(ffemu_t *handle)
|
||||
{
|
||||
if (handle->thread)
|
||||
{
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
handle->alive = false;
|
||||
handle->can_sleep = false;
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
|
||||
SDL_CondSignal(handle->cond);
|
||||
SDL_WaitThread(handle->thread, NULL);
|
||||
|
||||
SDL_DestroyMutex(handle->lock);
|
||||
SDL_DestroyMutex(handle->cond_lock);
|
||||
SDL_DestroyCond(handle->cond);
|
||||
|
||||
handle->thread = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void deinit_thread_buf(ffemu_t *handle)
|
||||
{
|
||||
if (handle->audio_fifo)
|
||||
{
|
||||
fifo_free(handle->audio_fifo);
|
||||
handle->audio_fifo = NULL;
|
||||
}
|
||||
|
||||
if (handle->attr_fifo)
|
||||
{
|
||||
fifo_free(handle->attr_fifo);
|
||||
handle->attr_fifo = NULL;
|
||||
}
|
||||
|
||||
if (handle->video_fifo)
|
||||
{
|
||||
fifo_free(handle->video_fifo);
|
||||
handle->video_fifo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ffemu_t *ffemu_new(const struct ffemu_params *params)
|
||||
{
|
||||
avcodec_init();
|
||||
av_register_all();
|
||||
|
||||
ffemu_t *handle = calloc(1, sizeof(*handle));
|
||||
@@ -219,20 +251,17 @@ ffemu_t *ffemu_new(const struct ffemu_params *params)
|
||||
goto error;
|
||||
|
||||
handle->params = *params;
|
||||
if (handle->params.vcodec != FFEMU_VIDEO_NONE)
|
||||
handle->video.enabled = true;
|
||||
if (handle->params.acodec != FFEMU_AUDIO_NONE)
|
||||
handle->audio.enabled = true;
|
||||
|
||||
if (handle->video.enabled)
|
||||
if (init_video(&handle->video, &handle->params) < 0)
|
||||
goto error;
|
||||
if (!init_video(&handle->video, &handle->params))
|
||||
goto error;
|
||||
|
||||
if (handle->audio.enabled)
|
||||
if (init_audio(&handle->audio, &handle->params) < 0)
|
||||
goto error;
|
||||
if (!init_audio(&handle->audio, &handle->params))
|
||||
goto error;
|
||||
|
||||
if (init_muxer(handle) < 0)
|
||||
if (!init_muxer(handle))
|
||||
goto error;
|
||||
|
||||
if (!init_thread(handle))
|
||||
goto error;
|
||||
|
||||
return handle;
|
||||
@@ -246,6 +275,9 @@ void ffemu_free(ffemu_t *handle)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
deinit_thread(handle);
|
||||
deinit_thread_buf(handle);
|
||||
|
||||
if (handle->audio.codec)
|
||||
{
|
||||
avcodec_close(handle->audio.codec);
|
||||
@@ -267,43 +299,112 @@ void ffemu_free(ffemu_t *handle)
|
||||
if (handle->video.conv_frame_buf)
|
||||
av_free(handle->video.conv_frame_buf);
|
||||
|
||||
if (handle->video.sws_ctx)
|
||||
sws_freeContext(handle->video.sws_ctx);
|
||||
|
||||
free(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Need to make this thread based, but hey.
|
||||
int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
{
|
||||
if (!handle->video.enabled)
|
||||
return -1;
|
||||
for (;;)
|
||||
{
|
||||
SDL_mutexP(handle->lock);
|
||||
unsigned avail = fifo_write_avail(handle->attr_fifo);
|
||||
SDL_mutexV(handle->lock);
|
||||
|
||||
// This is deprecated, can't find a replacement... :(
|
||||
// Hardcode pixel format for now (SNES)
|
||||
struct SwsContext *conv_ctx = sws_getContext(data->width, data->height, PIX_FMT_RGB555LE,
|
||||
handle->params.out_width, handle->params.out_height, PIX_FMT_YUV420P, handle->params.rescaler == FFEMU_RESCALER_LANCZOS ? SWS_LANCZOS : SWS_POINT,
|
||||
if (!handle->alive)
|
||||
return -1;
|
||||
|
||||
if (avail >= sizeof(*data))
|
||||
break;
|
||||
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
if (handle->can_sleep)
|
||||
{
|
||||
handle->can_sleep = false;
|
||||
SDL_CondWait(handle->cond, handle->cond_lock);
|
||||
handle->can_sleep = true;
|
||||
}
|
||||
else
|
||||
SDL_CondSignal(handle->cond);
|
||||
|
||||
SDL_mutexV(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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
SDL_mutexP(handle->lock);
|
||||
unsigned avail = fifo_write_avail(handle->audio_fifo);
|
||||
SDL_mutexV(handle->lock);
|
||||
|
||||
if (!handle->alive)
|
||||
return -1;
|
||||
|
||||
if (avail >= data->frames * handle->params.channels * sizeof(int16_t))
|
||||
break;
|
||||
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
if (handle->can_sleep)
|
||||
{
|
||||
handle->can_sleep = false;
|
||||
SDL_CondWait(handle->cond, handle->cond_lock);
|
||||
handle->can_sleep = true;
|
||||
}
|
||||
else
|
||||
SDL_CondSignal(handle->cond);
|
||||
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
}
|
||||
|
||||
SDL_mutexP(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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
int linesize = data->pitch;
|
||||
|
||||
sws_scale(conv_ctx, (const uint8_t* const*)&data->data, &linesize, 0, handle->params.out_width, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
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);
|
||||
|
||||
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);
|
||||
int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf,
|
||||
handle->video.outbuf_size, handle->video.conv_frame);
|
||||
|
||||
if (outsize < 0)
|
||||
return -1;
|
||||
|
||||
sws_freeContext(conv_ctx);
|
||||
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.vstream->index;
|
||||
pkt.data = handle->video.outbuf;
|
||||
pkt.size = outsize;
|
||||
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
||||
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;
|
||||
@@ -319,16 +420,8 @@ int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
{
|
||||
if (!handle->audio.enabled)
|
||||
return -1;
|
||||
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.astream->index;
|
||||
pkt.data = handle->audio.outbuf;
|
||||
|
||||
size_t written_frames = 0;
|
||||
while (written_frames < data->frames)
|
||||
{
|
||||
@@ -344,6 +437,10 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
|
||||
if (handle->audio.frames_in_buffer == (size_t)handle->audio.codec->frame_size)
|
||||
{
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.data = handle->audio.outbuf;
|
||||
pkt.stream_index = handle->muxer.astream->index;
|
||||
|
||||
int out_size = avcodec_encode_audio(handle->audio.codec, handle->audio.outbuf, handle->audio.outbuf_size, handle->audio.buffer);
|
||||
if (out_size < 0)
|
||||
@@ -351,13 +448,9 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
|
||||
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.flags |= AV_PKT_FLAG_KEY;
|
||||
handle->audio.frames_in_buffer = 0;
|
||||
@@ -375,38 +468,147 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
|
||||
int ffemu_finalize(ffemu_t *handle)
|
||||
{
|
||||
// Push out delayed frames. (MPEG codecs)
|
||||
if (handle->video.enabled)
|
||||
deinit_thread(handle);
|
||||
|
||||
// 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)
|
||||
{
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.vstream->index;
|
||||
pkt.data = handle->video.outbuf;
|
||||
|
||||
int out_size = 0;
|
||||
do
|
||||
struct ffemu_video_data attr_buf;
|
||||
while (fifo_read_avail(handle->attr_fifo) >= sizeof(attr_buf))
|
||||
{
|
||||
out_size = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, NULL);
|
||||
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);
|
||||
fifo_read(handle->attr_fifo, &attr_buf, sizeof(attr_buf));
|
||||
fifo_read(handle->video_fifo, video_buf, attr_buf.height * attr_buf.pitch);
|
||||
attr_buf.data = video_buf;
|
||||
ffemu_push_video_thread(handle, &attr_buf);
|
||||
}
|
||||
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)
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.vstream->index;
|
||||
pkt.data = handle->video.outbuf;
|
||||
|
||||
int out_size = 0;
|
||||
do
|
||||
{
|
||||
out_size = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, NULL);
|
||||
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);
|
||||
|
||||
// Write final data.
|
||||
av_write_trailer(handle->muxer.ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int SDLCALL ffemu_thread(void *data)
|
||||
{
|
||||
ffemu_t *ff = data;
|
||||
|
||||
// For some reason, FFmpeg has a tendency to crash if we don't overallocate a bit. :s
|
||||
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);
|
||||
int16_t *audio_buf = av_malloc(audio_buf_size);
|
||||
assert(audio_buf);
|
||||
|
||||
struct ffemu_video_data attr_buf;
|
||||
|
||||
while (ff->alive)
|
||||
{
|
||||
bool avail_video = false;
|
||||
bool avail_audio = false;
|
||||
|
||||
SDL_mutexP(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);
|
||||
|
||||
if (!avail_video && !avail_audio)
|
||||
{
|
||||
SDL_mutexP(ff->cond_lock);
|
||||
if (ff->can_sleep)
|
||||
{
|
||||
ff->can_sleep = false;
|
||||
SDL_CondWait(ff->cond, ff->cond_lock);
|
||||
ff->can_sleep = true;
|
||||
}
|
||||
else
|
||||
SDL_CondSignal(ff->cond);
|
||||
|
||||
SDL_mutexV(ff->cond_lock);
|
||||
}
|
||||
|
||||
if (avail_video)
|
||||
{
|
||||
SDL_mutexP(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);
|
||||
|
||||
attr_buf.data = video_buf;
|
||||
ffemu_push_video_thread(ff, &attr_buf);
|
||||
}
|
||||
|
||||
if (avail_audio)
|
||||
{
|
||||
SDL_mutexP(ff->lock);
|
||||
fifo_read(ff->audio_fifo, audio_buf, audio_buf_size);
|
||||
SDL_mutexV(ff->lock);
|
||||
SDL_CondSignal(ff->cond);
|
||||
|
||||
struct ffemu_audio_data aud = {
|
||||
.frames = 128,
|
||||
.data = audio_buf
|
||||
};
|
||||
|
||||
ffemu_push_audio_thread(ff, &aud);
|
||||
}
|
||||
}
|
||||
|
||||
av_free(video_buf);
|
||||
av_free(audio_buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+7
-43
@@ -8,36 +8,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Available video codecs
|
||||
typedef enum ffemu_video_codec
|
||||
{
|
||||
FFEMU_VIDEO_NONE,
|
||||
FFEMU_VIDEO_H264,
|
||||
FFEMU_VIDEO_MPEG4,
|
||||
} ffemu_video_codec;
|
||||
|
||||
// Available audio codecs
|
||||
typedef enum ffemu_audio_codec
|
||||
{
|
||||
FFEMU_AUDIO_NONE,
|
||||
FFEMU_AUDIO_VORBIS,
|
||||
FFEMU_AUDIO_MP3,
|
||||
FFEMU_AUDIO_AAC,
|
||||
} ffemu_audio_codec;
|
||||
|
||||
// Available pixel formats
|
||||
typedef enum ffemu_pixel_format
|
||||
{
|
||||
FFEMU_FMT_XBGR1555,
|
||||
FFEMU_FMT_RGB888,
|
||||
} ffemu_pixel_format;
|
||||
|
||||
typedef enum ffemu_rescaler
|
||||
{
|
||||
FFEMU_RESCALER_LANCZOS,
|
||||
FFEMU_RESCALER_POINT
|
||||
} ffemu_rescaler;
|
||||
|
||||
struct ffemu_rational
|
||||
{
|
||||
unsigned num;
|
||||
@@ -47,20 +17,15 @@ struct ffemu_rational
|
||||
// Parameters passed to ffemu_new()
|
||||
struct ffemu_params
|
||||
{
|
||||
// Video codec to use. If not recording video, select FFEMU_VIDEO_NONE.
|
||||
ffemu_video_codec vcodec;
|
||||
|
||||
// Desired output resolution.
|
||||
unsigned out_width;
|
||||
unsigned out_height;
|
||||
|
||||
// Total size of framebuffer used in input.
|
||||
unsigned fb_width;
|
||||
unsigned fb_height;
|
||||
float aspect_ratio;
|
||||
|
||||
// Rescaler for video.
|
||||
ffemu_rescaler rescaler;
|
||||
|
||||
// Pixel format for video input.
|
||||
ffemu_pixel_format format;
|
||||
|
||||
// FPS of video input.
|
||||
struct ffemu_rational fps;
|
||||
|
||||
@@ -71,15 +36,15 @@ struct ffemu_params
|
||||
// Define some video codec dependent option. (E.g. h264 profiles)
|
||||
uint64_t video_opt;
|
||||
|
||||
// Audio codec. If not recording audio, select FFEMU_AUDIO_NONE.
|
||||
ffemu_audio_codec acodec;
|
||||
|
||||
// Audio sample rate.
|
||||
unsigned samplerate;
|
||||
|
||||
// Audio channels.
|
||||
unsigned channels;
|
||||
|
||||
// If input is ARGB or XRGB1555.
|
||||
bool rgb32;
|
||||
|
||||
// Audio bits. Sample format is always signed PCM in native byte order.
|
||||
//unsigned bits;
|
||||
|
||||
@@ -119,7 +84,6 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data);
|
||||
int ffemu_finalize(ffemu_t *handle);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+74
-26
@@ -20,6 +20,7 @@
|
||||
#include <assert.h>
|
||||
#include "strl.h"
|
||||
#include "config.def.h"
|
||||
#include "file.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -28,6 +29,7 @@
|
||||
#include <ctype.h>
|
||||
|
||||
struct settings g_settings;
|
||||
struct global g_extern;
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
static void read_keybinds(config_file_t *conf);
|
||||
@@ -71,12 +73,18 @@ static void set_defaults(void)
|
||||
case AUDIO_ROAR:
|
||||
def_audio = "roar";
|
||||
break;
|
||||
case AUDIO_COREAUDIO:
|
||||
def_audio = "coreaudio";
|
||||
break;
|
||||
case AUDIO_AL:
|
||||
def_audio = "openal";
|
||||
break;
|
||||
case AUDIO_SDL:
|
||||
def_audio = "sdl";
|
||||
break;
|
||||
case AUDIO_DSOUND:
|
||||
def_audio = "dsound";
|
||||
break;
|
||||
case AUDIO_XAUDIO:
|
||||
def_audio = "xaudio";
|
||||
break;
|
||||
@@ -111,10 +119,11 @@ static void set_defaults(void)
|
||||
|
||||
g_settings.video.xscale = xscale;
|
||||
g_settings.video.yscale = yscale;
|
||||
g_settings.video.fullscreen = fullscreen;
|
||||
g_settings.video.fullscreen = g_extern.force_fullscreen ? true : fullscreen;
|
||||
g_settings.video.fullscreen_x = fullscreen_x;
|
||||
g_settings.video.fullscreen_y = fullscreen_y;
|
||||
g_settings.video.force_16bit = force_16bit;
|
||||
g_settings.video.disable_composition = disable_composition;
|
||||
g_settings.video.vsync = vsync;
|
||||
g_settings.video.smooth = video_smooth;
|
||||
g_settings.video.force_aspect = force_aspect;
|
||||
@@ -135,6 +144,9 @@ static void set_defaults(void)
|
||||
g_settings.video.second_pass_smooth = second_pass_smooth;
|
||||
#endif
|
||||
|
||||
g_settings.video.hires_record = hires_record;
|
||||
g_settings.video.post_filter_record = post_filter_record;
|
||||
|
||||
g_settings.audio.enable = audio_enable;
|
||||
g_settings.audio.out_rate = out_rate;
|
||||
g_settings.audio.in_rate = in_rate;
|
||||
@@ -173,7 +185,6 @@ static void parse_config_file(void);
|
||||
|
||||
void parse_config(void)
|
||||
{
|
||||
memset(&g_settings, 0, sizeof(struct settings));
|
||||
set_defaults();
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
@@ -181,6 +192,7 @@ void parse_config(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
static config_file_t *open_default_config_file(void)
|
||||
{
|
||||
config_file_t *conf = NULL;
|
||||
@@ -192,9 +204,9 @@ static config_file_t *open_default_config_file(void)
|
||||
const char *appdata = getenv("APPDATA");
|
||||
if (appdata)
|
||||
{
|
||||
char conf_path[strlen(appdata) + strlen("/ssnes.cfg ")];
|
||||
strcpy(conf_path, appdata);
|
||||
strcat(conf_path, "/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);
|
||||
}
|
||||
}
|
||||
@@ -202,9 +214,9 @@ static config_file_t *open_default_config_file(void)
|
||||
const char *home = getenv("HOME");
|
||||
if (home)
|
||||
{
|
||||
char conf_path[strlen(home) + strlen("/.ssnes.cfg ")];
|
||||
strcpy(conf_path, home);
|
||||
strcat(conf_path, "/.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);
|
||||
}
|
||||
if (!conf)
|
||||
@@ -217,16 +229,16 @@ 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 ")];
|
||||
strcpy(conf_path, xdg);
|
||||
strcat(conf_path, "/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 ")];
|
||||
strcpy(conf_path, home);
|
||||
strcat(conf_path, "/.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);
|
||||
}
|
||||
// Try this as a last chance...
|
||||
@@ -236,6 +248,7 @@ static config_file_t *open_default_config_file(void)
|
||||
|
||||
return conf;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Macros to ease config getting.
|
||||
#define CONFIG_GET_BOOL(var, key) if (config_get_bool(conf, key, &tmp_bool)) \
|
||||
@@ -247,13 +260,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)
|
||||
@@ -281,14 +289,20 @@ static void parse_config_file(void)
|
||||
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");
|
||||
CONFIG_GET_INT(video.fullscreen_x, "video_fullscreen_x");
|
||||
CONFIG_GET_INT(video.fullscreen_y, "video_fullscreen_y");
|
||||
CONFIG_GET_BOOL(video.fullscreen, "video_fullscreen");
|
||||
|
||||
if (!g_extern.force_fullscreen)
|
||||
{
|
||||
CONFIG_GET_BOOL(video.fullscreen, "video_fullscreen");
|
||||
}
|
||||
|
||||
CONFIG_GET_BOOL(video.force_16bit, "video_force_16bit");
|
||||
CONFIG_GET_BOOL(video.disable_composition, "video_disable_composition");
|
||||
CONFIG_GET_BOOL(video.vsync, "video_vsync");
|
||||
CONFIG_GET_BOOL(video.smooth, "video_smooth");
|
||||
CONFIG_GET_BOOL(video.force_aspect, "video_force_aspect");
|
||||
@@ -310,6 +324,9 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_DOUBLE(video.msg_pos_y, "video_message_pos_y");
|
||||
#endif
|
||||
|
||||
CONFIG_GET_BOOL(video.hires_record, "video_hires_record");
|
||||
CONFIG_GET_BOOL(video.post_filter_record, "video_post_filter_record");
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
CONFIG_GET_STRING(video.filter_path, "video_filter");
|
||||
CONFIG_GET_STRING(video.external_driver, "video_external_driver");
|
||||
@@ -317,7 +334,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;
|
||||
@@ -327,8 +344,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
|
||||
|
||||
@@ -369,7 +384,13 @@ static void parse_config_file(void)
|
||||
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");
|
||||
|
||||
@@ -383,6 +404,27 @@ static void parse_config_file(void)
|
||||
|
||||
CONFIG_GET_STRING(cheat_database, "cheat_database_path");
|
||||
|
||||
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);
|
||||
|
||||
config_file_free(conf);
|
||||
@@ -414,6 +456,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player1_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player1_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -448,6 +491,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player2_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player2_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -482,6 +526,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player3_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player3_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -516,6 +561,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player4_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player4_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -550,6 +596,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player5_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player5_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -643,6 +690,7 @@ static const struct key_map sdlk_map[] = {
|
||||
{ "print_screen", SDLK_PRINT },
|
||||
{ "scroll_lock", SDLK_SCROLLOCK },
|
||||
{ "tilde", SDLK_BACKQUOTE },
|
||||
{ "backquote", SDLK_BACKQUOTE },
|
||||
{ "nul", SDLK_UNKNOWN },
|
||||
};
|
||||
|
||||
|
||||
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.win32 clean || die "Failed to clean ..."
|
||||
make -f Makefile.win32 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! :)"
|
||||
|
||||
@@ -31,7 +31,10 @@ extract_zip()
|
||||
die "Cannot find .smc nor .sfc file in archive"
|
||||
fi
|
||||
|
||||
unzip -p "$1" "$FILENAME" > "$TMPROM"
|
||||
# The people who made the [!] naming convention obviously have never used regex ... <_<
|
||||
FILENAME_FIXED="`echo $FILENAME | sed -e 's|\[|\\\\[|g' -e 's|!|\\\\!|g' -e 's|\]|\\\\]|g'`"
|
||||
|
||||
unzip -p "$1" "$FILENAME_FIXED" > "$TMPROM"
|
||||
}
|
||||
|
||||
extract_7z()
|
||||
@@ -79,13 +82,14 @@ esac
|
||||
SAVENAME="`basename \"$COMP_FILE\" $EXTENSION`"
|
||||
DIRNAME="`dirname \"$COMP_FILE\"`"
|
||||
SAVENAME="${SAVENAME}.srm"
|
||||
echo "Savename: ${DIRNAME}/${SAVENAME}"
|
||||
|
||||
SSNES_PATH="`which ssnes`"
|
||||
if [ -z "$SSNES_PATH" ]; then
|
||||
die "Cannot find SSNES in path."
|
||||
fi
|
||||
|
||||
echo "Temp ROM: $TMPROM"
|
||||
echo "Savename: ${DIRNAME}/${SAVENAME}"
|
||||
"$SSNES_PATH" "$TMPROM" -s "${DIRNAME}/${SAVENAME}" "$@"
|
||||
rm "$TMPROM"
|
||||
rm -r "$TMPDIR"
|
||||
@@ -49,23 +49,30 @@
|
||||
#include "SDL.h"
|
||||
// OSX seems to really need -lSDLmain,
|
||||
// so we include SDL.h here so it can hack our main.
|
||||
// I had issues including this in Win32 for some reason. :)
|
||||
// We want to use -mconsole in Win32, so we need main().
|
||||
#endif
|
||||
|
||||
struct global g_extern = {
|
||||
.video_active = true,
|
||||
.audio_active = true,
|
||||
.game_type = SSNES_CART_NORMAL,
|
||||
};
|
||||
|
||||
// To avoid continous switching if we hold the button down, we require that the button must go from pressed, unpressed back to pressed to be able to toggle between then.
|
||||
static void set_fast_forward_button(bool new_button_state)
|
||||
static void set_fast_forward_button(bool new_button_state, bool new_hold_button_state)
|
||||
{
|
||||
bool update_sync = false;
|
||||
static bool old_button_state = false;
|
||||
static bool old_hold_button_state = false;
|
||||
static bool syncing_state = false;
|
||||
|
||||
if (new_button_state && !old_button_state)
|
||||
{
|
||||
syncing_state = !syncing_state;
|
||||
update_sync = true;
|
||||
}
|
||||
else if (old_hold_button_state != new_hold_button_state)
|
||||
{
|
||||
syncing_state = new_hold_button_state;
|
||||
update_sync = true;
|
||||
}
|
||||
|
||||
if (update_sync)
|
||||
{
|
||||
if (g_extern.video_active)
|
||||
driver.video->set_nonblock_state(driver.video_data, syncing_state);
|
||||
if (g_extern.audio_active)
|
||||
@@ -76,12 +83,14 @@ static void set_fast_forward_button(bool new_button_state)
|
||||
else
|
||||
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
|
||||
}
|
||||
|
||||
old_button_state = new_button_state;
|
||||
old_hold_button_state = new_hold_button_state;
|
||||
}
|
||||
|
||||
static void take_screenshot(const uint16_t *frame, unsigned width, unsigned height)
|
||||
{
|
||||
if (!*(g_settings.screenshot_directory))
|
||||
if (!(*g_settings.screenshot_directory))
|
||||
return;
|
||||
|
||||
bool ret = screenshot_dump(g_settings.screenshot_directory, frame,
|
||||
@@ -127,14 +136,16 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
if (g_extern.do_screenshot)
|
||||
take_screenshot(data, width, height);
|
||||
|
||||
// Slightly messy code,
|
||||
// but we really need to do processing before blocking on VSync for best possible scheduling.
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording)
|
||||
if (g_extern.recording && (!g_extern.filter.active || !g_settings.video.post_filter_record))
|
||||
{
|
||||
struct ffemu_video_data ffemu_data = {
|
||||
.data = data,
|
||||
.pitch = height == 448 || height == 478 ? 1024 : 2048,
|
||||
.width = width,
|
||||
.height = height
|
||||
.height = height,
|
||||
};
|
||||
ffemu_push_video(g_extern.rec, &ffemu_data);
|
||||
}
|
||||
@@ -150,6 +161,20 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
g_extern.filter.psize(&owidth, &oheight);
|
||||
g_extern.filter.prender(g_extern.filter.colormap, g_extern.filter.buffer,
|
||||
g_extern.filter.pitch, data, (height == 448 || height == 478) ? 1024 : 2048, width, height);
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording && g_settings.video.post_filter_record)
|
||||
{
|
||||
struct ffemu_video_data ffemu_data = {
|
||||
.data = g_extern.filter.buffer,
|
||||
.pitch = g_extern.filter.pitch,
|
||||
.width = owidth,
|
||||
.height = oheight,
|
||||
};
|
||||
ffemu_push_video(g_extern.rec, &ffemu_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!driver.video->frame(driver.video_data, g_extern.filter.buffer, owidth, oheight, g_extern.filter.pitch, msg))
|
||||
g_extern.video_active = false;
|
||||
}
|
||||
@@ -163,32 +188,35 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
|
||||
static void audio_sample(uint16_t left, uint16_t right)
|
||||
{
|
||||
if ( !g_extern.audio_active )
|
||||
if (!g_extern.audio_active)
|
||||
return;
|
||||
|
||||
const float *output_data = NULL;
|
||||
unsigned output_frames = 0;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr] = left;
|
||||
#endif
|
||||
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)left/0x8000;
|
||||
#ifdef HAVE_FFMPEG
|
||||
g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr] = right;
|
||||
#endif
|
||||
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)right/0x8000;
|
||||
|
||||
if (g_extern.audio_data.data_ptr < g_extern.audio_data.chunk_size)
|
||||
return;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording)
|
||||
{
|
||||
static int16_t static_data[2];
|
||||
static_data[0] = left;
|
||||
static_data[1] = right;
|
||||
struct ffemu_audio_data ffemu_data = {
|
||||
.data = static_data,
|
||||
.frames = 1
|
||||
.data = g_extern.audio_data.conv_outsamples,
|
||||
.frames = g_extern.audio_data.data_ptr / 2
|
||||
};
|
||||
ffemu_push_audio(g_extern.rec, &ffemu_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
const float *output_data = NULL;
|
||||
unsigned output_frames = 0;
|
||||
|
||||
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)left/0x8000;
|
||||
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)right/0x8000;
|
||||
|
||||
if (g_extern.audio_data.data_ptr < g_extern.audio_data.chunk_size)
|
||||
return;
|
||||
|
||||
ssnes_dsp_input_t dsp_input = {
|
||||
.samples = g_extern.audio_data.data,
|
||||
.frames = g_extern.audio_data.data_ptr / 2
|
||||
@@ -280,9 +308,13 @@ static int16_t input_state(bool port, unsigned device, unsigned index, unsigned
|
||||
}
|
||||
}
|
||||
|
||||
const struct snes_keybind *binds[MAX_PLAYERS];
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
binds[i] = g_settings.input.binds[i];
|
||||
static const struct snes_keybind *binds[MAX_PLAYERS] = {
|
||||
g_settings.input.binds[0],
|
||||
g_settings.input.binds[1],
|
||||
g_settings.input.binds[2],
|
||||
g_settings.input.binds[3],
|
||||
g_settings.input.binds[4]
|
||||
};
|
||||
|
||||
int16_t res = driver.input->input_state(driver.input_data, binds, port, device, index, id);
|
||||
if (g_extern.bsv_movie && !g_extern.bsv_movie_playback)
|
||||
@@ -291,34 +323,16 @@ static int16_t input_state(bool port, unsigned device, unsigned index, unsigned
|
||||
return res;
|
||||
}
|
||||
|
||||
static void fill_pathname(char *out_path, const char *in_path, const char *replace, size_t size)
|
||||
{
|
||||
char tmp_path[strlen(in_path) + 1];
|
||||
strlcpy(tmp_path, in_path, sizeof(tmp_path));
|
||||
char *tok = NULL;
|
||||
tok = strrchr(tmp_path, '.');
|
||||
if (tok != NULL)
|
||||
*tok = '\0';
|
||||
assert(strlcpy(out_path, tmp_path, size) < size);
|
||||
assert(strlcat(out_path, replace, size) < size);
|
||||
}
|
||||
|
||||
static 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);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#define SSNES_DEFAULT_CONF_PATH_STR "\n\tDefaults to ssnes.cfg in same directory as ssnes.exe"
|
||||
#define SSNES_DEFAULT_CONF_PATH_STR "\n\t\tDefaults to ssnes.cfg in same directory as ssnes.exe."
|
||||
#elif defined(__APPLE__)
|
||||
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $HOME/.ssnes.cfg"
|
||||
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $HOME/.ssnes.cfg."
|
||||
#else
|
||||
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg"
|
||||
#define SSNES_DEFAULT_CONF_PATH_STR " Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg,\n\t\tor $HOME/.ssnes.cfg, if $XDG_CONFIG_HOME is not defined."
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define PACKAGE_VERSION "0.5"
|
||||
#define PACKAGE_VERSION "0.8"
|
||||
#endif
|
||||
|
||||
#include "config.features.h"
|
||||
@@ -329,6 +343,7 @@ static void print_features(void)
|
||||
puts("");
|
||||
puts("Features:");
|
||||
_PSUPP(sdl, "SDL", "SDL drivers");
|
||||
_PSUPP(xvideo, "XVideo", "XVideo output");
|
||||
_PSUPP(alsa, "ALSA", "audio driver");
|
||||
_PSUPP(oss, "OSS", "audio driver");
|
||||
_PSUPP(jack, "Jack", "audio driver");
|
||||
@@ -340,7 +355,7 @@ static void print_features(void)
|
||||
_PSUPP(dylib, "External", "External filter and driver support");
|
||||
_PSUPP(cg, "Cg", "Cg pixel shaders");
|
||||
_PSUPP(xml, "XML", "bSNES XML pixel shaders");
|
||||
_PSUPP(imlib, "Imlib2", "Imlib2 image loading");
|
||||
_PSUPP(sdl_image, "SDL_image", "SDL_image image loading");
|
||||
_PSUPP(fbo, "FBO", "OpenGL render-to-texture (multi-pass shaders)");
|
||||
_PSUPP(dynamic, "Dynamic", "Dynamic run-time loading of libsnes library");
|
||||
_PSUPP(ffmpeg, "FFmpeg", "On-the-fly recording of gameplay with libavcodec");
|
||||
@@ -348,6 +363,7 @@ static void print_features(void)
|
||||
_PSUPP(configfile, "Config file", "Configuration file support");
|
||||
_PSUPP(freetype, "FreeType", "TTF font rendering with FreeType");
|
||||
_PSUPP(netplay, "Netplay", "Peer-to-peer netplay");
|
||||
_PSUPP(python, "Python", "Script support in shaders");
|
||||
}
|
||||
#undef _PSUPP
|
||||
|
||||
@@ -357,8 +373,9 @@ static void print_help(void)
|
||||
puts("ssnes: Simple Super Nintendo Emulator (libsnes) -- v" PACKAGE_VERSION " --");
|
||||
puts("===================================================================");
|
||||
puts("Usage: ssnes [rom file] [options...]");
|
||||
puts("\t-h/--help: Show this help message");
|
||||
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin");
|
||||
puts("\t-h/--help: Show this help message.");
|
||||
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin.");
|
||||
puts("\t-f/--fullscreen: Start SSNES in fullscreen regardless of config settings.");
|
||||
puts("\t-S/--savestate: Path to use for save states. If not selected, *.state will be assumed.");
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
puts("\t-c/--config: Path for config file." SSNES_DEFAULT_CONF_PATH_STR);
|
||||
@@ -369,6 +386,7 @@ static void print_help(void)
|
||||
puts("\t--sufamiA: Path to A slot of Sufami Turbo. Load Sufami base cart as regular rom.");
|
||||
puts("\t--sufamiB: Path to B slot of Sufami Turbo.");
|
||||
puts("\t-m/--mouse: Connect a virtual mouse into designated port of the SNES (1 or 2).");
|
||||
puts("\t-N/--nodevice: Disconnects the controller device connected to the emulated SNES (1 or 2).");
|
||||
puts("\t\tThis argument can be specified several times to connect more mice.");
|
||||
puts("\t-p/--scope: Connect a virtual SuperScope into port 2 of the SNES.");
|
||||
puts("\t-j/--justifier: Connect a virtual Konami Justifier into port 2 of the SNES.");
|
||||
@@ -383,10 +401,12 @@ static void print_help(void)
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file.");
|
||||
puts("\t-r/--record: Path to record video file.\n\t\tUsing .mkv extension is recommended, and codecs used are FFV1/FLAC.");
|
||||
#endif
|
||||
puts("\t-v/--verbose: Verbose logging");
|
||||
puts("\t-v/--verbose: Verbose logging.");
|
||||
puts("\t-U/--ups: Specifies path for UPS patch that will be applied to ROM.");
|
||||
puts("\t--bps: Specifies path for BPS patch that will be applied to ROM.");
|
||||
puts("\t-X/--xml: Specifies path to XML memory map.");
|
||||
puts("\t-D/--detach: Detach SSNES from the running console. Not relevant for all platforms.\n");
|
||||
|
||||
print_features();
|
||||
@@ -394,12 +414,84 @@ static void print_help(void)
|
||||
|
||||
static void set_basename(const char *path)
|
||||
{
|
||||
char tmp[strlen(path) + 1];
|
||||
strcpy(tmp, path);
|
||||
char *dst = strrchr(tmp, '.');
|
||||
strlcpy(g_extern.basename, path, sizeof(g_extern.basename));
|
||||
char *dst = strrchr(g_extern.basename, '.');
|
||||
if (dst)
|
||||
*dst = '\0';
|
||||
strlcpy(g_extern.basename, tmp, sizeof(g_extern.basename));
|
||||
}
|
||||
|
||||
static void set_paths(const char *path)
|
||||
{
|
||||
set_basename(path);
|
||||
|
||||
SSNES_LOG("Opening file: \"%s\"\n", path);
|
||||
g_extern.rom_file = fopen(path, "rb");
|
||||
if (g_extern.rom_file == NULL)
|
||||
{
|
||||
SSNES_ERR("Could not open file: \"%s\"\n", path);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!g_extern.has_set_save_path)
|
||||
fill_pathname_noext(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm));
|
||||
if (!g_extern.has_set_state_path)
|
||||
fill_pathname_noext(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name));
|
||||
|
||||
if (path_is_directory(g_extern.savefile_name_srm))
|
||||
{
|
||||
fill_pathname_dir(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm));
|
||||
SSNES_LOG("Redirecting save file to \"%s\".\n", g_extern.savefile_name_srm);
|
||||
}
|
||||
if (path_is_directory(g_extern.savestate_name))
|
||||
{
|
||||
fill_pathname_dir(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name));
|
||||
SSNES_LOG("Redirecting save state to \"%s\".\n", g_extern.savestate_name);
|
||||
}
|
||||
if (*g_extern.config_path && path_is_directory(g_extern.config_path))
|
||||
{
|
||||
fill_pathname_dir(g_extern.config_path, g_extern.basename, ".cfg", sizeof(g_extern.config_path));
|
||||
SSNES_LOG("Redirecting config file to \"%s\".\n", g_extern.config_path);
|
||||
if (!path_file_exists(g_extern.config_path))
|
||||
{
|
||||
*g_extern.config_path = '\0';
|
||||
SSNES_LOG("Did not find config file. Using system default.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void verify_stdin_paths(void)
|
||||
{
|
||||
if (strlen(g_extern.savefile_name_srm) == 0)
|
||||
{
|
||||
SSNES_ERR("Need savefile path argument (--save) when reading rom from stdin.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
else if (strlen(g_extern.savestate_name) == 0)
|
||||
{
|
||||
SSNES_ERR("Need savestate path argument (--savestate) when reading rom from stdin.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (path_is_directory(g_extern.savefile_name_srm))
|
||||
{
|
||||
SSNES_ERR("Cannot specify directory for path argument (--save) when reading from stdin.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
else if (path_is_directory(g_extern.savestate_name))
|
||||
{
|
||||
SSNES_ERR("Cannot specify directory for path argument (--savestate) when reading from stdin.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
else if (path_is_directory(g_extern.config_path))
|
||||
{
|
||||
SSNES_ERR("Cannot specify directory for config file (--config) when reading from stdin.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_input(int argc, char *argv[])
|
||||
@@ -415,6 +507,7 @@ static void parse_input(int argc, char *argv[])
|
||||
struct option opts[] = {
|
||||
{ "help", 0, NULL, 'h' },
|
||||
{ "save", 1, NULL, 's' },
|
||||
{ "fullscreen", 0, NULL, 'f' },
|
||||
#ifdef HAVE_FFMPEG
|
||||
{ "record", 1, NULL, 'r' },
|
||||
#endif
|
||||
@@ -424,6 +517,7 @@ static void parse_input(int argc, char *argv[])
|
||||
{ "config", 0, NULL, 'c' },
|
||||
#endif
|
||||
{ "mouse", 1, NULL, 'm' },
|
||||
{ "nodevice", 1, NULL, 'N' },
|
||||
{ "scope", 0, NULL, 'p' },
|
||||
{ "savestate", 1, NULL, 'S' },
|
||||
{ "bsx", 1, NULL, 'b' },
|
||||
@@ -439,6 +533,8 @@ static void parse_input(int argc, char *argv[])
|
||||
{ "frames", 1, NULL, 'F' },
|
||||
{ "port", 1, &val, 'p' },
|
||||
{ "ups", 1, NULL, 'U' },
|
||||
{ "bps", 1, &val, 'B' },
|
||||
{ "xml", 1, NULL, 'X' },
|
||||
{ "detach", 0, NULL, 'D' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
@@ -457,7 +553,7 @@ static void parse_input(int argc, char *argv[])
|
||||
#define CONFIG_FILE_ARG
|
||||
#endif
|
||||
|
||||
char optstring[] = "hs:vS:m:p4jJg:b:B:Y:Z:P:HC:F:U:D" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
|
||||
char optstring[] = "hs:fvS:m:p4jJg:b:B:Y:Z:P:HC:F:U:DN:X:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
|
||||
for(;;)
|
||||
{
|
||||
val = 0;
|
||||
@@ -490,6 +586,10 @@ static void parse_input(int argc, char *argv[])
|
||||
g_extern.has_set_save_path = true;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
g_extern.force_fullscreen = true;
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
strlcpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path));
|
||||
g_extern.game_type = SSNES_CART_SGB;
|
||||
@@ -535,6 +635,17 @@ static void parse_input(int argc, char *argv[])
|
||||
g_extern.has_mouse[port - 1] = true;
|
||||
break;
|
||||
|
||||
case 'N':
|
||||
port = strtol(optarg, NULL, 0);
|
||||
if (port < 1 || port > 2)
|
||||
{
|
||||
SSNES_ERR("Disconnected device from port 1 or 2.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
g_extern.disconnect_device[port - 1] = true;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
g_extern.has_scope[1] = true;
|
||||
break;
|
||||
@@ -574,6 +685,11 @@ static void parse_input(int argc, char *argv[])
|
||||
|
||||
case 'U':
|
||||
strlcpy(g_extern.ups_name, optarg, sizeof(g_extern.ups_name));
|
||||
g_extern.ups_pref = true;
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
strlcpy(g_extern.xml_name, optarg, sizeof(g_extern.xml_name));
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
@@ -586,7 +702,11 @@ static void parse_input(int argc, char *argv[])
|
||||
switch (val)
|
||||
{
|
||||
case 'p':
|
||||
g_extern.netplay_port = strtol(optarg, NULL, 0);
|
||||
g_extern.netplay_port = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
case 'B':
|
||||
strlcpy(g_extern.bps_name, optarg, sizeof(g_extern.bps_name));
|
||||
g_extern.bps_pref = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -604,34 +724,9 @@ static void parse_input(int argc, char *argv[])
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
{
|
||||
set_basename(argv[optind]);
|
||||
|
||||
SSNES_LOG("Opening file: \"%s\"\n", argv[optind]);
|
||||
g_extern.rom_file = fopen(argv[optind], "rb");
|
||||
if (g_extern.rom_file == NULL)
|
||||
{
|
||||
SSNES_ERR("Could not open file: \"%s\"\n", argv[optind]);
|
||||
exit(1);
|
||||
}
|
||||
// strl* would be nice :D
|
||||
if (!g_extern.has_set_save_path)
|
||||
fill_pathname_noext(g_extern.savefile_name_srm, g_extern.basename, ".srm", sizeof(g_extern.savefile_name_srm));
|
||||
if (!g_extern.has_set_state_path)
|
||||
fill_pathname_noext(g_extern.savestate_name, g_extern.basename, ".state", sizeof(g_extern.savestate_name));
|
||||
}
|
||||
else if (strlen(g_extern.savefile_name_srm) == 0)
|
||||
{
|
||||
SSNES_ERR("Need savefile path argument (--save) when reading rom from stdin.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
else if (strlen(g_extern.savestate_name) == 0)
|
||||
{
|
||||
SSNES_ERR("Need savestate path argument (--savefile) when reading rom from stdin.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
set_paths(argv[optind]);
|
||||
else
|
||||
verify_stdin_paths();
|
||||
}
|
||||
|
||||
// TODO: Add rest of the controllers.
|
||||
@@ -649,21 +744,26 @@ static void init_controllers(void)
|
||||
}
|
||||
else if (g_extern.has_multitap)
|
||||
{
|
||||
SSNES_LOG("Connecting multitap to port 2.\n");
|
||||
SSNES_LOG("Connecting Multitap to port 2.\n");
|
||||
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_MULTITAP);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (g_extern.has_mouse[i])
|
||||
if (g_extern.disconnect_device[i])
|
||||
{
|
||||
SSNES_LOG("Connecting mouse to port %d\n", i + 1);
|
||||
SSNES_LOG("Disconnecting device from port %u.\n", i + 1);
|
||||
psnes_set_controller_port_device(i, SNES_DEVICE_NONE);
|
||||
}
|
||||
else if (g_extern.has_mouse[i])
|
||||
{
|
||||
SSNES_LOG("Connecting mouse to port %u.\n", i + 1);
|
||||
psnes_set_controller_port_device(i, SNES_DEVICE_MOUSE);
|
||||
}
|
||||
else if (g_extern.has_scope[i])
|
||||
{
|
||||
SSNES_LOG("Connecting scope to port %d\n", i + 1);
|
||||
SSNES_LOG("Connecting scope to port %u.\n", i + 1);
|
||||
psnes_set_controller_port_device(i, SNES_DEVICE_SUPER_SCOPE);
|
||||
}
|
||||
}
|
||||
@@ -738,30 +838,53 @@ static inline void save_files(void)
|
||||
#ifdef HAVE_FFMPEG
|
||||
static void init_recording(void)
|
||||
{
|
||||
// Hardcode these options at the moment. Should be specificed in the config file later on.
|
||||
if (g_extern.recording)
|
||||
{
|
||||
// Not perfectly SNES accurate, but this can be adjusted later during processing
|
||||
// and playback losslessly, and we please the containers more by
|
||||
// using "sane" values.
|
||||
struct ffemu_rational ntsc_fps = {60000, 1000};
|
||||
struct ffemu_rational pal_fps = {50000, 1000};
|
||||
struct ffemu_params params = {
|
||||
.vcodec = FFEMU_VIDEO_H264,
|
||||
.acodec = FFEMU_AUDIO_VORBIS,
|
||||
.rescaler = FFEMU_RESCALER_POINT,
|
||||
.out_width = 512,
|
||||
.out_height = 448,
|
||||
.out_width = 256,
|
||||
.out_height = 224,
|
||||
.fb_width = 1024,
|
||||
.fb_height = 512,
|
||||
.channels = 2,
|
||||
.samplerate = 32000,
|
||||
.filename = g_extern.record_path,
|
||||
.fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
|
||||
.aspect_ratio = 4.0/3
|
||||
.aspect_ratio = 4.0 / 3,
|
||||
.rgb32 = false,
|
||||
};
|
||||
SSNES_LOG("Recording with FFmpeg to %s.\n", g_extern.record_path);
|
||||
|
||||
if (g_settings.video.hires_record)
|
||||
{
|
||||
params.out_width = 512;
|
||||
params.out_height = 448;
|
||||
}
|
||||
|
||||
if (g_settings.video.post_filter_record && g_extern.filter.active)
|
||||
{
|
||||
g_extern.filter.psize(¶ms.out_width, ¶ms.out_height);
|
||||
params.rgb32 = true;
|
||||
|
||||
unsigned max_width = 512;
|
||||
unsigned max_height = 512;
|
||||
g_extern.filter.psize(&max_width, &max_height);
|
||||
params.fb_width = g_extern.filter.pitch >> 2;
|
||||
params.fb_height = next_pow2(max_height);
|
||||
}
|
||||
|
||||
SSNES_LOG("Recording with FFmpeg to %s @ %ux%u. (FB size: %ux%u 32-bit: %s)\n", g_extern.record_path, params.out_width, params.out_height, params.fb_width, params.fb_height, params.rgb32 ? "yes" : "no");
|
||||
g_extern.rec = ffemu_new(¶ms);
|
||||
if (!g_extern.rec)
|
||||
{
|
||||
SSNES_ERR("Failed to start FFmpeg recording.\n");
|
||||
g_extern.recording = false;
|
||||
}
|
||||
else
|
||||
g_settings.video.crop_overscan = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,6 +958,7 @@ static void init_movie(void)
|
||||
}
|
||||
|
||||
msg_queue_push(g_extern.msg_queue, "Starting movie playback!", 2, 180);
|
||||
g_settings.rewind_granularity = 1;
|
||||
SSNES_LOG("Starting movie playback!\n");
|
||||
}
|
||||
}
|
||||
@@ -855,7 +979,6 @@ static void init_netplay(void)
|
||||
struct snes_callbacks cbs = {
|
||||
.frame_cb = video_frame,
|
||||
.sample_cb = audio_sample,
|
||||
.poll_cb = input_poll,
|
||||
.state_cb = input_state
|
||||
};
|
||||
|
||||
@@ -984,8 +1107,17 @@ static void fill_pathnames(void)
|
||||
if (!g_extern.bsv_movie_playback)
|
||||
fill_pathname(g_extern.bsv_movie_path, g_extern.savefile_name_srm, "", sizeof(g_extern.bsv_movie_path));
|
||||
|
||||
if (!(*g_extern.ups_name) && *g_extern.basename)
|
||||
fill_pathname_noext(g_extern.ups_name, g_extern.basename, ".ups", sizeof(g_extern.ups_name));
|
||||
if (*g_extern.basename)
|
||||
{
|
||||
if (!(*g_extern.ups_name))
|
||||
fill_pathname_noext(g_extern.ups_name, g_extern.basename, ".ups", sizeof(g_extern.ups_name));
|
||||
|
||||
if (!(*g_extern.bps_name))
|
||||
fill_pathname_noext(g_extern.bps_name, g_extern.basename, ".bps", sizeof(g_extern.bps_name));
|
||||
|
||||
if (!(*g_extern.xml_name))
|
||||
fill_pathname_noext(g_extern.xml_name, g_extern.basename, ".xml", sizeof(g_extern.xml_name));
|
||||
}
|
||||
}
|
||||
|
||||
// Save or load state here.
|
||||
@@ -995,7 +1127,7 @@ static void check_savestates(void)
|
||||
bool should_savestate = driver.input->key_pressed(driver.input_data, SSNES_SAVE_STATE_KEY);
|
||||
if (should_savestate && !old_should_savestate)
|
||||
{
|
||||
char save_path[strlen(g_extern.savestate_name) * 2];
|
||||
char save_path[MAXPATHLEN];
|
||||
|
||||
if (g_extern.state_slot > 0)
|
||||
snprintf(save_path, sizeof(save_path), "%s%u", g_extern.savestate_name, g_extern.state_slot);
|
||||
@@ -1021,7 +1153,7 @@ static void check_savestates(void)
|
||||
bool should_loadstate = driver.input->key_pressed(driver.input_data, SSNES_LOAD_STATE_KEY);
|
||||
if (!should_savestate && should_loadstate && !old_should_loadstate)
|
||||
{
|
||||
char load_path[strlen(g_extern.savestate_name) * 2];
|
||||
char load_path[MAXPATHLEN];
|
||||
|
||||
if (g_extern.state_slot > 0)
|
||||
snprintf(load_path, sizeof(load_path), "%s%u", g_extern.savestate_name, g_extern.state_slot);
|
||||
@@ -1140,8 +1272,7 @@ static void check_rewind(void)
|
||||
|
||||
if (g_extern.bsv_movie)
|
||||
{
|
||||
for (unsigned i = 0; i < (g_settings.rewind_granularity ? g_settings.rewind_granularity : 1); i++)
|
||||
bsv_movie_frame_rewind(g_extern.bsv_movie);
|
||||
bsv_movie_frame_rewind(g_extern.bsv_movie);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1175,6 +1306,7 @@ static void check_movie_record(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
g_settings.rewind_granularity = 1;
|
||||
char path[512];
|
||||
if (g_extern.state_slot > 0)
|
||||
snprintf(path, sizeof(path), "%s%d.bsv", g_extern.bsv_movie_path, g_extern.state_slot);
|
||||
@@ -1375,7 +1507,9 @@ static void do_state_checks(void)
|
||||
if (g_extern.is_paused)
|
||||
return;
|
||||
|
||||
set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY));
|
||||
set_fast_forward_button(
|
||||
driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY),
|
||||
driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_HOLD_KEY));
|
||||
|
||||
if (!g_extern.bsv_movie)
|
||||
{
|
||||
@@ -1415,9 +1549,20 @@ static void fill_title_buf(void)
|
||||
snprintf(g_extern.title_buf, sizeof(g_extern.title_buf), "SSNES");
|
||||
}
|
||||
|
||||
static void init_state(void)
|
||||
{
|
||||
// Make absolutely sure our big global structs are in-fact zeroed out.
|
||||
memset(&g_extern, 0, sizeof(g_extern));
|
||||
memset(&g_settings, 0, sizeof(g_settings));
|
||||
g_extern.video_active = true;
|
||||
g_extern.audio_active = true;
|
||||
g_extern.game_type = SSNES_CART_NORMAL;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
init_state();
|
||||
|
||||
parse_input(argc, argv);
|
||||
parse_config();
|
||||
init_dlsym();
|
||||
@@ -1455,14 +1600,13 @@ int main(int argc, char *argv[])
|
||||
#ifdef HAVE_NETPLAY
|
||||
psnes_set_video_refresh(g_extern.netplay ? video_frame_net : video_frame);
|
||||
psnes_set_audio_sample(g_extern.netplay ? audio_sample_net : audio_sample);
|
||||
psnes_set_input_poll(g_extern.netplay ? input_poll_net : input_poll);
|
||||
psnes_set_input_state(g_extern.netplay ? input_state_net : input_state);
|
||||
#else
|
||||
psnes_set_video_refresh(video_frame);
|
||||
psnes_set_audio_sample(audio_sample);
|
||||
psnes_set_input_poll(input_poll);
|
||||
psnes_set_input_state(input_state);
|
||||
#endif
|
||||
psnes_set_input_poll(input_poll);
|
||||
|
||||
init_controllers();
|
||||
|
||||
@@ -1476,6 +1620,10 @@ int main(int argc, char *argv[])
|
||||
// Main loop
|
||||
for(;;)
|
||||
{
|
||||
// DSP plugin GUI events.
|
||||
if (g_extern.audio_data.dsp_handle && g_extern.audio_data.dsp_plugin->events)
|
||||
g_extern.audio_data.dsp_plugin->events(g_extern.audio_data.dsp_handle);
|
||||
|
||||
// Time to drop?
|
||||
if (driver.input->key_pressed(driver.input_data, SSNES_QUIT_KEY) ||
|
||||
!driver.video->alive(driver.video_data) || g_extern.bsv_movie_end)
|
||||
@@ -1562,3 +1710,4 @@ error:
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -25,6 +33,9 @@
|
||||
# Force 16-bit colors. Apparently some video cards in use today have troubles with 32-bit ...
|
||||
# video_force_16bit = false
|
||||
|
||||
# Forcibly disable composition. Only works in Windows Vista/7 for now.
|
||||
# video_disable_composition = false
|
||||
|
||||
# Video vsync.
|
||||
# video_vsync = true
|
||||
|
||||
@@ -225,6 +236,9 @@
|
||||
# Toggles between fast-forwarding and normal speed.
|
||||
# input_toggle_fast_forward = space
|
||||
|
||||
# Hold for fast-forward. Releasing button disables fast-forward.
|
||||
# input_hold_fast_forward = l
|
||||
|
||||
# Key to exit emulator cleanly.
|
||||
# Killing it in any hard way (SIGTERM, SIGKILL, etc, will terminate emulator without saving RAM, etc.)
|
||||
# input_exit_emulator = escape
|
||||
@@ -287,3 +301,9 @@
|
||||
|
||||
# Directory to dump screenshots to.
|
||||
# screenshot_directory =
|
||||
|
||||
# Records video assuming video is hi-res.
|
||||
# video_hires_record = false
|
||||
|
||||
# Records video after CPU video filter.
|
||||
# video_post_filter_record = false
|
||||
@@ -86,6 +86,7 @@ static struct bind binds[] = {
|
||||
MISC_BIND("Save state slot increase", state_slot_increase)
|
||||
MISC_BIND("Save state slot decrease", state_slot_decrease)
|
||||
MISC_BIND("Toggle fast forward", toggle_fast_forward)
|
||||
MISC_BIND("Hold fast forward", hold_fast_forward)
|
||||
MISC_BIND("Audio input rate step up", rate_step_up)
|
||||
MISC_BIND("Audio input rate step down", rate_step_down)
|
||||
MISC_BIND("Rewind", rewind)
|
||||
|
||||
Reference in new issue
Block a user