Vendor the Shockolate engine and build it via a Docker image with every
build / build (push) Failing after 5s
build / build (push) Failing after 5s
dependency (SDL2, SDL2_mixer, fluidsynth-lite, a MIDI soundfont) prebuilt, so compiling the engine needs no network access - just the image and the engine source. Add res/assets/extract_assets.sh to pull the game's data files out of a purchased GOG installer, and a Makefile that assembles a runnable dist/ from the two. Also add `make package`, which builds a redistributable tarball that omits the proprietary game assets (shipping res/GET_ASSETS.txt instead) plus license information for both the MIT tooling and the GPLv3 engine, and a Gitea Actions workflow that builds and publishes it to dl.ladkau.de on every push.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 1994-1995 Looking Glass Technologies, Inc.
|
||||
Copyright (C) 2015-2018 Night Dive Studios, LLC.
|
||||
Copyright (C) 2018-2020 Shockolate Project
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
// InitMac.c - Initialize Mac toolbox managers and setup the application's globals.
|
||||
|
||||
//--------------------
|
||||
// Includes
|
||||
//--------------------
|
||||
#include <SDL.h>
|
||||
|
||||
#include "InitMac.h"
|
||||
#include "Shock.h"
|
||||
#include "ShockBitmap.h"
|
||||
#include "shockolate_version.h"
|
||||
|
||||
// Globals
|
||||
|
||||
intptr_t *gScreenAddress;
|
||||
int32_t gScreenRowbytes;
|
||||
int32_t gScreenWide, gScreenHigh;
|
||||
|
||||
// Time Manager routines and globals
|
||||
|
||||
uint32_t gShockTicks;
|
||||
uint32_t *tmd_ticks;
|
||||
|
||||
void InitMac(void) {
|
||||
INFO("Starting %s", SHOCKOLATE_VERSION);
|
||||
InstallShockTimers(); // needed for the tick pointer
|
||||
}
|
||||
|
||||
void InstallShockTimers(void) {
|
||||
gShockTicks = 0;
|
||||
tmd_ticks = &gShockTicks;
|
||||
}
|
||||
|
||||
void CleanupAndExit(void) {
|
||||
/*Cleanup();
|
||||
ExitToShell();*/
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 1994-1995 Looking Glass Technologies, Inc.
|
||||
Copyright (C) 2015-2018 Night Dive Studios, LLC.
|
||||
Copyright (C) 2018-2020 Shockolate Project
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
// InitMac.h - Initialize Mac toolbox managers and setup the application's globals.
|
||||
|
||||
// How many tick passed since game startup
|
||||
extern uint32_t gShockTicks;
|
||||
|
||||
// Pointer to screen
|
||||
extern intptr_t *gScreenAddress;
|
||||
extern int32_t gScreenRowbytes;
|
||||
// Size of current window
|
||||
extern int32_t gScreenWide, gScreenHigh;
|
||||
|
||||
//--------------------
|
||||
// Prototypes
|
||||
//--------------------
|
||||
|
||||
/// Initialize the Macintosh managers.
|
||||
void InitMac(void);
|
||||
|
||||
/**
|
||||
* Normal cleanup when the program quits.
|
||||
* @deprecated Actually does nothing
|
||||
*/
|
||||
void CleanupAndExit(void);
|
||||
|
||||
/// Startup the SystemShock timer.
|
||||
void InstallShockTimers(void);
|
||||
@@ -0,0 +1,396 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2015-2018 Night Dive Studios, LLC.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
//==============================================================================
|
||||
//
|
||||
// System Shock - ©1994-1995 Looking Glass Technologies, Inc.
|
||||
//
|
||||
// MacTune.c - Rewrite of Shock's MLIMBS.C file to use QuickTime MIDI rather than AIL.
|
||||
//
|
||||
//==============================================================================
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "MacTune.h"
|
||||
#include "miscqvar.h"
|
||||
#include "Xmi.h"
|
||||
|
||||
//-----------------
|
||||
// GLOBALS
|
||||
//-----------------
|
||||
uchar mlimbs_on = FALSE;
|
||||
char mlimbs_status = 0;
|
||||
|
||||
// Handle gHeaderHdl, gTuneHdl, gOfsHdl; // Holds the tune-related data for the current
|
||||
// theme file.
|
||||
long *gOffsets; // Array of offsets for the beginning of each tune.
|
||||
// TunePlayer gPlayer; // The Tune
|
||||
// Player.
|
||||
bool gTuneDone; // True when a sequence has finished playing (set by CB proc).
|
||||
bool gReadyToQueue; // True when it's time to queue up a new sequence.
|
||||
int gOverlayTime; // Amount of time (in millisecs) to wait for overlays.
|
||||
int gQueueTime; // Amount of time (in millisecs) to wait to queue next tune.
|
||||
|
||||
// TuneCallBackUPP gTuneCBProc; // Pointer to tune-finished callback
|
||||
// proc.
|
||||
|
||||
// CalcTuneTask gCalcTuneTask; // Global to hold task info.
|
||||
// TimerUPP gCalcTuneProcPtr; // UPP for the 6-second time manager tune
|
||||
// determiner task.
|
||||
|
||||
//-------------------
|
||||
// INTERNAL PROTOTYPES
|
||||
//-------------------
|
||||
// pascal void TuneEndCB(const TuneStatus *status, long refCon);
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// The following section is the time manager task for determining the next tune to play.
|
||||
//---------------------------------------------------------------
|
||||
#pragma require_prototypes off
|
||||
|
||||
//---------------------------------------------------------------
|
||||
void CalcTuneProc(void) {
|
||||
gReadyToQueue = TRUE; // It's time to queue up another tune.
|
||||
}
|
||||
#pragma require_prototypes on
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Initializes the MacTune system.
|
||||
//------------------------------------------------------------------------------
|
||||
int MacTuneInit(void) {
|
||||
/*if (mlimbs_status != 0) // If already inited, return
|
||||
return 0;
|
||||
|
||||
//¥¥¥ if (!music_card) return -1;
|
||||
// Put something here to check for the existence of the QuickTime Musical Instruments. Or maybe
|
||||
// in music_init.
|
||||
|
||||
// See if there is enough memory to load the QuickTime Music stuff. If not, return an error.
|
||||
Handle syshdl = NewHandleSys(819200); // Try to allocate an 800K handle in system heap.
|
||||
if (!syshdl) // If it couldn't allocate the
|
||||
memory, return 1; // return an error. else
|
||||
DisposeHandle(syshdl); // We don't need this, it was just a
|
||||
test.
|
||||
|
||||
// Load up a player to play the tunes.
|
||||
gPlayer = OpenDefaultComponent(kTunePlayerType, 0);
|
||||
if (!gPlayer)
|
||||
{
|
||||
DebugString("Error: Could not open a tune player."); //¥¥¥ Handle this!
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Setup the end-of-tune callback proc.
|
||||
gTuneCBProc = NewTuneCallBackProc(TuneEndCB);
|
||||
|
||||
// Setup a time-manager task for queueing up new tunes.
|
||||
gCalcTuneProcPtr = NewTimerProc(CalcTuneProc); // Make a UPP for the TM task
|
||||
gCalcTuneTask.task.tmAddr = gCalcTuneProcPtr; // Insert the calc tune TM task
|
||||
gCalcTuneTask.task.tmWakeUp = 0;
|
||||
gCalcTuneTask.task.tmReserved = 0;
|
||||
#ifndef __powerc
|
||||
gCalcTuneTask.appA5 = SetCurrentA5();
|
||||
#endif
|
||||
InsTime((QElemPtr)&gCalcTuneTask);
|
||||
|
||||
// Initialize some globals.
|
||||
gHeaderHdl = NULL;
|
||||
gTuneHdl = NULL;
|
||||
gOfsHdl = NULL;
|
||||
mlimbs_status = 1;*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Shuts down the system so it stops playing music and releases all resources.
|
||||
//------------------------------------------------------------------------------
|
||||
void MacTuneShutdown(void) {
|
||||
/*if (mlimbs_status == 0) // If already shut down, do
|
||||
nothing return;
|
||||
|
||||
MacTunePurgeCurrentTheme(); // Kill the current theme (unloading theme data)
|
||||
|
||||
RmvTime((QElemPtr)&gCalcTuneTask); // Stop the calc tune task
|
||||
DisposeRoutineDescriptor(gCalcTuneProcPtr); // Dispose its UPP
|
||||
|
||||
DisposeRoutineDescriptor(gTuneCBProc); // Cleanup callback and player
|
||||
CloseComponent(gPlayer);
|
||||
|
||||
mlimbs_status = 0;*/
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Call-back routine. Get's called when tune is finished.
|
||||
//--------------------------------------------------------------------------
|
||||
/*pascal void TuneEndCB(const TuneStatus *s, long refCon)
|
||||
{
|
||||
gTuneDone = TRUE;
|
||||
}*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Loads all resources associated with a theme file. Stops and purges and currently loaded theme. Returns
|
||||
// a 1 if successful, < 0 if an error.
|
||||
//------------------------------------------------------------------------------
|
||||
/*int MacTuneLoadTheme(FSSpec *themeSpec, int themeID)
|
||||
{
|
||||
short filenum;
|
||||
Handle binHdl;
|
||||
Ptr p;
|
||||
|
||||
extern uchar track_table[NUM_SCORES][SUPERCHUNKS_PER_SCORE];
|
||||
extern uchar transition_table[NUM_TRANSITIONS];
|
||||
extern uchar layering_table[NUM_LAYERS][MAX_KEYS];
|
||||
extern uchar key_table[NUM_LAYERABLE_SUPERCHUNKS][KEY_BAR_RESOLUTION];
|
||||
|
||||
if (mlimbs_status == 0)
|
||||
// Only do this if MacTune is inited. return (-1);
|
||||
|
||||
MacTunePurgeCurrentTheme(); // Purge the current
|
||||
theme.
|
||||
|
||||
// Open the theme file.
|
||||
filenum = FSpOpenResFile(themeSpec, fsRdPerm);
|
||||
if (filenum == -1)
|
||||
return (-2);
|
||||
|
||||
// First, get the 'tbin' resource and copy its data to the MusicAI global arrays.
|
||||
binHdl = GetResource('tbin', 128);
|
||||
if (binHdl == NULL)
|
||||
{
|
||||
CloseResFile(filenum);
|
||||
Debugger(); //¥¥¥
|
||||
return (-3);
|
||||
}
|
||||
HLock(binHdl);
|
||||
p = *binHdl;
|
||||
BlockMoveData(p, track_table, NUM_SCORES * SUPERCHUNKS_PER_SCORE);
|
||||
p += NUM_SCORES * SUPERCHUNKS_PER_SCORE;
|
||||
BlockMoveData(p, transition_table, NUM_TRANSITIONS);
|
||||
p += NUM_TRANSITIONS;
|
||||
BlockMoveData(p, layering_table, NUM_LAYERS * MAX_KEYS);
|
||||
p += NUM_LAYERS * MAX_KEYS;
|
||||
BlockMoveData(p, key_table, NUM_LAYERABLE_SUPERCHUNKS * KEY_BAR_RESOLUTION);
|
||||
p += NUM_LAYERABLE_SUPERCHUNKS * KEY_BAR_RESOLUTION;
|
||||
gOverlayTime = *(int *)p;
|
||||
p += 4;
|
||||
gQueueTime = *(int *)p;
|
||||
HUnlock(binHdl);
|
||||
ReleaseResource(binHdl);
|
||||
|
||||
// Next, get the theme-related resources.
|
||||
gHeaderHdl = GetResource('thdr', 128);
|
||||
if (gHeaderHdl == NULL)
|
||||
{
|
||||
CloseResFile(filenum);
|
||||
return (-4);
|
||||
}
|
||||
gTuneHdl = GetResource('them', 128);
|
||||
if (gTuneHdl == NULL)
|
||||
{
|
||||
CloseResFile(filenum);
|
||||
return (-5);
|
||||
}
|
||||
gOfsHdl = GetResource('tofs', 128);
|
||||
if (gOfsHdl == NULL)
|
||||
{
|
||||
CloseResFile(filenum);
|
||||
return (-6);
|
||||
}
|
||||
|
||||
DetachResource(gHeaderHdl); // Turn these into normal handles.
|
||||
HLockHi(gHeaderHdl);
|
||||
DetachResource(gTuneHdl);
|
||||
HLockHi(gTuneHdl);
|
||||
DetachResource(gOfsHdl);
|
||||
HLockHi(gOfsHdl);
|
||||
|
||||
CloseResFile(filenum);
|
||||
|
||||
// Set the tune header (load instruments, etc, can take a second or two).
|
||||
TuneSetHeader(gPlayer, (unsigned long *)*gHeaderHdl);
|
||||
TunePreroll(gPlayer);
|
||||
|
||||
// Setup the tune offset pointer.
|
||||
gOffsets = (long *)*gOfsHdl;
|
||||
|
||||
// Initialize our playtime globals.
|
||||
gTuneDone = FALSE;
|
||||
gReadyToQueue = FALSE;
|
||||
|
||||
// Here's a big hack. If we're loading theme 0 (machine sounds only), then don't do an
|
||||
// intro transition.
|
||||
if (themeID == 0)
|
||||
current_mode = NORMAL_MODE;
|
||||
|
||||
return(1);
|
||||
}*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// If there is a theme loaded, start playing it.
|
||||
//------------------------------------------------------------------------------
|
||||
void MacTuneStartCurrentTheme(void) {
|
||||
/*if (mlimbs_status && gTuneHdl) // If MacTune is inited and there is a theme
|
||||
loaded,
|
||||
{
|
||||
int pid = current_request[0].pieceID;
|
||||
if (pid != 255) // If there is a tune
|
||||
requested,
|
||||
{
|
||||
MacTunePlayTune(pid); // play it right now.
|
||||
}
|
||||
else
|
||||
gTuneDone = TRUE; // else make sure we check again
|
||||
soon.
|
||||
|
||||
}*/
|
||||
|
||||
int track = 1 + current_request[0].pieceID;
|
||||
if (track >= 0 && track < NumTracks) {
|
||||
int i = 0;
|
||||
|
||||
if (!IsPlaying(i)) {
|
||||
// extern uchar curr_vol_lev;
|
||||
// int volume = (int)curr_vol_lev * 127 / 100; //convert from 0-100 to 0-127
|
||||
StartTrack(i, track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Stop the current tune playing, stop the tune queue timer task.
|
||||
//------------------------------------------------------------------------------
|
||||
/*void MacTuneKillCurrentTheme(void)
|
||||
{
|
||||
if (mlimbs_status && gTuneHdl) // Only do this if MacTune is inited and
|
||||
there is a
|
||||
{
|
||||
// current theme. RmvTime((QElemPtr)&gCalcTuneTask); // Remove the TimeManager task that queues up
|
||||
InsTime((QElemPtr)&gCalcTuneTask); // next tune, and re-insert to prevent any tasks
|
||||
from
|
||||
// triggering.
|
||||
TuneStop(gPlayer, kStopTuneFade); // Stop the current tune (if any).
|
||||
TuneFlush(gPlayer); // Flush the
|
||||
queue.
|
||||
|
||||
gReadyToQueue = FALSE;
|
||||
}
|
||||
}*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Stop the current tune playing, stop the tune queue timer task.
|
||||
//------------------------------------------------------------------------------
|
||||
void MacTunePurgeCurrentTheme() {
|
||||
/*MacTuneKillCurrentTheme(); // Kill the current theme.
|
||||
|
||||
// Dispose of all the theme's data.
|
||||
if (gHeaderHdl)
|
||||
{
|
||||
HUnlock(gHeaderHdl);
|
||||
DisposeHandle(gHeaderHdl);
|
||||
gHeaderHdl = NULL;
|
||||
}
|
||||
if (gTuneHdl)
|
||||
{
|
||||
HUnlock(gTuneHdl);
|
||||
DisposeHandle(gTuneHdl);
|
||||
gTuneHdl = NULL;
|
||||
}
|
||||
if (gOfsHdl)
|
||||
{
|
||||
HUnlock(gOfsHdl);
|
||||
DisposeHandle(gOfsHdl);
|
||||
gOfsHdl = NULL;
|
||||
}
|
||||
|
||||
// Free the tune player component, then open it back up again. This should make the
|
||||
// game run much faster.
|
||||
CloseComponent(gPlayer);
|
||||
gPlayer = OpenDefaultComponent(kTunePlayerType, 0);
|
||||
if (!gPlayer)
|
||||
DebugString("Error: Could not open a tune player."); //¥¥¥ Handle this!
|
||||
|
||||
// Clear our the current request array.
|
||||
for (int i = 0; i < MLIMBS_MAX_SEQUENCES -1; i++)
|
||||
{
|
||||
current_request[i].pieceID = 255;
|
||||
}*/
|
||||
mlimbs_counter = 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Play a tune right now (prime the TM task). Usually this is called when music is first started.
|
||||
//------------------------------------------------------------------------------
|
||||
void MacTunePlayTune(int tune) {
|
||||
/*if (tune == 255 || tune == -1)
|
||||
DebugString("Eep Eep Invalid tune!"); //¥¥¥
|
||||
|
||||
if (gOffsets[tune] != -1) // If there really is a tune there, play it
|
||||
now.
|
||||
{
|
||||
TuneQueue(gPlayer, (unsigned long *)(*gTuneHdl + gOffsets[tune]), 0x10000,
|
||||
0, 0x7FFFFFFF, kTuneStartNow, gTuneCBProc, 0);
|
||||
PrimeTime((QElemPtr)&gCalcTuneTask, gOverlayTime + gQueueTime);
|
||||
//¥¥¥ temp
|
||||
// the above amount for PrimeTime is temporary because we're not doing overlays yet.
|
||||
// so just queue up the next tune at queue time.
|
||||
}
|
||||
|
||||
// If there was no tune to play this time, set a flag so it will prime the timer again.
|
||||
else
|
||||
gTuneDone = TRUE;*/
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Add a tune to the tune queue.
|
||||
//------------------------------------------------------------------------------
|
||||
void MacTuneQueueTune(int tune) {
|
||||
/*if (tune == 255 || tune == -1)
|
||||
DebugString("Eep Eep Invalid tune!"); //¥¥¥
|
||||
|
||||
if (gOffsets[tune] != -1) // If there really is a tune there, queue it
|
||||
up.
|
||||
{
|
||||
TuneStatus tpStatus;
|
||||
TuneGetStatus(gPlayer, &tpStatus); // We'll need this later.
|
||||
|
||||
TuneQueue(gPlayer, (unsigned long *)(*gTuneHdl + gOffsets[tune]), 0x10000,
|
||||
0, 0x7FFFFFFF, 0, gTuneCBProc, 0);
|
||||
|
||||
// Normally we don't prime it yet; we want to wait until the currently playing tune finishes.
|
||||
// However, if tune was playing before we queued this one, go ahead and prime away.
|
||||
if (tpStatus.queueTime == 0)
|
||||
PrimeTime((QElemPtr)&gCalcTuneTask, gOverlayTime + gQueueTime);
|
||||
}
|
||||
|
||||
// If there was no tune to queue this time, set a flag so it will prime the timer again.
|
||||
else
|
||||
gTuneDone = TRUE;*/
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Prime the TM task to trigger the next tune queueing.
|
||||
//------------------------------------------------------------------------------
|
||||
void MacTunePrimeTimer(void) {
|
||||
// PrimeTime((QElemPtr)&gCalcTuneTask, gOverlayTime + gQueueTime);
|
||||
//¥¥¥ temp
|
||||
// the above amount for PrimeTime is temporary because we're not doing overlays yet.
|
||||
// so just queue up the next tune at queue time.
|
||||
}
|
||||
|
||||
void MacTuneUpdateVolume(void) { UpdateVolumeXMI(); }
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2015-2018 Night Dive Studios, LLC.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
//==============================================================================
|
||||
//
|
||||
// System Shock - ©1994-1995 Looking Glass Technologies, Inc.
|
||||
//
|
||||
// MacTune.h - Rewrite of Shock's MLIMBS.H file to use QuickTime MIDI rather than AIL.
|
||||
//
|
||||
//==============================================================================
|
||||
|
||||
//-----------------
|
||||
// TYPES & DEFINES
|
||||
//-----------------
|
||||
typedef struct {
|
||||
int pieceID; // Indexes an array of XMIDI_info structs. Specifies which piece to play.
|
||||
int priority; // Priority of this request.
|
||||
int loops; // Number of loops. -1 => until deliberately stopped.
|
||||
uint rel_vol; // Specifies at what relative volume to play it at. (percent)
|
||||
uint ramp_time; // Specifies the time to ramp in to the specified rel_vol, or ramp out to 0.
|
||||
int pan; // Note that this pan value affects all channels.
|
||||
uchar channel_prioritize;
|
||||
char crossfade; // 0 - don't crossfade, <0 - crossfade out, >0 - crossfade in.
|
||||
char ramp; // 0 - don't ramp, <0 - ramp out >0 - ramp in
|
||||
uchar pad;
|
||||
} mlimbs_request_info;
|
||||
|
||||
#define MLIMBS_MAX_SEQUENCES 8
|
||||
#define MLIMBS_MAX_CHANNELS 8
|
||||
|
||||
#include "mlimbs.h"
|
||||
|
||||
//-----------------
|
||||
// EXTERN GLOBALS
|
||||
//-----------------
|
||||
extern bool gReadyToQueue; // True when it's time to queue up a new sequence.
|
||||
|
||||
// extern TuneCallBackUPP gTuneCBProc; // The tune's callback proc.
|
||||
// extern CalcTuneTask gCalcTuneTask; // Global to hold task info.
|
||||
// extern TimerUPP gCalcTuneProcPtr; // UPP for the 6-second time manager
|
||||
// tune determiner task.
|
||||
|
||||
//-----------------
|
||||
// PROTOTYPES
|
||||
//-----------------
|
||||
int MacTuneInit(void);
|
||||
void MacTuneShutdown(void);
|
||||
int MacTuneLoadTheme(char *theme, int themeID);
|
||||
void MacTuneStartCurrentTheme(void);
|
||||
void MacTuneKillCurrentTheme(void);
|
||||
void MacTunePurgeCurrentTheme(void);
|
||||
void MacTunePlayTune(int tune);
|
||||
void MacTuneQueueTune(int tune);
|
||||
void MacTunePrimeTimer(void);
|
||||
void MacTuneUpdateVolume(void);
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2018-2020 Shockolate Project
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#include "Modding.h"
|
||||
#include "lg.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
|
||||
char *modding_archive_override;
|
||||
char *modding_additional_files[MAX_MOD_FILES];
|
||||
int num_mod_files;
|
||||
|
||||
int StringEndsWith(char *src, char *dst) {
|
||||
char *s = strrchr(src, '.');
|
||||
|
||||
if (s != NULL)
|
||||
return (strcmp(s, dst)) == 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AddResourceFile(char *filename) {
|
||||
printf("Found resource file: %s\n", filename);
|
||||
|
||||
if (num_mod_files < MAX_MOD_FILES) {
|
||||
char *f = (char *)malloc(strlen(filename) + 1);
|
||||
strcpy(f, filename);
|
||||
modding_additional_files[num_mod_files++] = f;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int AddArchiveFile(char *filename) {
|
||||
printf("Found archive file: %s\n", filename);
|
||||
|
||||
char *f = (char *)malloc(strlen(filename) + 1);
|
||||
strcpy(f, filename);
|
||||
modding_archive_override = f;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int ProcessModFile(char *filename, uchar follow_dirs) {
|
||||
printf("ProcessModFile: %s\n", filename);
|
||||
|
||||
if (StringEndsWith(filename, ".dat")) {
|
||||
AddArchiveFile(filename);
|
||||
} else if (StringEndsWith(filename, ".res")) {
|
||||
AddResourceFile(filename);
|
||||
} else if (follow_dirs) {
|
||||
ProcessModDirectory(filename);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int ProcessModArgs(int argc, char **argv) {
|
||||
// Default things
|
||||
num_mod_files = 0;
|
||||
int mod_args_start = 1;
|
||||
|
||||
// Skip arguments
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
mod_args_start++;
|
||||
}
|
||||
}
|
||||
|
||||
// Default the mod list to empty
|
||||
modding_archive_override = NULL;
|
||||
for (int i = 0; i < MAX_MOD_FILES; i++) {
|
||||
modding_additional_files[i] = NULL;
|
||||
}
|
||||
|
||||
// Now go process args
|
||||
for (int i = mod_args_start; i < argc; i++) {
|
||||
ProcessModFile(argv[i], TRUE);
|
||||
}
|
||||
|
||||
// shamaz: return value is not checked anyway
|
||||
return OK;
|
||||
}
|
||||
|
||||
int ProcessModDirectory(char *dirname) {
|
||||
// Check if this is a directory
|
||||
|
||||
printf("ProcessModDirectory %s\n", dirname);
|
||||
|
||||
DIR *dp = opendir(dirname);
|
||||
if (dp != NULL) {
|
||||
struct dirent *ep;
|
||||
|
||||
// Loop through all files here, calling ProcessModFile for each
|
||||
while ((ep = readdir(dp))) {
|
||||
|
||||
printf("ep->d_name %s\n", ep->d_name);
|
||||
char *buf = (char *)malloc(strlen(dirname) + strlen(ep->d_name) + 2);
|
||||
|
||||
strcpy(buf, dirname);
|
||||
|
||||
// Windows, why do you have to be weird?
|
||||
#ifdef _WIN32
|
||||
strcat(buf, "\\");
|
||||
#else
|
||||
strcat(buf, "/");
|
||||
#endif
|
||||
|
||||
strcat(buf, ep->d_name);
|
||||
|
||||
ProcessModFile(buf, FALSE);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
closedir(dp);
|
||||
}
|
||||
|
||||
// shamaz: Returned value is not checked
|
||||
return OK;
|
||||
}
|
||||
|
||||
int LoadModFiles() {
|
||||
for (int i = 0; i < num_mod_files; i++) {
|
||||
if (modding_additional_files[i] != NULL) {
|
||||
printf("Loading mod file %s\n", modding_additional_files[i]);
|
||||
ResOpenFile(modding_additional_files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// shamaz: Returned value is not checked
|
||||
return OK;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2018-2020 Shockolate Project
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __MODDING_H
|
||||
#define __MODDING_H
|
||||
|
||||
//--------------------
|
||||
// Defines
|
||||
//--------------------
|
||||
|
||||
#define MAX_MOD_FILES 100
|
||||
|
||||
//--------------------
|
||||
// Public Globals
|
||||
//--------------------
|
||||
|
||||
// Let people override the default game archive
|
||||
extern char *modding_archive_override;
|
||||
|
||||
// Additional resource files to load
|
||||
extern char *modding_additional_files[MAX_MOD_FILES];
|
||||
|
||||
extern int num_mod_files;
|
||||
|
||||
//--------------------
|
||||
// Function Prototypes
|
||||
//--------------------
|
||||
|
||||
int AddResourceFile(char *filename);
|
||||
int AddArchiveFile(char *filename);
|
||||
int ProcessModFile(char *filename, uchar follow_dirs);
|
||||
int ProcessModArgs(int argc, char **argv);
|
||||
int ProcessModDirectory(char *dirname);
|
||||
int LoadModFiles(void);
|
||||
|
||||
#endif //__MODDING_H
|
||||
@@ -0,0 +1,978 @@
|
||||
#ifdef USE_OPENGL
|
||||
|
||||
#include <cstdio>
|
||||
#include "OpenGL.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define GLEW_STATIC 1
|
||||
#include <SDL.h>
|
||||
#include <GL/glew.h>
|
||||
#else
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl.h>
|
||||
#else
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#endif
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_opengl.h>
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include "mainloop.h"
|
||||
#include "map.h"
|
||||
#include "frintern.h"
|
||||
#include "frflags.h"
|
||||
#include "player.h"
|
||||
#include "textmaps.h"
|
||||
#include "star.h"
|
||||
#include "tools.h"
|
||||
#include "Prefs.h"
|
||||
#include "Shock.h"
|
||||
#include "faketime.h"
|
||||
#include "render.h"
|
||||
#include "wares.h"
|
||||
|
||||
extern SDL_Renderer *renderer;
|
||||
extern SDL_Palette *sdlPalette;
|
||||
}
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
struct CachedTexture {
|
||||
SDL_Surface *bitmap;
|
||||
SDL_Surface *converted;
|
||||
GLuint texture;
|
||||
long lastDrawTime;
|
||||
bool locked;
|
||||
};
|
||||
|
||||
struct Shader {
|
||||
GLuint shaderProgram;
|
||||
GLint uniView;
|
||||
GLint uniProj;
|
||||
GLint uniNightSight;
|
||||
GLint uniMutant;
|
||||
GLint tcAttrib;
|
||||
GLint lightAttrib;
|
||||
GLint colorAttrib;
|
||||
};
|
||||
|
||||
struct FrameBuffer {
|
||||
GLuint frameBuffer;
|
||||
GLuint stencilBuffer;
|
||||
GLuint texture;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
#define MAX_CACHED_TEXTURES 1024
|
||||
|
||||
static Shader textureShaderProgram;
|
||||
static Shader colorShaderProgram;
|
||||
static Shader starShaderProgram;
|
||||
|
||||
static FrameBuffer backupBuffer;
|
||||
|
||||
static SDL_GLContext context;
|
||||
static GLuint dynTexture;
|
||||
|
||||
static SDL_Palette *opaquePalette;
|
||||
static SDL_Palette *transparentPalette;
|
||||
|
||||
// Texture cache to keep SDL surfaces and GL textures in memory
|
||||
static std::map<uint64_t, CachedTexture> texturesByBitsPtr;
|
||||
|
||||
static float view_scale;
|
||||
static int phys_width;
|
||||
static int phys_height;
|
||||
static int phys_offset_x;
|
||||
static int phys_offset_y;
|
||||
|
||||
static int render_width;
|
||||
static int render_height;
|
||||
|
||||
static bool opengl_enabled = true;
|
||||
static bool palette_dirty = false;
|
||||
static bool blend_enabled = true;
|
||||
static GLuint bound_texture = -1;
|
||||
|
||||
// View matrix; Z offset experimentally tweaked for near-perfect alignment
|
||||
// between GL projection and software projection (sprite screen coordinates)
|
||||
static const float ViewMatrix[] = {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -0.01, 1.0};
|
||||
|
||||
// Projection matrix; experimentally tweaked for near-perfect alignment:
|
||||
// FOV 89.5 deg, aspect ratio 1:1, near plane 0, far plane 100
|
||||
static const float ProjectionMatrix[] = {1.00876, 0.0, 0.0, 0.0, 0.0, 1.00876, 0.0, 0.0,
|
||||
0.0, 0.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0};
|
||||
|
||||
// Identity matrix for sprite rendering
|
||||
static const float IdentityMatrix[] = {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0};
|
||||
|
||||
static inline GLint get_texture_min_func() {
|
||||
// convert prefs texture filtering mode to GL min func value
|
||||
switch (gShockPrefs.doTextureFilter) {
|
||||
case 0:
|
||||
return GL_NEAREST;
|
||||
case 1:
|
||||
return GL_LINEAR;
|
||||
// case 2: return GL_LINEAR_MIPMAP_LINEAR;
|
||||
}
|
||||
|
||||
WARN("gShockPrefs.doTextureFilter=%d is invalid; resetting to zero (unflitered)", gShockPrefs.doTextureFilter);
|
||||
gShockPrefs.doTextureFilter = 0;
|
||||
return GL_NEAREST;
|
||||
}
|
||||
|
||||
static inline GLint get_texture_mag_func() {
|
||||
// convert prefs texture filtering mode to GL mag func value
|
||||
switch (gShockPrefs.doTextureFilter) {
|
||||
case 0:
|
||||
return GL_NEAREST;
|
||||
case 1:
|
||||
return GL_LINEAR;
|
||||
case 2:
|
||||
return GL_LINEAR;
|
||||
}
|
||||
|
||||
WARN("gShockPrefs.doTextureFilter=%d is invalid; resetting to zero (unflitered)", gShockPrefs.doTextureFilter);
|
||||
gShockPrefs.doTextureFilter = 0;
|
||||
return GL_NEAREST;
|
||||
}
|
||||
|
||||
static void set_blend_mode(bool enabled) {
|
||||
// change the blend mode, if not set already
|
||||
if (blend_enabled != enabled) {
|
||||
blend_enabled = enabled;
|
||||
if (blend_enabled) {
|
||||
glEnable(GL_BLEND);
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bind_texture(GLuint tex) {
|
||||
// bind a texture, if not bound already
|
||||
if (bound_texture != tex) {
|
||||
bound_texture = tex;
|
||||
glBindTexture(GL_TEXTURE_2D, bound_texture);
|
||||
}
|
||||
}
|
||||
|
||||
static GLuint compileShader(GLenum type, const char *source) {
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &source, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint status;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status != GL_TRUE) {
|
||||
char buffer[512];
|
||||
glGetShaderInfoLog(shader, 512, nullptr, buffer);
|
||||
ERROR("Error compiling shader: %s", buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
static GLuint loadShader(GLenum type, const char *filename) {
|
||||
|
||||
DEBUG("Loading shader %s", filename);
|
||||
|
||||
#ifdef __APPLE__
|
||||
char fb[1024];
|
||||
sprintf(fb, "%s/shaders/%s", SDL_GetBasePath(), filename);
|
||||
#else
|
||||
char fb[256];
|
||||
sprintf(fb, "shaders/%s", filename);
|
||||
#endif
|
||||
|
||||
FILE *file = fopen(fb, "r");
|
||||
if (file == nullptr) {
|
||||
ERROR("Could not open shader file %s!", fb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::stringstream source;
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), file)) {
|
||||
char *c = &line[strlen(line) - 1];
|
||||
while (c >= line && (*c == '\r' || *c == '\n'))
|
||||
*(c--) = '\0';
|
||||
source << line << '\n';
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
GLuint s = compileShader(type, source.str().c_str());
|
||||
|
||||
if (s == 0) {
|
||||
ERROR("Could not compile shader %s", filename);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static int CreateShader(const char *vertexShaderFile, const char *fragmentShaderFile, Shader *outShader) {
|
||||
GLuint vertShader = loadShader(GL_VERTEX_SHADER, vertexShaderFile);
|
||||
GLuint fragShader = loadShader(GL_FRAGMENT_SHADER, fragmentShaderFile);
|
||||
|
||||
if (vertShader == 0 || fragShader == 0) {
|
||||
ERROR("Could not create shader %s : %s", vertexShaderFile, fragmentShaderFile);
|
||||
context = nullptr;
|
||||
return 1; // Error!
|
||||
}
|
||||
|
||||
GLuint shaderProgram = glCreateProgram();
|
||||
glAttachShader(shaderProgram, vertShader);
|
||||
glAttachShader(shaderProgram, fragShader);
|
||||
glLinkProgram(shaderProgram);
|
||||
glUseProgram(shaderProgram);
|
||||
|
||||
outShader->shaderProgram = shaderProgram;
|
||||
outShader->uniView = glGetUniformLocation(shaderProgram, "view");
|
||||
outShader->uniProj = glGetUniformLocation(shaderProgram, "proj");
|
||||
outShader->uniNightSight = glGetUniformLocation(shaderProgram, "nightsight");
|
||||
outShader->uniMutant = glGetUniformLocation(shaderProgram, "mutant");
|
||||
outShader->tcAttrib = glGetAttribLocation(shaderProgram, "texcoords");
|
||||
outShader->lightAttrib = glGetAttribLocation(shaderProgram, "light");
|
||||
outShader->colorAttrib = glGetAttribLocation(shaderProgram, "color");
|
||||
|
||||
glUniformMatrix4fv(outShader->uniView, 1, false, IdentityMatrix);
|
||||
glUniformMatrix4fv(outShader->uniProj, 1, false, IdentityMatrix);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static FrameBuffer CreateFrameBuffer(int width, int height) {
|
||||
FrameBuffer newBuffer{};
|
||||
newBuffer.width = width;
|
||||
newBuffer.height = height;
|
||||
|
||||
// Make a frame buffer, texture for color, and render buffer for stencil
|
||||
glGenFramebuffers(1, &newBuffer.frameBuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, newBuffer.frameBuffer);
|
||||
|
||||
glGenRenderbuffers(1, &newBuffer.stencilBuffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, newBuffer.stencilBuffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, newBuffer.stencilBuffer);
|
||||
|
||||
glGenTextures(1, &newBuffer.texture);
|
||||
glBindTexture(GL_TEXTURE_2D, newBuffer.texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, newBuffer.texture, 0);
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
ERROR("Could not make FrameBuffer!: %x \n", status);
|
||||
context = nullptr;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
static void BindFrameBuffer(FrameBuffer *buffer) {
|
||||
if (buffer == nullptr) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
} else {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, buffer->frameBuffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, buffer->stencilBuffer);
|
||||
glViewport(0, 0, buffer->width, buffer->height);
|
||||
}
|
||||
}
|
||||
|
||||
int init_opengl() {
|
||||
DEBUG("Initializing OpenGL");
|
||||
|
||||
// Are we running in OpenGL mode?
|
||||
if (SDL_GL_GetCurrentContext() == nullptr) {
|
||||
ERROR("No OpenGL context! Falling back to Software mode.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Can we create the world rendering context?
|
||||
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
|
||||
context = SDL_GL_CreateContext(window);
|
||||
if (context == nullptr) {
|
||||
ERROR("Could not create an OpenGL context! Falling back to Software mode.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
glewExperimental = GL_TRUE;
|
||||
GLenum err = glewInit();
|
||||
#endif
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
glEnable(GL_ALPHA_TEST);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glAlphaFunc(GL_GEQUAL, 0.05f);
|
||||
|
||||
CreateShader("main.vert", "texture.frag", &textureShaderProgram);
|
||||
CreateShader("main.vert", "color.frag", &colorShaderProgram);
|
||||
CreateShader("main.vert", "star.frag", &starShaderProgram);
|
||||
|
||||
glGenTextures(1, &dynTexture);
|
||||
|
||||
// Did the setup go okay?
|
||||
if (context == nullptr) {
|
||||
ERROR("OpenGL could not be initialized, falling back to Software mode.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
opengl_resize(width, height);
|
||||
|
||||
// Now make the palettes
|
||||
opaquePalette = SDL_AllocPalette(256);
|
||||
transparentPalette = SDL_AllocPalette(256);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void opengl_resize(int width, int height) {
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
int logical_width, logical_height;
|
||||
SDL_RenderGetLogicalSize(renderer, &logical_width, &logical_height);
|
||||
|
||||
float scale_x = (float)width / logical_width;
|
||||
float scale_y = (float)height / logical_height;
|
||||
|
||||
if (scale_x >= scale_y) {
|
||||
// physical aspect ratio is wider; black borders left and right
|
||||
view_scale = scale_y;
|
||||
} else {
|
||||
// physical aspect ratio is narrower; black borders at top and bottom
|
||||
view_scale = scale_x;
|
||||
}
|
||||
|
||||
phys_width = view_scale * logical_width;
|
||||
phys_height = view_scale * logical_height;
|
||||
|
||||
int border_x = width - phys_width;
|
||||
int border_y = height - phys_height;
|
||||
phys_offset_x = border_x / 2;
|
||||
phys_offset_y = border_y / 2;
|
||||
|
||||
backupBuffer = CreateFrameBuffer(logical_width, logical_height);
|
||||
|
||||
INFO("OpenGL Resize %i %i %i %i", width, height, phys_width, phys_height);
|
||||
|
||||
// Redraw the options menu background in the new resolution
|
||||
extern uchar wrapper_screenmode_hack;
|
||||
if (wrapper_screenmode_hack) {
|
||||
render_run();
|
||||
wrapper_screenmode_hack = false;
|
||||
}
|
||||
}
|
||||
|
||||
void opengl_change_palette() { palette_dirty = true; }
|
||||
|
||||
bool can_use_opengl() { return context != nullptr; }
|
||||
|
||||
bool use_opengl() {
|
||||
return can_use_opengl() && gShockPrefs.doUseOpenGL && opengl_enabled &&
|
||||
(_current_loop == GAME_LOOP || _current_loop == FULLSCREEN_LOOP) && !global_fullmap->cyber &&
|
||||
!(_fr_curflags & (FR_PICKUPM_MASK | FR_HACKCAM_MASK));
|
||||
}
|
||||
|
||||
bool should_opengl_swap() {
|
||||
return can_use_opengl() && gShockPrefs.doUseOpenGL && opengl_enabled &&
|
||||
(_current_loop == GAME_LOOP || _current_loop == FULLSCREEN_LOOP) && !global_fullmap->cyber;
|
||||
}
|
||||
|
||||
void opengl_end_frame() {
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
// Done rendering to the frame buffer, reset back to normal
|
||||
BindFrameBuffer(nullptr);
|
||||
palette_dirty = false;
|
||||
}
|
||||
|
||||
static void updatePalette(SDL_Palette *palette, bool transparent) {
|
||||
// Update from the base
|
||||
SDL_SetPaletteColors(palette, sdlPalette->colors, 0, 256);
|
||||
|
||||
if (transparent)
|
||||
palette->colors[0].a = 0x00;
|
||||
for (int i = 1; i < 256; i++) {
|
||||
// colors 1..31, except 2: always at maximum light level
|
||||
// colors 2, 32..255: no minimum brightness, use interpolated vertex light level
|
||||
// encode emissive property in the top half of the alpha color, since we don't use those bytes
|
||||
if (i < 32 && i != 2)
|
||||
palette->colors[i].a = 0xff;
|
||||
else
|
||||
palette->colors[i].a = 0x7f;
|
||||
}
|
||||
}
|
||||
|
||||
static bool nightsight_active() { return WareActive(player_struct.hardwarez_status[HARDWARE_GOGGLE_INFRARED]); }
|
||||
|
||||
void opengl_start_frame() {
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
// Start rendering to our frame buffer canvas
|
||||
BindFrameBuffer(&backupBuffer);
|
||||
|
||||
// Setup the render width
|
||||
int logical_width, logical_height;
|
||||
SDL_RenderGetLogicalSize(renderer, &logical_width, &logical_height);
|
||||
|
||||
render_height = logical_height;
|
||||
render_width = logical_width;
|
||||
|
||||
// Update the palettes for this frame
|
||||
updatePalette(opaquePalette, false);
|
||||
updatePalette(transparentPalette, true);
|
||||
}
|
||||
|
||||
void get_hdpi_scaling(int *x_scale, int *y_scale) {
|
||||
// We may need to scale up our OpenGL output to match some HDPI scaling
|
||||
int output_width, output_height;
|
||||
SDL_GetRendererOutputSize(renderer, &output_width, &output_height);
|
||||
|
||||
int screen_width, screen_height;
|
||||
SDL_GetWindowSize(window, &screen_width, &screen_height);
|
||||
|
||||
*x_scale = output_width / screen_width;
|
||||
*y_scale = output_height / screen_height;
|
||||
}
|
||||
|
||||
void opengl_swap_and_restore(SDL_Surface *ui) {
|
||||
// restore the view backup (without HUD overlay) for incremental
|
||||
// updates in the subsequent frame
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
int x_hdpi_scale, y_hdpi_scale;
|
||||
get_hdpi_scaling(&x_hdpi_scale, &y_hdpi_scale);
|
||||
|
||||
// Set the drawable area for the 3d view
|
||||
glViewport(phys_offset_x * x_hdpi_scale, phys_offset_y * y_hdpi_scale, phys_width * x_hdpi_scale,
|
||||
phys_height * y_hdpi_scale);
|
||||
set_blend_mode(false);
|
||||
|
||||
// Bind and setup our general shader program
|
||||
glUseProgram(textureShaderProgram.shaderProgram);
|
||||
GLint tcAttrib = textureShaderProgram.tcAttrib;
|
||||
GLint lightAttrib = textureShaderProgram.lightAttrib;
|
||||
|
||||
glUniform1i(textureShaderProgram.uniNightSight, nightsight_active());
|
||||
|
||||
glUniformMatrix4fv(textureShaderProgram.uniView, 1, false, IdentityMatrix);
|
||||
glUniformMatrix4fv(textureShaderProgram.uniProj, 1, false, IdentityMatrix);
|
||||
|
||||
// Draw the frame buffer to the screen as a quad
|
||||
bind_texture(backupBuffer.texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glVertexAttrib2f(tcAttrib, 1.0f, 0.0f);
|
||||
glVertexAttrib1f(lightAttrib, 1.0f);
|
||||
glVertex3f(1.0f, -1.0f, 0.0f);
|
||||
glVertexAttrib2f(tcAttrib, 1.0f, 1.0f);
|
||||
glVertexAttrib1f(lightAttrib, 1.0f);
|
||||
glVertex3f(1.0f, 1.0f, 0.0f);
|
||||
glVertexAttrib2f(tcAttrib, 0.0f, 0.0f);
|
||||
glVertexAttrib1f(lightAttrib, 1.0f);
|
||||
glVertex3f(-1.0f, -1.0f, 0.0f);
|
||||
glVertexAttrib2f(tcAttrib, 0.0f, 1.0f);
|
||||
glVertexAttrib1f(lightAttrib, 1.0f);
|
||||
glVertex3f(-1.0f, 1.0f, 0.0f);
|
||||
glEnd();
|
||||
|
||||
// Finish drawing the 3d view
|
||||
glFlush();
|
||||
|
||||
glUniform1i(textureShaderProgram.uniNightSight, false);
|
||||
|
||||
// Check for OpenGL errors that might have happened
|
||||
GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR)
|
||||
ERROR("OpenGL error: %i", err);
|
||||
|
||||
// Blit the UI canvas over the 3d view
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, ui);
|
||||
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
||||
SDL_DestroyTexture(texture);
|
||||
|
||||
// Finally, swap to the screen
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
void toggle_opengl() {
|
||||
if (gShockPrefs.doUseOpenGL) {
|
||||
switch (gShockPrefs.doTextureFilter) {
|
||||
case 0: {
|
||||
message_info("Switching to OpenGL bilinear rendering");
|
||||
gShockPrefs.doTextureFilter = 1;
|
||||
} break;
|
||||
case 1: {
|
||||
message_info("Switching to sofware rendering");
|
||||
gShockPrefs.doUseOpenGL = false;
|
||||
gShockPrefs.doTextureFilter = 0;
|
||||
} break;
|
||||
}
|
||||
} else {
|
||||
message_info("Switching to OpenGL unfiltered");
|
||||
gShockPrefs.doUseOpenGL = true;
|
||||
gShockPrefs.doTextureFilter = 0;
|
||||
}
|
||||
SavePrefs();
|
||||
}
|
||||
|
||||
void opengl_set_viewport(int x, int y, int width, int height) {
|
||||
render_width = width;
|
||||
render_height = height;
|
||||
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
int lw, lh;
|
||||
SDL_RenderGetLogicalSize(renderer, &lw, &lh);
|
||||
|
||||
int draw_y = lh - height - y;
|
||||
glViewport(x, draw_y, width, height);
|
||||
|
||||
// Make sure everything starts with a stencil of 0xFF
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(x, draw_y, width, height);
|
||||
glClearStencil(0xFF);
|
||||
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
|
||||
// Draw everything with a stencil of 0
|
||||
opengl_set_stencil(0x00);
|
||||
}
|
||||
|
||||
static bool opengl_cache_texture(CachedTexture toCache, grs_bitmap *bm) {
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, bm->row);
|
||||
|
||||
if (texturesByBitsPtr.size() < MAX_CACHED_TEXTURES) {
|
||||
// We have enough room, generate the new texture
|
||||
glGenTextures(1, &toCache.texture);
|
||||
bind_texture(toCache.texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bm->w, bm->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, toCache.converted->pixels);
|
||||
|
||||
texturesByBitsPtr[(uint64_t)bm->bits | ((uint64_t)bm->w << 32u) | ((uint64_t)bm->h << 48u)] = toCache;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not enough room, just use the dynTexture
|
||||
bind_texture(dynTexture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bm->w, bm->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, toCache.converted->pixels);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static CachedTexture *opengl_get_texture(grs_bitmap *bm) {
|
||||
auto iter = texturesByBitsPtr.find((uint64_t)bm->bits | ((uint64_t)bm->w << 32u) | ((uint64_t)bm->h << 48u));
|
||||
if (iter != texturesByBitsPtr.end()) {
|
||||
return &iter->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void opengl_clear_texture_cache() {
|
||||
if (texturesByBitsPtr.empty())
|
||||
return;
|
||||
|
||||
DEBUG("Clearing OpenGL texture cache.");
|
||||
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
for (auto iter = texturesByBitsPtr.begin(); iter != texturesByBitsPtr.end();) {
|
||||
// don't free locked surfaces
|
||||
if (!iter->second.locked) {
|
||||
SDL_FreeSurface(iter->second.bitmap);
|
||||
SDL_FreeSurface(iter->second.converted);
|
||||
glDeleteTextures(1, &iter->second.texture);
|
||||
|
||||
iter = texturesByBitsPtr.erase(iter);
|
||||
} else {
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setPaletteForBitmap(grs_bitmap *bm, SDL_Surface *surface) {
|
||||
if (bm->flags & BMF_TRANS)
|
||||
SDL_SetSurfacePalette(surface, transparentPalette);
|
||||
else
|
||||
SDL_SetSurfacePalette(surface, opaquePalette);
|
||||
}
|
||||
|
||||
static void convert_texture(grs_bitmap *bm, bool locked) {
|
||||
SDL_Surface *surface;
|
||||
if (bm->type == BMT_RSD8) {
|
||||
grs_bitmap decoded;
|
||||
gr_rsd8_convert(bm, &decoded);
|
||||
surface = SDL_CreateRGBSurfaceFrom(decoded.bits, bm->w, bm->h, 8, bm->row, 0, 0, 0, 0);
|
||||
} else {
|
||||
surface = SDL_CreateRGBSurfaceFrom(bm->bits, bm->w, bm->h, 8, bm->row, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
setPaletteForBitmap(bm, surface);
|
||||
SDL_Surface *rgba = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA32, 0);
|
||||
|
||||
// Cache this new surface.
|
||||
CachedTexture ct{};
|
||||
ct.bitmap = surface;
|
||||
ct.converted = rgba;
|
||||
ct.lastDrawTime = *tmd_ticks;
|
||||
ct.locked = locked;
|
||||
|
||||
bool cached = opengl_cache_texture(ct, bm);
|
||||
if (!cached) {
|
||||
DEBUG("Not enough room to cache texture!");
|
||||
SDL_FreeSurface(surface);
|
||||
SDL_FreeSurface(rgba);
|
||||
}
|
||||
}
|
||||
|
||||
void opengl_cache_wall_texture(int idx, int size, grs_bitmap *bm) {
|
||||
if (idx < NUM_LOADED_TEXTURES) {
|
||||
CachedTexture *t = opengl_get_texture(bm);
|
||||
if (t == nullptr) {
|
||||
convert_texture(bm, true);
|
||||
} else {
|
||||
// Need to refresh this texture
|
||||
t->lastDrawTime = -1;
|
||||
palette_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void set_texture(grs_bitmap *bm) {
|
||||
CachedTexture *t = opengl_get_texture(bm);
|
||||
if (t == nullptr) {
|
||||
// Not cached, have to make it
|
||||
convert_texture(bm, false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isDirty = false;
|
||||
|
||||
if (t->locked) {
|
||||
// Locked surfaces only need to update once
|
||||
if (palette_dirty && t->lastDrawTime != *tmd_ticks) {
|
||||
SDL_SetSurfacePalette(t->bitmap, transparentPalette); // Walls should show stars
|
||||
SDL_BlitSurface(t->bitmap, nullptr, t->converted, nullptr);
|
||||
|
||||
isDirty = true;
|
||||
}
|
||||
} else {
|
||||
if (bm->type == BMT_RSD8) {
|
||||
grs_bitmap decoded;
|
||||
gr_rsd8_convert(bm, &decoded);
|
||||
SDL_memmove(t->bitmap->pixels, decoded.bits, bm->w * bm->h);
|
||||
} else {
|
||||
SDL_memmove(t->bitmap->pixels, bm->bits, bm->w * bm->h);
|
||||
}
|
||||
|
||||
setPaletteForBitmap(bm, t->bitmap);
|
||||
SDL_BlitSurface(t->bitmap, nullptr, t->converted, nullptr);
|
||||
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
bind_texture(t->texture);
|
||||
t->lastDrawTime = *tmd_ticks;
|
||||
|
||||
if (isDirty) {
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, bm->row);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bm->w, bm->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, t->converted->pixels);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_vertex(const g3s_point &vertex, GLint tcAttrib, GLint lightAttrib) {
|
||||
|
||||
// Default, per-vertex lighting
|
||||
float light = 1.0f - (vertex.i / 4096.0f);
|
||||
|
||||
if (nightsight_active()) {
|
||||
light = 1.0f;
|
||||
} else if (gr_get_fill_type() == FILL_CLUT) {
|
||||
// Could be a CLUT color instead, use that for lighting
|
||||
// Ugly hack: We don't get the original light value, so we have to
|
||||
// recalculate it from the offset into the global lighting lookup
|
||||
// table.
|
||||
auto *clut = (uint8_t *)gr_get_fill_parm();
|
||||
light = 1.0f - (clut - grd_screen->ltab) / 4096.0f;
|
||||
}
|
||||
|
||||
if (tcAttrib >= 0)
|
||||
glVertexAttrib2f(tcAttrib, vertex.uv.u / 256.0, vertex.uv.v / 256.0);
|
||||
glVertexAttrib1f(lightAttrib, light);
|
||||
glVertex3f(vertex.x / 65536.0f, vertex.y / 65536.0f, -vertex.z / 65536.0f);
|
||||
}
|
||||
|
||||
int opengl_draw_tmap(int n, g3s_phandle *vp, grs_bitmap *bm) { return opengl_light_tmap(n, vp, bm); }
|
||||
|
||||
int opengl_light_tmap(int n, g3s_phandle *vp, grs_bitmap *bm) {
|
||||
if (n != 3 && n != 4) {
|
||||
WARN("Unexpected number of texture vertices (%d)", n);
|
||||
return CLIP_ALL;
|
||||
}
|
||||
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
set_blend_mode(bm->flags & BMF_TRANS);
|
||||
|
||||
glUseProgram(textureShaderProgram.shaderProgram);
|
||||
|
||||
set_texture(bm);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, get_texture_min_func());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, get_texture_mag_func());
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
|
||||
GLint tcAttrib = textureShaderProgram.tcAttrib;
|
||||
GLint lightAttrib = textureShaderProgram.lightAttrib;
|
||||
|
||||
glUniformMatrix4fv(textureShaderProgram.uniView, 1, false, ViewMatrix);
|
||||
glUniformMatrix4fv(textureShaderProgram.uniProj, 1, false, ProjectionMatrix);
|
||||
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
draw_vertex(*(vp[1]), tcAttrib, lightAttrib);
|
||||
draw_vertex(*(vp[0]), tcAttrib, lightAttrib);
|
||||
draw_vertex(*(vp[2]), tcAttrib, lightAttrib);
|
||||
if (n > 3)
|
||||
draw_vertex(*(vp[3]), tcAttrib, lightAttrib);
|
||||
glEnd();
|
||||
|
||||
return CLIP_NONE;
|
||||
}
|
||||
|
||||
static float convx(float x) { return x / 32768.0f / render_width - 1; }
|
||||
|
||||
static float convy(float y) { return -y / 32768.0f / render_height + 1; }
|
||||
|
||||
int opengl_bitmap(grs_bitmap *bm, int n, grs_vertex **vpl, grs_tmap_info *ti) {
|
||||
if (n != 4) {
|
||||
WARN("Unexpected number of bitmap vertices (%d)", n);
|
||||
return CLIP_ALL;
|
||||
}
|
||||
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
set_blend_mode(bm->flags & BMF_TRANS);
|
||||
|
||||
glUseProgram(textureShaderProgram.shaderProgram);
|
||||
GLint tcAttrib = textureShaderProgram.tcAttrib;
|
||||
GLint lightAttrib = textureShaderProgram.lightAttrib;
|
||||
|
||||
glUniformMatrix4fv(textureShaderProgram.uniView, 1, false, IdentityMatrix);
|
||||
glUniformMatrix4fv(textureShaderProgram.uniProj, 1, false, IdentityMatrix);
|
||||
|
||||
set_texture(bm);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, get_texture_min_func());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, get_texture_mag_func());
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
|
||||
float light = 1.0f;
|
||||
if ((ti->flags & TMF_CLUT) && !nightsight_active()) {
|
||||
// Ugly hack: We don't get the original 'i' value, so we have to
|
||||
// recalculate it from the offset into the global lighting lookup
|
||||
// table.
|
||||
light = 1.0 - (ti->clut - grd_screen->ltab) / 4096.0f;
|
||||
}
|
||||
|
||||
glUniform1i(textureShaderProgram.uniMutant, (bm->type == BMT_TLUC8) || (bm->flags == BMF_TLUC8));
|
||||
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glVertexAttrib2f(tcAttrib, 1.0f, 0.0f);
|
||||
glVertexAttrib1f(lightAttrib, light);
|
||||
glVertex3f(convx(vpl[1]->x), convy(vpl[1]->y), 0.0f);
|
||||
glVertexAttrib2f(tcAttrib, 0.0f, 0.0f);
|
||||
glVertexAttrib1f(lightAttrib, light);
|
||||
glVertex3f(convx(vpl[0]->x), convy(vpl[0]->y), 0.0f);
|
||||
glVertexAttrib2f(tcAttrib, 1.0f, 1.0f);
|
||||
glVertexAttrib1f(lightAttrib, light);
|
||||
glVertex3f(convx(vpl[2]->x), convy(vpl[2]->y), 0.0f);
|
||||
glVertexAttrib2f(tcAttrib, 0.0f, 1.0f);
|
||||
glVertexAttrib1f(lightAttrib, light);
|
||||
glVertex3f(convx(vpl[3]->x), convy(vpl[3]->y), 0.0f);
|
||||
glEnd();
|
||||
|
||||
glUniform1i(textureShaderProgram.uniMutant, false);
|
||||
|
||||
return CLIP_NONE;
|
||||
}
|
||||
|
||||
static void set_color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
|
||||
glVertexAttrib4f(colorShaderProgram.colorAttrib, red / 255.0f, green / 255.0f, blue / 255.0f, alpha / 255.0f);
|
||||
set_blend_mode(alpha < 255);
|
||||
}
|
||||
|
||||
int opengl_draw_poly(long c, int n_verts, g3s_phandle *p, char gour_flag) {
|
||||
if (n_verts < 3) {
|
||||
WARN("Unexpected number of polygon vertices (%d)", n_verts);
|
||||
return CLIP_ALL;
|
||||
}
|
||||
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
glUseProgram(colorShaderProgram.shaderProgram);
|
||||
glUniformMatrix4fv(colorShaderProgram.uniView, 1, false, ViewMatrix);
|
||||
glUniformMatrix4fv(colorShaderProgram.uniProj, 1, false, ProjectionMatrix);
|
||||
|
||||
if (gour_flag == 1 || gour_flag == 3) {
|
||||
// translucent; see init_pal_fx() for translucency parameters
|
||||
switch (c) {
|
||||
case 247:
|
||||
set_color(120, 120, 120, 80);
|
||||
break; // dark fog
|
||||
case 248:
|
||||
set_color(170, 170, 170, 80);
|
||||
break; // medium fog
|
||||
case 249:
|
||||
set_color(255, 0, 0, 80);
|
||||
break; // red fog
|
||||
case 250:
|
||||
set_color(0, 255, 0, 80);
|
||||
break; // green fog
|
||||
case 251:
|
||||
set_color(0, 0, 255, 80);
|
||||
break; // blue fog
|
||||
case 252:
|
||||
set_color(240, 240, 240, 80);
|
||||
break; // light fog
|
||||
case 253:
|
||||
set_color(0, 0, 255, 128);
|
||||
break; // blue force field
|
||||
case 254:
|
||||
set_color(0, 255, 0, 128);
|
||||
break; // green force field
|
||||
case 255:
|
||||
set_color(255, 0, 0, 128);
|
||||
break; // red force field
|
||||
default:
|
||||
return CLIP_ALL;
|
||||
}
|
||||
} else if (c == 255) {
|
||||
// transparent
|
||||
return CLIP_NONE;
|
||||
} else {
|
||||
// solid color
|
||||
SDL_Color color = sdlPalette->colors[c];
|
||||
set_color(color.r, color.g, color.b, 255);
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, get_texture_min_func());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, get_texture_mag_func());
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
|
||||
GLint lightAttrib = colorShaderProgram.lightAttrib;
|
||||
|
||||
long a = 0, b = n_verts - 1;
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
while (true) {
|
||||
draw_vertex(*(p[a]), -1, lightAttrib);
|
||||
if (a++ == b)
|
||||
break;
|
||||
draw_vertex(*(p[b]), -1, lightAttrib);
|
||||
if (b-- == a)
|
||||
break;
|
||||
}
|
||||
glEnd();
|
||||
|
||||
return CLIP_NONE;
|
||||
}
|
||||
|
||||
void opengl_set_stencil(int v) {
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
|
||||
glStencilFunc(GL_ALWAYS, v, ~0u);
|
||||
}
|
||||
|
||||
void opengl_begin_stars() {
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
glPointSize(1.5 * (render_width / 320.0));
|
||||
|
||||
glUseProgram(starShaderProgram.shaderProgram);
|
||||
glUniformMatrix4fv(starShaderProgram.uniView, 1, false, IdentityMatrix);
|
||||
glUniformMatrix4fv(starShaderProgram.uniProj, 1, false, IdentityMatrix);
|
||||
|
||||
// Only draw stars where the stencil value is 0xFF (Sky!)
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||
glStencilFunc(GL_EQUAL, 0xFF, ~0u);
|
||||
|
||||
set_blend_mode(true);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, get_texture_mag_func());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, get_texture_mag_func());
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
|
||||
glBegin(GL_POINTS);
|
||||
}
|
||||
|
||||
void opengl_end_stars() {
|
||||
glEnd();
|
||||
|
||||
// Turn off the stencil test and blending
|
||||
opengl_set_stencil(0x00);
|
||||
set_blend_mode(false);
|
||||
}
|
||||
|
||||
int opengl_draw_star(fix star_x, fix star_y, int c, bool anti_alias) {
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
GLint lightAttrib = starShaderProgram.lightAttrib;
|
||||
|
||||
float x = fix_float(star_x);
|
||||
float y = fix_float(star_y);
|
||||
|
||||
// Lower screen resolutions should snap to pixels to avoid shimmering
|
||||
if (!anti_alias) {
|
||||
x = (int)x + 0.5f;
|
||||
y = (int)y + 0.5f;
|
||||
}
|
||||
|
||||
x = (x / render_width) * 2.0 - 1.0;
|
||||
y = (y / render_height) * -2.0 + 1.0;
|
||||
|
||||
// rescale the color so that it's 0..255, 0 = dark, 255 = light
|
||||
int std_color_base = 208;
|
||||
int std_color_range = 16;
|
||||
int color = (std_color_base + std_color_range - 1 - c);
|
||||
color = (255 * color) / (std_color_range + 1);
|
||||
|
||||
glVertexAttrib1f(lightAttrib, color / 255.0f);
|
||||
glVertex3f(x, y, -0.25f);
|
||||
|
||||
return CLIP_NONE;
|
||||
}
|
||||
|
||||
void opengl_begin_sensaround(uchar version) {
|
||||
if (version == 1) {
|
||||
// Version 1 of the sensaround is old tech, and should render in software mode :D
|
||||
opengl_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void opengl_end_sensaround() { opengl_enabled = true; }
|
||||
|
||||
#endif // USE_OPENGL
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef __MACSRC_OPENGL_H
|
||||
#define __MACSRC_OPENGL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <3d.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#ifdef USE_OPENGL
|
||||
|
||||
int init_opengl();
|
||||
void opengl_cache_wall_texture(int idx, int size, grs_bitmap *bm);
|
||||
void opengl_clear_texture_cache();
|
||||
|
||||
bool can_use_opengl();
|
||||
bool use_opengl();
|
||||
void toggle_opengl();
|
||||
void opengl_resize(int width, int height);
|
||||
bool should_opengl_swap();
|
||||
void opengl_swap_and_restore(SDL_Surface *ui);
|
||||
void opengl_change_palette();
|
||||
|
||||
void opengl_set_viewport(int x, int y, int width, int height);
|
||||
int opengl_draw_tmap(int n, g3s_phandle *vp, grs_bitmap *bm);
|
||||
int opengl_light_tmap(int n, g3s_phandle *vp, grs_bitmap *bm);
|
||||
int opengl_bitmap(grs_bitmap *bm, int n, grs_vertex **vpl, grs_tmap_info *ti);
|
||||
int opengl_draw_poly(long c, int n_verts, g3s_phandle *p, char gour_flag);
|
||||
int opengl_draw_star(fix star_x, fix star_y, int c, bool anti_alias);
|
||||
void opengl_begin_stars();
|
||||
void opengl_end_stars();
|
||||
void opengl_set_stencil(int v);
|
||||
void opengl_start_frame();
|
||||
void opengl_end_frame();
|
||||
void opengl_begin_sensaround(uchar version);
|
||||
void opengl_end_sensaround();
|
||||
|
||||
#else
|
||||
|
||||
static int init_opengl() { return 0; }
|
||||
static void opengl_cache_wall_texture(int idx, int size, grs_bitmap *bm) {}
|
||||
static void opengl_clear_texture_cache(){};
|
||||
|
||||
static bool can_use_opengl() { return false; }
|
||||
static bool use_opengl() { return false; }
|
||||
static void toggle_opengl() {}
|
||||
static void opengl_resize(int width, int height) {}
|
||||
static bool should_opengl_swap() { return false; }
|
||||
static void opengl_swap_and_restore(SDL_Surface *ui) {}
|
||||
static void opengl_change_palette() {}
|
||||
|
||||
static void opengl_set_viewport(int x, int y, int width, int height) {}
|
||||
static int opengl_draw_tmap(int n, g3s_phandle *vp, grs_bitmap *bm) { return 0; }
|
||||
static int opengl_light_tmap(int n, g3s_phandle *vp, grs_bitmap *bm) { return 0; }
|
||||
static int opengl_bitmap(grs_bitmap *bm, int n, grs_vertex **vpl, grs_tmap_info *ti) { return 0; }
|
||||
static int opengl_draw_poly(long c, int n_verts, g3s_phandle *p, char gour_flag) { return 0; }
|
||||
static int opengl_draw_star(fix star_x, fix star_y, int c, bool anti_alias) { return 0; }
|
||||
static void opengl_begin_stars() {}
|
||||
static void opengl_end_stars() {}
|
||||
static void opengl_set_stencil(int v) {}
|
||||
static void opengl_start_frame() {}
|
||||
static void opengl_end_frame() {}
|
||||
static void opengl_begin_sensaround(uchar version) {}
|
||||
static void opengl_end_sensaround() {}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2015-2018 Night Dive Studios, LLC.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
//====================================================================================
|
||||
//
|
||||
// System Shock - ©1994-1995 Looking Glass Technologies, Inc.
|
||||
//
|
||||
// Prefs.h - Handles saving and loading preferences.
|
||||
//
|
||||
//====================================================================================
|
||||
|
||||
//--------------------
|
||||
// Types
|
||||
//--------------------
|
||||
typedef struct {
|
||||
short prefVer; // Version - set to 0 for now.
|
||||
short prefPlayIntro; // Play intro at startup if non-zero.
|
||||
|
||||
// Game Options
|
||||
short goMsgLength; // 0 - normal, 1 - brief
|
||||
bool goPopupLabels;
|
||||
bool goOnScreenHelp;
|
||||
short goLanguage; // 0 - English, 1 - French, 2 - German
|
||||
bool goCaptureMouse;
|
||||
bool goInvertMouseY;
|
||||
|
||||
// Sound Options
|
||||
bool soBackMusic;
|
||||
bool soSoundFX;
|
||||
short soMusicVolume;
|
||||
short soSfxVolume;
|
||||
short soAudioLogVolume;
|
||||
short soMidiBackend; // 0 => adlmidi, 1 => native, 2 => fluidsynth
|
||||
short soMidiOutput; // which of the MIDI backend's outputs to use
|
||||
|
||||
// Display Options
|
||||
short doVideoMode;
|
||||
short doResolution; // 0 - High, 1 - Low
|
||||
short doDetail; // 0 - Min, 1-Low, 2-High, 3-Max
|
||||
short doGamma;
|
||||
bool doUseQD;
|
||||
bool doUseOpenGL;
|
||||
// 0 => unfiltered
|
||||
// 1 => bilinear
|
||||
// TODO: add trilinear, anisotropic?
|
||||
short doTextureFilter;
|
||||
} ShockPrefs;
|
||||
|
||||
//--------------------
|
||||
// Globals
|
||||
//--------------------
|
||||
extern ShockPrefs gShockPrefs;
|
||||
|
||||
//--------------------
|
||||
// Prototypes
|
||||
//--------------------
|
||||
void SetDefaultPrefs(void);
|
||||
int16_t LoadPrefs(void);
|
||||
int16_t SavePrefs(void);
|
||||
|
||||
//-------------------
|
||||
// Enums
|
||||
//-------------------
|
||||
enum OPT_SEQ_ { // Must be in the same order as in wraper.h
|
||||
OPT_SEQ_ADLMIDI = 0,
|
||||
OPT_SEQ_NativeMI,
|
||||
#ifdef USE_FLUIDSYNTH
|
||||
OPT_SEQ_FluidSyn,
|
||||
#endif // USE_FLUIDSYNTH
|
||||
OPT_SEQ_Max
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
#include "Xmi.h"
|
||||
#include "MusicDevice.h"
|
||||
|
||||
static snd_digi_parms digi_parms_by_channel[SND_MAX_SAMPLES];
|
||||
|
||||
#ifdef USE_SDL_MIXER
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
static Mix_Chunk *samples_by_channel[SND_MAX_SAMPLES];
|
||||
|
||||
extern SDL_AudioStream *cutscene_audiostream;
|
||||
extern struct MusicDevice *MusicDev;
|
||||
|
||||
extern void AudioStreamCallback(void *userdata, unsigned char *stream, int len);
|
||||
extern void MusicCallback(void *userdata, Uint8 *stream, int len);
|
||||
|
||||
int snd_start_digital(void) {
|
||||
|
||||
// Startup the sound system
|
||||
|
||||
SDL_AudioSpec spec, obtained;
|
||||
spec.freq = 48000;
|
||||
spec.format = AUDIO_S16SYS;
|
||||
spec.channels = 2;
|
||||
spec.samples = 2048;
|
||||
spec.callback = AudioStreamCallback;
|
||||
spec.userdata = (void *)&cutscene_audiostream;
|
||||
|
||||
extern SDL_AudioDeviceID device;
|
||||
device = SDL_OpenAudioDevice(NULL, 0, &spec, &obtained, 0);
|
||||
|
||||
if (device == 0) {
|
||||
ERROR("Could not open SDL audio: %s", SDL_GetError());
|
||||
} else {
|
||||
INFO("Opened Music Stream, deviceID %d, freq %d, size %d, format %d, channels %d, samples %d", device,
|
||||
obtained.freq, obtained.size, obtained.format, obtained.channels, obtained.samples);
|
||||
}
|
||||
|
||||
if (Mix_Init(MIX_INIT_MP3) < 0) {
|
||||
ERROR("%s: Init failed", __FUNCTION__);
|
||||
}
|
||||
|
||||
if (Mix_OpenAudio(48000, AUDIO_S16SYS, 2, 2048) < 0) {
|
||||
ERROR("%s: Couldn't open audio device", __FUNCTION__);
|
||||
}
|
||||
|
||||
Mix_AllocateChannels(SND_MAX_SAMPLES);
|
||||
|
||||
Mix_HookMusic(MusicCallback, (void *)&MusicDev);
|
||||
Mix_VolumeMusic(MIX_MAX_VOLUME); // use max volume for music stream
|
||||
|
||||
InitReadXMI();
|
||||
|
||||
atexit(Mix_CloseAudio);
|
||||
atexit(SDL_CloseAudio);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int snd_sample_play(int snd_ref, int len, uchar *smp, struct snd_digi_parms *dprm) {
|
||||
|
||||
// Play one of the VOC format sounds
|
||||
|
||||
Mix_Chunk *sample = Mix_LoadWAV_RW(SDL_RWFromConstMem(smp, len), 1);
|
||||
if (sample == NULL) {
|
||||
DEBUG("%s: Failed to load sample", __FUNCTION__);
|
||||
return ERR_NOEFFECT;
|
||||
}
|
||||
|
||||
int loops = dprm->loops > 0 ? dprm->loops - 1 : -1;
|
||||
int channel = Mix_PlayChannel(-1, sample, loops);
|
||||
if (channel < 0) {
|
||||
DEBUG("%s: Failed to play sample", __FUNCTION__);
|
||||
Mix_FreeChunk(sample);
|
||||
return ERR_NOEFFECT;
|
||||
}
|
||||
|
||||
if (samples_by_channel[channel])
|
||||
Mix_FreeChunk(samples_by_channel[channel]);
|
||||
|
||||
samples_by_channel[channel] = sample;
|
||||
digi_parms_by_channel[channel] = *dprm;
|
||||
snd_sample_reload_parms(&digi_parms_by_channel[channel]);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
void snd_end_sample(int hnd_id) {
|
||||
Mix_HaltChannel(hnd_id);
|
||||
if (samples_by_channel[hnd_id]) {
|
||||
Mix_FreeChunk(samples_by_channel[hnd_id]);
|
||||
samples_by_channel[hnd_id] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool snd_sample_playing(int hnd_id) { return Mix_Playing(hnd_id); }
|
||||
|
||||
snd_digi_parms *snd_sample_parms(int hnd_id) { return &digi_parms_by_channel[hnd_id]; }
|
||||
|
||||
void snd_kill_all_samples(void) {
|
||||
for (int channel = 0; channel < SND_MAX_SAMPLES; channel++) {
|
||||
snd_end_sample(channel);
|
||||
}
|
||||
|
||||
// assume we want these too
|
||||
// StopTheMusic(); // no, don't stop the music
|
||||
if (cutscene_audiostream != NULL)
|
||||
SDL_AudioStreamClear(cutscene_audiostream);
|
||||
}
|
||||
|
||||
void snd_sample_reload_parms(snd_digi_parms *sdp) {
|
||||
// ignore if *sdp is not one of the items in digi_parms_by_channel[]
|
||||
if (sdp < digi_parms_by_channel || sdp > digi_parms_by_channel + SND_MAX_SAMPLES)
|
||||
return;
|
||||
int channel = sdp - digi_parms_by_channel;
|
||||
|
||||
if (!Mix_Playing(channel))
|
||||
return;
|
||||
|
||||
// sdp->vol ranges from 0..255
|
||||
Mix_Volume(channel, (sdp->vol * 128) / 100);
|
||||
|
||||
// sdp->pan ranges from 1 (left) to 127 (right)
|
||||
uint8_t right = 2 * sdp->pan;
|
||||
Mix_SetPanning(channel, 254 - right, right);
|
||||
}
|
||||
|
||||
int is_playing = 0;
|
||||
|
||||
int MacTuneLoadTheme(char *theme_base, int themeID) {
|
||||
char filename[40];
|
||||
FILE *f;
|
||||
int i;
|
||||
|
||||
#define NUM_SCORES 8
|
||||
#define SUPERCHUNKS_PER_SCORE 4
|
||||
#define NUM_TRANSITIONS 9
|
||||
#define NUM_LAYERS 32
|
||||
#define MAX_KEYS 10
|
||||
#define NUM_LAYERABLE_SUPERCHUNKS 22
|
||||
#define KEY_BAR_RESOLUTION 2
|
||||
|
||||
extern uchar track_table[NUM_SCORES][SUPERCHUNKS_PER_SCORE];
|
||||
extern uchar transition_table[NUM_TRANSITIONS];
|
||||
extern uchar layering_table[NUM_LAYERS][MAX_KEYS];
|
||||
extern uchar key_table[NUM_LAYERABLE_SUPERCHUNKS][KEY_BAR_RESOLUTION];
|
||||
|
||||
StopTheMusic();
|
||||
|
||||
FreeXMI();
|
||||
|
||||
if (strncmp(theme_base, "thm", 3)) {
|
||||
sprintf(filename, "res/sound/%s/%s.xmi", MusicDev->musicType, theme_base);
|
||||
ReadXMI(filename);
|
||||
} else {
|
||||
sprintf(filename, "res/sound/%s/thm%i.xmi", MusicDev->musicType, themeID);
|
||||
ReadXMI(filename);
|
||||
|
||||
sprintf(filename, "res/sound/thm%i.bin", themeID);
|
||||
extern FILE *fopen_caseless(const char *path, const char *mode); // see caseless.c
|
||||
f = fopen_caseless(filename, "rb");
|
||||
if (f != 0) {
|
||||
fread(track_table, NUM_SCORES * SUPERCHUNKS_PER_SCORE, 1, f);
|
||||
fread(transition_table, NUM_TRANSITIONS, 1, f);
|
||||
fread(layering_table, NUM_LAYERS * MAX_KEYS, 1, f);
|
||||
fread(key_table, NUM_LAYERABLE_SUPERCHUNKS * KEY_BAR_RESOLUTION, 1, f);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void MacTuneKillCurrentTheme(void) { StopTheMusic(); }
|
||||
|
||||
#else
|
||||
|
||||
// Sound stubs that do nothing, when SDL Mixer is not found
|
||||
|
||||
int snd_start_digital(void) { return OK; }
|
||||
int snd_sample_play(int snd_ref, int len, uchar *smp, struct snd_digi_parms *dprm) { return OK; }
|
||||
int snd_alog_play(int snd_ref, int len, uchar *smp, struct snd_digi_parms *dprm) { return OK; }
|
||||
void snd_end_sample(int hnd_id) {}
|
||||
void snd_kill_all_samples(void) {}
|
||||
int MacTuneLoadTheme(char *theme_base, int themeID) { return OK; }
|
||||
void MacTuneKillCurrentTheme(void) {}
|
||||
snd_digi_parms *snd_sample_parms(int hnd_id) { return &digi_parms_by_channel[0]; }
|
||||
bool snd_sample_playing(int hnd_id) { return false; }
|
||||
void snd_sample_reload_parms(snd_digi_parms *sdp) {}
|
||||
|
||||
#endif
|
||||
|
||||
// Unimplemented sound stubs
|
||||
|
||||
void snd_startup(void) {}
|
||||
int snd_stop_digital(void) { return 1; }
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2015-2018 Night Dive Studios, LLC.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
//====================================================================================
|
||||
//
|
||||
// System Shock - ©1994-1995 Looking Glass Technologies, Inc.
|
||||
//
|
||||
// Shock.c - Mac-specific initialization and main event loop.
|
||||
//
|
||||
//====================================================================================
|
||||
|
||||
//--------------------
|
||||
// Includes
|
||||
//--------------------
|
||||
#include <math.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#include "InitMac.h"
|
||||
#include "Modding.h"
|
||||
#include "OpenGL.h"
|
||||
#include "Prefs.h"
|
||||
#include "Shock.h"
|
||||
#include "ShockBitmap.h"
|
||||
|
||||
#include "amaploop.h"
|
||||
#include "gr2ss.h"
|
||||
#include "hkeyfunc.h"
|
||||
#include "mainloop.h"
|
||||
#include "setup.h"
|
||||
#include "shockolate_version.h"
|
||||
#include "status.h"
|
||||
#include "version.h"
|
||||
|
||||
//--------------------
|
||||
// Globals
|
||||
//--------------------
|
||||
bool gPlayingGame;
|
||||
|
||||
grs_screen *cit_screen;
|
||||
SDL_Window *window;
|
||||
SDL_Palette *sdlPalette;
|
||||
SDL_Renderer *renderer;
|
||||
|
||||
SDL_AudioDeviceID device;
|
||||
|
||||
int num_args;
|
||||
char **arg_values;
|
||||
|
||||
extern grs_screen *svga_screen;
|
||||
extern frc *svga_render_context;
|
||||
|
||||
//--------------------
|
||||
// Prototypes
|
||||
//--------------------
|
||||
extern void init_all(void);
|
||||
extern void inv_change_fullscreen(uchar on);
|
||||
extern void object_data_flush(void);
|
||||
extern errtype load_da_palette(void);
|
||||
|
||||
// see Prefs.c
|
||||
extern void CreateDefaultKeybindsFile(void);
|
||||
extern void LoadHotkeyKeybinds(void);
|
||||
extern void LoadMoveKeybinds(void);
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Main function.
|
||||
//------------------------------------------------------------------------------------
|
||||
int main(int argc, char **argv) {
|
||||
// Save the arguments for later
|
||||
|
||||
num_args = argc;
|
||||
arg_values = argv;
|
||||
|
||||
// FIXME externalize this
|
||||
log_set_quiet(0);
|
||||
log_set_level(LOG_INFO);
|
||||
|
||||
INFO("Logger initialized");
|
||||
|
||||
// init mac managers
|
||||
|
||||
InitMac();
|
||||
|
||||
// Initialize the preferences file.
|
||||
|
||||
SetDefaultPrefs();
|
||||
LoadPrefs();
|
||||
|
||||
// see Prefs.c
|
||||
CreateDefaultKeybindsFile(); // only if it doesn't already exist
|
||||
// even if keybinds file still doesn't exist, defaults will be set here
|
||||
LoadHotkeyKeybinds();
|
||||
LoadMoveKeybinds();
|
||||
|
||||
// Process some startup arguments
|
||||
|
||||
bool show_splash = !CheckArgument("-nosplash");
|
||||
|
||||
// CC: Modding support! This is so exciting.
|
||||
|
||||
ProcessModArgs(argc, argv);
|
||||
|
||||
// Initialize
|
||||
|
||||
init_all();
|
||||
setup_init();
|
||||
|
||||
gPlayingGame = true;
|
||||
|
||||
load_da_palette();
|
||||
gr_clear(0xFF);
|
||||
|
||||
// Draw the splash screen
|
||||
|
||||
INFO("Showing splash screen");
|
||||
splash_draw(show_splash);
|
||||
|
||||
// Start in the Main Menu loop
|
||||
|
||||
_new_mode = _current_loop = SETUP_LOOP;
|
||||
loopmode_enter(SETUP_LOOP);
|
||||
|
||||
// Start the main loop
|
||||
|
||||
INFO("Showing main menu, starting game loop");
|
||||
mainloop(argc, argv);
|
||||
|
||||
status_bio_end();
|
||||
stop_music();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CheckArgument(char *arg) {
|
||||
if (arg == NULL)
|
||||
return false;
|
||||
|
||||
for (int i = 1; i < num_args; i++) {
|
||||
if (strcmp(arg_values[i], arg) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void InitSDL() {
|
||||
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
|
||||
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) < 0) {
|
||||
DEBUG("%s: Init failed", __FUNCTION__);
|
||||
}
|
||||
|
||||
// TODO: figure out some universal set of settings that work...
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
|
||||
|
||||
gr_init();
|
||||
|
||||
extern short svga_mode_data[];
|
||||
gr_set_mode(svga_mode_data[gShockPrefs.doVideoMode], TRUE);
|
||||
|
||||
INFO("Setting up screen and render contexts");
|
||||
|
||||
// Create a canvas to draw to
|
||||
|
||||
SetupOffscreenBitmaps(grd_cap->w, grd_cap->h);
|
||||
|
||||
// Open our window!
|
||||
char window_title[128];
|
||||
sprintf(window_title, "System Shock - %s", SHOCKOLATE_VERSION);
|
||||
|
||||
window = SDL_CreateWindow(window_title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, grd_cap->w, grd_cap->h,
|
||||
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL);
|
||||
|
||||
// Create the palette
|
||||
|
||||
sdlPalette = SDL_AllocPalette(256);
|
||||
|
||||
// Setup the screen
|
||||
|
||||
svga_screen = cit_screen = gr_alloc_screen(grd_cap->w, grd_cap->h);
|
||||
gr_set_screen(svga_screen);
|
||||
|
||||
gr_alloc_ipal();
|
||||
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
|
||||
atexit(SDL_Quit);
|
||||
|
||||
SDL_RaiseWindow(window);
|
||||
|
||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);
|
||||
SDL_RenderSetLogicalSize(renderer, grd_cap->w, grd_cap->h);
|
||||
|
||||
// Startup OpenGL
|
||||
|
||||
init_opengl();
|
||||
|
||||
SDLDraw();
|
||||
|
||||
SDL_ShowWindow(window);
|
||||
}
|
||||
|
||||
SDL_Color gamePalette[256];
|
||||
bool UseCutscenePalette = FALSE; // see cutsloop.c
|
||||
void SetSDLPalette(int index, int count, uchar *pal) {
|
||||
static bool gammalut_init = 0;
|
||||
static uchar gammalut[100 - 10 + 1][256];
|
||||
if (!gammalut_init) {
|
||||
double factor = (use_opengl() ? 1.0 : 2.2); // OpenGL uses 2.2
|
||||
int i, j;
|
||||
for (i = 10; i <= 100; i++) {
|
||||
double gamma = (double)i * 1.0 / 100;
|
||||
gamma = 1 - gamma;
|
||||
gamma *= gamma;
|
||||
gamma = 1 - gamma;
|
||||
gamma = 1 / (gamma * factor);
|
||||
for (j = 0; j < 256; j++)
|
||||
gammalut[i - 10][j] = (uchar)(pow((double)j / 255, gamma) * 255);
|
||||
}
|
||||
gammalut_init = 1;
|
||||
INFO("Gamma LUT init\'ed");
|
||||
}
|
||||
|
||||
int gam = gShockPrefs.doGamma;
|
||||
if (gam < 10)
|
||||
gam = 10;
|
||||
if (gam > 100)
|
||||
gam = 100;
|
||||
gam -= 10;
|
||||
|
||||
for (int i = index; i < index + count; i++) {
|
||||
gamePalette[i].r = gammalut[gam][*pal++];
|
||||
gamePalette[i].g = gammalut[gam][*pal++];
|
||||
gamePalette[i].b = gammalut[gam][*pal++];
|
||||
gamePalette[i].a = 0xff;
|
||||
}
|
||||
|
||||
if (!UseCutscenePalette) {
|
||||
// Hack black!
|
||||
gamePalette[255].r = 0x0;
|
||||
gamePalette[255].g = 0x0;
|
||||
gamePalette[255].b = 0x0;
|
||||
gamePalette[255].a = 0xff;
|
||||
}
|
||||
|
||||
SDL_SetPaletteColors(sdlPalette, gamePalette, 0, 256);
|
||||
SDL_SetSurfacePalette(drawSurface, sdlPalette);
|
||||
SDL_SetSurfacePalette(offscreenDrawSurface, sdlPalette);
|
||||
|
||||
if (should_opengl_swap())
|
||||
opengl_change_palette();
|
||||
}
|
||||
|
||||
void SDLDraw() {
|
||||
if (should_opengl_swap()) {
|
||||
// We want the UI background to be transparent!
|
||||
sdlPalette->colors[255].a = 0x00;
|
||||
|
||||
// Draw the OpenGL view
|
||||
opengl_swap_and_restore(drawSurface);
|
||||
|
||||
// Set the palette back, and we are done
|
||||
sdlPalette->colors[255].a = 0xff;
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the screen!
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, drawSurface);
|
||||
|
||||
// Blit to the screen by drawing the surface
|
||||
SDL_Rect srcRect = {0, 0, gScreenWide, gScreenHigh};
|
||||
SDL_RenderCopy(renderer, texture, &srcRect, NULL);
|
||||
SDL_DestroyTexture(texture);
|
||||
|
||||
// Show everything we've drawn
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
bool MouseCaptured = FALSE;
|
||||
|
||||
extern int mlook_enabled;
|
||||
|
||||
void CaptureMouse(bool capture) {
|
||||
MouseCaptured = (capture && gShockPrefs.goCaptureMouse);
|
||||
|
||||
if (!MouseCaptured && mlook_enabled && SDL_GetRelativeMouseMode() == SDL_TRUE) {
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
SDL_WarpMouseInWindow(window, w / 2, h / 2);
|
||||
} else
|
||||
SDL_SetRelativeMouseMode(MouseCaptured ? SDL_TRUE : SDL_FALSE);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2015-2018 Night Dive Studios, LLC.
|
||||
Copyright (C) 2018-2020 Shockolate Project
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
//====================================================================================
|
||||
//
|
||||
// System Shock - ©1994-1995 Looking Glass Technologies, Inc.
|
||||
//
|
||||
// Shock.h - Mac-specific initialization and main event loop.
|
||||
//
|
||||
//====================================================================================
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
//--------------------
|
||||
// Function Prototypes
|
||||
//--------------------
|
||||
int main(int argc, char **argv);
|
||||
|
||||
void InitSDL();
|
||||
void SetSDLPalette(int index, int count, uchar *pal);
|
||||
void SDLDraw();
|
||||
void CaptureMouse(bool capture);
|
||||
bool CheckArgument(char *name);
|
||||
|
||||
//--------------------
|
||||
// Public Globals
|
||||
//--------------------
|
||||
|
||||
// Is game being playing?
|
||||
extern bool gPlayingGame;
|
||||
|
||||
extern grs_screen *cit_screen;
|
||||
extern SDL_Renderer *renderer;
|
||||
extern SDL_Window *window;
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 1994-1995 Looking Glass Technologies, Inc.
|
||||
Copyright (C) 2015-2018 Night Dive Studios, LLC.
|
||||
Copyright (C) 2018-2020 Shockolate Project
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
// ShockBitmap.c - Manages off-screen bitmaps and palettes.
|
||||
|
||||
//--------------------
|
||||
// Includes
|
||||
//--------------------
|
||||
#include "InitMac.h"
|
||||
#include "Shock.h"
|
||||
#include "ShockBitmap.h"
|
||||
#include "2d.h"
|
||||
|
||||
//--------------------
|
||||
// Globals
|
||||
//--------------------
|
||||
SDL_Surface *drawSurface;
|
||||
SDL_Surface *offscreenDrawSurface;
|
||||
|
||||
void ChangeScreenSize(int width, int height) {
|
||||
if (gScreenWide == width && gScreenHigh == height)
|
||||
return;
|
||||
|
||||
INFO("ChangeScreenSize");
|
||||
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
extern bool fullscreenActive;
|
||||
SDL_SetWindowFullscreen(window, fullscreenActive ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
|
||||
SDL_SetWindowSize(window, width, height);
|
||||
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
|
||||
SDL_RenderSetLogicalSize(renderer, width, height);
|
||||
|
||||
SetupOffscreenBitmaps(width, height);
|
||||
|
||||
gScreenWide = width;
|
||||
gScreenHigh = height;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Setup the main offscreen bitmaps.
|
||||
//------------------------------------------------------------------------------------
|
||||
void SetupOffscreenBitmaps(int width, int height) {
|
||||
DEBUG("SetupOffscreenBitmaps %i %i", width, height);
|
||||
|
||||
if (drawSurface != NULL) {
|
||||
SDL_FreeSurface(drawSurface);
|
||||
}
|
||||
if (offscreenDrawSurface != NULL) {
|
||||
SDL_FreeSurface(offscreenDrawSurface);
|
||||
}
|
||||
|
||||
drawSurface = SDL_CreateRGBSurface(0, width, height, 8, 0, 0, 0, 0);
|
||||
if (!drawSurface) {
|
||||
ERROR("SDL: Failed to create draw surface");
|
||||
return;
|
||||
}
|
||||
|
||||
offscreenDrawSurface = SDL_CreateRGBSurface(0, width, height, 8, 0, 0, 0, 0);
|
||||
if (!offscreenDrawSurface) {
|
||||
ERROR("SDL: Failed to create offscreen draw surface");
|
||||
return;
|
||||
}
|
||||
|
||||
// Point the renderer at the screen bytes
|
||||
gScreenRowbytes = drawSurface->w;
|
||||
gScreenAddress = drawSurface->pixels;
|
||||
|
||||
grd_mode_cap.vbase = gScreenAddress;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 1994-1995 Looking Glass Technologies, Inc.
|
||||
Copyright (C) 2015-2018 Night Dive Studios, LLC.
|
||||
Copyright (C) 2018-2020 Shockolate Project
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
// ShockBitmap.c - Manages off-screen bitmaps and palettes.
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
// Globals
|
||||
|
||||
// Current draw surface
|
||||
extern SDL_Surface *drawSurface;
|
||||
// Offscreen draw surface
|
||||
extern SDL_Surface *offscreenDrawSurface;
|
||||
|
||||
// Prototypes
|
||||
|
||||
/// Change screen size.
|
||||
void ChangeScreenSize(int width, int height);
|
||||
|
||||
/// Setup the main offscreen bitmaps.
|
||||
void SetupOffscreenBitmaps(int width, int height);
|
||||
@@ -0,0 +1,889 @@
|
||||
#include <SDL.h>
|
||||
|
||||
#include "Xmi.h"
|
||||
#include "MusicDevice.h"
|
||||
#include "Prefs.h"
|
||||
|
||||
unsigned int NumTracks;
|
||||
char ChannelThread[16]; // 16 device channels
|
||||
int NumUsedChannels; // number of in-use device channels
|
||||
|
||||
MIDI_EVENT **TrackEvents;
|
||||
short *TrackTiming;
|
||||
unsigned short *TrackUsedChannels;
|
||||
|
||||
MIDI_EVENT *ThreadEventList[NUM_THREADS];
|
||||
int ThreadTiming[NUM_THREADS];
|
||||
char ThreadChannelRemap[16 * NUM_THREADS];
|
||||
SDL_atomic_t DeviceChannelVolume[16]; // only msb: 0-127
|
||||
SDL_atomic_t ThreadPlaying[NUM_THREADS];
|
||||
SDL_atomic_t ThreadCommand[NUM_THREADS];
|
||||
|
||||
MusicDevice *MusicDev;
|
||||
static SDL_mutex *MyMutex;
|
||||
|
||||
void MusicCallback(void *userdata, Uint8 *stream, int len) {
|
||||
MusicDevice *dev;
|
||||
|
||||
SDL_LockMutex(MyMutex);
|
||||
dev = *(MusicDevice **)userdata;
|
||||
if (!dev || !dev->isOpen) {
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_memset(stream, 0, (size_t)len); // in case we don't get anything
|
||||
dev->generate(dev, (short *)((void *)stream), len / (int)(2 * sizeof(short)));
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
|
||||
void FreeXMI(void) {
|
||||
unsigned int track;
|
||||
MIDI_EVENT *event, *next;
|
||||
|
||||
for (track = 0; track < NumTracks; track++) {
|
||||
event = TrackEvents[track];
|
||||
while (event) {
|
||||
next = event->next;
|
||||
if (event->buffer)
|
||||
free(event->buffer);
|
||||
free(event);
|
||||
event = next;
|
||||
}
|
||||
}
|
||||
|
||||
if (TrackEvents) {
|
||||
free(TrackEvents);
|
||||
TrackEvents = 0;
|
||||
}
|
||||
if (TrackTiming) {
|
||||
free(TrackTiming);
|
||||
TrackTiming = 0;
|
||||
}
|
||||
if (TrackUsedChannels) {
|
||||
free(TrackUsedChannels);
|
||||
TrackUsedChannels = 0;
|
||||
}
|
||||
|
||||
NumTracks = 0;
|
||||
}
|
||||
|
||||
MIDI_EVENT *NewMIDIEvent(MIDI_EVENT **eventlist, MIDI_EVENT *curevent, int time) {
|
||||
if (*eventlist == 0) {
|
||||
*eventlist = curevent = (MIDI_EVENT *)malloc(sizeof(MIDI_EVENT));
|
||||
|
||||
curevent->next = 0;
|
||||
|
||||
if (time < 0)
|
||||
curevent->time = 0;
|
||||
else
|
||||
curevent->time = time;
|
||||
curevent->buffer = 0;
|
||||
curevent->len = 0;
|
||||
|
||||
return curevent;
|
||||
}
|
||||
|
||||
if (time < 0) {
|
||||
MIDI_EVENT *event = (MIDI_EVENT *)malloc(sizeof(MIDI_EVENT));
|
||||
|
||||
event->next = *eventlist;
|
||||
*eventlist = curevent = event;
|
||||
|
||||
curevent->time = 0;
|
||||
curevent->buffer = 0;
|
||||
curevent->len = 0;
|
||||
|
||||
return curevent;
|
||||
}
|
||||
|
||||
if (curevent->time > time)
|
||||
curevent = *eventlist;
|
||||
|
||||
while (curevent->next) {
|
||||
if (curevent->next->time > time) {
|
||||
MIDI_EVENT *event = (MIDI_EVENT *)malloc(sizeof(MIDI_EVENT));
|
||||
|
||||
event->next = curevent->next;
|
||||
curevent->next = event;
|
||||
curevent = event;
|
||||
|
||||
curevent->time = time;
|
||||
curevent->buffer = 0;
|
||||
curevent->len = 0;
|
||||
|
||||
return curevent;
|
||||
}
|
||||
|
||||
curevent = curevent->next;
|
||||
}
|
||||
|
||||
curevent->next = (MIDI_EVENT *)malloc(sizeof(MIDI_EVENT));
|
||||
|
||||
curevent = curevent->next;
|
||||
curevent->next = 0;
|
||||
|
||||
curevent->time = time;
|
||||
curevent->buffer = 0;
|
||||
curevent->len = 0;
|
||||
|
||||
return curevent;
|
||||
}
|
||||
|
||||
int ReadXMI(const char *filename) {
|
||||
FILE *f;
|
||||
int size, start, begin, pos, time, end, tempo, tempo_set;
|
||||
unsigned int i, count, len, chunk_len, quant, status, delta, b0, b1, b2, b3;
|
||||
unsigned char *data, *p;
|
||||
short ppqn;
|
||||
unsigned short used_channels;
|
||||
MIDI_EVENT *eventlist, *curevent, *prev;
|
||||
char buf[32];
|
||||
MusicMode mode = Music_GeneralMidi;
|
||||
|
||||
INFO("Reading XMI %s", filename);
|
||||
|
||||
extern FILE *fopen_caseless(const char *path, const char *mode); // see caseless.c
|
||||
f = fopen_caseless(filename, "rb");
|
||||
if (f == 0) {
|
||||
ERROR("Could not read XMI");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
data = (unsigned char *)malloc(size);
|
||||
if (fread(data, size, 1, f) != 1) {
|
||||
free(data);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
p = data;
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
if (memcmp(buf, "FORM", 4)) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // is not an xmi
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
|
||||
start = p - data;
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
if (!memcmp(buf, "XMID", 4))
|
||||
NumTracks = 1; // XMI doesn't have XDIR, so there's only one track
|
||||
else if (memcmp(buf, "XDIR", 4)) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // invalid XMI format
|
||||
else {
|
||||
NumTracks = 0;
|
||||
|
||||
for (i = 4; i < len; i++) {
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
chunk_len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
|
||||
i += 8;
|
||||
|
||||
if (memcmp(buf, "INFO", 4)) {
|
||||
p += ((chunk_len + 1) & ~1);
|
||||
i += ((chunk_len + 1) & ~1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk_len < 2)
|
||||
break;
|
||||
|
||||
b0 = *p++;
|
||||
b1 = *p++;
|
||||
NumTracks = b0 | (b1 << 8);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (NumTracks == 0) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // xmi must have at least one track
|
||||
|
||||
p = data + start + ((len + 1) & ~1);
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
if (memcmp(buf, "CAT ", 4)) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // invalid XMI format
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
if (memcmp(buf, "XMID", 4)) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // invalid XMI format
|
||||
}
|
||||
|
||||
INFO("NumTracks: %i", NumTracks);
|
||||
|
||||
TrackEvents = (MIDI_EVENT **)malloc(NumTracks * sizeof(MIDI_EVENT *));
|
||||
TrackTiming = (short *)malloc(NumTracks * sizeof(short));
|
||||
TrackUsedChannels = (unsigned short *)malloc(NumTracks * sizeof(unsigned short));
|
||||
|
||||
for (i = 0; i < NumTracks; i++)
|
||||
TrackEvents[i] = 0;
|
||||
|
||||
count = 0;
|
||||
|
||||
while (p - data < size && count != NumTracks) {
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
|
||||
if (!memcmp(buf, "FORM", 4)) {
|
||||
p += 4;
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
}
|
||||
|
||||
if (memcmp(buf, "EVNT", 4)) {
|
||||
p += ((len + 1) & ~1);
|
||||
continue;
|
||||
}
|
||||
|
||||
eventlist = 0;
|
||||
curevent = 0;
|
||||
time = 0;
|
||||
end = 0;
|
||||
tempo = 500000;
|
||||
tempo_set = 0;
|
||||
status = 0;
|
||||
used_channels = 0;
|
||||
|
||||
begin = p - data;
|
||||
|
||||
while (!end && p - data < size) {
|
||||
quant = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
b0 = *p++;
|
||||
if (b0 & 0x80) {
|
||||
p--;
|
||||
break;
|
||||
}
|
||||
quant += b0;
|
||||
}
|
||||
time += quant * 3;
|
||||
|
||||
status = *p++;
|
||||
switch (status >> 4) {
|
||||
case 0x9: // note on
|
||||
used_channels |= (1 << (status & 15));
|
||||
b0 = *p++;
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time);
|
||||
curevent->status = status;
|
||||
curevent->data[0] = b0;
|
||||
curevent->data[1] = *p++;
|
||||
delta = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
b1 = *p++;
|
||||
delta <<= 7;
|
||||
delta |= b1 & 0x7F;
|
||||
if (!(b1 & 0x80))
|
||||
break;
|
||||
}
|
||||
prev = curevent;
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time + delta * 3);
|
||||
curevent->status = status;
|
||||
curevent->data[0] = b0;
|
||||
curevent->data[1] = 0;
|
||||
curevent = prev;
|
||||
break;
|
||||
|
||||
case 0x8:
|
||||
case 0xA:
|
||||
case 0xB:
|
||||
case 0xE: // note off, aftertouch, controller, pitch wheel
|
||||
used_channels |= (1 << (status & 15));
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time);
|
||||
curevent->status = status;
|
||||
curevent->data[0] = *p++;
|
||||
curevent->data[1] = *p++;
|
||||
break;
|
||||
|
||||
case 0xC:
|
||||
case 0xD: // program change, pressure
|
||||
used_channels |= (1 << (status & 15));
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time);
|
||||
curevent->status = status;
|
||||
curevent->data[0] = *p++;
|
||||
break;
|
||||
|
||||
case 0xF: // sysex
|
||||
if (status == 0xFF) {
|
||||
pos = p - data;
|
||||
b0 = *p++;
|
||||
if (b0 == 0x2F)
|
||||
end = 1;
|
||||
else if (b0 == 0x51 && !tempo_set) {
|
||||
p++;
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
tempo = (b1 | (b2 << 8) | (b3 << 16)) * 3;
|
||||
tempo_set = 1;
|
||||
} else if (b0 == 0x51 && tempo_set) {
|
||||
quant = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
b1 = *p++;
|
||||
quant <<= 7;
|
||||
quant |= b1 & 0x7F;
|
||||
if (!(b1 & 0x80))
|
||||
break;
|
||||
}
|
||||
p += quant;
|
||||
break;
|
||||
}
|
||||
p = data + pos;
|
||||
}
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time);
|
||||
curevent->status = status;
|
||||
if (status == 0xFF)
|
||||
curevent->data[0] = *p++;
|
||||
quant = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
b0 = *p++;
|
||||
quant <<= 7;
|
||||
quant |= b0 & 0x7F;
|
||||
if (!(b0 & 0x80))
|
||||
break;
|
||||
}
|
||||
curevent->len = quant;
|
||||
if (curevent->len) {
|
||||
curevent->buffer = (unsigned char *)malloc(curevent->len);
|
||||
memcpy(curevent->buffer, p, curevent->len);
|
||||
p += curevent->len;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ppqn = (tempo * 3) / 25000;
|
||||
if (!ppqn)
|
||||
break; // unable to convert data
|
||||
|
||||
TrackEvents[count] = eventlist;
|
||||
TrackTiming[count] = ppqn;
|
||||
TrackUsedChannels[count] = used_channels;
|
||||
count++;
|
||||
|
||||
p = data + begin + ((len + 1) & ~1);
|
||||
}
|
||||
|
||||
if (count != NumTracks) // failed to extract all tracks from XMI
|
||||
{
|
||||
free(data);
|
||||
NumTracks = count;
|
||||
FreeXMI();
|
||||
return 0;
|
||||
}
|
||||
|
||||
free(data);
|
||||
|
||||
// Setup a sound bank for res/sound/sblaster, or res/sound/genmidi
|
||||
if (strstr(filename, "sblaster") != NULL)
|
||||
mode = Music_SoundBlaster;
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
MusicDev->setupMode(MusicDev, mode);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
switch (mode) {
|
||||
case Music_GeneralMidi:
|
||||
INFO("Set General MIDI mode");
|
||||
break;
|
||||
case Music_SoundBlaster:
|
||||
INFO("Set Sound Blaster mode");
|
||||
break;
|
||||
}
|
||||
|
||||
return 1; // success
|
||||
}
|
||||
|
||||
int MyThread(void *arg) {
|
||||
int i;
|
||||
|
||||
MIDI_EVENT *event[NUM_THREADS];
|
||||
int ppqn[NUM_THREADS];
|
||||
double Ippqn[NUM_THREADS];
|
||||
int tempo[NUM_THREADS];
|
||||
double tick[NUM_THREADS];
|
||||
double last_tick[NUM_THREADS];
|
||||
double last_time[NUM_THREADS];
|
||||
unsigned int start[NUM_THREADS];
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
event[i] = 0;
|
||||
ppqn[i] = 1;
|
||||
Ippqn[i] = 1;
|
||||
tempo[i] = 0x07A120;
|
||||
tick[i] = 1;
|
||||
last_tick[i] = 0;
|
||||
last_time[i] = 0;
|
||||
start[i] = 0;
|
||||
|
||||
SDL_AtomicSet(&ThreadPlaying[i], 0);
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_READY);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
int delay = 1;
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
if (event[i] && SDL_AtomicGet(&ThreadCommand[i]) != THREAD_STOPTRACK) {
|
||||
double aim = last_time[i] + (event[i]->time - last_tick[i]) * tick[i];
|
||||
double diff = aim - ((SDL_GetTicks() - start[i]) * 1000.0);
|
||||
|
||||
if (diff > 0) {
|
||||
if (diff < 1200.0)
|
||||
delay = 0;
|
||||
continue;
|
||||
}
|
||||
delay = 0;
|
||||
|
||||
last_tick[i] = event[i]->time;
|
||||
last_time[i] = aim;
|
||||
|
||||
if (event[i]->status == 0xFF && event[i]->data[0] == 0x51) // tempo change
|
||||
{
|
||||
tempo[i] = (event[i]->buffer[0] << 16) | (event[i]->buffer[1] << 8) | event[i]->buffer[2];
|
||||
tick[i] = tempo[i] * Ippqn[i];
|
||||
} else if (event[i]->status >= 0x80 && event[i]->status < 0xF0) {
|
||||
int channel = (event[i]->status & 15);
|
||||
|
||||
if (channel != 9)
|
||||
channel = ThreadChannelRemap[channel + 16 * i]; // remap channel, except 9 (percussion)
|
||||
|
||||
uint8_t p1 = event[i]->data[0];
|
||||
uint8_t p2 = event[i]->data[1];
|
||||
|
||||
if ((event[i]->status & ~15) == 0xB0 && event[i]->data[0] == 0x07) {
|
||||
// set volume msb
|
||||
// store in array in case global volume changes later
|
||||
SDL_AtomicSet(&DeviceChannelVolume[channel], p2); // 0-127
|
||||
// send volume change to device
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev && MusicDev->isOpen) {
|
||||
// scale new volume according to global music volume
|
||||
extern uchar curr_vol_lev; // 0-100
|
||||
const int scaledVolume = ((int)p2 * (int)curr_vol_lev) / 100;
|
||||
MusicDev->sendControllerChange(MusicDev, channel, p1, scaledVolume);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
} else {
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev && MusicDev->isOpen) {
|
||||
switch (event[i]->status & ~15) {
|
||||
case 0x80:
|
||||
MusicDev->sendNoteOff(MusicDev, channel, p1, p2);
|
||||
break;
|
||||
case 0x90:
|
||||
MusicDev->sendNoteOn(MusicDev, channel, p1, p2);
|
||||
break;
|
||||
case 0xA0:
|
||||
MusicDev->sendNoteAfterTouch(MusicDev, channel, p1, p2);
|
||||
break;
|
||||
case 0xB0:
|
||||
MusicDev->sendControllerChange(MusicDev, channel, p1, p2);
|
||||
break;
|
||||
case 0xC0:
|
||||
MusicDev->sendProgramChange(MusicDev, channel, p1);
|
||||
break;
|
||||
case 0xD0:
|
||||
MusicDev->sendChannelAfterTouch(MusicDev, channel, p1);
|
||||
break;
|
||||
case 0xE0:
|
||||
MusicDev->sendPitchBendML(MusicDev, channel, p2, p1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
}
|
||||
|
||||
event[i] = event[i]->next;
|
||||
if (event[i] == 0)
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_STOPTRACK);
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&ThreadCommand[i]) == THREAD_STOPTRACK) {
|
||||
int channel;
|
||||
|
||||
delay = 0;
|
||||
|
||||
event[i] = 0;
|
||||
last_tick[i] = 0;
|
||||
last_time[i] = 0;
|
||||
start[i] = SDL_GetTicks();
|
||||
|
||||
// here we should turn off all notes for these channels plus 9 (percussion)
|
||||
for (channel = 0; channel < 16; channel++)
|
||||
if (ChannelThread[channel] == i) {
|
||||
ChannelThread[channel] = -1;
|
||||
NumUsedChannels--;
|
||||
SDL_AtomicSet(&DeviceChannelVolume[channel], 0);
|
||||
}
|
||||
|
||||
SDL_AtomicSet(&ThreadPlaying[i], 0);
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_READY);
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&ThreadCommand[i]) == THREAD_PLAYTRACK) {
|
||||
delay = 0;
|
||||
|
||||
event[i] = ThreadEventList[i];
|
||||
ppqn[i] = ThreadTiming[i];
|
||||
Ippqn[i] = 1.0 / ppqn[i];
|
||||
tempo[i] = 0x07A120;
|
||||
tick[i] = tempo[i] * Ippqn[i];
|
||||
last_tick[i] = 0;
|
||||
last_time[i] = 0;
|
||||
start[i] = SDL_GetTicks();
|
||||
|
||||
SDL_AtomicSet(&ThreadPlaying[i], 1);
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_READY);
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&ThreadCommand[i]) == THREAD_EXIT)
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_Delay(delay);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GetTrackNumChannels(unsigned int track) {
|
||||
int num = 0, channel;
|
||||
|
||||
// count channels used by track (could be zero if only percussion channel (9) is used)
|
||||
for (channel = 0; channel < 16; channel++)
|
||||
if (channel != 9 && (TrackUsedChannels[track] & (1 << channel)))
|
||||
num++;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
void StartTrack(int thread, unsigned int track) {
|
||||
int num, trackChannel, deviceChannel;
|
||||
char channel_remap[16];
|
||||
|
||||
if (track >= NumTracks)
|
||||
return;
|
||||
|
||||
num = GetTrackNumChannels(track);
|
||||
|
||||
while (SDL_AtomicGet(&ThreadCommand[thread]) != THREAD_READY)
|
||||
SDL_Delay(1);
|
||||
|
||||
// check if enough device channels free; 16 channels available except one (percussion)
|
||||
if (NumUsedChannels + num <= 16 - 1) {
|
||||
NumUsedChannels += num;
|
||||
|
||||
memset(channel_remap, 0, 16);
|
||||
|
||||
// assign channels used by track to device channels that are currently free
|
||||
for (trackChannel = 0; trackChannel < 16; trackChannel++) {
|
||||
// only map used, non-percussion channels
|
||||
if (trackChannel != 9 && (TrackUsedChannels[track] & (1 << trackChannel))) {
|
||||
// find first unassigned device channel
|
||||
for (deviceChannel = 0; deviceChannel < 16; deviceChannel++) {
|
||||
if (deviceChannel != 9 && ChannelThread[deviceChannel] == -1)
|
||||
break;
|
||||
}
|
||||
channel_remap[trackChannel] = deviceChannel;
|
||||
ChannelThread[deviceChannel] = thread;
|
||||
// default to full volume
|
||||
SDL_AtomicSet(&DeviceChannelVolume[deviceChannel], 127);
|
||||
}
|
||||
}
|
||||
|
||||
ThreadEventList[thread] = TrackEvents[track];
|
||||
ThreadTiming[thread] = TrackTiming[track];
|
||||
memcpy(ThreadChannelRemap + 16 * thread, channel_remap, 16);
|
||||
|
||||
SDL_AtomicSet(&ThreadCommand[thread], THREAD_PLAYTRACK);
|
||||
|
||||
while (SDL_AtomicGet(&ThreadCommand[thread]) != THREAD_READY)
|
||||
SDL_Delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
void StopTrack(int i) {
|
||||
if (!SDL_AtomicGet(&ThreadPlaying[i]))
|
||||
return;
|
||||
|
||||
while (SDL_AtomicGet(&ThreadCommand[i]) != THREAD_READY)
|
||||
SDL_Delay(1);
|
||||
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_STOPTRACK);
|
||||
|
||||
while (SDL_AtomicGet(&ThreadCommand[i]) != THREAD_READY)
|
||||
SDL_Delay(1);
|
||||
}
|
||||
|
||||
void StopTheMusic(void) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++)
|
||||
StopTrack(i);
|
||||
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
MusicDev->reset(MusicDev);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
|
||||
int IsPlaying(int i) { return SDL_AtomicGet(&ThreadPlaying[i]); }
|
||||
|
||||
void InitReadXMI(void) {
|
||||
int channel, i;
|
||||
SDL_Thread *thread;
|
||||
|
||||
InitDecXMI();
|
||||
|
||||
MyMutex = SDL_CreateMutex();
|
||||
|
||||
for (channel = 0; channel < 16; channel++) {
|
||||
ChannelThread[channel] = -1;
|
||||
SDL_AtomicSet(&DeviceChannelVolume[channel], 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
SDL_AtomicSet(&ThreadPlaying[i], 0);
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_INIT);
|
||||
}
|
||||
|
||||
thread = SDL_CreateThread(MyThread, "MyThread", NULL);
|
||||
SDL_DetachThread(thread); // thread will go away on its own upon completion
|
||||
|
||||
i = 0;
|
||||
while (SDL_AtomicGet(&ThreadCommand[i]) == THREAD_INIT)
|
||||
SDL_Delay(1);
|
||||
|
||||
atexit(ShutdownReadXMI);
|
||||
}
|
||||
|
||||
void InitDecXMI(void) {
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
WARN("InitDecXMI(): *****WARNING***** Creating new music device, but one already exists!");
|
||||
}
|
||||
|
||||
// Start the Midi device
|
||||
MusicDevice *musicdev = NULL;
|
||||
int musicrate = 48000;
|
||||
|
||||
switch (gShockPrefs.soMidiBackend) {
|
||||
case OPT_SEQ_ADLMIDI: // adlmidi
|
||||
{
|
||||
INFO("Creating ADLMIDI device");
|
||||
musicdev = CreateMusicDevice(Music_AdlMidi);
|
||||
} break;
|
||||
case OPT_SEQ_NativeMI: // native midi
|
||||
{
|
||||
INFO("Creating native MIDI device");
|
||||
musicdev = CreateMusicDevice(Music_Native);
|
||||
} break;
|
||||
#ifdef USE_FLUIDSYNTH
|
||||
case OPT_SEQ_FluidSyn: // fluidsynth
|
||||
{
|
||||
INFO("Creating FluidSynth MIDI device");
|
||||
musicdev = CreateMusicDevice(Music_FluidSynth);
|
||||
} break;
|
||||
#endif
|
||||
}
|
||||
|
||||
// init chosen music device
|
||||
INFO("Opening MIDI device using output %d", gShockPrefs.soMidiOutput);
|
||||
if (musicdev && musicdev->init(musicdev, gShockPrefs.soMidiOutput, musicrate) != 0) {
|
||||
musicdev->destroy(musicdev);
|
||||
musicdev = NULL;
|
||||
}
|
||||
|
||||
// fallback to dummy
|
||||
if (!musicdev) {
|
||||
WARN("Using dummy MIDI driver");
|
||||
musicdev = CreateMusicDevice(Music_None);
|
||||
if (musicdev) {
|
||||
musicdev->init(musicdev, gShockPrefs.soMidiOutput, musicrate);
|
||||
}
|
||||
}
|
||||
|
||||
// force prefs to align with music device output
|
||||
if (musicdev) {
|
||||
gShockPrefs.soMidiOutput = musicdev->outputIndex;
|
||||
}
|
||||
|
||||
MusicDev = musicdev;
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
|
||||
void ReloadDecXMI(void) {
|
||||
int i;
|
||||
|
||||
// determine whether a device type change is being requested, by comparing the
|
||||
// current device type (if any) with current preferences setting
|
||||
short deviceTypeMatch = 0;
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
switch (MusicDev->deviceType) {
|
||||
case Music_None:
|
||||
deviceTypeMatch = 0;
|
||||
break;
|
||||
case Music_AdlMidi:
|
||||
deviceTypeMatch = (gShockPrefs.soMidiBackend == 0);
|
||||
break;
|
||||
case Music_Native:
|
||||
deviceTypeMatch = (gShockPrefs.soMidiBackend == 1);
|
||||
break;
|
||||
#ifdef USE_FLUIDSYNTH
|
||||
case Music_FluidSynth:
|
||||
deviceTypeMatch = (gShockPrefs.soMidiBackend == 2);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// only destroy the device if it exists and any of the following apply:
|
||||
// - it hasn't been opened yet
|
||||
// - device type change requested
|
||||
// - device output change requested
|
||||
// this is needed to protect against reload spam generated by the UI slider
|
||||
if (MusicDev && (!MusicDev->isOpen || !deviceTypeMatch || MusicDev->outputIndex != gShockPrefs.soMidiOutput)) {
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
INFO("Closing MIDI driver due to reload");
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
StopTrack(i);
|
||||
}
|
||||
|
||||
SDL_LockMutex(MyMutex);
|
||||
MusicDev->destroy(MusicDev);
|
||||
MusicDev = NULL;
|
||||
}
|
||||
|
||||
// only init music device if it doesn't still exist
|
||||
if (!MusicDev) {
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
InitDecXMI();
|
||||
} else {
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownReadXMI(void) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
StopTrack(i);
|
||||
// don't set THREAD_EXIT yet, as this seems to cause deadlocks
|
||||
}
|
||||
SDL_Delay(50);
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_EXIT);
|
||||
}
|
||||
SDL_Delay(50); // wait a bit for thread to hopefully exit
|
||||
|
||||
INFO("Closing MIDI driver due to shutdown");
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
MusicDev->destroy(MusicDev);
|
||||
MusicDev = NULL;
|
||||
} else {
|
||||
WARN("ShutdownReadXMI(): Shutdown request received, but no music device exists!");
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
|
||||
FreeXMI();
|
||||
|
||||
SDL_DestroyMutex(MyMutex);
|
||||
}
|
||||
|
||||
unsigned int GetOutputCountXMI(void) {
|
||||
unsigned int outputCount = 0;
|
||||
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
outputCount = MusicDev->getOutputCount(MusicDev);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
|
||||
return outputCount;
|
||||
}
|
||||
|
||||
void GetOutputNameXMI(const unsigned int outputIndex, char *buffer, const unsigned int bufferSize) {
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev && buffer && bufferSize >= 1) {
|
||||
MusicDev->getOutputName(MusicDev, outputIndex, buffer, bufferSize);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
|
||||
void UpdateVolumeXMI(void) {
|
||||
// global volume has been changed
|
||||
extern uchar curr_vol_lev; // 0-100
|
||||
INFO("UpdateVolumeXMI(): Global music volume change to %d percent", curr_vol_lev);
|
||||
|
||||
// tell the music driver
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev && MusicDev->isOpen) {
|
||||
// send volume change controller (#7) for all channels
|
||||
for (int i = 0; i <= 15; ++i) {
|
||||
// skip unused device channels
|
||||
if (i != 9 && ChannelThread[i] == -1)
|
||||
continue;
|
||||
// scale new volume according to global music volume
|
||||
const int scaledVolume = ((int)SDL_AtomicGet(&DeviceChannelVolume[i]) * (int)curr_vol_lev) / 100;
|
||||
MusicDev->sendControllerChange(MusicDev, i, 7, scaledVolume);
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#define NUM_THREADS 8
|
||||
|
||||
#define THREAD_INIT 0
|
||||
#define THREAD_READY 1
|
||||
#define THREAD_PLAYTRACK 2
|
||||
#define THREAD_STOPTRACK 3
|
||||
#define THREAD_EXIT 4
|
||||
|
||||
extern unsigned int NumTracks;
|
||||
|
||||
//-1: no thread is using this device channel; 0- : thread index that is using this device channel
|
||||
extern char ChannelThread[16]; // 16 device channels
|
||||
|
||||
extern int NumUsedChannels; // number of in-use device channels
|
||||
|
||||
void FreeXMI(void);
|
||||
int ReadXMI(const char *filename);
|
||||
void StartTrack(int i, unsigned int track);
|
||||
void StopTrack(int i);
|
||||
void StopTheMusic(void);
|
||||
int IsPlaying(int i);
|
||||
void InitReadXMI(void);
|
||||
void InitDecXMI(void);
|
||||
void ReloadDecXMI(void);
|
||||
void ShutdownReadXMI(void);
|
||||
unsigned int GetOutputCountXMI(void);
|
||||
void GetOutputNameXMI(const unsigned int outputIndex, char *buffer, const unsigned int bufferSize);
|
||||
void UpdateVolumeXMI(void);
|
||||
|
||||
struct midi_event_struct {
|
||||
int time;
|
||||
unsigned char status;
|
||||
unsigned char data[2];
|
||||
unsigned int len;
|
||||
unsigned char *buffer;
|
||||
struct midi_event_struct *next;
|
||||
};
|
||||
|
||||
typedef struct midi_event_struct MIDI_EVENT;
|
||||
|
||||
extern MIDI_EVENT **TrackEvents;
|
||||
extern short *TrackTiming;
|
||||
extern unsigned short *TrackUsedChannels;
|
||||
|
||||
extern MIDI_EVENT *ThreadEventList[NUM_THREADS];
|
||||
extern int ThreadTiming[NUM_THREADS];
|
||||
extern char ThreadChannelRemap[16 * NUM_THREADS];
|
||||
extern SDL_atomic_t DeviceChannelVolume[16]; // only msb: 0-127
|
||||
extern SDL_atomic_t ThreadPlaying[NUM_THREADS];
|
||||
extern SDL_atomic_t ThreadCommand[NUM_THREADS];
|
||||
|
||||
struct thread_data {
|
||||
int i; // thread index
|
||||
};
|
||||
Reference in New Issue
Block a user