diff --git a/CMakeLists.txt b/CMakeLists.txt index 993e42c1..d72cb933 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ if(NOT DEFINED CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo" FORCE) endif() -project(VanillaConquer CXX) +project(VanillaConquer CXX ASM) option(BUILD_REMASTERTD "Build Tiberian Dawn remaster dll." OFF) option(BUILD_REMASTERRA "Build Red Alert remaster dll." OFF) @@ -34,13 +34,19 @@ if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Windows") add_feature_info(DirectDraw DDRAW "DirectDraw video backend (deprecated)") add_feature_info(SDL2 SDL2 "SDL2 video backend") add_feature_info(OpenAL OPENAL "OpenAL audio backend") +elseif(NDS) + set(SDL2 FALSE) + set(OPENAL FALSE) + set(DSOUND FALSE) + set(DDRAW FALSE) + set(NETWORKING FALSE) else() set(SDL2 TRUE) set(OPENAL TRUE) endif() if(APPLE OR ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - option(MAKE_BUNDLE "Create a standard app bundle rather than command line program." ON) + option(MAKE_BUNDLE "Create a standard app bundle rather than command line program." ON) add_feature_info(MakeBundle MAKE_BUNDLE "App bundles will be generated") if(MAKE_BUNDLE) set(MAKE_BUNDLE_OPTION MACOSX_BUNDLE) @@ -74,6 +80,10 @@ if(NOT MSVC) set(CMAKE_CXX_FLAGS_DEBUG "-gstabs3") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive") set(STATIC_LIBS "-static-libstdc++ -static-libgcc") + elseif(NDS) + # We need to enable optimizations, else the code doesn't fit in + # correct address region. + set(CMAKE_CXX_FLAGS_DEBUG "-O2 -g3") else() set(CMAKE_CXX_FLAGS_DEBUG "-g3") set(CMAKE_FIND_FRAMEWORK "LAST") @@ -134,6 +144,25 @@ if(OPENAL) set(DSOUND OFF) endif() +if(NDS) + # This directory contains code that will run on the ARM7 chip. So we don't + # define the arch variables until this directory has already been compiled. + add_subdirectory(arm7) + + # The code below will run on ARM9 chip. + add_definitions(-DARM9) + + set(ARCH "-mthumb -mthumb-interwork -mcpu=arm946e-s -mtune=arm946e-s") + set(CMAKE_C_FLAGS "${ARCH} ${CMAKE_C_CFLAGS} -fomit-frame-pointer -fno-rtti -fno-exceptions -ffast-math -fno-unwind-tables") + set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -specs=ds_arm9.specs -mthumb -mthumb-interwork -Wl,-Map,vanilla.map") + + # Link with those libraries. + link_libraries("-lc") # C library + link_libraries("-lfat") + link_libraries("-lnds9") +endif() + if(BUILD_TESTS) # Tests need to respect some options so need to occur after the options are set. enable_testing() diff --git a/arm7/CMakeLists.txt b/arm7/CMakeLists.txt new file mode 100644 index 00000000..0d01490b --- /dev/null +++ b/arm7/CMakeLists.txt @@ -0,0 +1,20 @@ +add_definitions(-DARM7 -D_NDS) + +set(ARCH "-mthumb -mthumb-interwork -mcpu=arm7tdmi -mtune=arm7tdmi") +set(CMAKE_C_FLAGS "${ARCH} -g -fomit-frame-pointer -fno-rtti -fno-exceptions -ffast-math -fstack-protector-all") +set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS}") +SET(CMAKE_CXX_FLAGS_DEBUG "-Os -g") +set(CMAKE_CXX_FLAGS_RELEASE "-Os") +set(CMAKE_EXE_LINKER_FLAGS "-specs=ds_arm7.specs -mthumb -mthumb-interwork -Wl,-Map,vanilla.map") + +set(ARM7_SRC + main.cpp + audio.cpp + printf.cpp +) + +# Link with those libraries +link_libraries("-lc") # C library +link_libraries("-lnds7d") + +add_executable(arm7.elf ${ARM7_SRC}) diff --git a/arm7/audio.cpp b/arm7/audio.cpp new file mode 100644 index 00000000..ceb89444 --- /dev/null +++ b/arm7/audio.cpp @@ -0,0 +1,938 @@ +/** Sound engine for Vanilla-Conquer to the Nintendo DSi ARM7 chip. + * + * Author: mrparrot (aka giulianob) + * + * This engine is asynchronous and it was designed to not lag behind the main + * console CPU. Every sound or music is sent either using a message or a + * command, which is then processed from the ARM7 side. No decompression is + * done here, so its overhead is minimal. + **/ + +#include +#include +#include +#include +#include +#include +#include + +#include "../common/memflag.h" +#include "../common/audio.h" +#include "../common/audio_fifocommon.h" + +#include "printf.h" + +#define ARM7 + +// Not good practice, but who cares. +#include "../common/soscodec.cpp" +#include "../common/auduncmp.cpp" + +#define IS_CHANNEL_FREE(i) (!(SCHANNEL_CR(i) & SCHANNEL_ENABLE)) +#define VQA_CHANNEL 7 + +// Calculate the ceil of division x / y. +#define CEIL_DIV(x, y) (1 + (((x) - 1) / (y))) + +// Provide an implementation of timerElapsed. Stolen from libnds. + +//--------------------------------------------------------------------------------- +static u16 timerElapsedARM7(void) +{ + //--------------------------------------------------------------------------------- + static u16 elapsed = 0; + u16 time = TIMER_DATA(1); + + s32 result = (s32)time - (s32)elapsed; + + //overflow...this will only be accurate if it has overflowed no more than once. + if (result < 0) { + result = time + (0x10000 - elapsed); + } + + elapsed = time; + + return (u16)result; +} + +// Nintendo DS supported sounds format. +typedef enum +{ + SoundFormat_16Bit = 1, /*!< 16-bit PCM */ + SoundFormat_8Bit = 0, /*!< 8-bit PCM */ + SoundFormat_PSG = 3, /*!< PSG (programmable sound generator?) */ + SoundFormat_ADPCM = 2 /*!< IMA ADPCM compressed audio */ +} SoundFormat; + +// Global values used in the engine. +enum +{ + AUD_CHUNK_MAGIC_ID = 0x0000DEAF, + VOLUME_MIN = 0, + VOLUME_MAX = 255, + PRIORITY_MIN = 0, + PRIORITY_MAX = 255, + MAX_SAMPLE_TRACKERS = 5, + DECOMP_BUFFER_COUNT = 2, + BUFFER_CHUNK_SIZE = 4096, + UNCOMP_BUFFER_SIZE = 2098, + BUFFER_TOTAL_BYTES = BUFFER_CHUNK_SIZE * DECOMP_BUFFER_COUNT, + INVALID_AUDIO_HANDLE = -1, + INVALID_FILE_HANDLE = -1, +}; + +/* +** Define the different type of sound compression avaliable to the westwood +** library. +*/ +typedef enum +{ + SCOMP_NONE = 0, // No compression -- raw data. + SCOMP_WESTWOOD = 1, // Special sliding window delta compression. + SCOMP_SOS = 99 // SOS frame compression. +} SCompressType; + +class SoundTracker; +static inline int __attribute__((pure)) Get_Channel_Index(SoundTracker*); + +// Functions used in decompressing westwood audio. +int Simple_Copy(void**, int*, void**, int*, void*); +int Sample_Copy(SoundTracker*, void**, int*, void**, int*, void*, int, SCompressType, void*, int16_t*); + +/* Speed in which the timer used to track where the playing cursor is. + Unfortunately the DS doesn't give an interface to query in which + +*/ +#define TIMER_SPEED (BUS_CLOCK / 256) + +/* Tracker class. Represent a track in game. This works as follows: + + When the game wants to play a sound, it queries a a free tracker for it to + play (see Get_Free_Sound_Tracker). If it can't find a free tracker, then it + stops the tracker which has the lowest priority. + + Then it set the sound to be played. However, the sound is compressed thus we + need to decompress it as we play, and we only have a very limited memory for + that. So we use a BUFFER_TOTAL_BYTES buffer divided into 2 BUFFER_CHUNK_SIZE, + in order to double buffer, and we keep track how much of the original + compressed audio we could decompress. We then send the small BUFFER_TOTAL_BYTES + into the hardware and set it to loop around it, so we have to keep the buffer + updated in order for it to not get stuck as a scratched recording. + + Later on, the CPU picks up the tracker for Update, and it decompresses the + part of the buffer which is not currently playing. If it detects that + there is no more to decompress, it set the tracker as a ONE_SHOT to avoid it + to wrap around again when finished. + + To detect when to update the buffer, we use a timer and the Active boolean. + The timer is set to be preicise and we use it to keep track the position + in where the hardware is when playing the small BUFFER_TOTAL_BYTES buffer, + as there is no way to directly query that. The Active flag is used to signalize + that the tracker is active. +*/ +class SoundTracker +{ +public: + // Initialize Tracker. + SoundTracker() + { + memset(this, 0, sizeof(*this)); + } + + // Get the tracker's number. Used to access the sound hardware. + inline int Get_Channel_Index(); + + static SoundFormat DS_Sound_Format(SCompressType type, unsigned char bits) + { + if (type == SCOMP_SOS) { + return SoundFormat_ADPCM; + } else if (bits == 16) { + return SoundFormat_16Bit; + } + return SoundFormat_8Bit; + } + + // How many ticks has elapsed since StartTime? + unsigned long long Get_Elapsed_Ticks() + { + SoundTracker::Now += timerElapsedARM7(); + return SoundTracker::Now - StartedTime; + } + + // Set the tracker StartTime. + void Set_Tracker_Timer(unsigned bias = 0) + { + SoundTracker::Now += timerElapsedARM7(); + StartedTime = Now - bias; + } + + inline void* Get_Sample() + { + return Sample; + } + + inline bool Is_Sample_Playing() + { + return Active; + } + + // Stop this tracker from playing a sample. + void Stop_Sample() + { + int channel = Get_Channel_Index(); + SCHANNEL_CR(channel) = 0; + Active = false; + MoreSource = false; + OneShot = false; + QueueBuffer = NULL; + QueueSize = false; + Remainder = 0; + Decomp_Buffer_Index = 0; + Size = 0; + Sample = NULL; + OriginalSample = NULL; + SampleSize = 0; + IsMusic = false; + MusicStreamIndex = 0; + StartedTime = 0; + } + + inline unsigned char Get_Priority() + { + return Priority; + } + + inline unsigned Get_Handle() + { + return SoundHandle; + } + + // Play watever is in this track. + int Play(); + + // Play a sample. + int Play_Sample(const void* sample, + unsigned char priority, + unsigned char volume, + unsigned char panloc, + u16 handle, + bool is_music) + { + // Set attributes given by call. + Priority = priority; + Volume = volume; + Panloc = panloc; + SoundHandle = handle; + IsMusic = is_music; + + // Load the AUD header; + AUDHeaderType raw_header; + memcpy(&raw_header, sample, sizeof(raw_header)); + + // We don't support anything lower than 20000 hz. + if (raw_header.Rate < 24000 && raw_header.Rate > 20000) { + raw_header.Rate = 22050; + } + + // Get number of bits in sample. + Bits = (raw_header.Flags & 2) ? 16 : 8; + + // Update Frequency according to header/ + Frequency = raw_header.Rate; + + // Check the Compression. + Compression = SCompressType(raw_header.Compression); + OriginalSample = (void*)sample; + Sample = Add_Long_To_Pointer(sample, sizeof(AUDHeaderType)); + + if (IsMusic) { + QueueBuffer = Add_Long_To_Pointer(sample, MUSIC_CHUNK_SIZE); + QueueSize = MUSIC_CHUNK_SIZE; + Remainder = MUSIC_CHUNK_SIZE - sizeof(AUDHeaderType); + } else { + Remainder = raw_header.Size; + } + + // Compression is ADPCM so we need to init it's stream info. + if (Compression == SCOMP_SOS) { + sosinfo.wChannels = (raw_header.Flags & 1) + 1; + sosinfo.wBitSize = raw_header.Flags & 2 ? 16 : 8; + sosinfo.dwCompSize = raw_header.Size; + sosinfo.dwUnCompSize = raw_header.Size * (sosinfo.wBitSize / 4); + sosCODECInitStream(&sosinfo); + } else if (Compression == SCOMP_WESTWOOD) { + Volume = Volume / 4; // SCOMP_WESTWOOD seems incorrectly pitched, + // decrease its volume to avoid ear rape. + } + + // Decompress two chunks of data from the compressed audio. + // This function Sample_Copy code is completely awful and even I don't + // understand it fully. Please do not ask me how it works. + int bytes_read = Sample_Copy(this, + &Sample, + &Remainder, + &QueueBuffer, + &QueueSize, + Decomp_Buff[0], + BUFFER_TOTAL_BYTES, + Compression, + nullptr, + nullptr); + + Size = bytes_read; // Keep track of size + if (bytes_read == BUFFER_TOTAL_BYTES) { + // We need to flag that we have more data to decompress. + MoreSource = true; + OneShot = false; + } else { + // We already decompressed everything. + MoreSource = false; + OneShot = true; + Remainder = 0; + QueueBuffer = 0; + } + + Decomp_Buffer_Index = 0; + + // Set the tacker to be played. + return Play(); + } + + // Hash the sample pointer to 16-bit pointers. + inline u16 Get_Sample_16() + { + return (u16)((u32)OriginalSample & 0xFFFF); + } + + // Request music data to the ARM9 chip. + void Request_Music_Data() + { + fifoSendValue32(FIFO_USER_02, USR2::MUSIC_REQUEST_CHUNK); + QueueBuffer = (char*)OriginalSample + MusicStreamIndex * MUSIC_CHUNK_SIZE; + MusicStreamIndex = (MusicStreamIndex + 1) % 2; + QueueSize = MUSIC_CHUNK_SIZE; + + // Do not wait for confirmation. + // while(!fifoCheckValue32(FIFO_USER_01)); + } + + // Update the tracker. This is the core routine of this sound engine. + inline void Update() + { + // If tracker is not playing, return and go to next. + if (!Active) + return; + + // Compute the time (in ticks) of this chunk. + unsigned length_in_ticks = CEIL_DIV(Size * TIMER_SPEED, 2 * Frequency); + + // If it is not time to update the timer then goto next. + if (Get_Elapsed_Ticks() < length_in_ticks) + return; + + // It is time to update the tracker. Set the timer to where should be + // the position when the chunk started playing. + Set_Tracker_Timer(Get_Elapsed_Ticks() - length_in_ticks); + + // If it is OneShot, set it to false to signal it was processed. + if (OneShot) { + OneShot = false; + } + + // We are doing a double buffering. The previous buffer was already + // decompressed on Play_Sample, or a previous iteration of Update. + // But the earlier buffer need update to hold the next uncompressed + // data + + char to_update = Decomp_Buffer_Index; + Decomp_Buffer_Index = (Decomp_Buffer_Index + 1) % DECOMP_BUFFER_COUNT; + + if (MoreSource) { + // Update stopped buffer with new data + int bytes_read = Sample_Copy(this, + &Sample, + &Remainder, + &QueueBuffer, + &QueueSize, + Decomp_Buff[to_update], + BUFFER_CHUNK_SIZE, + Compression, + nullptr, + nullptr); + + // Fill remaining buffer with 0 in case it case not enough data. + if (bytes_read < BUFFER_CHUNK_SIZE) { + memset(Decomp_Buff[to_update] + bytes_read, 0, BUFFER_CHUNK_SIZE - bytes_read); + } + + if (IsMusic && !QueueBuffer) { + Request_Music_Data(); + } + + if (bytes_read == 0) { + // The entire sample have been decompressed, no more precessing is + // necessary. + MoreSource = false; + + int current_channel = Get_Channel_Index(); + SCHANNEL_CR(current_channel) = (SCHANNEL_CR(current_channel) & (~SOUND_REPEAT)) | SOUND_ONE_SHOT; + } + } else { + if (IsMusic) { + // Request more data so that the ARM9 chip realize that the + // stream ended, so it can queue the next music. + Request_Music_Data(); + } + + // If we are here, it means that the audio has ended. Deinitialize the + // tracker. + Stop_Sample(); + } + } + + bool Is_Music() + { + return Active && IsMusic; + } + + void Set_Volume(unsigned char volume) + { + SCHANNEL_VOL(Get_Channel_Index()) = (volume & 0x7F); + } + + // Used to Debug. + void Print_IsMusic() + { + if (IsMusic) + nocashPrintf("1 "); + else + nocashPrintf("0 "); + } + +private: + // Total size is BUFFER_TOTAL_BYTES + unsigned char Decomp_Buff[DECOMP_BUFFER_COUNT][BUFFER_CHUNK_SIZE]; + + unsigned char Priority; // The priority of current sound. + unsigned char Bits; // 8 or 16 bits. + unsigned char Panloc; // Directional Audio. + unsigned char Volume; // Volume of current sound. + SCompressType Compression; // Compression that this sound data is using. + int Frequency; // Frequency of the sample. + u16 SoundHandle; // The Nintendo DS sound handle. + void* Sample; // Playable sample. May be compressed or not. + void* OriginalSample; + int SampleSize; // Size of the Sample. + + int Remainder; // Number of bytes remaining in the source data + // as pointed by the "Source" element. + void* QueueBuffer; // Pointer to continued sample data. + int QueueSize; // Size of queue buffer attached. + bool MoreSource; // Indicate that we have more stuff to decompress. + bool OneShot; + + short Size; + char Decomp_Buffer_Index; + + bool Active; + bool IsMusic; + + char MusicStreamIndex; + + unsigned long long StartedTime; // Tick number which this track started to play. + static unsigned long long Now; // Track the current time in ticks. +public: + _SOS_COMPRESS_INFO sosinfo; +}; + +// Keep track of the number of ticks representing the current time. +unsigned long long SoundTracker::Now; + +// This is an interface to a bunch of `Tracker`. +class SoundTrackers +{ +public: + inline SoundTracker* Get_Sample_Tracker(int i) + { + return &Trackers[i]; + } + + SoundTracker* Get_Tracker_By_Handle(int handle) + { + SoundTracker* st; + for (int i = 0; i < MAX_SAMPLE_TRACKERS; i++) { + st = Get_Sample_Tracker(i); + + if (st->Is_Sample_Playing() && st->Get_Handle() == handle) + return st; + } + + return NULL; + } + + bool Is_Sample_Playing(const void* sample) + { + SoundTracker* st; + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; i++) { + st = Get_Sample_Tracker(i); + + if (st->Get_Sample() == sample && st->Is_Sample_Playing()) + return true; + } + + return false; + } + + void Stop_Sample_Handle(u16 handle) + { + SoundTracker* st; + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; i++) { + st = Get_Sample_Tracker(i); + + if (st->Get_Handle() == handle && st->Is_Sample_Playing()) + st->Stop_Sample(); + } + } + + void Stop_Sample(u16 sample) + { + SoundTracker* st; + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; i++) { + st = Get_Sample_Tracker(i); + + if (st->Get_Sample_16() == sample && st->Is_Sample_Playing()) + st->Stop_Sample(); + } + } + + // Returns a free tracker index or the tracker with had the smallest + // priority. + int Get_Free_Sound_Tracker(int priority) + { + int i; + unsigned int min_priority = 255; + unsigned int min_priority_pos = -1; + + // Look in all trackers for a free slot. + for (i = MAX_SAMPLE_TRACKERS - 1; i >= 0; --i) { + SoundTracker* st = Get_Sample_Tracker(i); + unsigned char current_priority = st->Get_Priority(); + + if (!st->Is_Sample_Playing()) { + return i; + } + + if (current_priority < min_priority) { + min_priority = current_priority; + min_priority_pos = i; + } + } + + if (priority > min_priority) { + return min_priority_pos; + } + + return 0; + } + + // Play sample in one of the trackers. + int Play_Sample(void const* sample, int priority, int volume, signed short panloc, u16 handle, bool is_music) + { + int free_tracker = Get_Free_Sound_Tracker(priority); + SoundTracker* st = Get_Sample_Tracker(free_tracker); + + // Stop sound if currently playing + st->Stop_Sample(); + return st->Play_Sample(sample, priority, volume, panloc, handle, is_music); + } + + // Used to debug. + void Print_Priorities() + { + + nocashPrintf("Prio: "); + for (int i = 0; i < MAX_SAMPLE_TRACKERS; i++) { + SoundTracker* st = Get_Sample_Tracker(i); + + nocashPrintf("%d ", (int)st->Get_Priority()); + } + nocashPrintf("\n"); + } + + // Used to debug. + void Print_Active() + { + + nocashPrintf("Actv: "); + for (int i = 0; i < MAX_SAMPLE_TRACKERS; i++) { + SoundTracker* st = Get_Sample_Tracker(i); + int active = (st->Is_Sample_Playing()) ? 1 : 0; + nocashPrintf("%d ", active); + } + nocashPrintf("\n"); + } + + // Update all trackers. + void Update_Trackers() + { + for (int i = MAX_SAMPLE_TRACKERS - 1; i >= 0; i--) { + SoundTracker* st = Get_Sample_Tracker(i); + st->Update(); + } + } + + void Stop_Trackers() + { + for (int i = MAX_SAMPLE_TRACKERS - 1; i >= 0; i--) { + SoundTracker* st = Get_Sample_Tracker(i); + st->Stop_Sample(); + } + } + + // Set the music volume. + void Set_Music_Vol(int volume) + { + for (int i = MAX_SAMPLE_TRACKERS - 1; i >= 0; i--) { + SoundTracker* st = Get_Sample_Tracker(i); + + if (st->Is_Music()) { + st->Set_Volume(volume); + return; + } + } + } + +private: + SoundTracker Trackers[MAX_SAMPLE_TRACKERS]; +}; + +static SoundTrackers Trackers; + +// On a Tracker, get which index corresponds to the hardware. +int SoundTracker::Get_Channel_Index() +{ + return ((unsigned long)this - (unsigned long)Trackers.Get_Sample_Tracker(0)) / sizeof(SoundTracker); +} + +int SoundTracker::Play() +{ + // Play sound in system. + void* sample; + SoundFormat format; + int size; + + unsigned short freq = Frequency; + unsigned char volume = Volume; + unsigned char panloc = Panloc; + int channel = Get_Channel_Index(); + + format = DS_Sound_Format(SCOMP_NONE, Bits); + sample = Decomp_Buff[0]; //Decomp_Buff[Decomp_Buffer_Index]; + size = Size; + freq = Frequency; + if (size == 0) { + if (IsMusic && Active) { + // Request more data so that the ARM9 chip realize that the + // stream ended, so it can queue the next music. + Request_Music_Data(); + } + } else { + Active = true; + SCHANNEL_SOURCE(channel) = (u32)sample; + SCHANNEL_REPEAT_POINT(channel) = 0; + SCHANNEL_LENGTH(channel) = size >> 2; + SCHANNEL_TIMER(channel) = SOUND_FREQ(freq); + SCHANNEL_CR(channel) = SCHANNEL_ENABLE | SOUND_VOL(volume) | SOUND_PAN(panloc) | (format << 29); + if (OneShot) { + SCHANNEL_CR(channel) |= SOUND_ONE_SHOT; + } else { + SCHANNEL_CR(channel) |= SOUND_REPEAT; + // Set size to BUFFER_CHUNK_SIZE so that the buffer gets correctly + // updated when the track position has passed half of the audio buffer. + Size = BUFFER_CHUNK_SIZE; + } + Set_Tracker_Timer(); + } + + return SoundHandle; +} + +// Software audio decompression. The code is crap as hell... +// ---------------------------------------------------------------------------- +int Simple_Copy(void** source, int* ssize, void** alternate, int* altsize, void** dest, int size) +{ + int out = 0; + + if (*ssize == 0) { + *source = *alternate; + *ssize = *altsize; + *alternate = nullptr; + *altsize = 0; + } + + if (*source == nullptr || *ssize == 0) { + return out; + } + + int s = size; + + if (*ssize < size) { + s = *ssize; + } + + memcpy(*dest, *source, s); + *source = static_cast(*source) + s; + *ssize -= s; + *dest = static_cast(*dest) + s; + out = s; + + if ((size - s) == 0) { + return out; + } + + *source = *alternate; + *ssize = *altsize; + *alternate = nullptr; + *altsize = 0; + + out = Simple_Copy(source, ssize, alternate, altsize, dest, (size - s)) + s; + + return out; +} + +int Sample_Copy(SoundTracker* st, + void** source, + int* ssize, + void** alternate, + int* altsize, + void* dest, + int size, + SCompressType scomp, + void* trailer, + int16_t* trailersize) +{ + unsigned char uncomp_buffer[UNCOMP_BUFFER_SIZE]; + int datasize = 0; + + // There is no compression or it doesn't match any of the supported compressions so we just copy the data over. + if (scomp == SCOMP_NONE || (scomp != SCOMP_WESTWOOD && scomp != SCOMP_SOS)) { + return Simple_Copy(source, ssize, alternate, altsize, &dest, size); + } + + _SOS_COMPRESS_INFO* s = &st->sosinfo; + + while (size > 0) { + uint16_t fsize; + uint16_t dsize; + unsigned magic; + + void* fptr = &fsize; + void* dptr = &dsize; + void* mptr = &magic; + + // Verify and seek over the chunk header. + if (Simple_Copy(source, ssize, alternate, altsize, &fptr, sizeof(fsize)) < sizeof(fsize)) { + break; + } + + if (Simple_Copy(source, ssize, alternate, altsize, &dptr, sizeof(dsize)) < sizeof(dsize) || dsize > size) { + break; + } + + if (Simple_Copy(source, ssize, alternate, altsize, &mptr, sizeof(magic)) < sizeof(magic) + || magic != AUD_CHUNK_MAGIC_ID) { + break; + } + + if (fsize == dsize) { + // File size matches size to decompress, so there's nothing to do other than copy the buffer over. + if (Simple_Copy(source, ssize, alternate, altsize, &dest, fsize) < dsize) { + return datasize; + } + } else { + // Else we need to decompress it. + void* uptr = uncomp_buffer; //LockedData.UncompBuffer; + memset(uncomp_buffer, 0, sizeof(uncomp_buffer)); + + if (Simple_Copy(source, ssize, alternate, altsize, &uptr, fsize) < fsize) { + return datasize; + } + + if (scomp == SCOMP_WESTWOOD) { + Audio_Unzap(uncomp_buffer, dest, dsize); + } else { + s->lpSource = (char*)uncomp_buffer; + s->lpDest = (char*)dest; + + sosCODECDecompressData(s, dsize); + } + + dest = reinterpret_cast(dest) + dsize; + } + + datasize += dsize; + size -= dsize; + } + + return datasize; +} + +void Sound_Update() +{ + Trackers.Update_Trackers(); + + // Used to debug priorities on emulator. + //Trackers.Print_Priorities(); + //Trackers.Print_Active(); +} + +// End software decompression code. +//----------------------------------------------------------------------------- + +/* Queue used to serialize messages. + + When the ARM7 receives a message, is generates an interruption to process + it. That is not a good idea, as it may create race conditions, so we use + this queue to serialize it. */ +// MESSAGES_MAX must be a power of two. +template class MessageQueue +{ +public: + MessageQueue() + { + memset(this, 0, sizeof(*this)); + } + + int Pop_Message(USR1::FifoMessage* msg) + { + if (Is_Empty()) + return 0; + + memcpy(msg, &Messages[Tail], sizeof(*msg)); + Tail = (Tail + 1) % MESSAGES_MAX; + return 1; + } + + int Push_Message(USR1::FifoMessage* msg) + { + if (Is_Full()) { + return 0; + } + + memcpy(&Messages[Head], msg, sizeof(*msg)); + Head = (Head + 1) % MESSAGES_MAX; + return 1; + } + +private: + inline bool Is_Full(void) + { + return (Tail + 1) % MESSAGES_MAX == Head; + } + + inline bool Is_Empty(void) + { + return Head == Tail; + } + + USR1::FifoMessage Messages[MESSAGES_MAX]; + unsigned Head, Tail; +}; + +static MessageQueue<32> MQueue; + +//--------------------------------------------------------------------------------- +void user01CommandHandler(u32 command, void* userdata) +{ + //--------------------------------------------------------------------------------- + + int cmd = (command)&0x00F00000; + int data = command & 0xFFFF; + int channel = (command >> 16) & 0xF; + + switch (cmd) { + + case USR1::SOUND_KILL: + Trackers.Stop_Trackers(); + SCHANNEL_CR(VQA_CHANNEL) &= ~SCHANNEL_ENABLE; + break; + + case USR1::MUSIC_CHUNK_UPDATED: + // Ignore messages that the MUSIC_CHUNK was updated. + break; + + case USR1::STOP_SAMPLE_HANDLE: + Trackers.Stop_Sample_Handle(data); + + case USR1::STOP_SAMPLE: + Trackers.Stop_Sample(data); + + case USR1::SET_MUSIC_VOL: + Trackers.Set_Music_Vol(data); + + default: + break; + } +} + +// Process one element of the Sound Queue. +void Process_Queue() +{ + USR1::FifoMessage msg; + if (MQueue.Pop_Message(&msg) == 0) + return; + + if (msg.type == USR1::SOUND_PLAY_MESSAGE) { + const void* sample = msg.SoundPlay.data; + u16 handle = msg.SoundPlay.handle; + u8 priority = msg.SoundPlay.priority; + u8 volume = msg.SoundPlay.volume; + u8 panloc = msg.SoundPlay.pan; + bool is_music = msg.SoundPlay.is_music; + + Trackers.Play_Sample(sample, priority, volume, panloc, handle, is_music); + } else if (msg.type == USR1::SOUND_VQA_MESSAGE) { + const void* sample = msg.SoundVQAChunk.data; + u16 freq = msg.SoundVQAChunk.freq; + u32 size = msg.SoundVQAChunk.size; + u8 volume = msg.SoundVQAChunk.volume; + u8 bits = msg.SoundVQAChunk.bits; + unsigned format = SoundTracker::DS_Sound_Format(SCOMP_NONE, bits); + + SCHANNEL_SOURCE(VQA_CHANNEL) = (u32)sample; + SCHANNEL_REPEAT_POINT(VQA_CHANNEL) = 0; + SCHANNEL_LENGTH(VQA_CHANNEL) = size >> 2; + SCHANNEL_TIMER(VQA_CHANNEL) = SOUND_FREQ(freq); + SCHANNEL_CR(VQA_CHANNEL) = + SCHANNEL_ENABLE | SOUND_VOL(volume) | SOUND_PAN(64) | (format << 29) | (SOUND_REPEAT); + + SCHANNEL_REPEAT_POINT(VQA_CHANNEL) = 0; + } +} + +void user01DataHandler(int bytes, void* user_data) +{ + USR1::FifoMessage msg; + fifoGetDatamsg(FIFO_USER_01, bytes, (u8*)&msg); + MQueue.Push_Message(&msg); + + // Don't send confirmation -- This engine is asynchronous. + //fifoSendValue32(FIFO_USER_01, (u32)channel); +} + +//--------------------------------------------------------------------------------- +void installUser01FIFO(void) +{ + //--------------------------------------------------------------------------------- + + fifoSetDatamsgHandler(FIFO_USER_01, user01DataHandler, 0); + fifoSetValue32Handler(FIFO_USER_01, user01CommandHandler, 0); + + // Setup timer which will tell when to update the circular queue. + // Use the macro version, as the C function version fails to link. + TIMER_DATA(1) = 0; + TIMER_CR(1) = ClockDivider_256 | TIMER_ENABLE; +} diff --git a/arm7/main.cpp b/arm7/main.cpp new file mode 100644 index 00000000..3cf4b717 --- /dev/null +++ b/arm7/main.cpp @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------- + + default ARM7 core + + Copyright (C) 2005 - 2010 + Michael Noland (joat) + Jason Rogers (dovoto) + Dave Murphy (WinterMute) + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +---------------------------------------------------------------------------------*/ +#include +#include "printf.h" + +// We don't support network yet. +#if 0 +#include +#endif + +void VblankHandler(void) +{ + //Wifi_Update(); +} + +void VcountHandler() +{ + inputGetAndSend(); +} + +volatile bool exitflag = false; + +void powerButtonCB() +{ + exitflag = true; +} + +void installUser01FIFO(); + +extern "C" void nocashWrite(const char* str, int len); + +void Sound_Update(); + +void Process_Queue(); + +int main() +{ + // clear sound registers + dmaFillWords(0, (void*)0x04000400, 0x100); + + REG_SOUNDCNT |= SOUND_ENABLE; + writePowerManagement(PM_CONTROL_REG, (readPowerManagement(PM_CONTROL_REG) & ~PM_SOUND_MUTE) | PM_SOUND_AMP); + powerOn(POWER_SOUND); + + readUserSettings(); + ledBlink(0); + + irqInit(); + // Start the RTC tracking IRQ + initClockIRQ(); + fifoInit(); + touchInit(); + + SetYtrigger(80); + + //installWifiFIFO(); + installSoundFIFO(); + + installUser01FIFO(); + + installSystemFIFO(); + + irqSet(IRQ_VCOUNT, VcountHandler); + irqSet(IRQ_VBLANK, VblankHandler); + + irqEnable(IRQ_VBLANK | IRQ_VCOUNT | IRQ_NETWORK); + + setPowerButtonCB(powerButtonCB); + + while (!exitflag) { + Process_Queue(); + Sound_Update(); + if (0 == (REG_KEYINPUT & (KEY_SELECT | KEY_START | KEY_L | KEY_R))) { + exitflag = true; + } + //swiWaitForVBlank(); + } + return 0; +} diff --git a/arm7/printf.cpp b/arm7/printf.cpp new file mode 100644 index 00000000..515e1704 --- /dev/null +++ b/arm7/printf.cpp @@ -0,0 +1,1029 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "printf.h" + +extern "C" { +void nocashMessage(const char*); +void nocashWrite(const char*, int len); +} + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_NTOA_BUFFER_SIZE +#define PRINTF_NTOA_BUFFER_SIZE 32U +#endif + +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_FTOA_BUFFER_SIZE +#define PRINTF_FTOA_BUFFER_SIZE 32U +#endif + +// support for the floating point type (%f) +// default: activated +#define PRINTF_DISABLE_SUPPORT_FLOAT +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +// default: 6 digits +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +// default: 1e9 +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + +// output function type +typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); + +// wrapper (used as buffer) for output function type +typedef struct +{ + void (*fct)(char character, void* arg); + void* arg; +} out_fct_wrap_type; + +// internal buffer output +static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) +{ + if (idx < maxlen) { + ((char*)buffer)[idx] = character; + } +} + +// internal null output +static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)character; + (void)buffer; + (void)idx; + (void)maxlen; +} + +// internal _putchar wrapper +static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)buffer; + (void)idx; + (void)maxlen; + if (character) { + _putchar(character); + } +} + +// internal output function wrapper +static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)idx; + (void)maxlen; + if (character) { + // buffer is the output fct pointer + ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); + } +} + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char* str, size_t maxsize) +{ + const char* s; + for (s = str; *s && maxsize--; ++s) + ; + return (unsigned int)(s - str); +} + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char** str) +{ + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev(out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + const char* buf, + size_t len, + unsigned int width, + unsigned int flags) +{ + const size_t start_idx = idx; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + while (len) { + out(buf[--len], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + +// internal itoa format +static size_t _ntoa_format(out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + char* buf, + size_t len, + bool negative, + unsigned int base, + unsigned int prec, + unsigned int width, + unsigned int flags) +{ + // pad leading zeros + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { + len--; + if (len && (base == 16U)) { + len--; + } + } + if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + +// internal itoa for 'long' type +static size_t _ntoa_long(out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + unsigned long value, + bool negative, + unsigned long base, + unsigned int prec, + unsigned int width, + unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long(out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + unsigned long long value, + bool negative, + unsigned long long base, + unsigned int prec, + unsigned int width, + unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + +#if defined(PRINTF_SUPPORT_FLOAT) + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +o static size_t _etoa(out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + double value, + unsigned int prec, + unsigned int width, + unsigned int flags); +#endif + +// internal ftoa for fixed decimal floating point +static size_t _ftoa(out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + double value, + unsigned int prec, + unsigned int width, + unsigned int flags) +{ + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // powers of 10 + static const double pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; + + // test for special values + if (value != value) + return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); + if (value < -DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); + if (value > DBL_MAX) + return _out_rev(out, + buffer, + idx, + maxlen, + (flags & FLAGS_PLUS) ? "fni+" : "fni", + (flags & FLAGS_PLUS) ? 4U : 3U, + width, + flags); + + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad + if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); +#else + return 0U; +#endif + } + + // test for negative + bool negative = false; + if (value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } else if (diff < 0.5) { + } else if ((frac == 0U) || (frac & 1U)) { + // if halfway, round up if odd OR if last digit is 0 + ++frac; + } + + if (prec == 0U) { + diff = value - (double)whole; + if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if (!(frac /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while (len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse +static size_t _etoa(out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + double value, + unsigned int prec, + unsigned int width, + unsigned int flags) +{ + // check for NaN and special values + if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { + return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + } + + // determine the sign + const bool negative = value < 0; + if (negative) { + value = -value; + } + + // default precision + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union + { + uint64_t U; + double F; + } conv; + + conv.F = value; + int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval * 3.321928094887362 + 0.5); + const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + conv.U = (uint64_t)(exp2 + 1023) << 52U; + // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + // correct for rounding errors + if (value < conv.F) { + expval--; + conv.F /= 10; + } + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode? + if ((value >= 1e-4) && (value < 1e6)) { + if ((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } else { + prec = 0; + } + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + expval = 0; + } else { + // we use one sigfig for the whole part + if ((prec > 0) && (flags & FLAGS_PRECISION)) { + --prec; + } + } + } + + // will everything fit? + unsigned int fwidth = width; + if (width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + fwidth -= minwidth; + } else { + // not enough characters, so go back to default sizing + fwidth = 0U; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + // rescale the float value + if (expval) { + value /= conv.F; + } + + // output the floating part + const size_t start_idx = idx; + idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if (minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); + // output the exponent value + idx = _ntoa_long(out, + buffer, + idx, + maxlen, + (expval < 0) ? -expval : expval, + expval < 0, + 10, + 0, + minwidth - 1, + FLAGS_ZEROPAD | FLAGS_PLUS); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) + out(' ', buffer, idx++, maxlen); + } + } + return idx; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + +// internal vsnprintf +static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) +{ + unsigned int flags, width, precision, n; + size_t idx = 0U; + + if (!buffer) { + // use null output function + out = _out_null; + } + + while (*format) { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': + flags |= FLAGS_ZEROPAD; + format++; + n = 1U; + break; + case '-': + flags |= FLAGS_LEFT; + format++; + n = 1U; + break; + case '+': + flags |= FLAGS_PLUS; + format++; + n = 1U; + break; + case ' ': + flags |= FLAGS_SPACE; + format++; + n = 1U; + break; + case '#': + flags |= FLAGS_HASH; + format++; + n = 1U; + break; + default: + n = 0U; + break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } else if (*format == '*') { + const int w = va_arg(va, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } else if (*format == '*') { + const int prec = (int)va_arg(va, int); + precision = prec > 0 ? (unsigned int)prec : 0U; + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l': + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h': + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't': + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j': + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z': + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default: + break; + } + + // evaluate specifier + switch (*format) { + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'b': { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = 16U; + } else if (*format == 'o') { + base = 8U; + } else if (*format == 'b') { + base = 2U; + } else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value = va_arg(va, long long); + idx = _ntoa_long_long(out, + buffer, + idx, + maxlen, + (unsigned long long)(value > 0 ? value : 0 - value), + value < 0, + base, + precision, + width, + flags); +#endif + } else if (flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long(out, + buffer, + idx, + maxlen, + (unsigned long)(value > 0 ? value : 0 - value), + value < 0, + base, + precision, + width, + flags); + } else { + const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) + : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) + : va_arg(va, int); + idx = _ntoa_long(out, + buffer, + idx, + maxlen, + (unsigned int)(value > 0 ? value : 0 - value), + value < 0, + base, + precision, + width, + flags); + } + } else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long( + out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); +#endif + } else if (flags & FLAGS_LONG) { + idx = _ntoa_long( + out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); + } else { + const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) + : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) + : va_arg(va, unsigned int); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f': + case 'F': + if (*format == 'F') + flags |= FLAGS_UPPERCASE; + idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g') || (*format == 'G')) + flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E') || (*format == 'G')) + flags |= FLAGS_UPPERCASE; + idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c': { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + out((char)va_arg(va, int), buffer, idx++, maxlen); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's': { + const char* p = va_arg(va, char*); + unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), buffer, idx++, maxlen); + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 'p': { + width = sizeof(void*) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if (is_ll) { + idx = _ntoa_long_long( + out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); + } else { +#endif + idx = _ntoa_long(out, + buffer, + idx, + maxlen, + (unsigned long)((uintptr_t)va_arg(va, void*)), + false, + 16U, + precision, + width, + flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%': + out('%', buffer, idx++, maxlen); + format++; + break; + + default: + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return (int)idx; +} + +/////////////////////////////////////////////////////////////////////////////// + +int printf_(const char* format, ...) +{ + va_list va; + va_start(va, format); + char buffer[1]; + const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + +int sprintf_(char* buffer, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + +int snprintf_(char* buffer, size_t count, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); + va_end(va); + return ret; +} + +int vprintf_(const char* format, va_list va) +{ + char buffer[1]; + return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); +} + +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) +{ + return _vsnprintf(_out_buffer, buffer, count, format, va); +} + +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) +{ + va_list va; + va_start(va, format); + const out_fct_wrap_type out_fct_wrap = {out, arg}; + const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); + va_end(va); + return ret; +} + +void nocashPrintf(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + char buf[128]; + int len = vsnprintf(buf, 128, fmt, args); + va_end(args); + nocashWrite(buf, len); +} diff --git a/arm7/printf.h b/arm7/printf.h new file mode 100644 index 00000000..f6e018bf --- /dev/null +++ b/arm7/printf.h @@ -0,0 +1,110 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Output a character to a custom device like UART, used by the printf() function + * This function is declared here only. You have to write your custom implementation somewhere + * \param character Character to output + */ +void _putchar(char character); + +/** + * Tiny printf implementation + * You have to implement _putchar if you use printf() + * To avoid conflicts with the regular printf() API it is overridden by macro defines + * and internal underscore-appended functions like printf_() are used + * \param format A string that specifies the format of the output + * \return The number of characters that are written into the array, not counting the terminating null character + */ +#define printf printf_ +int printf_(const char* format, ...); + +/** + * Tiny sprintf implementation + * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! + * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! + * \param format A string that specifies the format of the output + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define sprintf sprintf_ +int sprintf_(char* buffer, const char* format, ...); + +/** + * Tiny snprintf/vsnprintf implementation + * \param buffer A pointer to the buffer where to store the formatted string + * \param count The maximum number of characters to store in the buffer, including a terminating null character + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that COULD have been written into the buffer, not counting the terminating + * null character. A value equal or larger than count indicates truncation. Only when the returned value + * is non-negative and less than count, the string has been completely written. + */ +#define snprintf snprintf_ +#define vsnprintf vsnprintf_ +int snprintf_(char* buffer, size_t count, const char* format, ...); +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); + +/** + * Tiny vprintf implementation + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define vprintf vprintf_ +int vprintf_(const char* format, va_list va); + +/** + * printf with output function + * You may use this as dynamic alternative to printf() with its fixed _putchar() output + * \param out An output function which takes one character and an argument pointer + * \param arg An argument pointer for user data passed to output function + * \param format A string that specifies the format of the output + * \return The number of characters that are sent to the output function, not counting the terminating null character + */ +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); + +void nocashPrintf(const char* fmt, ...); + +#ifdef __cplusplus +} +#endif + +#endif // _PRINTF_H_ diff --git a/cmake/devkitarm-nds-toolchain.cmake b/cmake/devkitarm-nds-toolchain.cmake new file mode 100644 index 00000000..9e81de93 --- /dev/null +++ b/cmake/devkitarm-nds-toolchain.cmake @@ -0,0 +1,39 @@ +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR armv5te) + +set(DEVKITARM $ENV{DEVKITARM}) +set(DEVKITPRO $ENV{DEVKITPRO}) + +set(CMAKE_C_COMPILER "${DEVKITARM}/bin/arm-none-eabi-gcc") +set(CMAKE_CXX_COMPILER "${DEVKITARM}/bin/arm-none-eabi-g++") +set(CMAKE_ASM_COMPILER "${DEVKITARM}/bin/arm-none-eabi-gcc") +set(CMAKE_AR "${DEVKITARM}/bin/arm-none-eabi-gcc-ar") +set(CMAKE_RANLIB "${DEVKITARM}/bin/arm-none-eabi-gcc-ranlib") +set(NDSTOOL "${DEVKITARM}/bin/ndstool") + +set(CMAKE_FIND_ROOT_PATH ${DEVKITPRO}) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set(CMAKE_FIND_LIBRARY_PREFIXES "lib") +set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".la") + +set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES ${DEVKITARM}/include ${DEVKITPRO}/libnds/include ${DEVKITPRO}/libgba/include) + +set(NDS TRUE) + +SET(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Shared libs not available" ) + +add_definitions(-D_NDS) + +# Define paths to include libraries +include_directories("${DEVKITPRO}/libnds/include") + +# Define paths to libraries. Use link_libraries, as link_directories don't +# seem to work. +link_libraries("-L${DEVKITARM}/lib") +link_libraries("-L${DEVKITARM}/arm-none-eabi/lib") +link_libraries("-L${DEVKITPRO}/libnds/lib") +link_libraries("-L${DEVKITPRO}/libgba/lib") diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index e689f6b1..efd48d39 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -3,6 +3,11 @@ set(GIT_PRE_CONFIGURE_FILE "gitinfo.cpp.in") set(GIT_POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/gitinfo.cpp") include(GitWatcher) +if (NDS) + # This folder contains ARM9 code. + add_definitions(-DARM9) +endif() + set(COMMON_SRC ${GIT_POST_CONFIGURE_FILE} _diptabl.cpp @@ -54,7 +59,6 @@ set(COMMON_SRC lcw.cpp lcwpipe.cpp lcwstraw.cpp - linear.cpp link.cpp load.cpp memrev.cpp @@ -101,13 +105,14 @@ set(COMMON_SRC ) if (WIN32) - list(APPEND COMMON_SRC file_win.cpp paths_win.cpp) + list(APPEND COMMON_SRC file_win.cpp paths_win.cpp wintimer.cpp) +elseif (NDS) + list(APPEND COMMON_SRC fnmatch.cpp file_posix.cpp paths_nds.cpp wintimer_nds.cpp memcpy.s rmemcpy.s memmove.s) else() - list(APPEND COMMON_SRC file_posix.cpp paths_posix.cpp) + list(APPEND COMMON_SRC file_posix.cpp paths_posix.cpp wintimer.cpp) endif() set(COMMONR_SRC - framelimit.cpp gbuffer.cpp interpal.cpp soundio_null.cpp @@ -117,7 +122,6 @@ set(COMMONR_SRC ) set(COMMONV_SRC - framelimit.cpp gbuffer.cpp interpal.cpp unvqbuff.cpp @@ -131,11 +135,24 @@ set(COMMONV_SRC wwmouse.cpp ) +# Functions optimized for the Nintendo DS +if (NDS) + list(APPEND COMMON_SRC linear_nds.cpp) + list(APPEND COMMONR_SRC framelimit_nds.cpp) + list(APPEND COMMONV_SRC framelimit_nds.cpp) +else() + list(APPEND COMMON_SRC linear.cpp) + list(APPEND COMMONR_SRC framelimit.cpp) + list(APPEND COMMONV_SRC framelimit.cpp) +endif() + if(DSOUND) list(APPEND COMMONV_SRC soundio.cpp vqaaudio_dsound.cpp) list(APPEND VANILLA_LIBS dsound) elseif(OPENAL) list(APPEND COMMONV_SRC soundio_openal.cpp vqaaudio_openal.cpp) +elseif(NDS) + list(APPEND COMMONV_SRC soundio_nds.cpp vqaaudio_nds.cpp) else() list(APPEND COMMONV_SRC soundio_null.cpp vqaaudio_null.cpp) endif() @@ -145,6 +162,8 @@ if(DDRAW) list(APPEND VANILLA_LIBS ddraw) elseif(SDL2) list(APPEND COMMONV_SRC video_sdl2.cpp) +elseif(NDS) + list(APPEND COMMONV_SRC video_nds.cpp) else() list(APPEND COMMONV_SRC video_null.cpp) endif() diff --git a/common/alloc.cpp b/common/alloc.cpp index 3338d910..943d9ada 100644 --- a/common/alloc.cpp +++ b/common/alloc.cpp @@ -41,6 +41,19 @@ #include #include "wwmem.h" +#include "debugstring.h" + +#ifdef _NDS +#include +#include +#endif + +#if defined(__unix__) || defined(__unix) +#include +#include +#elif defined(_WIN32) +#include +#endif size_t Largest_Mem_Block(void); @@ -96,7 +109,9 @@ void* Alloc(size_t bytes_to_alloc, MemoryFlagType flags) #endif // MEM_CHECK mem_ptr = malloc(bytes_to_alloc); - + if (mem_ptr == NULL) { + DBG_LOG("Unable to allocate memory\n"); + } if (!mem_ptr && Memory_Error) { Memory_Error(); } @@ -219,9 +234,88 @@ void* Resize_Alloc(void* original_ptr, size_t new_size_in_bytes) * HISTORY: * * 09/03/1991 JLB : Commented. * *=========================================================================*/ -int Ram_Free(MemoryFlagType) +size_t Ram_Free(MemoryFlagType) { - return (64 * 1024 * 1024); + return Ram_Free(); +} + +// Some systems only implements sbrk. So we implement brk with sbrk. +#if !defined(__APPLE__) && !defined(__unix__) && !defined(__unix) && !defined(_WIN32) +static int brk_(ptrdiff_t a) +{ + ptrdiff_t old = (ptrdiff_t)sbrk(0); + if (sbrk(a - old) == (void*)-1UL) { + return -1; + } + + sbrk(old - a); + return 0; +} +#endif + +size_t Ram_Free(void) +{ +#if defined(__APPLE__) + // Someone has to implement this for Mac. + return 128 * 1024 * 1024; +#elif defined(__unix__) || defined(__unix) + // Get amount of memory available to program. + return sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE); +#elif defined(_WIN32) + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + return status.ullAvailPhys; +#else + + // Oh dear. We are running in some kind of baremetal machine. + // + // Assume that available memory is the maximum heap size minus + // the current heap position. + + struct mallinfo mi = mallinfo(); + ptrdiff_t m; + + if (TotalRam == 0) { + // Find how much the heap can grow to calculate the maximum RAM + // available in the system. + ptrdiff_t curr_heap = (ptrdiff_t)sbrk(0); + + ptrdiff_t x1 = curr_heap; + ptrdiff_t x0 = curr_heap; + + // Find rightmost element; + do { + if (brk_(x1) == 0) { + x0 = x1; + x1 *= 2; + } else { + break; + } + } while (1); + + // Binary search a value between x0 and x1; + m = (x1 + x0) / 2; + while (x0 < m) { + if (brk_(m) == 0) { + x0 = m; + } else { + x1 = m; + } + + m = (x1 + x0) / 2; + } + + brk_(curr_heap); + + // Total RAM is the difference between the top heap address minus + // where we were plus what malloc is using. + TotalRam = m - curr_heap + mi.uordblks; + } + m = TotalRam - mi.uordblks; + DBG_LOG("RAM avaliable: %ldkb\n", m / 1024); + return m; +#endif } /*************************************************************************** diff --git a/common/audio_fifocommon.h b/common/audio_fifocommon.h new file mode 100644 index 00000000..ada0910f --- /dev/null +++ b/common/audio_fifocommon.h @@ -0,0 +1,90 @@ +#ifndef AUDIO_FIFOCOMMON +#define AUDIO_FIFOCOMMON + +// FIFO interface with the ARM7 chip. + +// Define the music chunk size. We can't load the entire music in RAM so we +// have to stream it from the SD Card. +#define MUSIC_CHUNK_SIZE 32768 + +// USR1: ARM9 to ARM7 +namespace USR1 +{ + + // Define some commands which we implement from the ARM7 side. A command + // is a full 32-bit integer in which we break down in two pieces: the 16 + // least significative bits are used for arguments (like passing the + // SoundHandle in STOP_SAMPLE_HANDLE) and the most significative 16 bits + // are used for commands indeed. + typedef enum + { + // Stop all playing sounds. + SOUND_KILL = 0 << 20, + + // Alert the ARM7 that a new chunk of music is available. + MUSIC_CHUNK_UPDATED = 1 << 20, + + // Stop a sample by its Handle. + STOP_SAMPLE_HANDLE = 2 << 20, + + // Stop a sample by its pointer (use least signficiative 16-bits as hash) + STOP_SAMPLE = 3 << 20, + + // Set volume of music. + SET_MUSIC_VOL = 4 << 20, + } FifoSoundCommand; + + // Define message kinds. Used to distinguish packages one from another. + typedef enum + { + // Ordinary sound message. + SOUND_PLAY_MESSAGE = 0x1234, + + // Sound message comming from VQA Player. + SOUND_VQA_MESSAGE, + } FifoSoundMessageType; + + // Define what can be in a message. Message must have a maximum length of + // 32 bytes IIRC. + typedef struct FifoMessage + { + u16 type; + + union + { + struct + { + const void* data; + u16 handle; + u16 freq; + u8 volume; + u8 pan; + u8 priority; + u8 is_music : 1; + } SoundPlay; + + struct + { + const void* data; + u32 size; + u16 freq; + u8 volume; + u8 bits; + } SoundVQAChunk; + }; + + } ALIGN(4) FifoSoundMessage; +} // namespace USR1 + +// USR2: Used to communicate from ARM7 to ARM9. +namespace USR2 +{ + //! Enum values for the fifo sound commands. + typedef enum + { + // Ask the ARM9 for more music data. + MUSIC_REQUEST_CHUNK = 1 << 20, + } SoundMusicChunk; +} // namespace USR2 + +#endif //AUDIO_FIFOCOMMON diff --git a/common/connect.cpp b/common/connect.cpp index 7b8b8848..391e2def 100644 --- a/common/connect.cpp +++ b/common/connect.cpp @@ -50,6 +50,13 @@ //#include "WolDebug.h" +#ifdef _NDS +int ftime(struct timeb* tb) +{ + return 0; +} +#endif + /* ********************************* Globals *********************************** */ diff --git a/common/fading.cpp b/common/fading.cpp index 690e0d16..37d696c0 100644 --- a/common/fading.cpp +++ b/common/fading.cpp @@ -10,6 +10,7 @@ // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection #include "fading.h" +#include void* Build_Fading_Table(void const* palette, void* dest, int color, int frac) { diff --git a/common/file_posix.cpp b/common/file_posix.cpp index 387512f9..a58e8e59 100644 --- a/common/file_posix.cpp +++ b/common/file_posix.cpp @@ -8,6 +8,16 @@ #include #include +#ifdef _NDS + +/* Nintendo DS only supports FAT as filesystem, which is case-insensitive, so + it should not really matter that FNM_CASEFOLD is unsupported. */ +#define FNM_CASEFOLD 0 + +/* Function which initializes the Nintendo DS file system. */ +void DS_Filesystem_Init(); +#endif + class Find_File_Data_Posix : public Find_File_Data { public: @@ -80,6 +90,10 @@ bool Find_File_Data_Posix::FindNextWithFilter() bool Find_File_Data_Posix::FindFirst(const char* fname) { +#ifdef _NDS + DS_Filesystem_Init(); +#endif + Close(); FullName[0] = '\0'; DirName[0] = '\0'; diff --git a/common/fnmatch.cpp b/common/fnmatch.cpp new file mode 100644 index 00000000..dfebb21e --- /dev/null +++ b/common/fnmatch.cpp @@ -0,0 +1,230 @@ +/* mrparrot 10/03/2021: This file is part of newlib's libc, and it is here to + provide fnmatch function for platforms that doesn't support it + (e.g. Nintendo DS). All copyright belongs to the following copyright owner, + and the license is compatible with GPL. */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NO_FNMATCH + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)fnmatch.c 8.2 (Berkeley) 4/16/94"; +#endif /* LIBC_SCCS and not lint */ +#include + +/* + * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. + * Compares a filename or pathname to a pattern. + */ + +#include +#include +#include +#include + +//#include "collate.h" + +#define EOS '\0' + +#define RANGE_MATCH 1 +#define RANGE_NOMATCH 0 +#define RANGE_ERROR (-1) + +/* mrparrot 10/03/2021: Some flags shouldn't matter on FAT systems, which is + the only thing we support for now. */ +#define FNM_CASEFOLD 0 + +/* mrparrot 10/03/2021: Don't support GNU extensions for now. */ +#define FNM_LEADING_DIR 0 + +static int rangematch(const char*, char, int, char**); + +int fnmatch(const char* pattern, const char* string, int flags) +{ + const char* stringstart; + char* newp; + char c, test; + + for (stringstart = string;;) + switch (c = *pattern++) { + case EOS: + if ((flags & FNM_LEADING_DIR) && *string == '/') + return (0); + return (*string == EOS ? 0 : FNM_NOMATCH); + case '?': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) + && (string == stringstart || ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') + c = *++pattern; + + if (*string == '.' && (flags & FNM_PERIOD) + && (string == stringstart || ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) + if (flags & FNM_PATHNAME) + return ((flags & FNM_LEADING_DIR) || strchr(string, '/') == NULL ? 0 : FNM_NOMATCH); + else + return (0); + else if (c == '/' && flags & FNM_PATHNAME) { + if ((string = strchr(string, '/')) == NULL) + return (FNM_NOMATCH); + break; + } + + /* General case, use recursion. */ + while ((test = *string) != EOS) { + if (!fnmatch(pattern, string, flags & ~FNM_PERIOD)) + return (0); + if (test == '/' && flags & FNM_PATHNAME) + break; + ++string; + } + return (FNM_NOMATCH); + case '[': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) + && (string == stringstart || ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + switch (rangematch(pattern, *string, flags, &newp)) { + case RANGE_ERROR: + goto norm; + case RANGE_MATCH: + pattern = newp; + break; + case RANGE_NOMATCH: + return (FNM_NOMATCH); + } + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = *pattern++) == EOS) { + c = '\\'; + --pattern; + } + } + /* FALLTHROUGH */ + default: + norm: + if (c == *string) + ; + else if ((flags & FNM_CASEFOLD) && (tolower((unsigned char)c) == tolower((unsigned char)*string))) + ; + else + return (FNM_NOMATCH); + string++; + break; + } + /* NOTREACHED */ +} + +static int rangematch(const char* pattern, char test, int flags, char** newp) +{ + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ((negate = (*pattern == '!' || *pattern == '^'))) + ++pattern; + + if (flags & FNM_CASEFOLD) + test = tolower((unsigned char)test); + + /* + * A right bracket shall lose its special meaning and represent + * itself in a bracket expression if it occurs first in the list. + * -- POSIX.2 2.8.3.2 + */ + ok = 0; + c = *pattern++; + do { + if (c == '\\' && !(flags & FNM_NOESCAPE)) + c = *pattern++; + if (c == EOS) + return (RANGE_ERROR); + + if (c == '/' && (flags & FNM_PATHNAME)) + return (RANGE_NOMATCH); + + if (flags & FNM_CASEFOLD) + c = tolower((unsigned char)c); + + if (*pattern == '-' && (c2 = *(pattern + 1)) != EOS && c2 != ']') { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) + c2 = *pattern++; + if (c2 == EOS) + return (RANGE_ERROR); + + if (flags & FNM_CASEFOLD) + c2 = tolower((unsigned char)c2); +/* mrparrot: 03/10/2021: Let's hope this is unecessary. */ +#if 0 + if (__collate_load_error ? + c <= test && test <= c2 : + __collate_range_cmp(c, test) <= 0 + && __collate_range_cmp(test, c2) <= 0 + ) + ok = 1; +#endif + } else if (c == test) + ok = 1; + } while ((c = *pattern++) != ']'); + + *newp = (char*)pattern; + return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); +} + +#endif /* !_NO_FNMATCH */ diff --git a/common/framelimit.cpp b/common/framelimit.cpp index ac6c3596..9cf68d3f 100644 --- a/common/framelimit.cpp +++ b/common/framelimit.cpp @@ -11,14 +11,14 @@ extern WWMouseClass* WWMouse; -#ifdef SDL2_BUILD +#if defined(SDL2_BUILD) void Video_Render_Frame(); #endif void Frame_Limiter(FrameLimitFlags flags) { static auto frame_start = std::chrono::steady_clock::now(); -#ifdef SDL2_BUILD +#if defined(SDL2_BUILD) static auto render_avg = 0; auto render_start = std::chrono::steady_clock::now(); diff --git a/common/framelimit_nds.cpp b/common/framelimit_nds.cpp new file mode 100644 index 00000000..45076225 --- /dev/null +++ b/common/framelimit_nds.cpp @@ -0,0 +1,12 @@ +#include "framelimit.h" +#include "wwmouse.h" +#include "settings.h" + +#include + +void Update_HWCursor(); + +void Frame_Limiter(FrameLimitFlags flags) +{ + Update_HWCursor(); +} diff --git a/common/keybuff.cpp b/common/keybuff.cpp index c69e9eef..695e3ffc 100644 --- a/common/keybuff.cpp +++ b/common/keybuff.cpp @@ -1219,7 +1219,7 @@ void Buffer_Frame_To_Page(int x, int flags, ...) { - bool use_old_drawer = true; // false; New draw system not supported in TD. + bool use_old_drawer = false; //true; // false; New draw system not supported in TD. int fade_count = 0; ShapeHeaderType* draw_header = nullptr; unsigned char* fade_table = nullptr; @@ -1269,7 +1269,7 @@ void Buffer_Frame_To_Page(int x, ghost_table = ghost_lookup + 256; } - if (!UseBigShapeBuffer /*|| UseOldShapeDraw */) { + if (!UseBigShapeBuffer || UseOldShapeDraw) { use_old_drawer = true; } diff --git a/common/keyframe.cpp b/common/keyframe.cpp index 3df75b72..9b497850 100644 --- a/common/keyframe.cpp +++ b/common/keyframe.cpp @@ -59,13 +59,14 @@ typedef struct unsigned short largest_frame_size; short flags; } KeyFrameHeaderType; - #pragma pack(pop) #define INITIAL_BIG_SHAPE_BUFFER_SIZE 12000 * 1024 #define THEATER_BIG_SHAPE_BUFFER_SIZE 2000 * 1024 #define UNCOMPRESS_MAGIC_NUMBER 56789 +bool UseOldShapeDraw = false; + static unsigned short CurrentUncompressMagicNum = UNCOMPRESS_MAGIC_NUMBER; static int BigShapeBufferLength = INITIAL_BIG_SHAPE_BUFFER_SIZE; static int TheaterShapeBufferLength = THEATER_BIG_SHAPE_BUFFER_SIZE; @@ -166,6 +167,7 @@ void Reallocate_Big_Shape_Buffer() ** It may still be possible to continue with compressed shapes */ if (!BigShapeBufferStart) { + DBG_LOG("Out of Memory: disabling BigShapeBuffer"); UseBigShapeBuffer = false; return; } @@ -175,6 +177,7 @@ void Reallocate_Big_Shape_Buffer() // is flushing and refilling it in the hope of discarding shapes not // very often used, like enemy building animations, radar animations, // and so on. + DBG_LOG("BigShpBuffer memory depleted. Rebuilding..."); Reset_Theater_Shapes(); Reset_BigShapeBuffer(); CurrentUncompressMagicNum++; @@ -189,6 +192,7 @@ void Check_Use_Compressed_Shapes() // Uncompressed shapes enabled for performance reasons. We don't need to worry about memory. // Uncompressed shapes don't seem to work in RA for rotated/scaled objects so wherever scale/rotate is used, // we will need to disable it (like in Techno_Draw_Object). ST - 11/6/2019 2:09PM + UseBigShapeBuffer = true; OriginalUseBigShapeBuffer = true; } @@ -275,6 +279,48 @@ uintptr_t Build_Frame(void const* dataptr, unsigned short framenumber, void* buf ** */ if (!BigShapeBufferStart) { + /* Check how much RAM we have to decide the length of our buffers. */ + + // 300 * 1024 is okay for RA. + // 400 * 1024 seems ok for TD. + const size_t eps = 400 * 1024; /* Leave some memory for later buffers. */ + size_t ram_free = Ram_Free(MEM_NORMAL); + + /* In case we REALLY are near our limit, to avoid underflow. */ + if (ram_free > eps) { + ram_free -= eps; + } + + if (ram_free > INITIAL_BIG_SHAPE_BUFFER_SIZE + THEATER_BIG_SHAPE_BUFFER_SIZE) { + BigShapeBufferLength = INITIAL_BIG_SHAPE_BUFFER_SIZE; + TheaterShapeBufferLength = THEATER_BIG_SHAPE_BUFFER_SIZE; + } else if (ram_free > 768 * 1024) { + /* Try to distribute the memory we have between the two buffers. + BigShapeBuffer is more used and require more memory. This formula + has been archived by linear interpolating the two BigShapeBuffer + sizes from the TD and RA ports of Nintendo DS: + + f(7500) = 6800; + f(1500) = 1200; + + */ + + BigShapeBufferLength = ((ram_free * 56) / 60 - (200 * 1024)) & (~0x1FFUL); + TheaterShapeBufferLength = ram_free - BigShapeBufferLength; + + DBG_LOG("BigShape: %ldk\n", BigShapeBufferLength / 1024); + DBG_LOG("TheaterShape: %ldk\n", TheaterShapeBufferLength / 1024); + } else { + + /* Too little memory available. Disable BigShapeBuffers to avoid + constant flushing of buffers. */ + + UseBigShapeBuffer = false; + OriginalUseBigShapeBuffer = false; + + goto skip_bigshp_allocation; + } + BigShapeBufferStart = (char*)Alloc(BigShapeBufferLength, MEM_NORMAL); BigShapeBufferPtr = BigShapeBufferStart; @@ -323,6 +369,8 @@ uintptr_t Build_Frame(void const* dataptr, unsigned short framenumber, void* buf } } +skip_bigshp_allocation: + // calc buff size buffsize = keyfr.width * keyfr.height; diff --git a/common/lcw.cpp b/common/lcw.cpp index cdeaabd2..d571d3b7 100644 --- a/common/lcw.cpp +++ b/common/lcw.cpp @@ -32,6 +32,7 @@ * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "lcw.h" +#include "wwstd.h" #include /*************************************************************************** @@ -67,7 +68,7 @@ * HISTORY: * * 03/20/1995 IML : Created. * *=========================================================================*/ -int LCW_Uncompress(void const* source, void* dest, unsigned length) +int OPTIMIZE_AGGRESSIVELY LCW_Uncompress(void const* source, void* dest, unsigned length) { unsigned char *source_ptr, *dest_ptr, *copy_ptr, *dest_end, op_code; unsigned count; diff --git a/common/linear_nds.cpp b/common/linear_nds.cpp new file mode 100644 index 00000000..7bffb963 --- /dev/null +++ b/common/linear_nds.cpp @@ -0,0 +1,242 @@ +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection +#include "linear.h" +#include "graphicsviewport.h" +#include "wwstd.h" +#include +#include +#include +#include + +extern "C" { +void memcpy32(void* dst, const void* src, unsigned int wdcount); +void rmemcpy(void* dst, const void* src, size_t n); +} + +// This function is optimized for the Nintendo DS. Huge performance increase +// when compared with the default from the default one. + +int OPTIMIZE_AGGRESSIVELY +Linear_Blit_To_Linear(void* thisptr, void* dest, int src_x, int src_y, int dst_x, int dst_y, int w, int h, int use_key) +{ + GraphicViewPortClass& src_vp = *static_cast(thisptr); + GraphicViewPortClass& dst_vp = *static_cast(dest); + unsigned char* src = reinterpret_cast(src_vp.Get_Offset()); + unsigned char* dst = reinterpret_cast(dst_vp.Get_Offset()); + int src_pitch = (src_vp.Get_Pitch() + src_vp.Get_XAdd() + src_vp.Get_Width()); + int dst_pitch = (dst_vp.Get_Pitch() + dst_vp.Get_XAdd() + dst_vp.Get_Width()); + + if (src_x >= src_vp.Get_Width() || src_y >= src_vp.Get_Height() || dst_x >= dst_vp.Get_Width() + || dst_y >= dst_vp.Get_Height() || h < 0 || w < 1) { + return 0; + } + + src_x = std::max(0, src_x); + src_y = std::max(0, src_y); + dst_x = std::max(0, dst_x); + dst_y = std::max(0, dst_y); + + h = (dst_y + h) > dst_vp.Get_Height() ? dst_vp.Get_Height() - 1 - dst_y : h; + w = (dst_x + w) > dst_vp.Get_Width() ? dst_vp.Get_Width() - 1 - dst_x : w; + + // move our pointers to the start locations + src += src_x + src_y * src_pitch; + dst += dst_x + dst_y * dst_pitch; + + // If src is before dst, we run the risk of overlapping memory regions so we + // need to move src and dst to the last line and work backwards + if (src < dst && dst < src + (h - 1) * src_pitch) { + unsigned char* esrc = src + (h - 1) * src_pitch; + unsigned char* edst = dst + (h - 1) * dst_pitch; + + if (dst < src + w) { + while (h-- != 0) { + rmemcpy(edst, esrc, w); + edst -= dst_pitch; + esrc -= src_pitch; + } + } else { + while (h-- != 0) { + memcpy(edst, esrc, w); + edst -= dst_pitch; + esrc -= src_pitch; + } + } + } else { + if ((uintptr_t)src % 4 == 0 && (uintptr_t)dst % 4 == 0) { + while (h-- != 0) { + memcpy32(dst, src, w / 4); + dst += dst_pitch; + src += src_pitch; + } + } else { + /* src or dst is unaligned. This happens when scrolling the screen. */ + while (h-- != 0) { + // Use memcpy, else the screen gets garbaged when scrolling on lower + // speed. This might affect performance when scrolling. + memcpy(dst, src, w); + dst += dst_pitch; + src += src_pitch; + } + } + } + + // Return supposed to match DDraw to 0 is DD_OK. + return 0; +} + +// Note that this function does not generate pixel perfect results when compared to the ASM implementation +// It should not generate anything that is visibly different without doing side byt side comparisons however. +bool Linear_Scale_To_Linear(void* thisptr, + void* dest, + int src_x, + int src_y, + int dst_x, + int dst_y, + int src_width, + int src_height, + int dst_width, + int dst_height, + bool trans, + char* remap) +{ + GraphicViewPortClass& src_vp = *static_cast(thisptr); + GraphicViewPortClass& dst_vp = *static_cast(dest); + // If there is nothing to scale, just return. + if (src_width <= 0 || src_height <= 0 || dst_width <= 0 || dst_height <= 0) { + return true; + } + + int src_x0 = src_x; + int src_y0 = src_y; + int dst_x0 = dst_x; + int dst_y0 = dst_y; + int dst_x1 = dst_width + dst_x; + int dst_y1 = dst_height + dst_y; + + // These ifs are all for clipping purposes incase coords are outside + // the expected area. + if (src_x < 0) { + src_x0 = 0; + dst_x0 = dst_x + ((dst_width * -src_x) / src_width); + } + + if (src_y < 0) { + src_y0 = 0; + dst_y0 = dst_y + ((dst_height * -src_y) / src_height); + } + + if (src_x + src_width > src_vp.Get_Width() + 1) { + dst_x1 = dst_x + (dst_width * (src_vp.Get_Width() - src_x) / src_width); + } + + if (src_y + src_height > src_vp.Get_Height() + 1) { + dst_y1 = dst_y + (dst_height * (src_vp.Get_Height() - src_y) / src_height); + } + + if (dst_x0 < 0) { + dst_x0 = 0; + src_x0 = src_x + ((src_width * -dst_x) / dst_width); + } + + if (dst_y0 < 0) { + dst_y0 = 0; + src_y0 = src_y + ((src_height * -dst_y) / dst_height); + } + + if (dst_x1 > dst_vp.Get_Width() + 1) { + dst_x1 = dst_vp.Get_Width(); + } + + if (dst_y1 > dst_vp.Get_Height() + 1) { + dst_y1 = dst_vp.Get_Height(); + } + + if (dst_y0 > dst_y1 || dst_x0 > dst_x1) { + return true; + } + + char* src = src_y0 * (src_vp.Get_Pitch() + src_vp.Get_XAdd() + src_vp.Get_Width()) + src_x0 + + reinterpret_cast(src_vp.Get_Offset()); + char* dst = dst_y0 * (dst_vp.Get_Pitch() + dst_vp.Get_XAdd() + dst_vp.Get_Width()) + dst_x0 + + reinterpret_cast(dst_vp.Get_Offset()); + dst_x1 -= dst_x0; + dst_y1 -= dst_y0; + int x_ratio = ((src_width << 16) / dst_x1) + 1; + int y_ratio = ((src_height << 16) / dst_y1) + 1; + + // trans basically means do we skip index 0 entries, thus treating them as + // transparent? + if (trans) { + if (remap != nullptr) { + for (int i = 0; i < dst_y1; ++i) { + char* d = dst + i * (dst_vp.Get_Pitch() + dst_vp.Get_XAdd() + dst_vp.Get_Width()); + char* s = src + ((i * y_ratio) >> 16) * (src_vp.Get_Pitch() + src_vp.Get_XAdd() + src_vp.Get_Width()); + int xrat = 0; + + for (int j = 0; j < dst_x1; ++j) { + unsigned char tmp = s[xrat >> 16]; + + if (tmp != 0) { + *d = (remap)[tmp]; + } + + ++d; + xrat += x_ratio; + } + } + } else { + for (int i = 0; i < dst_y1; ++i) { + char* d = dst + i * (dst_vp.Get_Pitch() + dst_vp.Get_XAdd() + dst_vp.Get_Width()); + char* s = src + ((i * y_ratio) >> 16) * (src_vp.Get_Pitch() + src_vp.Get_XAdd() + src_vp.Get_Width()); + int xrat = 0; + + for (int j = 0; j < dst_x1; ++j) { + unsigned char tmp = s[xrat >> 16]; + + if (tmp != 0) { + *d = tmp; + } + + ++d; + xrat += x_ratio; + } + } + } + } else { + if (remap != nullptr) { + for (int i = 0; i < dst_y1; ++i) { + char* d = dst + i * (dst_vp.Get_Pitch() + dst_vp.Get_XAdd() + dst_vp.Get_Width()); + char* s = src + ((i * y_ratio) >> 16) * (src_vp.Get_Pitch() + src_vp.Get_XAdd() + src_vp.Get_Width()); + int xrat = 0; + + for (int j = 0; j < dst_x1; ++j) { + *d++ = (remap)[s[xrat >> 16]]; + xrat += x_ratio; + } + } + } else { + for (int i = 0; i < dst_y1; ++i) { + char* d = dst + i * (dst_vp.Get_Pitch() + dst_vp.Get_XAdd() + dst_vp.Get_Width()); + char* s = src + ((i * y_ratio) >> 16) * (src_vp.Get_Pitch() + src_vp.Get_XAdd() + src_vp.Get_Width()); + int xrat = 0; + + for (int j = 0; j < dst_x1; ++j) { + *d++ = s[xrat >> 16]; + xrat += x_ratio; + } + } + } + } + + return true; +} diff --git a/common/memcpy.s b/common/memcpy.s new file mode 100644 index 00000000..03378e9f --- /dev/null +++ b/common/memcpy.s @@ -0,0 +1,174 @@ +@ === void memcpy32(void *dst, const void *src, uint wdcount) IWRAM_CODE; ============= +@ Source: https://www.coranac.com/tonc/text/asm.htm +@ r0, r1: dst, src +@ r2: wdcount, then wdcount>>3 +@ r3-r10: data buffer +@ r12: wdn&7 + .section .iwram,"ax", %progbits + .align 2 + .code 32 + .global memcpy32 + .type memcpy32 STT_FUNC +memcpy32: + and r12, r2, #7 @ r12= residual word count + movs r2, r2, lsr #3 @ r2=block count + beq .Lres_cpy32 + push {r4-r10} + @ Copy 32byte chunks with 8fold xxmia + @ r2 in [1,inf> +.Lmain_cpy32: + ldmia r1!, {r3-r10} + stmia r0!, {r3-r10} + subs r2, #1 + bne .Lmain_cpy32 + pop {r4-r10} + @ And the residual 0-7 words. r12 in [0,7] +.Lres_cpy32: + subs r12, #1 + ldrcs r3, [r1], #4 + strcs r3, [r0], #4 + bcs .Lres_cpy32 + bx lr + +/* +=============================================================================== + ABI: + __aeabi_memcpy, __aeabi_memcpy4, __aeabi_memcpy8 + Standard: + memcpy + Support: + __agbabi_memcpy2 + Copyright (C) 2021-2022 agbabi contributors + For conditions of distribution and use, see copyright notice in LICENSE.md + Source: https://github.com/felixjones/agbabi/blob/2.0/source/memcpy.s + + + + libagbabi is available under the zlib license : + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it freely, + subject to the following restrictions: + + The origin of this software must not be misrepresented; you must not claim + that you wrote the original software. If you use this software in a product, + an acknowledgment in the product documentation would be appreciated but is not + required. + + Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + This notice may not be removed or altered from any source distribution. + +=============================================================================== +*/ + + .arm + .align 2 + + .section .iwram.__aeabi_memcpy, "ax", %progbits + .global __aeabi_memcpy +__aeabi_memcpy: + // Check pointer alignment + eor r3, r1, r0 + // JoaoBapt carry & sign bit test + movs r3, r3, lsl #31 + bmi .Lcopy1 + bcs .Lcopy2 + +.Lcopy4: + // Handle <= 2 byte copies byte-by-byte + cmp r2, #2 + ble .Lcopy1 + + // Copy half and byte head + rsb r3, r0, #4 + // JoaoBapt carry & sign bit test + movs r3, r3, lsl #31 + ldrmib r3, [r1], #1 + strmib r3, [r0], #1 + submi r2, r2, #1 + ldrcsh r3, [r1], #2 + strcsh r3, [r0], #2 + subcs r2, r2, #2 + // Fallthrough + + .global __aeabi_memcpy8 +__aeabi_memcpy8: + .global __aeabi_memcpy4 +__aeabi_memcpy4: + // Copy 8 words + movs r12, r2, lsr #5 + beq .Lskip32 + lsl r3, r12, #5 + sub r2, r2, r3 + push {r4-r10} +.LcopyWords8: + ldmia r1!, {r3-r10} + stmia r0!, {r3-r10} + subs r12, r12, #1 + bne .LcopyWords8 + pop {r4-r10} +.Lskip32: + + // Copy words + movs r12, r2, lsr #2 +.LcopyWords: + subs r12, r12, #1 + ldrhs r3, [r1], #4 + strhs r3, [r0], #4 + bhs .LcopyWords + + // Copy half and byte tail + // JoaoBapt carry & sign bit test + movs r3, r2, lsl #31 + ldrcsh r3, [r1], #2 + strcsh r3, [r0], #2 + ldrmib r3, [r1] + strmib r3, [r0] + bx lr + +.Lcopy2: + // Copy byte head + tst r0, #1 + cmpne r2, #0 + ldrneb r3, [r1], #1 + strneb r3, [r0], #1 + subne r2, r2, #1 + // Fallthrough + + .global __agbabi_memcpy2 +__agbabi_memcpy2: + // Copy halves + movs r12, r2, lsr #1 +.LcopyHalves: + subs r12, r12, #1 + ldrhsh r3, [r1], #2 + strhsh r3, [r0], #2 + bhs .LcopyHalves + + // Copy byte tail + tst r2, #1 + ldrneb r3, [r1] + strneb r3, [r0] + bx lr + +.Lcopy1: + subs r2, r2, #1 + ldrhsb r3, [r1], #1 + strhsb r3, [r0], #1 + bhs .Lcopy1 + bx lr + + .section .iwram.memcpy, "ax", %progbits + .global memcpy + .type memcpy STT_FUNC +memcpy: + push {r0, lr} + bl __aeabi_memcpy + pop {r0, lr} + bx lr diff --git a/common/memflag.h b/common/memflag.h index ad05bd22..45971721 100644 --- a/common/memflag.h +++ b/common/memflag.h @@ -64,7 +64,8 @@ void* operator new[](size_t size, MemoryFlagType flag); void* Alloc(size_t bytes_to_alloc, MemoryFlagType flags); void Free(void const* pointer); void* Resize_Alloc(void* original_ptr, size_t new_size_in_bytes); -int Ram_Free(MemoryFlagType flag); +size_t Ram_Free(MemoryFlagType flag); +size_t Ram_Free(void); int Heap_Size(MemoryFlagType flag); int Total_Ram_Free(MemoryFlagType flag); diff --git a/common/memmove.s b/common/memmove.s new file mode 100644 index 00000000..66e56f38 --- /dev/null +++ b/common/memmove.s @@ -0,0 +1,49 @@ +/* +=============================================================================== + ABI: + __aeabi_memmove, __aeabi_memmove4, __aeabi_memmove8 + Standard: + memmove + Copyright (C) 2021-2022 agbabi contributors + For conditions of distribution and use, see copyright notice in LICENSE.md +=============================================================================== +*/ + + .arm + .align 2 + + .section .iwram.__aeabi_memmove, "ax", %progbits + .global __aeabi_memmove +__aeabi_memmove: + cmp r0, r1 + .extern __agbabi_rmemcpy + bgt __agbabi_rmemcpy + .extern __aeabi_memcpy + b __aeabi_memcpy + + .global __aeabi_memmove8 +__aeabi_memmove8: + .global __aeabi_memmove4 +__aeabi_memmove4: + cmp r0, r1 + .extern __agbabi_rmemcpy4 + bgt __agbabi_rmemcpy4 + .extern __aeabi_memcpy4 + b __aeabi_memcpy4 + + .global __agbabi_memmove2 +__agbabi_memmove2: + cmp r0, r1 + .extern __agbabi_rmemcpy2 + bgt __agbabi_rmemcpy2 + .extern __agbabi_memcpy2 + b __agbabi_memcpy2 + + .section .iwram.memmove, "ax", %progbits + .global memmove + .type memmove STT_FUNC +memmove: + push {r0, lr} + bl __aeabi_memmove + pop {r0, lr} + bx lr diff --git a/common/paths_nds.cpp b/common/paths_nds.cpp new file mode 100644 index 00000000..d8af068f --- /dev/null +++ b/common/paths_nds.cpp @@ -0,0 +1,148 @@ +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection +#include "paths.h" +#include "debugstring.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +/* DS is quasi-posix compliant: it don't provide some functions. */ +extern "C" { + +const char* basename(const char* path) +{ + const char* base = strrchr(path, '/'); + + /* If strrchr returned non-null, it means that it found the last '/' in the + * path, so add one to get the base name. */ + if (base) { + return base + 1; + } + + return path; +} +} + +/* Nintendo DS require its filesystem structures to be explicitely initialized. */ +void DS_Filesystem_Init() +{ + static bool fs_initialized = false; + + if (fs_initialized) + return; + + if (!fatInitDefault()) { + DBG_LOG("FATAL ERROR: Unable to initialize file system"); + swiWaitForVBlank(); + while (1) + ; + } + + fs_initialized = true; +} + +namespace +{ + const std::string& User_Home() + { + return std::string("/vanilla-conquer"); + } + + std::string Get_Posix_Default(const char* env_var, const char* relative_path) + { + const char* tmp = std::getenv(env_var); + + if (tmp != nullptr && tmp[0] != '\0') { + if (tmp[0] != '/') { + char buffer[200]; + std::snprintf(buffer, + sizeof(buffer), + "'%s' should start with '/' as per XDG specification that the value must be absolute. " + "The current value is: " + "'%s'.", + tmp, + env_var); + DBG_WARN(buffer); + } else { + return tmp; + } + } + + return User_Home() + "/" + relative_path; + } +} // namespace + +const char* PathsClass::Program_Path() +{ + if (ProgramPath.empty()) { + ProgramPath = std::string("/vanilla-conquer/") + Suffix; + } + + return ProgramPath.c_str(); +} + +const char* PathsClass::Data_Path() +{ + if (DataPath.empty()) { + DataPath = std::string("/vanilla-conquer/") + Suffix; + } + + return DataPath.c_str(); +} + +const char* PathsClass::User_Path() +{ + if (UserPath.empty()) { + UserPath = std::string("/vanilla-conquer/") + Suffix; + } + + return UserPath.c_str(); +} + +bool PathsClass::Create_Directory(const char* dirname) +{ + bool ret = true; + + if (dirname == nullptr) { + return ret; + } + + std::string temp = dirname; + size_t pos = 0; + do { + pos = temp.find_first_of("/", pos + 1); + if (mkdir(temp.substr(0, pos).c_str(), 0700) != 0) { + if (errno != EEXIST) { + ret = false; + break; + } + } + } while (pos != std::string::npos); + + return ret; +} + +bool PathsClass::Is_Absolute(const char* path) +{ + return path != nullptr && path[0] == '/'; +} + +std::string PathsClass::Argv_Path(const char* cmd_arg) +{ + return std::string("/vanilla-conquer"); +} diff --git a/common/rawfile.cpp b/common/rawfile.cpp index 4db8be8b..c2a8efef 100644 --- a/common/rawfile.cpp +++ b/common/rawfile.cpp @@ -874,7 +874,6 @@ int RawFileClass::Raw_Seek(int pos, int dir) if (!Is_Open()) { Error(EBADF, false, Filename); } else { - clearerr(Handle); /* @@ -892,6 +891,7 @@ int RawFileClass::Raw_Seek(int pos, int dir) pos = ftell(Handle); } + /* ** Return with the new position of the file. This will range between zero and the number of ** bytes the file contains. diff --git a/common/rmemcpy.s b/common/rmemcpy.s new file mode 100644 index 00000000..03f8a38c --- /dev/null +++ b/common/rmemcpy.s @@ -0,0 +1,126 @@ +/* +=============================================================================== + Support: + __agbabi_rmemcpy, __agbabi_rmemcpy2, __agbabi_rmemcpy4 + Copyright (C) 2021-2022 agbabi contributors + For conditions of distribution and use, see copyright notice in LICENSE.md +=============================================================================== +*/ + + .arm + .align 2 + + .section .iwram.__agbabi_rmemcpy, "ax", %progbits + .global __agbabi_rmemcpy +__agbabi_rmemcpy: + // Adjust pointers + add r0, r0, r2 + add r1, r1, r2 + + // Check pointer alignment + eor r3, r1, r0 + // JoaoBapt carry & sign bit test + movs r3, r3, lsl #31 + bmi .Lcopy1 + bcs .Lcopy2 + +.Lcopy4: + // Handle <= 2 byte copies byte-by-byte + cmp r2, #2 + ble .Lcopy1 + + // Copy byte and half tail + movs r3, r0, lsl #31 + ldrmib r3, [r1, #-1]! + strmib r3, [r0, #-1]! + submi r2, r2, #1 + ldrcsh r3, [r1, #-2]! + strcsh r3, [r0, #-2]! + subcs r2, r2, #2 + + b .LskipAdjust4 + + .global __agbabi_rmemcpy4 +__agbabi_rmemcpy4: + // Adjust pointers + add r0, r0, r2 + add r1, r1, r2 + +.LskipAdjust4: + // Copy 8 words + movs r12, r2, lsr #5 + beq .Lskip32 + lsl r3, r12, #5 + sub r2, r2, r3 + push {r4-r10} +.LcopyWords8: + ldmdb r1!, {r3-r10} + stmdb r0!, {r3-r10} + subs r12, r12, #1 + bne .LcopyWords8 + pop {r4-r10} +.Lskip32: + + // Copy words + movs r12, r2, lsr #2 +.LcopyWords: + subs r12, r12, #1 + ldrhs r3, [r1, #-4]! + strhs r3, [r0, #-4]! + bhs .LcopyWords + + // Copy half and byte head + // JoaoBapt carry & sign bit test + movs r3, r2, lsl #31 + ldrcsh r3, [r1, #-2]! + strcsh r3, [r0, #-2]! + ldrmib r3, [r1, #-1] + strmib r3, [r0, #-1] + bx lr + +.Lcopy2: + // Copy byte tail + tst r0, #1 + cmpne r2, #0 + ldrneb r3, [r1, #-1]! + strneb r3, [r0, #-1]! + subne r2, r2, #1 + + b .LskipAdjust2 + + .global __agbabi_rmemcpy2 +__agbabi_rmemcpy2: + // Adjust pointers + add r0, r0, r2 + add r1, r1, r2 + +.LskipAdjust2: + // Copy halves + movs r12, r2, lsr #1 +.LcopyHalves: + subs r12, r12, #1 + ldrhsh r3, [r1, #-2]! + strhsh r3, [r0, #-2]! + bhs .LcopyHalves + + // Copy byte head + tst r2, #1 + ldrneb r3, [r1, #-1] + strneb r3, [r0, #-1] + bx lr + +.Lcopy1: + subs r2, r2, #1 + ldrhsb r3, [r1, #-1]! + strhsb r3, [r0, #-1]! + bhs .Lcopy1 + bx lr + + .section .iwram.memmove, "ax", %progbits + .global rmemcpy + .type rmemcpy STT_FUNC +rmemcpy: + push {r0, lr} + bl __agbabi_rmemcpy + pop {r0, lr} + bx lr diff --git a/common/sockets.h b/common/sockets.h index fb2b052c..b083e4ec 100644 --- a/common/sockets.h +++ b/common/sockets.h @@ -32,12 +32,15 @@ static inline int socket_cleanup(void) #else /* Assume posix style sockets on non-windows */ +#ifndef _NDS #include -#include #include // for getaddrinfo() and freeaddrinfo() #include #include #include +#endif + +#include #include // for close() typedef int SOCKET; #define INVALID_SOCKET (-1) diff --git a/common/soundio_nds.cpp b/common/soundio_nds.cpp new file mode 100644 index 00000000..188451a3 --- /dev/null +++ b/common/soundio_nds.cpp @@ -0,0 +1,409 @@ +#include "audio.h" +#include "memflag.h" +#include "file.h" +#include +#include +#include +#include +#include +#include +#include "audio_fifocommon.h" + +/** Sound interface between C&C and the ARM7 chip. + * + * Author: mrparrot (aka giulianob) + * + * This engine is asynchronous and it was designed to not lag behind the main + * console CPU. Every sound or music is sent either using a message or a + * command, which is then processed from the ARM7 side. No decompression is + * done here, so its overhead is minimal. + **/ + +/* Converts C&C volume unit to value which the DS supports. DS volume ranges + from 0 (min) to 127 (max), where the game ranges from 0 to 255. This + conversion is not 100% precise but is good enough. */ +#define VOL_CNC2DS(x) ((x) / 2) +#define VOL_DS2CNC(x) ((x)*2) + +/* Some enumerations defining global constants.*/ +enum +{ + VOLUME_MIN = 0, + VOLUME_MAX = 255, + DS_VOLUME_MAX = VOL_CNC2DS(VOLUME_MAX), // Max volume supported by DS. + INVALID_AUDIO_HANDLE = -1, + INVALID_FILE_HANDLE = -1, +}; + +/* Define a music handle as being a 16-bit unsigned number ranging from + 0 to (2**16-1). We do this way because we communicate the handle using + a command rather than a message, and we can only use a 16-bit integer + as an argument to command. This is good enough, as it will take a while + to wrap and hopefully the previous sound that got the same handle was + already stopped. */ +static inline u16 Get_Next_Handle() +{ + static u16 handle = 0; + return ++handle; +} + +// Everything down there is present to conform to the game's API. +bool StreamLowImpact = false; // Unused, required by game engine. + +SFX_Type SoundType; // Required by game engine. +Sample_Type SampleType; // Required by game engine +static int DigiHandle = INVALID_AUDIO_HANDLE; // Required by game engine. +static bool AudioInitialized = false; // Flags that the audio was already initialized. + +/* Defines the current volume that the user set in the game settings. This has + nothing to do with the DS hardware volume which you setup by pressing some + buttons on its case. */ +static int SampleVolume = DS_VOLUME_MAX; + +/* This class represents a music buffer. It is used to stream music data to the + ARM7 chip, as it doesn't have enough memory to load the entire music there. + + This works as following: The game calls Set_File_Stream, which will open + the music file, read a chunk of data and send it to the ARM7 CPU through + a message. When the ARM7 realizes that it has already processed a good + amount of this chunk, it then sends a USR2::REQUEST_MUSIC_DATA to the + ARM9 chip, which ends up calling Mark_For_Update, and then we update it + on Update_File_Stream. + + We keep track of its volume and if it is playing on this chip because we + need to constantly communicate with the game that it is playing. +*/ +class MusicBuffer +{ +public: + // Basic constructor. + MusicBuffer() + { + memset(this, 0, sizeof(*this)); + Handle = -1; + Volume = DS_VOLUME_MAX; + } + + // Set muisic volume. + inline int Set_Volume(int volume) + { + int oldvol = Volume; + unsigned command = USR1::SET_MUSIC_VOL | (volume & 0xFFFF); + Volume = volume; + return oldvol; + } + + // Checks if given handle is a music handle. + inline bool Is_Music_Handle(u16 handle) + { + if (handle == Handle) + return true; + + return false; + } + + // Check if the music is playing. + inline bool Sample_Status() + { + return IsPlaying; + } + + // Set music streaming from filename. + inline int Set_File_Stream(const char* filename, unsigned char volume) + { + int bytes; + + // If volume is too low then disable music to save resources + if (Volume <= 0) { + return INVALID_AUDIO_HANDLE; + } + + FileHandle = Open_File(filename, 1); + if (FileHandle == INVALID_FILE_HANDLE) { + return INVALID_AUDIO_HANDLE; + } + + if (Buffer == NULL) { + Buffer = (char*)malloc(2 * MUSIC_CHUNK_SIZE); + } + + if (Buffer == NULL) { + printf("Failure allocating music buffer\n"); + while (1) + ; + } + + memset(Buffer, 0, 2 * MUSIC_CHUNK_SIZE); + + bytes = Read_File(FileHandle, Buffer, 2 * MUSIC_CHUNK_SIZE); + if (bytes == 0) { + IsPlaying = false; + return INVALID_AUDIO_HANDLE; + } + + ToUpdate = 0; + IsPlaying = true; + HasMoreSource = true; + Handle = Get_Next_Handle(); + Send_Chunk(); + return Handle; + } + + // Forcely stops the file stream. + inline int Stop_File_Stream() + { + ShouldBeUpdated = 0; + IsPlaying = false; + HasMoreSource = false; + if (FileHandle >= 0) + Close_File(FileHandle); + FileHandle = -1; + return 0; + } + + // Mark file stream to be updated on the next maintenance call. + inline void Mark_For_Update() + { + ShouldBeUpdated++; + } + + // Update the music chunk with more data. + inline void Update_File_Stream() + { + if (ShouldBeUpdated == 0) + return; + + ShouldBeUpdated--; + + if (!Buffer || !IsPlaying) + return; + + // We use a double-buffer so that we always update the part which is not + // yet playing. + unsigned char* to_update = (unsigned char*)Buffer + ToUpdate * MUSIC_CHUNK_SIZE; + if (HasMoreSource) { + int bytes = Read_File(FileHandle, to_update, MUSIC_CHUNK_SIZE); + if (bytes == 0) { + HasMoreSource = false; + Close_File(FileHandle); + FileHandle = -1; + } + } else { + IsPlaying = false; + } + + ToUpdate = (ToUpdate + 1) % 2; + } + + // Send the music chunk to the ARM7. + inline void Send_Chunk() + { + if (!Buffer) + return; + + USR1::FifoMessage msg; + + msg.type = USR1::SOUND_PLAY_MESSAGE; + msg.SoundPlay.priority = 255; + msg.SoundPlay.handle = Handle; + msg.SoundPlay.data = Buffer; + msg.SoundPlay.volume = Volume; + msg.SoundPlay.pan = 64; + msg.SoundPlay.is_music = true; + + // Asynchronous send sound play command + fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg); + } + +private: + char* Buffer; // Music chunk data loaded. + int FileHandle; // Music file descriptor. + bool IsPlaying; // Is it playing? + int ToUpdate; // Which side of the buffer should be updated? + int ShouldBeUpdated; // Should this buffer be updated? + u16 Handle; // Sound Handle. + unsigned char Volume; // Volume + bool HasMoreSource; // Is there more stuff in the file? +}; + +// Declare the Music Buffer instance. +static MusicBuffer MBuffer; + +// Declare the command handler which will decompress messages from ARM9 related +// to this sound engine. +void user02CommandHandler(u32 command, void* userdata) +{ + int cmd = (command)&0x00F00000; + int data = command & 0xFFFF; + int channel = (command >> 16) & 0xF; + + switch (cmd) { + + case USR2::MUSIC_REQUEST_CHUNK: + MBuffer.Mark_For_Update(); + break; + + default: + break; + } +} + +/* -------------------------------------------------------------------- + * Everything down here is an interface to the game's engine. + * -------------------------------------------------------------------- + */ + +int File_Stream_Sample(char const* filename, bool real_time_start) +{ + return File_Stream_Sample_Vol(filename, 255, real_time_start); +}; + +// Open a audio file for streaming. Assume this is music. +int File_Stream_Sample_Vol(char const* filename, int volume, bool real_time_start) +{ + return MBuffer.Set_File_Stream(filename, VOL_CNC2DS(volume)); +}; + +// Sound maintenance callback. This is called at most every frame to update +// audio-related stuff. In our case we only have to be concerned about the +// music stream. +void Sound_Callback(void) +{ + MBuffer.Update_File_Stream(); +} + +// Unused but required by game engine. +void maintenance_callback(void){}; + +// Unsused but required by game engine. +void* Load_Sample(char const* filename) +{ + return nullptr; +}; + +// Unused but required by game engine. +void Free_Sample(void const* sample){}; + +// Initialize audio-related structures. +bool Audio_Init(int bits_per_sample, bool stereo, int rate, bool reverse_channels) +{ + // Initialize Nintendo DS sound system. + soundEnable(); + + // Install ARM7 to ARM9 Queue, used to request music data. + fifoSetValue32Handler(FIFO_USER_02, user02CommandHandler, 0); + + // Set Global structures required by game's API. + SoundType = SFX_ALFX; + SampleType = SAMPLE_SB; + DigiHandle = 1; + AudioInitialized = true; + + return true; +}; + +// Unused, but requited by game engine. +void Sound_End(void) +{ +} + +// Stop a sample by its handle. +void Stop_Sample(int handle) +{ + if (MBuffer.Is_Music_Handle(handle)) { + // In case it is music, we need to stop our file stream. + MBuffer.Stop_File_Stream(); + } + + // Send command to the ARM7 to stop the stuff that is going there. + unsigned command = USR1::STOP_SAMPLE_HANDLE | ((u32)handle & 0xFFFF); + fifoSendValue32(FIFO_USER_01, command); +} + +bool Sample_Status(int handle) +{ + // It seems we only need to implement this for music? + if (MBuffer.Is_Music_Handle(handle)) + return MBuffer.Sample_Status(); + + return false; +}; + +bool Is_Sample_Playing(void const* sample) +{ + /* Don't implement that. It is called constantly and may flood the ARM7 + with messages more than we already is. */ + return false; +}; + +/* Stop a sample that is playing. We just pass that to the ARM7... */ +void Stop_Sample_Playing(void const* sample) +{ + unsigned command = USR1::STOP_SAMPLE | ((u32)sample & 0xFFFF); + fifoSendValue32(FIFO_USER_01, command); +}; + +/* Play a sample. We just pass that to the ARM7. */ +int Play_Sample(void const* sample, int priority, int volume, signed short panloc) +{ + u16 handle = Get_Next_Handle(); + USR1::FifoMessage msg; + + // We set the samples volume by actually lowering the volume argument + // passed to the ARM7. + volume = (SampleVolume * VOL_CNC2DS(volume)) / DS_VOLUME_MAX; + + msg.type = USR1::SOUND_PLAY_MESSAGE; + msg.SoundPlay.priority = priority; + msg.SoundPlay.handle = handle; + msg.SoundPlay.data = sample; + msg.SoundPlay.volume = volume; + + // Convert the panloc to a value the DS understand. + msg.SoundPlay.pan = ((int)panloc + 32767) / 517; + msg.SoundPlay.is_music = false; + + // Asynchronous send sound play command + fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg); + + return handle; +} + +// Set global sound volume. +int Set_Sound_Vol(int volume) +{ + int oldval = VOL_DS2CNC(SampleVolume); + SampleVolume = VOL_CNC2DS(volume); + return oldval; +}; + +// Set music volume. +int Set_Score_Vol(int volume) +{ + return VOL_DS2CNC(MBuffer.Set_Volume(VOL_CNC2DS(volume))); +}; + +// This actually has to fade the sample slowly until it stops playing. But we +// don't care too much about that so we just stop the sample right away. +void Fade_Sample(int handle, int ticks) +{ + Stop_Sample(handle); +} + +// Return the DigiHandle, necessary to the game engine. +int Get_Digi_Handle(void) +{ + return DigiHandle; +} + +// Unused, but required by the game engine. +bool Set_Primary_Buffer_Format(void) +{ + return 0; +} + +// Unused, but required by the game engine. +bool Start_Primary_Sound_Buffer(bool forced) +{ + return 0; +} diff --git a/common/timer.cpp b/common/timer.cpp index ea6a5c05..1fa21c8d 100644 --- a/common/timer.cpp +++ b/common/timer.cpp @@ -40,18 +40,14 @@ #include "timer.h" -#include - -using namespace std::chrono; - ///////////////////////////////////////////////////////////////////////////////// /////////////////////////////// Global Data ///////////////////////////////////// -// Global timers that the library or user can count on existing. -TimerClass WinTickCount(BT_SYSTEM); CountDownTimerClass CountDown(BT_SYSTEM, false); bool TimerSystemOn = false; -WinTimerClass WindowsTimer(60); + +// Reference to system timer, in wintimer.cpp +extern WinTimerClass WindowsTimer; ///////////////////////////////////////////////////////////////////////////////// /////////////////////////////////// Code //////////////////////////////////////// @@ -199,109 +195,6 @@ int TimerClass::Set(int value, bool start) return (Time()); } -///////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////// Code //////////////////////////////////////// - -/*************************************************************************** - * WinTimerClass::WinTimerClass -- Initialize the WW timer system. * - * * - * * - * INPUT: unsigned int : user timer frequency. * - * * - * OUTPUT: * - * * - * WARNINGS: * - * * - * HISTORY: * - * 10/5/95 3:47PM : ST Created. * - *=========================================================================*/ -WinTimerClass::WinTimerClass() - : Frequency(60) - , Start(Now()) -{ - WinTickCount.Start(); - TimerSystemOn = true; -} - -WinTimerClass::WinTimerClass(unsigned int freq) - : Frequency(freq) - , Start(Now()) -{ - WinTickCount.Start(); - TimerSystemOn = true; -} - -/*************************************************************************** - * WinTimerClass::~WinTimerClass -- Removes the timer system. * - * * - * * - * INPUT: NONE. * - * * - * OUTPUT: bool was it removed successfuly * - * * - * WARNINGS: * - * * - * HISTORY: * - * 10/5/95 3:47PM : ST Created. * - *=========================================================================*/ -WinTimerClass::~WinTimerClass() -{ -} - -void WinTimerClass::Init(unsigned int freq) -{ - WindowsTimer = WinTimerClass(freq); -} - -/*********************************************************************************************** - * WinTimerClass::Get_System_Tick_Count -- returns the system tick count * - * * - * INPUT: Nothing * - * * - * OUTPUT: tick count * - * * - * WARNINGS: None * - * * - * HISTORY: * - * 10/5/95 4:02PM ST : Created * - *=============================================================================================*/ - -unsigned int WinTimerClass::Get_System_Tick_Count() -{ - if (Frequency == 0) { - return 0; - } - unsigned long long delta = Now() - Start; - return (unsigned int)(delta / (1000 / Frequency)); -} - -/*********************************************************************************************** - * WinTimerClass::Get_User_Tick_Count -- returns the user tick count * - * * - * INPUT: Nothing * - * * - * OUTPUT: tick count * - * * - * WARNINGS: None * - * * - * HISTORY: * - * 10/5/95 4:02PM ST : Created * - *=============================================================================================*/ - -unsigned int WinTimerClass::Get_User_Tick_Count() -{ - if (Frequency == 0) { - return 0; - } - unsigned long long delta = Now() - Start; - return (unsigned int)(delta / (1000 / Frequency)); -} - -unsigned long long WinTimerClass::Now() -{ - return duration_cast(system_clock::now().time_since_epoch()).count(); -} - int SystemTimerClass::operator()() const { return WindowsTimer.Get_System_Tick_Count(); diff --git a/common/timer.h b/common/timer.h index 654a881f..8ad9172e 100644 --- a/common/timer.h +++ b/common/timer.h @@ -163,11 +163,11 @@ class WinTimerClass unsigned int Get_System_Tick_Count(); unsigned int Get_User_Tick_Count(); + static unsigned long long Now(); + private: unsigned int Frequency; // Frequency of our windows timer in ticks per second unsigned long long Start; - - static unsigned long long Now(); }; ////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/common/video.h b/common/video.h index 6e9ae737..6b095832 100644 --- a/common/video.h +++ b/common/video.h @@ -61,9 +61,14 @@ class SurfaceMonitorClass extern SurfaceMonitorClass& AllSurfaces; // List of all surfaces bool Set_Video_Mode(int w, int h, int bits_per_pixel); -void Get_Video_Scale(float& x, float& y); void Set_Video_Cursor_Clip(bool clipped); +#ifdef _NDS /* Nintendo DS doesn't have FPU unit. */ +void Get_Video_Scale(int& x, int& y); +void Move_Video_Mouse(int xrel, int yrel); +#else +void Get_Video_Scale(float& x, float& y); void Move_Video_Mouse(float xrel, float yrel); +#endif void Get_Video_Mouse(int& x, int& y); void Toggle_Video_Fullscreen(); void Reset_Video_Mode(); diff --git a/common/video_nds.cpp b/common/video_nds.cpp new file mode 100644 index 00000000..9f1dff10 --- /dev/null +++ b/common/video_nds.cpp @@ -0,0 +1,676 @@ + +#include "gbuffer.h" +#include "palette.h" +#include "video.h" +#include "video.h" +#include "wwkeyboard.h" +#include "wwmouse.h" +#include +#include +#include + +/** Video interface for the Nintendo DSi. + * + * Author: mrparrot (aka giulianob) + * + * This video engine provides a drawable surface to the game's engine by + * setting up a 512x256 8-bit background. When the game is ready to draw + * to the screen, the game `Blt` the 'HidBuff' into this final plane + * (SeenBuff, or frontSurface). We also provide functions for Zooming in + * and out the screen, as the game expect at least a 320x200 screen, but + * the DSi resolution is 256x192. The default mode is use the GPU's + * affine transformations to 'compress' the background in order to fit + * into screen, with the consequence of losing a few lines. + **/ + +// Uncomment this to show FPS on screen. +//#define SHOW_FPS + +/* Function used to pause the console for debugging. */ +void DS_Pause(const char* format, ...) +{ + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + while (1) { + swiWaitForVBlank(); + scanKeys(); + int keys = keysDown(); + if (keys & KEY_A) + break; + } +} + +// Background 3 identifier. Set on video initialization, and used on the seen +// surface buffer. +static int bg3; + +// Mark the status of zoom. True means we are zoomed in, false means we are +// zoomed out. +static bool ZoomState = false; + +// Zoom in the Seen Surface. +void DS_SeenBuff_ZoomIn(void) +{ + int scale_x = (SCREEN_WIDTH * 256) / SCREEN_WIDTH; + int scale_y = (SCREEN_HEIGHT * 256) / SCREEN_HEIGHT; + + bgSetRotateScale(bg3, 0, scale_x, scale_y); + bgSetScroll(bg3, 32, 4); + + ZoomState = true; + + swiWaitForVBlank(); + bgUpdate(); +} + +// Zoom out the Seen Surface. +void DS_SeenBuff_ZoomOut(void) +{ + int scale_x = (320 * 256) / SCREEN_WIDTH; + int scale_y = (200 * 256) / SCREEN_HEIGHT; + + bgSetRotateScale(bg3, 0, scale_x, scale_y); + bgSetScroll(bg3, 0, 0); + + ZoomState = false; + + swiWaitForVBlank(); + bgUpdate(); +} + +void DS_SeenBuff_SwitchZoom() +{ + if (ZoomState == true) { + DS_SeenBuff_ZoomOut(); + } else { + DS_SeenBuff_ZoomIn(); + } +} + +/* + * We implement a 'hardware' cursor by using a sprite. It looks better than + * blitting the cursor into the seenbuff. + */ +class HardwareCursor +{ +public: + void Init() + { + + // Allocate 16Kb for the mouse sprites. + vramSetBankG(VRAM_G_MAIN_SPRITE_0x06400000); + + // Initialize the sprte engine. + oamInit(&oamMain, SpriteMapping_1D_256, false); + Surface = oamAllocateGfx(&oamMain, SpriteSize_32x32, SpriteColorFormat_256Color); + + X = 160; + Y = 100; + + oamSet(&oamMain, + 0, + X, + Y, + 0, + 0, + SpriteSize_32x32, + SpriteColorFormat_256Color, + Surface, + 0, + false, + false, + false, + false, + false); + + // Disable sprite scaling and rotating, we won't need it and we require + // it to be disabled to hide the sprite. + oamMain.oamMemory->isRotateScale = false; + } + + inline void Set_Cursor_Palette(const u16* palette) + { + // Copy the palette to the sprite engine. + dmaCopy(palette, SPRITE_PALETTE, 2 * 256); + } + + inline void Set_Video_Cursor(void* cursor, int w, int h, int hotx, int hoty) + { + Raw = cursor; + W = w; + H = h; + HotX = hotx; + HotY = hoty; + + uint8_t* src = (uint8_t*)Raw; + uint8_t* dst = (uint8_t*)Surface; + + // Mouse sprites aren't stored in VRAM so we only copy the new mouse + // shape in case it changes. + if (Raw != Last_Raw) { + Raw = Last_Raw; + + // DS sprites are tiled, so we remap the texture to be displayed + // correctly. + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + for (int ii = 0; ii < 8; ii++) { + for (int jj = 0; jj < 8; jj++) { + int real_j = 8 * j + jj; + int real_i = 8 * i + ii; + + if (real_j < w && real_i < h) + dst[256 * i + 8 * ii + 64 * j + jj] = src[real_i * w + real_j]; + } + } + } + } + } + } + + inline void Get_Video_Mouse(int& x, int& y) + { + x = X; + y = Y; + } + + inline void Set_Video_Cursor_Clip(bool clipped) + { + Clip = clipped; + } + + inline void Update_HWCursor() + { + /* Update sprite representing the mouse cursor. */ + int x_scaled; + int y_scaled; + + // Transform the coordinates to show the cursor in the correct + // screen position. + if (ZoomState) { + x_scaled = (X - HotX) - 32; + y_scaled = (Y - HotY) - 4; + } else { + x_scaled = ((X - HotX) * SCREEN_WIDTH) / 320; + y_scaled = ((Y - HotY) * SCREEN_HEIGHT) / 200; + } + + // Hide or show the cursor accordingly. + oamMain.oamMemory->isHidden = Get_Mouse_State(); + + // Update cursor sprite position + oamSetXY(&oamMain, 0, x_scaled, y_scaled); + oamUpdate(&oamMain); + } + + void Ensure_Mouse_Boundary(void) + { + if (X >= 320) { + X = 319; + } else if (X < 0) { + X = 0; + } + + if (Y >= 200) { + Y = 199; + } else if (Y < 0) { + Y = 0; + } + } + + void Move_Video_Mouse(int xrel, int yrel) + { + X += xrel; + Y += yrel; + + Ensure_Mouse_Boundary(); + } + + void Set_Video_Mouse(int x, int y) + { + if (ZoomState) { + X = x + 32; + Y = y + 4; + } else { + X = (x * 320) / SCREEN_WIDTH; + Y = (y * 200) / SCREEN_HEIGHT; + } + + // We don't need to ensure it. The input comes from the screen. + // Ensure_Mouse_Boundary(); + } + +private: + void* Raw; // Stores the cursor sprite untouched. + void* Surface; // Stores the sprite in 8x8 tiled format for the DS. + void* Last_Raw; // Stores the pointer to the last used sprite. + + bool Clip; // Flag to show or hide the mouse. + int X; // X position. + int Y; + int W; // Width. + int H; // Height + int HotX; // Cursor bias according to icon. + int HotY; +}; + +static HardwareCursor HWCursor; + +// Unsused. Required by the game's engine. +class SurfaceMonitorClassNDS : public SurfaceMonitorClass +{ + +public: + SurfaceMonitorClassNDS() + { + } + + virtual void Restore_Surfaces() + { + } + + virtual void Set_Surface_Focus(bool in_focus) + { + } + + virtual void Release() + { + } +}; + +SurfaceMonitorClassNDS AllSurfacesNDS; // List of all direct draw surfaces +SurfaceMonitorClass& AllSurfaces = AllSurfacesNDS; // List of all direct draw surfaces + +// VBlank timer function. +void Timer_VBlank(void); + +void On_VBlank() +{ + // Update the number of ticks passed. We use this to implement a 60FPS + // timer. Altough vanilla-conquer uses a milliseconds based timer, original + // game used a 60FPS timer. + Timer_VBlank(); +} + +bool Set_Video_Mode(int w, int h, int bits_per_pixel) +{ + // Allocate memory for our console. This has to be static because it must + // persists when this function exit, even if only used here. + static PrintConsole cs0; + + // Only turns on the 2D engine. The 3D chip is unused and disabling it + // should save battery life. + powerOn(POWER_ALL_2D); + + // If the ARM9 is set to 67MHz, set it to 133MHz now. + setCpuClock(true); + +#ifdef SHOW_FPS + // Start timer 0, used to measure FPS. + timerStart(0, ClockDivider_1024, 0, NULL); +#endif + + // Allocate 128Kb for the console on the upper screen. It is a bit + // overkill, but we got plenty of VRAM so far so it is OK. + vramSetBankC(VRAM_C_SUB_BG_0x06200000); + videoSetModeSub(MODE_0_2D); + + // Initialize the console on the top screen. + consoleInit(&cs0, 0, BgType_Text4bpp, BgSize_T_256x256, 2, 0, false, true); + + // Setup what should run on a VBlank interrupt. + irqSet(IRQ_VBLANK, On_VBlank); + + // Install the default Nintendo DS exception handler. It sucks, but that is + // what we got. + defaultExceptionHandler(); + + // Allocate 128kb of VRAM for the background that will hold the visible surface. + // The backgroung is 512x256, which is 128Kb, a full memory bank. + vramSetBankA(VRAM_A_MAIN_BG_0x06000000); + + // Put DS into 2D mode with extended background scaling/rotation support. This + // is necessary so we can downscale the game to fit into the DS low resolution + // screen. + videoSetMode(MODE_3_2D | DISPLAY_BG3_ACTIVE); + bg3 = bgInit(3, BgType_Bmp8, BgSize_B8_512x256, 0, 0); + + // Set downscaling 320x200 => 256x192 + DS_SeenBuff_ZoomOut(); + + // Initialize the Hardware cursor, which is basically a spite that is + // displayed on top of the background. + HWCursor.Init(); + + // Swap the LCD screen so that the main 2D engine is on the bottom screen. + lcdSwap(); + + if (w != 320 || h != 200 || bits_per_pixel != 8) + return false; + + return true; +} + +bool Is_Video_Fullscreen() +{ + return true; +} + +/*********************************************************************************************** + * Reset_Video_Mode -- Resets video mode and deletes Direct Draw Object * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: * + * * + * HISTORY: * + * 09/26/1995 PWG : Created. * + *=============================================================================================*/ +void Reset_Video_Mode(void) +{ +} + +/*********************************************************************************************** + * Get_Free_Video_Memory -- returns amount of free video memory * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: bytes of available video RAM * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 11/29/95 12:52PM ST : Created * + *=============================================================================================*/ +unsigned int Get_Free_Video_Memory(void) +{ + return 1000000000; +} + +/*********************************************************************************************** + * Get_Video_Hardware_Caps -- returns bitmask of direct draw video hardware support * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: hardware flags * + * * + * WARNINGS: Must call Set_Video_Mode 1st to create the direct draw object * + * * + * HISTORY: * + * 1/12/96 9:14AM ST : Created * + *=============================================================================================*/ +unsigned Get_Video_Hardware_Capabilities(void) +{ + return 0; +} + +/*********************************************************************************************** + * Wait_Vert_Blank -- Waits for the start (leading edge) of a vertical blank * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + *=============================================================================================*/ +void Wait_Vert_Blank(void) +{ + swiWaitForVBlank(); +} + +/*********************************************************************************************** + * Set_Palette -- set a direct draw palette * + * * + * * + * * + * INPUT: ptr to 768 rgb palette bytes * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 10/11/95 3:33PM ST : Created * + *=============================================================================================*/ +void Set_DD_Palette(void* palette) +{ + unsigned char r, g, b; + + unsigned char* rcolors = (unsigned char*)palette; + for (int i = 0; i < 256; i++) { + r = (unsigned char)rcolors[i * 3] << 2; + g = (unsigned char)rcolors[i * 3 + 1] << 2; + b = (unsigned char)rcolors[i * 3 + 2] << 2; + + BG_PALETTE[i] = RGB8(r, g, b); + } + + HWCursor.Set_Cursor_Palette(BG_PALETTE); +} + +void Wait_Blit(void) +{ +} + +void Set_Video_Cursor_Clip(bool clipped) +{ + HWCursor.Set_Video_Cursor_Clip(clipped); +} + +void Get_Video_Mouse(int& x, int& y) +{ + HWCursor.Get_Video_Mouse(x, y); +} + +void Set_Video_Mouse(int x, int y) +{ + HWCursor.Set_Video_Mouse(x, y); +} + +void Set_Video_Cursor(void* cursor, int w, int h, int hotx, int hoty) +{ + HWCursor.Set_Video_Cursor(cursor, w, h, hotx, hoty); +} + +/*********************************************************************************************** + * SMC::SurfaceMonitorClass -- constructor for surface monitor class * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 11/3/95 3:23PM ST : Created * + *=============================================================================================*/ + +SurfaceMonitorClass::SurfaceMonitorClass() +{ + SurfacesRestored = false; +} + +void Update_HWCursor() +{ + HWCursor.Update_HWCursor(); +} + +/* +** VideoSurfaceDDraw +*/ + +class VideoSurfaceNDS; +static VideoSurfaceNDS* frontSurface = nullptr; + +class VideoSurfaceNDS : public VideoSurface +{ +public: + VideoSurfaceNDS(int w, int h, GBC_Enum flags) + : flags(flags) + , windowSurface(nullptr) + { + if (w == 320 && h == 200) { + // The DS renderer works as follows: we pass the background + // buffer in VRAM to the game's software engine, which draws + // things there. The background is a 512x256 surface, but + // only 512x200 pixels are used. + + if (flags & GBC_VISIBLE) { + Pitch = 512; + surface = (char*)bgGetGfxPtr(bg3); + windowSurface = surface; + frontSurface = this; + } + } else { + swiWaitForVBlank(); + printf("ERROR - Unsupported surface size\n"); + while (1) + ; + } + } + + virtual ~VideoSurfaceNDS() + { + if (frontSurface == this) { + frontSurface = NULL; + } + } + + virtual void* GetData() const + { + return surface; + } + virtual int GetPitch() const + { + return Pitch; + } + virtual bool IsAllocated() const + { + return false; + } + + virtual void AddAttachedSurface(VideoSurface* surface) + { + } + + virtual bool IsReadyToBlit() + { + return false; + } + + virtual bool LockWait() + { + return true; + } + + virtual bool Unlock() + { + return true; + } + + virtual void Blt(const Rect& destRect, VideoSurface* src, const Rect& srcRect, bool mask) + { + } + + virtual void FillRect(const Rect& rect, unsigned char color) + { + } + + inline void RenderSurface() + { + } + +private: + int Pitch; + char* surface; + char* windowSurface; + GBC_Enum flags; +}; + +// Blits the argument page to the front buffer. This function is optimized to +// use the DMA, which should be faster on larger copies. +void DS_Blit_Display(GraphicViewPortClass& HidPage, GraphicViewPortClass& SeenPage) +{ + //size_t ram_free = Get_Free_RAM(); + //printf("Free RAM: %d\n", ram_free); + + const unsigned char* src = (const unsigned char*)HidPage.Get_Offset(); + unsigned char* dst = (unsigned char*)frontSurface->GetData(); + + int dst_pitch = frontSurface->GetPitch(); + int h = HidPage.Get_Height(); + int w = HidPage.Get_Width(); + + DC_FlushRange(src, w * h); + + while (h > 0) { + dmaCopyWordsAsynch(0, src, dst, w); + dst += dst_pitch; + src += w; + dmaCopyWordsAsynch(1, src, dst, w); + dst += dst_pitch; + src += w; + dmaCopyWordsAsynch(2, src, dst, w); + dst += dst_pitch; + src += w; + dmaCopyWordsAsynch(3, src, dst, w); + dst += dst_pitch; + src += w; + h -= 4; + } + +#ifdef SHOW_FPS + static long long now; + long long last, delta; + char textbuf[16]; + + const unsigned timer_speed = BUS_CLOCK / 1024; + + last = now; + now += timerElapsed(0); + delta = now - last; + + if (delta != 0) { + unsigned fps = timer_speed / delta; + if (fps > 99) + fps = 99; + snprintf(textbuf, 16, "%4llu", timer_speed / delta); + SeenPage.Print(textbuf, 12, HidPage.Get_Height() - 20, GREEN, BLACK); + } +#endif +} + +/* +** Video +*/ + +Video::Video() +{ +} + +Video::~Video() +{ +} + +Video& Video::Shared() +{ + static Video video; + return video; +} + +VideoSurface* Video::CreateSurface(int w, int h, GBC_Enum flags) +{ + return new VideoSurfaceNDS(w, h, flags); +} diff --git a/common/vqaaudio_nds.cpp b/common/vqaaudio_nds.cpp new file mode 100644 index 00000000..1c3134f3 --- /dev/null +++ b/common/vqaaudio_nds.cpp @@ -0,0 +1,299 @@ +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection +#include "vqaaudio.h" +#include "vqafile.h" +#include "vqaloader.h" +#include "vqatask.h" +#include "timer.h" +#include +#include +#include +#include +#include +#include "audio_fifocommon.h" + +#define CALLED printf("%s called\n", __func__) + +void Start_Primary_Sound_Buffer(bool); + +int AudioFlags; +int TimerIntCount; +int TimerMethod; +int VQATickCount; +int TickOffset; +unsigned VQAAudioPaused; +VQAHandle* AudioVQAHandle; + +static TimerClass Timer; + +int VQA_StartTimerInt(VQAHandle* handle, int a2) +{ + VQAData* data = handle->VQABuf; + VQAAudio* audio = &data->Audio; + + if (!(AudioFlags & VQA_AUDIO_FLAG_INTERRUPT_TIMER)) { + AudioFlags |= VQA_AUDIO_FLAG_UNKNOWN016; + } + + audio->Flags |= VQA_AUDIO_FLAG_UNKNOWN016; + ++TimerIntCount; + return 0; +} + +void VQA_StopTimerInt(VQAHandle* handle) +{ + if (TimerIntCount > 0) { + --TimerIntCount; + } + + AudioFlags &= ~VQA_AUDIO_FLAG_INTERRUPT_TIMER; +} + +int VQA_OpenAudio(VQAHandle* handle, void* hwnd) +{ + VQAConfig* config = &handle->Config; + VQAData* data = handle->VQABuf; + VQAHeader* header = &handle->Header; + VQAAudio* audio = &data->Audio; + + // We are assuming here that the main game already created an OpenAL device context. + Start_Primary_Sound_Buffer(true); + + audio->field_10 = 0; + audio->field_BC = 1; + + // Sampling rate (blocks per second) + if (config->AudioRate == -1) { + int rate = 0; + + if (header->FPS == config->FrameRate) { + rate = audio->SampleRate; + } else { + rate = config->FrameRate * audio->SampleRate / header->FPS; + } + + config->AudioRate = rate; + } + + audio->field_C0 = 1; + + if (audio->field_BC) { + audio->field_BC = 0; + } + + audio->Flags |= VQA_AUDIO_FLAG_UNKNOWN001; + AudioFlags |= VQA_AUDIO_FLAG_UNKNOWN001; + return 0; +} + +bool Queue_Audio(unsigned buffer) +{ + VQAConfig* config = &AudioVQAHandle->Config; + VQAData* data = AudioVQAHandle->VQABuf; + VQAAudio* audio = &data->Audio; + + if (!data) + return false; + + audio->field_B8 = audio->field_B0; + audio->field_B0 += config->HMIBufSize; + + if (audio->field_B0 >= audio->BuffBytes) { + audio->field_B0 = 0; + } + + audio->field_14 = audio->field_10 + 1; + + if (audio->field_14 >= audio->NumAudBlocks) { + audio->field_14 = 0; + } + + if (audio->IsLoaded[audio->field_14] != 1) { + if (VQAMovieDone) { + ++audio->field_B4; + } + + ++audio->NumSkipped; + config->DrawFlags &= 0xFB; + + return false; + } + + audio->IsLoaded[audio->field_10] = 0; + audio->PlayPosition += config->HMIBufSize; + ++audio->field_10; + + if (audio->PlayPosition >= config->AudioBufSize) { + audio->PlayPosition = 0; + audio->field_10 = 0; + } + + ++audio->field_B4; + return true; +} + +void VQA_CloseAudio(VQAHandle* handle) +{ + VQAConfig* config = &handle->Config; + VQAData* data = handle->VQABuf; + VQAAudio* audio = &data->Audio; + + VQA_StopAudio(handle); + AudioFlags &= ~(VQA_AUDIO_FLAG_UNKNOWN004 | VQA_AUDIO_FLAG_UNKNOWN008); + audio->Flags &= ~(VQA_AUDIO_FLAG_UNKNOWN004 | VQA_AUDIO_FLAG_UNKNOWN008); + + if (audio->field_C0) { + audio->field_C0 = 0; + } + + if (audio->field_BC) { + audio->field_BC = 0; + } + + audio->Flags &= ~(VQA_AUDIO_FLAG_UNKNOWN001 | VQA_AUDIO_FLAG_UNKNOWN002); + AudioFlags &= ~(VQA_AUDIO_FLAG_UNKNOWN001 | VQA_AUDIO_FLAG_UNKNOWN002 | VQA_AUDIO_FLAG_AUDIO_DMA_TIMER); +} + +int VQA_StartAudio(VQAHandle* handle) +{ + VQAConfig* config = &handle->Config; + VQAData* data = handle->VQABuf; + VQAAudio* audio = &data->Audio; + + AudioVQAHandle = handle; + + // Audio already started, abort. + if (AudioFlags & VQA_AUDIO_FLAG_AUDIO_DMA_TIMER) { + return -1; + } + + audio->BuffBytes = config->HMIBufSize * 4; + + audio->field_B0 = 0; + audio->field_B4 = 0; + + audio->Flags |= VQA_AUDIO_FLAG_AUDIO_DMA_TIMER; + AudioFlags |= VQA_AUDIO_FLAG_AUDIO_DMA_TIMER; + + USR1::FifoMessage msg; + + msg.type = USR1::SOUND_VQA_MESSAGE; + msg.SoundVQAChunk.data = audio->Buffer; + msg.SoundVQAChunk.freq = audio->SampleRate; + msg.SoundVQAChunk.size = 65536; + msg.SoundVQAChunk.volume = config->Volume; + msg.SoundVQAChunk.bits = audio->BitsPerSample; + + // Asynchronous send sound play command + fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg); + + return 0; +} + +void VQA_StopAudio(VQAHandle* handle) +{ + fifoSendValue32(FIFO_USER_01, USR1::SOUND_KILL); + AudioVQAHandle = nullptr; +} + +void VQA_PauseAudio() +{ + Timer.Stop(); +} + +void VQA_ResumeAudio() +{ + Timer.Start(); +} + +void VQA_AudioCallback() +{ + if (!VQAAudioPaused && AudioVQAHandle) { + Queue_Audio(0); + } +} + +int VQA_CopyAudio(VQAHandle* handle) +{ + VQAConfig* config = &handle->Config; + VQAData* data = handle->VQABuf; + VQAAudio* audio = &data->Audio; + + if (!audio) + return 0; + + VQA_AudioCallback(); + + if (config->OptionFlags & 1) { + if (audio->Buffer != nullptr) { + if (audio->TempBufSize > 0) { + int current_block = audio->AudBufPos / config->HMIBufSize; + int next_block = (audio->TempBufSize + audio->AudBufPos) / config->HMIBufSize; + + if ((unsigned)next_block >= audio->NumAudBlocks) { + next_block -= audio->NumAudBlocks; + } + + if (audio->IsLoaded[next_block] == 1) { + return -10; + } + + // Need to loop back and treat like circular buffer? + if (next_block < current_block) { + int end_space = config->AudioBufSize - audio->AudBufPos; + int remaining = audio->TempBufSize - end_space; + memcpy(&audio->Buffer[audio->AudBufPos], audio->TempBuf, end_space); + memcpy(audio->Buffer, &audio->TempBuf[end_space], remaining); + + audio->AudBufPos += audio->TempBufSize; + audio->TempBufSize = 0; + + audio->AudBufPos = remaining; + audio->TempBufSize = 0; + + for (unsigned i = current_block; i < audio->NumAudBlocks; ++i) { + audio->IsLoaded[i] = 1; + } + + for (int i = 0; i < next_block; ++i) { + audio->IsLoaded[i] = 1; + } + } else { + memcpy(&audio->Buffer[audio->AudBufPos], audio->TempBuf, audio->TempBufSize); + + audio->AudBufPos += audio->TempBufSize; + audio->TempBufSize = 0; + + for (int i = current_block; i < next_block; ++i) { + audio->IsLoaded[i] = 1; + } + } + } + } + } + return 0; +} + +void VQA_SetTimer(VQAHandle* handle, int time, int method) +{ + Timer.Set(0); +} + +unsigned VQA_GetTime(VQAHandle* handle) +{ + return Timer.Time(); +} + +int VQA_TimerMethod() +{ + return 0; +} diff --git a/common/wintimer.cpp b/common/wintimer.cpp new file mode 100644 index 00000000..c3864df4 --- /dev/null +++ b/common/wintimer.cpp @@ -0,0 +1,127 @@ +// +// Copyright 2020 Electronic Arts Inc. +// +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection + +// Implementation of system timers. + +#include "timer.h" +#include +using namespace std::chrono; + +// Global timers that the library or user can count on existing. +TimerClass WinTickCount(BT_SYSTEM); +WinTimerClass WindowsTimer(60); + +///////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////// Code //////////////////////////////////////// + +/*************************************************************************** + * WinTimerClass::WinTimerClass -- Initialize the WW timer system. * + * * + * * + * INPUT: unsigned int : user timer frequency. * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 10/5/95 3:47PM : ST Created. * + *=========================================================================*/ +WinTimerClass::WinTimerClass() + : Frequency(60) + , Start(Now()) +{ + WinTickCount.Start(); + TimerSystemOn = true; +} + +WinTimerClass::WinTimerClass(unsigned int freq) + : Frequency(freq) + , Start(Now()) +{ + WinTickCount.Start(); + TimerSystemOn = true; +} + +/*************************************************************************** + * WinTimerClass::~WinTimerClass -- Removes the timer system. * + * * + * * + * INPUT: NONE. * + * * + * OUTPUT: bool was it removed successfuly * + * * + * WARNINGS: * + * * + * HISTORY: * + * 10/5/95 3:47PM : ST Created. * + *=========================================================================*/ +WinTimerClass::~WinTimerClass() +{ +} + +void WinTimerClass::Init(unsigned int freq) +{ + WindowsTimer = WinTimerClass(freq); +} + +/*********************************************************************************************** + * WinTimerClass::Get_System_Tick_Count -- returns the system tick count * + * * + * INPUT: Nothing * + * * + * OUTPUT: tick count * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 10/5/95 4:02PM ST : Created * + *=============================================================================================*/ + +unsigned int WinTimerClass::Get_System_Tick_Count() +{ + if (Frequency == 0) { + return 0; + } + unsigned long long delta = Now() - Start; + return (unsigned int)(delta / (1000 / Frequency)); +} + +/*********************************************************************************************** + * WinTimerClass::Get_User_Tick_Count -- returns the user tick count * + * * + * INPUT: Nothing * + * * + * OUTPUT: tick count * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 10/5/95 4:02PM ST : Created * + *=============================================================================================*/ + +unsigned int WinTimerClass::Get_User_Tick_Count() +{ + if (Frequency == 0) { + return 0; + } + unsigned long long delta = Now() - Start; + return (unsigned int)(delta / (1000 / Frequency)); +} + +unsigned long long WinTimerClass::Now() +{ + return duration_cast(system_clock::now().time_since_epoch()).count(); +} diff --git a/common/wintimer_nds.cpp b/common/wintimer_nds.cpp new file mode 100644 index 00000000..7de19493 --- /dev/null +++ b/common/wintimer_nds.cpp @@ -0,0 +1,72 @@ + +#include "timer.h" +#include +#include + +// Global timers that the library or user can count on existing. +TimerClass WinTickCount(BT_SYSTEM); +WinTimerClass WindowsTimer(60); + +// Implement a system timer for the Nintendo DS. +WinTimerClass::WinTimerClass() + : Frequency(60) + , Start(Now()) +{ + WinTickCount.Start(); + TimerSystemOn = true; +} + +WinTimerClass::WinTimerClass(unsigned int freq) + : Frequency(freq) + , Start(Now()) +{ + WinTickCount.Start(); + TimerSystemOn = true; + + if (freq != 60) { + printf("Only 60FPS timers supported\n"); + while (1) + ; + } +} + +WinTimerClass::~WinTimerClass() +{ +} + +void WinTimerClass::Init(unsigned int freq) +{ + if (freq != 60) { + printf("Only 60FPS timers supported\n"); + while (1) + ; + } + + WindowsTimer = WinTimerClass(freq); +} + +// Number of Ticks passed. +static unsigned Ticks; + +// Update the number of ticks passed. We use this to implement a 60FPS +// timer. Altough vanilla-conquer uses a milliseconds based timer, original +// game used a 60FPS timer. +void Timer_VBlank() +{ + Ticks++; +} + +unsigned int WinTimerClass::Get_System_Tick_Count() +{ + return Ticks - Start; +} + +unsigned int WinTimerClass::Get_User_Tick_Count() +{ + return Ticks - Start; +} + +unsigned long long WinTimerClass::Now() +{ + return Ticks; +} diff --git a/common/wspudp.cpp b/common/wspudp.cpp index 155a921a..37d483d3 100644 --- a/common/wspudp.cpp +++ b/common/wspudp.cpp @@ -41,6 +41,8 @@ * TMC::Message_Handler -- Message handler function for Winsock related messages * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#ifdef NETWORKING + #include "internet.h" #include "endianness.h" #include "debugstring.h" @@ -57,8 +59,6 @@ extern WWKeyboardClass* WWKeyboard; #include #endif -#ifdef NETWORKING - /*********************************************************************************************** * UDPInterfaceClass::UDPInterfaceClass -- Class constructor. * * * diff --git a/common/wwkeyboard.cpp b/common/wwkeyboard.cpp index 36cc49e6..6293c456 100644 --- a/common/wwkeyboard.cpp +++ b/common/wwkeyboard.cpp @@ -61,6 +61,9 @@ #include #include "sdl_keymap.h" #endif +#ifdef _NDS +#include +#endif #include "settings.h" #define ARRAY_SIZE(x) int(sizeof(x) / sizeof(x[0])) @@ -533,6 +536,16 @@ bool WWKeyboardClass::Is_Buffer_Empty(void) const *=============================================================================================*/ void Process_Network(); +#ifdef _NDS +void DS_SeenBuff_ZoomIn(); + +void DS_SeenBuff_ZoomOut(); + +void DS_SeenBuff_SwitchZoom(); + +void Set_Video_Mouse(int, int); +#endif + void WWKeyboardClass::Fill_Buffer_From_System(void) { #ifdef SDL2_BUILD @@ -646,6 +659,118 @@ void WWKeyboardClass::Fill_Buffer_From_System(void) DispatchMessageA(&msg); } } +#elif defined(_NDS) + if (!Is_Buffer_Full()) { + static touchPosition position_old = {0}; + static uint32_t keys_old = 0; + + touchPosition position_now; + + uint32_t keys_current = keysCurrent(); + uint32_t keys_down = keys_current & ~keys_old; + uint32_t keys_up = (keys_current ^ keys_old) & (~keys_current); + keys_old = keys_current; + + // Read new touch screen position. + touchRead(&position_now); + + // Raw contains touch position without any kind of system correction + // with regard to touch position. Use that to know if the user is + // touching something. + bool was_touching = position_old.rawx | position_old.rawy; + bool is_touching = position_now.rawx | position_now.rawy; + + if (is_touching) { + Set_Video_Mouse(position_now.px, position_now.py); + + if (!was_touching) { + int x, y; + Get_Video_Mouse(x, y); + + Put_Mouse_Message(VK_LBUTTON, x, y, false); + } + } else /* !is_touching */ { + if (was_touching) { + int x, y; + // Get scaled cursor position. + Get_Video_Mouse(x, y); + Put_Mouse_Message(VK_LBUTTON, x, y, true); + } + } + + if (keys_down & KEY_L && !(keys_current & KEY_B)) { + int x, y; + Get_Video_Mouse(x, y); + Put_Mouse_Message(VK_RBUTTON, x, y, false); + } else if (keys_up & KEY_L && !(keys_current & KEY_B)) { + int x, y; + Get_Video_Mouse(x, y); + Put_Mouse_Message(VK_RBUTTON, x, y, true); + } + + if (keys_down & KEY_B) { + Put_Key_Message(VK_CONTROL, false); + } else if (keys_up & KEY_B) { + Put_Key_Message(VK_CONTROL, true); + } + + if (keys_current & KEY_L || keys_current & KEY_B) { + if (keys_down & KEY_UP) { + Put_Key_Message(VK_1, false); + } else if (keys_up & KEY_UP) { + Put_Key_Message(VK_1, true); + } + if (keys_down & KEY_RIGHT) { + Put_Key_Message(VK_2, false); + } else if (keys_up & KEY_RIGHT) { + Put_Key_Message(VK_2, true); + } + if (keys_down & KEY_DOWN) { + Put_Key_Message(VK_3, false); + } else if (keys_up & KEY_DOWN) { + Put_Key_Message(VK_3, true); + } + if (keys_down & KEY_LEFT) { + Put_Key_Message(VK_4, false); + } else if (keys_up & KEY_LEFT) { + Put_Key_Message(VK_4, true); + } + } + if (keys_down & KEY_START) { + Put_Key_Message(VK_ESCAPE, false); + } else if (keys_up & KEY_START) { + Put_Key_Message(VK_ESCAPE, true); + } + + if (keys_down & KEY_X) { + Put_Key_Message(VK_G, false); + } else if (keys_up & KEY_START) { + Put_Key_Message(VK_G, true); + } + + if (keys_down & KEY_Y) { + Put_Key_Message(VK_X, false); + } else if (keys_up & KEY_START) { + Put_Key_Message(VK_X, true); + } + + if (keys_down & KEY_A) { + Put_Key_Message(VK_ALT, false); + } else if (keys_up & KEY_A) { + Put_Key_Message(VK_ALT, true); + } + if (keys_down & KEY_R) { + Put_Key_Message(VK_H, false); + } else if (keys_up & KEY_R) { + Put_Key_Message(VK_H, true); + } + + if (keys_down & KEY_SELECT) { + DS_SeenBuff_SwitchZoom(); + } + + position_old = position_now; + } #endif } diff --git a/common/wwmouse.cpp b/common/wwmouse.cpp index c4560b14..a1582023 100644 --- a/common/wwmouse.cpp +++ b/common/wwmouse.cpp @@ -321,7 +321,7 @@ void WWMouseClass::Low_Show_Mouse(int x, int y) State--; // ST - 1/3/2019 10:50AM -#if !defined(REMASTER_BUILD) && !defined(SDL2_BUILD) +#if !defined(REMASTER_BUILD) && !defined(SDL2_BUILD) && !defined(_NDS) // // If the mouse is completely visible then draw it at its current @@ -449,7 +449,7 @@ void WWMouseClass::Conditional_Show_Mouse(void) void WWMouseClass::Draw_Mouse(GraphicViewPortClass* scr) { -#if defined(REMASTER_BUILD) || defined(SDL2_BUILD) +#if defined(REMASTER_BUILD) || defined(SDL2_BUILD) || defined(_NDS) scr; return; // ST - 1/3/2019 10:50AM @@ -510,7 +510,7 @@ void WWMouseClass::Draw_Mouse(GraphicViewPortClass* scr) void WWMouseClass::Erase_Mouse(GraphicViewPortClass* scr, int forced) { -#if defined(REMASTER_BUILD) || defined(SDL2_BUILD) +#if defined(REMASTER_BUILD) || defined(SDL2_BUILD) || defined(_NDS) // ST - 1/3/2019 10:50AM scr; forced; @@ -617,7 +617,7 @@ int WWMouseClass::Get_Mouse_Y(void) *=============================================================================================*/ void WWMouseClass::Get_Mouse_XY(int& x, int& y) { -#if defined(SDL2_BUILD) +#if defined(SDL2_BUILD) || defined(_NDS) Get_Video_Mouse(x, y); #elif defined(_WIN32) POINT pt; @@ -851,7 +851,7 @@ void* WWMouseClass::Set_Mouse_Cursor(int hotspotx, int hotspoty, Cursor* cursor) result = PrevCursor; PrevCursor = cursor; -#ifdef SDL2_BUILD +#if defined(SDL2_BUILD) || defined(_NDS) Set_Video_Cursor(MouseCursor, CursorWidth, CursorHeight, MouseXHot, MouseYHot); #endif diff --git a/common/wwstd.h b/common/wwstd.h index ab99bcc1..83e68da4 100644 --- a/common/wwstd.h +++ b/common/wwstd.h @@ -216,6 +216,12 @@ typedef enum : unsigned short COLOR_PADDING = 0x1000 } ColorType; +#ifdef _NDS +#define OPTIMIZE_AGGRESSIVELY __attribute__((optimize("Ofast"))) __attribute__((hot)) __attribute__((target("arm"))) +#else +#define OPTIMIZE_AGGRESSIVELY +#endif + /* ** Define some Windows specific values that are used throghout the games */ @@ -257,13 +263,14 @@ inline static void _splitpath(const char* path, char* drive, char* dir, char* fn } } } - +#ifndef _NDS inline static char* strupr(char* str) { for (int i = 0; i < strlen(str); i++) str[i] = toupper(str[i]); return str; } +#endif inline static void strrev(char* str) { diff --git a/common/xordelta.cpp b/common/xordelta.cpp index 58a3799c..df177fe0 100644 --- a/common/xordelta.cpp +++ b/common/xordelta.cpp @@ -10,6 +10,7 @@ // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection #include "xordelta.h" +#include "wwstd.h" #include #define XOR_SMALL 127 @@ -17,7 +18,7 @@ #define XOR_LARGE 16383 #define XOR_MAX 32767 -void Apply_XOR_Delta(void* dst, const void* src) +void OPTIMIZE_AGGRESSIVELY Apply_XOR_Delta(void* dst, const void* src) { unsigned char* putp = (unsigned char*)(dst); const unsigned char* getp = (const unsigned char*)(src); diff --git a/redalert/CMakeLists.txt b/redalert/CMakeLists.txt index 60a3e854..32950ed8 100644 --- a/redalert/CMakeLists.txt +++ b/redalert/CMakeLists.txt @@ -268,6 +268,13 @@ if(BUILD_VANILLARA) if(WIN32 AND NOT MSVC) set_target_properties(VanillaRA PROPERTIES LINK_FLAGS "-mwindows") endif() + if (NDS) + # Generate Nintendo DS ROM + add_custom_command(TARGET VanillaRA + POST_BUILD + COMMAND ${NDSTOOL} ARGS -c ../vanillara.nds -9 ../vanillara -7 ../arm7.elf -b ${CMAKE_SOURCE_DIR}/resources/vanillara_icon_ds.bmp "VanillaRA\;Vanilla\ Conquer\;Red\ Alert" + DEPENDS arm7.elf) + endif() if(BUILD_WITH_UBSAN) target_compile_options(VanillaRA PUBLIC -fsanitize=undefined,float-divide-by-zero,integer,implicit-conversion,implicit-integer-truncation,implicit-integer-arithmetic-value-change,local-bounds,nullability diff --git a/redalert/compat.h b/redalert/compat.h index a16373e9..b43a3c96 100644 --- a/redalert/compat.h +++ b/redalert/compat.h @@ -68,7 +68,10 @@ typedef enum MenuIndexType inline int Get_IconSet_MapWidth(void const* data) { if (data) { - return (((IControl_Type*)data)->MapWidth); + char const* bdata = (char const*)data; + uint16_t w; + memcpy(&w, bdata + offsetof(IControl_Type, MapWidth), sizeof(w)); + return (int)w; } return (0); } @@ -76,7 +79,10 @@ inline int Get_IconSet_MapWidth(void const* data) inline int Get_IconSet_MapHeight(void const* data) { if (data) { - return (((IControl_Type*)data)->MapHeight); + char const* bdata = (char const*)data; + uint16_t h; + memcpy(&h, bdata + offsetof(IControl_Type, MapHeight), sizeof(h)); + return (int)h; } return (0); } @@ -105,11 +111,17 @@ class IconsetClass : protected IControl_Type }; unsigned char* Control_Map(void) { - return ((unsigned char*)this + ColorMap); + char* t = (char*)this; + int32_t color_map; + memcpy(&color_map, t + offsetof(IconsetClass, ColorMap), sizeof(int32_t)); + return ((unsigned char*)this + color_map); }; unsigned char const* Control_Map(void) const { - return ((unsigned char const*)this + ColorMap); + char* t = (char*)this; + int32_t color_map; + memcpy(&color_map, t + offsetof(IconsetClass, ColorMap), sizeof(int32_t)); + return ((unsigned char const*)this + color_map); }; int Icon_Count(void) const { diff --git a/redalert/conquer.cpp b/redalert/conquer.cpp index 9bd70fd4..e8d7a2b0 100644 --- a/redalert/conquer.cpp +++ b/redalert/conquer.cpp @@ -130,8 +130,6 @@ static void Message_Input(KeyNumType& input); void Color_Cycle(void); bool Map_Edit_Loop(void); -bool UseOldShapeDraw = false; - #ifdef CHEAT_KEYS void Dump_Heap_Pointers(void); void Error_In_Heap_Pointers(char* string); @@ -3374,7 +3372,7 @@ int VQ_Call_Back(unsigned char*, int) #endif Frame_Limiter(); - if ((BreakoutAllowed || Debug_Flag) && key == KN_ESC) { + if ((BreakoutAllowed || Debug_Flag) && (key == KN_ESC || key == VK_LBUTTON)) { WWKeyboard->Clear(); Brokeout = true; return (true); diff --git a/redalert/const.cpp b/redalert/const.cpp index 854fd075..ff54012b 100644 --- a/redalert/const.cpp +++ b/redalert/const.cpp @@ -706,10 +706,10 @@ unsigned char const RemapEmber[256] = { //#endif // "\n"; -char const Keys[] = "[PublicKey]\n" - "1=AihRvNoIbTn85FZRYNZRcT+i6KpU+maCsEqr3Q5q+LDB5tH7Tz2qQ38V\n" +char const WWKeys[] = "[PublicKey]\n" + "1=AihRvNoIbTn85FZRYNZRcT+i6KpU+maCsEqr3Q5q+LDB5tH7Tz2qQ38V\n" #ifdef CHEAT_KEYS - "[PrivateKey]\n" - "1=AigKVje8mROcR8QixnxUEF5b29Curkq01DNDWCdOG99XBqH79OaCiTCB\n" + "[PrivateKey]\n" + "1=AigKVje8mROcR8QixnxUEF5b29Curkq01DNDWCdOG99XBqH79OaCiTCB\n" #endif - "\n"; + "\n"; diff --git a/redalert/defines.h b/redalert/defines.h index 7d690c16..195327ce 100644 --- a/redalert/defines.h +++ b/redalert/defines.h @@ -128,8 +128,16 @@ #endif // Test to see if partial object drawing is any faster. -//#define PARTIAL +#ifndef REMASTER_BUILD +//#define PARTIAL +#endif + +// SORTDRAW is used to synchronize object drawing across machines. Since this +// consumes CPU for sorting and memory to keep 4 more pointers in the +// CellClass, we can disable this when NETWORKING is disabled to save RAM. +#ifdef NETWORKING #define SORTDRAW +#endif /********************************************************************** ** If the scenario editor to to be active in this build then uncomment @@ -3537,7 +3545,9 @@ typedef enum WaypointEnum : unsigned char /**************************************************************************** ** This is the max number of events supported on one frame. */ -#define MAX_EVENTS 256 +// 06/27/2022: Changed by PG to 256. Original seems to be 64. +//#define MAX_EVENTS 256 +#define MAX_EVENTS 64 /* ** New Config structure for .CFG files diff --git a/redalert/externs.h b/redalert/externs.h index 42afeeb2..09f0af16 100644 --- a/redalert/externs.h +++ b/redalert/externs.h @@ -361,7 +361,7 @@ extern GroundType Ground[LAND_COUNT]; ** Constant externs (data is not modified during game play). */ extern char const* Missions[MISSION_COUNT]; -extern char const Keys[]; +extern char const WWKeys[]; extern char const* const VQName[VQ_COUNT]; extern int CrateData[CRATE_COUNT]; extern char const* const CrateNames[CRATE_COUNT]; diff --git a/redalert/globals.cpp b/redalert/globals.cpp index 13dca177..711d6b0a 100644 --- a/redalert/globals.cpp +++ b/redalert/globals.cpp @@ -189,7 +189,7 @@ int AllDone; ** This is true if the game is the currently in focus windows app ** */ -#ifdef SDL2_BUILD +#if defined(SDL2_BUILD) || defined(_NDS) bool GameInFocus = true; #else bool GameInFocus = false; diff --git a/redalert/gscreen.cpp b/redalert/gscreen.cpp index cf9c8a71..c3515ee3 100644 --- a/redalert/gscreen.cpp +++ b/redalert/gscreen.cpp @@ -419,11 +419,17 @@ void GScreenClass::Render(void) *=============================================================================================*/ void ModeX_Blit(GraphicBufferClass* source); +void DS_Blit_Display(GraphicViewPortClass& HidPage, GraphicViewPortClass& SeenPage); + void GScreenClass::Blit_Display(void) { BStart(BENCH_BLIT_DISPLAY); +#ifndef _NDS WWMouse->Draw_Mouse(&HidPage); HidPage.Blit(SeenBuff, 0, 0, 0, 0, HidPage.Get_Width(), HidPage.Get_Height(), false); WWMouse->Erase_Mouse(&HidPage, false); +#else + DS_Blit_Display(HidPage, SeenPage); +#endif BEnd(BENCH_BLIT_DISPLAY); } diff --git a/redalert/init.cpp b/redalert/init.cpp index d1193d65..5077b9d0 100644 --- a/redalert/init.cpp +++ b/redalert/init.cpp @@ -2731,7 +2731,7 @@ static void Init_Bulk_Data(void) *=============================================================================================*/ static void Init_Keys(void) { - RAMFileClass file((void*)Keys, strlen(Keys)); + RAMFileClass file((void*)WWKeys, strlen(WWKeys)); INIClass ini; ini.Load(file); diff --git a/redalert/loaddlg.cpp b/redalert/loaddlg.cpp index e32b61f7..102aa929 100644 --- a/redalert/loaddlg.cpp +++ b/redalert/loaddlg.cpp @@ -240,6 +240,11 @@ int LoadOptionsClass::Process(void) -1, EditClass::ALPHANUMERIC); +#ifdef _NDS + // Nintendo DS doesn't have a keyboard, so we hack a name for the user. + strcpy(game_descr, Scen.ScenarioName); +#endif + /* ** Initialize. */ @@ -467,6 +472,11 @@ int LoadOptionsClass::Process(void) } game_num = Files[game_idx]->Num; +#ifdef _NDS + // Append game_num to the file so we can have multiple saves of the + // same mission. + snprintf(game_descr, 40, "%s_%03d", Scen.ScenarioName, game_num); +#endif if (!Save_Game(game_num, game_descr)) { WWMessageBox().Process(TXT_ERROR_SAVING_GAME); } else { @@ -499,7 +509,7 @@ int LoadOptionsClass::Process(void) game_idx = listbtn.Current_Index(); game_num = Files[game_idx]->Num; if (WWMessageBox().Process(TXT_DELETE_FILE_QUERY, TXT_YES, TXT_NO) == 0) { - sprintf(fname, "SAVEGAME.%03d", game_num); + sprintf(fname, "savegame.%03d", game_num); Delete_File(fname); Clear_List(&listbtn); Fill_List(&listbtn); @@ -664,7 +674,7 @@ void LoadOptionsClass::Fill_List(ListClass* list) /* ** Find all savegame files */ - bool rc = Find_First("SAVEGAME.*", 0, &ff); + bool rc = Find_First("savegame.*", 0, &ff); while (rc) { diff --git a/redalert/mapsel.cpp b/redalert/mapsel.cpp index 1b147fba..f9bd3113 100644 --- a/redalert/mapsel.cpp +++ b/redalert/mapsel.cpp @@ -144,8 +144,7 @@ char const* Map_Selection(void) // Options.Set_Score_Volume(fixed(4, 10)); Theme.Queue_Song(THEME_MAP); - void* anim = Open_Animation( - _filename, NULL, 0L, (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), (unsigned char*)&mappalette); + void* anim = Open_Animation(_filename, NULL, 0L, (WSAOpenType)(WSA_OPEN_FROM_DISK), (unsigned char*)&mappalette); WWKeyboard->Clear(); SeenPage.Clear(); @@ -267,6 +266,7 @@ char const* Map_Selection(void) Theme.Fade_Out(); delete pseudoseenbuff; // Options.Set_Score_Volume(oldvolume); + delete pseudoseenbuff; // Scen.ScenVar = (ScenarioVarType)selection; // Mono_Printf("Chose variant %d \n", selection); diff --git a/redalert/menus.cpp b/redalert/menus.cpp index 86bd470c..25522e59 100644 --- a/redalert/menus.cpp +++ b/redalert/menus.cpp @@ -953,8 +953,12 @@ int Main_Menu(unsigned int) if (Is_Counterstrike_Installed() == true) { #endif if ((WWKeyboard->Down(KN_LSHIFT) || WWKeyboard->Down(KN_RSHIFT)) - && Coordinates_In_Region( - WWKeyboard->MouseQX, WWKeyboard->MouseQY, 260 * RESFACTOR, 0, 320 * RESFACTOR, 50 * RESFACTOR)) { + && Coordinates_In_Region(WWKeyboard->MouseQX, + WWKeyboard->MouseQY, + 260 * RESFACTOR, + 0, + 320 * RESFACTOR, + 50 * RESFACTOR)) { AntsEnabled = true; process = false; #ifdef FIXIT_VERSION_3 diff --git a/redalert/score.cpp b/redalert/score.cpp index 22f4ba86..a3f26773 100644 --- a/redalert/score.cpp +++ b/redalert/score.cpp @@ -357,6 +357,7 @@ void ScoreClass::Presentation(void) Disable_Uncompressed_Shapes(); #endif // FIXIT PseudoSeenBuff = new GraphicBufferClass(320, 200, (void*)NULL); + int i; void const* yellowptr; void const* redptr; @@ -385,11 +386,8 @@ void ScoreClass::Presentation(void) void const* sfx4 = MFCD::Retrieve("SFX4.AUD"); Beepy6 = MFCD::Retrieve("BEEPY6.AUD"); - void* anim = Open_Animation(AnimNames[house], - NULL, - 0L, - (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), - (unsigned char*)ScorePalette.Get_Data()); + void* anim = Open_Animation( + AnimNames[house], NULL, 0L, (WSAOpenType)(WSA_OPEN_FROM_DISK), (unsigned char*)ScorePalette.Get_Data()); unsigned minutes = (unsigned)((ElapsedTime / (int)TIMER_MINUTE)) + 1; // Load up the shapes for the Nod score screen @@ -1275,12 +1273,17 @@ void ScoreClass::Input_Name(char str[], int xpos, int ypos, char const pal[]) */ HidPage.Blit(HidPage, 0, 100 * RESFACTOR, 0, 0, 100 * RESFACTOR, 100 * RESFACTOR); +#ifdef _NDS + // Nintendo DS doesn't have a keyboard, so we hack something for the user. + strcpy(str, Scen.ScenarioName); +#endif + do { Call_Back(); Animate_Score_Objs(); Animate_Cursor(index, ypos); if (WWKeyboard->Check()) { - key = WWKeyboard->To_ASCII(WWKeyboard->Get()) & 0xFF; + key = WWKeyboard->Get(); Call_Back(); if (index == MAX_FAMENAME_LENGTH - 2) { @@ -1367,7 +1370,8 @@ void ScoreClass::Input_Name(char str[], int xpos, int ypos, char const pal[]) } Frame_Limiter(); - } while (key != KA_RETURN); // } while(key != KN_RETURN && key!=KN_KEYPAD_RETURN); + } while (key != KA_RETURN && key != VK_LBUTTON + && key != VK_ESCAPE); // } while(key != KN_RETURN && key!=KN_KEYPAD_RETURN); } void Animate_Cursor(int pos, int ypos) diff --git a/redalert/scroll.cpp b/redalert/scroll.cpp index 1e08b6b3..55d7bc52 100644 --- a/redalert/scroll.cpp +++ b/redalert/scroll.cpp @@ -36,6 +36,9 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" +#ifdef _NDS +#include +#endif #define SCROLL_DELAY 1 @@ -88,6 +91,31 @@ void ScrollClass::AI(KeyNumType& input, int x, int y) static DirType direction; int rate; +#ifdef _NDS + int true_x = x; + int true_y = y; + + // Hack scroll in DPAD + uint32_t keys_current = keysCurrent(); + x = 160; + y = 100; + + if (!(keys_current & KEY_L) && !(keys_current & KEY_B)) { + if (keys_current & KEY_UP) { + y -= 100; + } + if (keys_current & KEY_DOWN) { + y += 99; + } + if (keys_current & KEY_LEFT) { + x -= 160; + } + if (keys_current & KEY_RIGHT) { + x += 159; + } + } +#endif + /* ** If rubber band mode is in progress, then don't allow scrolling of the tactical map. */ @@ -227,6 +255,11 @@ void ScrollClass::AI(KeyNumType& input, int x, int y) } } #endif + +#ifdef _NDS + x = true_x; + y = true_y; +#endif HelpClass::AI(input, x, y); } diff --git a/redalert/session.cpp b/redalert/session.cpp index fd68d0e5..bfb68c8c 100644 --- a/redalert/session.cpp +++ b/redalert/session.cpp @@ -48,6 +48,9 @@ #include "function.h" #include // for station ID computation +#ifdef _NDS +#include +#endif //#include "WolDebug.h" @@ -574,9 +577,18 @@ void SessionClass::Read_MultiPlayer_Settings(void) CDFileClass file(CONFIG_FILE_NAME); if (ini.Load(file)) { +#ifdef _NDS + // Get DS user name as player name. + int i; + for (i = 0; i < PersonalData->nameLen; i++) { + Handle[i] = PersonalData->name[i] & 0xFF; + } + // Ensure that our string is null-terminated. + Handle[i] = '\0'; +#else // Get the player's last-used Handle ini.Get_String("MultiPlayer", "Handle", "Noname", Handle, sizeof(Handle)); - +#endif // Get the player's last-used Color PrefColor = (PlayerColorType)ini.Get_Int("MultiPlayer", "Color", 0); #ifdef FIXIT_VERSION_3 diff --git a/redalert/startup.cpp b/redalert/startup.cpp index 8ea4b6c6..e8614578 100644 --- a/redalert/startup.cpp +++ b/redalert/startup.cpp @@ -331,6 +331,9 @@ int main(int argc, char* argv[]) Read_Setup_Options(&cfile); #ifndef REMASTER_BUILD +#ifdef _NDS + Settings.Video.DOSMode = true; +#endif /* If DOSMode is enabled, adjust resolution accordingly. */ if (Settings.Video.DOSMode) { RESFACTOR = 1; diff --git a/resources/vanillara_icon_ds.bmp b/resources/vanillara_icon_ds.bmp new file mode 100644 index 00000000..56ea6f1a Binary files /dev/null and b/resources/vanillara_icon_ds.bmp differ diff --git a/resources/vanillatd_icon_ds.bmp b/resources/vanillatd_icon_ds.bmp new file mode 100644 index 00000000..580f97cc Binary files /dev/null and b/resources/vanillatd_icon_ds.bmp differ diff --git a/tiberiandawn/CMakeLists.txt b/tiberiandawn/CMakeLists.txt index a26cf467..7796058b 100644 --- a/tiberiandawn/CMakeLists.txt +++ b/tiberiandawn/CMakeLists.txt @@ -230,10 +230,23 @@ if(BUILD_VANILLATD) ) endif() - target_compile_definitions(VanillaTD PUBLIC $<$:_DEBUG> ${VANILLA_DEFS} MEGAMAPS) + if (NDS) + target_compile_definitions(VanillaTD PUBLIC $<$:_DEBUG> ${VANILLA_DEFS}) + else() + target_compile_definitions(VanillaTD PUBLIC $<$:_DEBUG> ${VANILLA_DEFS} MEGAMAPS) + endif() target_include_directories(VanillaTD PUBLIC ${CMAKE_SOURCE_DIR} .) target_link_libraries(VanillaTD commonv ${VANILLA_LIBS} ${STATIC_LIBS}) set_target_properties(VanillaTD PROPERTIES OUTPUT_NAME vanillatd) + if (NDS) + # Generate Nintendo DS ROM + add_custom_command(TARGET VanillaTD + POST_BUILD + COMMAND ${NDSTOOL} ARGS -c ../vanillatd.nds -9 ../vanillatd -7 ../arm7.elf -b ${CMAKE_SOURCE_DIR}/resources/vanillatd_icon_ds.bmp "VanillaTD\;Vanilla\ Conquer\;Tiberian\ Dawn" + DEPENDS arm7.elf) + endif() + + if(MAP_EDITORTD) target_compile_definitions(VanillaTD PUBLIC INTERNAL_VERSION) endif() diff --git a/tiberiandawn/aircraft.cpp b/tiberiandawn/aircraft.cpp index 17ebfad9..4a001607 100644 --- a/tiberiandawn/aircraft.cpp +++ b/tiberiandawn/aircraft.cpp @@ -240,10 +240,11 @@ AircraftClass::AircraftClass(AircraftType classid, HousesType house) ** Keep count of the number of units created. Dont track cargo planes as they are created ** automatically, not bought. */ +#ifndef _NDS if (classid != AIRCRAFT_CARGO && GameToPlay == GAME_INTERNET) { House->AircraftTotals.Increment_Unit_Total((int)classid); } - +#endif #ifdef USE_RA_AI // // Added for RA AI in TD. ST - 7/26/2019 9:12AM diff --git a/tiberiandawn/audio.cpp b/tiberiandawn/audio.cpp index 3211550e..f7d9cd57 100644 --- a/tiberiandawn/audio.cpp +++ b/tiberiandawn/audio.cpp @@ -342,8 +342,7 @@ int Sound_Effect(VocType voc, VolType volume, int variation, signed short pan_va ** If the sound data pointer is not null, then presume that it is valid. */ if (ptr) { - return ( - Play_Sample(ptr, Fixed_To_Cardinal(SoundEffectName[voc].Priority, (int)volume), (int)volume, pan_value)); + return Play_Sample(ptr, Fixed_To_Cardinal(SoundEffectName[voc].Priority, (int)volume), (int)volume, pan_value); } return (-1); } diff --git a/tiberiandawn/building.cpp b/tiberiandawn/building.cpp index 65926279..02824fa4 100644 --- a/tiberiandawn/building.cpp +++ b/tiberiandawn/building.cpp @@ -1878,11 +1878,11 @@ BuildingClass::BuildingClass(StructType type, HousesType house) ActLike = HOUSE_GOOD; IsCaptured = true; } - +#ifndef _NDS if (GameToPlay == GAME_INTERNET) { House->BuildingTotals.Increment_Unit_Total((int)type); } - +#endif #ifdef USE_RA_AI // // Added for RA AI in TD. ST - 7/26/2019 9:12AM diff --git a/tiberiandawn/conquer.cpp b/tiberiandawn/conquer.cpp index fd26637a..a97f1508 100644 --- a/tiberiandawn/conquer.cpp +++ b/tiberiandawn/conquer.cpp @@ -137,6 +137,11 @@ void Main_Game(int argc, char* argv[]) return; } + // Disable Uncompressed shapes so that the buffer allocation is moved + // to when we are in game. This way we can avoid allocating too much + // memory to it. + Disable_Uncompressed_Shapes(); + CCDebugString("C&C95 - Game initialisation complete.\n"); /* ** Game processing loop: @@ -183,6 +188,7 @@ void Main_Game(int argc, char* argv[]) InMainLoop = true; Set_Video_Cursor_Clip(true); + Enable_Uncompressed_Shapes(); #ifdef SCENARIO_EDITOR /* @@ -302,6 +308,8 @@ void Main_Game(int argc, char* argv[]) } } #endif + + Disable_Uncompressed_Shapes(); Set_Video_Cursor_Clip(false); InMainLoop = false; @@ -2638,6 +2646,7 @@ void CC_Draw_Line(int x, int y, int x1, int y1, unsigned char color, int frame, * HISTORY: * * 02/21/1995 JLB : Created. * *=============================================================================================*/ + //#pragma off(unreferenced) void CC_Draw_Shape(void const* shapefile, int shapenum, @@ -2943,7 +2952,7 @@ int VQ_Call_Back(unsigned char*, int) Interpolate_2X_Scale(&SysMemPage, &SeenBuff, NULL, Settings.Video.InterpolationMode); Frame_Limiter(); - if ((BreakoutAllowed || Debug_Flag) && key == KN_ESC) { + if ((BreakoutAllowed || Debug_Flag) && (key == KN_ESC || key == VK_LBUTTON)) { WWKeyboard->Clear(); Brokeout = true; return (true); diff --git a/tiberiandawn/credits.cpp b/tiberiandawn/credits.cpp index b05b8371..cd08b924 100644 --- a/tiberiandawn/credits.cpp +++ b/tiberiandawn/credits.cpp @@ -98,7 +98,6 @@ void CreditClass::Graphic_Logic(bool forced) Sound_Effect(VOC_DOWN, VOL_1); } } - /* ** Display the new current value. */ diff --git a/tiberiandawn/display.cpp b/tiberiandawn/display.cpp index 710212c3..179f6c62 100644 --- a/tiberiandawn/display.cpp +++ b/tiberiandawn/display.cpp @@ -243,8 +243,13 @@ void DisplayClass::One_Time(void) Mem_Copy(FadingShade, RemapTables[hindex][fade], 256); break; } - Mem_Copy( - &RemapTables[hindex][fade][((int)hindex + 11) * 16], &RemapTables[hindex][fade][(0 + 11) * 16], 16); + /* mparrot 2021-10-09: There is a buffer overflow if + hindex == HOUSE_COUNT - 1. It seems harmless in PC, but causes + a crash in other systems. Find a proper fix for it. */ + if (hindex < HOUSE_COUNT - 1) { + Mem_Copy( + &RemapTables[hindex][fade][((int)hindex + 11) * 16], &RemapTables[hindex][fade][(0 + 11) * 16], 16); + } } } } diff --git a/tiberiandawn/externs.h b/tiberiandawn/externs.h index 5689f26b..d4e98974 100644 --- a/tiberiandawn/externs.h +++ b/tiberiandawn/externs.h @@ -424,6 +424,10 @@ extern TheaterType LastTheater; extern bool ShareAllyVisibility; +#ifdef _NDS +void DS_Pause(const char*, ...); +#endif + // OmniBlade - Moves from tcpip.cpp as part of networking cleanup. extern bool Server; // Is this player acting as client or server diff --git a/tiberiandawn/function.h b/tiberiandawn/function.h index e4df3b53..d1954c95 100644 --- a/tiberiandawn/function.h +++ b/tiberiandawn/function.h @@ -374,6 +374,9 @@ void Go_Editor(bool flag); char* CC_Get_Shape_Filename(void const* shapeptr); void CC_Add_Shape_To_Global(void const* shapeptr, char* filename, char code); +void Disable_Uncompressed_Shapes(void); +void Enable_Uncompressed_Shapes(void); + void Bubba_Print(char* format, ...); void Heap_Dump_Check(const char* string); diff --git a/tiberiandawn/gscreen.cpp b/tiberiandawn/gscreen.cpp index be7a04b2..940a075a 100644 --- a/tiberiandawn/gscreen.cpp +++ b/tiberiandawn/gscreen.cpp @@ -455,8 +455,12 @@ extern bool CanVblankSync; * 02/14/1994 JLB : Created. * * 05/01/1994 JLB : Converted to member function. * *=============================================================================================*/ + +void DS_Blit_Display(GraphicViewPortClass& HidPage, GraphicViewPortClass& SeenPage); + void GScreenClass::Blit_Display(void) { +#ifndef _NDS #if (0) if (HidPage.Get_IsDirectDraw() && (Options.GameSpeed > 1 || Options.ScrollRate == 6 && CanVblankSync)) { WWMouse->Draw_Mouse(&HidPage); @@ -479,4 +483,7 @@ void GScreenClass::Blit_Display(void) #if (0) } #endif //(0) +#else + DS_Blit_Display(HidPage, SeenBuff); +#endif } diff --git a/tiberiandawn/house.cpp b/tiberiandawn/house.cpp index c08c8978..9a019067 100644 --- a/tiberiandawn/house.cpp +++ b/tiberiandawn/house.cpp @@ -356,6 +356,7 @@ HouseClass::HouseClass(HousesType house) , IonCannon(ION_CANNON_GONE_TIME, VOX_ION_READY, VOX_ION_CHARGING, VOX_ION_CHARGING, VOX_NO_POWER) , AirStrike(AIR_CANNON_GONE_TIME, VOX_AIRSTRIKE_READY, VOX_NONE, VOX_NOT_READY, VOX_NOT_READY) , NukeStrike(NUKE_GONE_TIME, VOX_NUKE_AVAILABLE, VOX_NONE, VOX_NOT_READY, VOX_NO_POWER) +#ifndef _NDS , AircraftTotals() , InfantryTotals() , UnitTotals() @@ -366,6 +367,7 @@ HouseClass::HouseClass(HousesType house) , DestroyedBuildings() , CapturedBuildings() , TotalCrates() +#endif { for (HousesType i = HOUSE_FIRST; i < HOUSE_COUNT; i++) { @@ -4852,18 +4854,19 @@ void HouseClass::Check_Pertinent_Structures(void) *=============================================================================================*/ void HouseClass::Init_Unit_Trackers(void) { + +#ifndef _NDS AircraftTotals.Init(); InfantryTotals.Init(); UnitTotals.Init(); BuildingTotals.Init(); - DestroyedAircraft.Init(); DestroyedInfantry.Init(); DestroyedUnits.Init(); DestroyedBuildings.Init(); - CapturedBuildings.Init(); TotalCrates.Init(); // 15 crate types +#endif } #ifdef USE_RA_AI diff --git a/tiberiandawn/house.h b/tiberiandawn/house.h index 8e0d3b09..04721415 100644 --- a/tiberiandawn/house.h +++ b/tiberiandawn/house.h @@ -302,6 +302,9 @@ class HouseClass */ unsigned IGaveUp : 1; + // UnitTracker has a leak and is only used for internet games, which we + // don't support for NDS. +#ifndef _NDS /* ** Stuff to keep track of the total number of units built by this house. */ @@ -317,6 +320,7 @@ class HouseClass UnitTrackerClass DestroyedInfantry; UnitTrackerClass DestroyedUnits; UnitTrackerClass DestroyedBuildings; +#endif /* ** Total number of enemy buildings captured by this house diff --git a/tiberiandawn/infantry.cpp b/tiberiandawn/infantry.cpp index 061c8070..6338d281 100644 --- a/tiberiandawn/infantry.cpp +++ b/tiberiandawn/infantry.cpp @@ -262,10 +262,11 @@ InfantryClass::InfantryClass(InfantryType classid, HousesType house) /* ** Keep count of the number of units created. Dont track civilians. */ +#ifndef _NDS if (!Class->IsCivilian && GameToPlay == GAME_INTERNET) { House->InfantryTotals.Increment_Unit_Total((int)classid); } - +#endif #ifdef USE_RA_AI // // Added for RA AI in TD. ST - 7/26/2019 9:12AM diff --git a/tiberiandawn/intro.cpp b/tiberiandawn/intro.cpp index daeace47..0790a5fc 100644 --- a/tiberiandawn/intro.cpp +++ b/tiberiandawn/intro.cpp @@ -224,7 +224,8 @@ void Choose_Side(void) speechhandle = Play_Sample(speechg); speechplaying = true; speech = speechg; - } else if ((WWKeyboard->MouseQX > 160 * scale_factor) && (WWKeyboard->MouseQX < 300 * scale_factor)) { + } else if ((WWKeyboard->MouseQX > 160 * scale_factor) + && (WWKeyboard->MouseQX < 300 * scale_factor)) { // Chose Nod selection = 1; endframe = 14; diff --git a/tiberiandawn/loaddlg.cpp b/tiberiandawn/loaddlg.cpp index fb7e3eaa..20467a78 100644 --- a/tiberiandawn/loaddlg.cpp +++ b/tiberiandawn/loaddlg.cpp @@ -242,6 +242,11 @@ int LoadOptionsClass::Process(void) -1, EditClass::ALPHANUMERIC); +#ifdef _NDS + // Nintendo DS doesn't have a keyboard, so we hack a name for the user. + strcpy(game_descr, Scen.ScenarioName); +#endif + /* ** Initialize. */ @@ -435,6 +440,11 @@ int LoadOptionsClass::Process(void) } game_num = Files[game_idx]->Num; +#ifdef _NDS + // Append game_num to the file so we can have multiple saves of the + // same mission. + snprintf(game_descr, 40, "%s_%03d", Scen.ScenarioName, game_num); +#endif if (!Save_Game(game_num, game_descr)) { WWMessageBox().Process(TXT_ERROR_SAVING_GAME); } else { diff --git a/tiberiandawn/mapsel.cpp b/tiberiandawn/mapsel.cpp index 93e914ca..5830e207 100644 --- a/tiberiandawn/mapsel.cpp +++ b/tiberiandawn/mapsel.cpp @@ -540,9 +540,9 @@ void Map_Selection(void) ** Now start the process where we fade the gray earth in. */ greyearth = - Open_Animation("GREYERTH.WSA", NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), localpalette); + Open_Animation("GREYERTH.WSA", NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_DISK | WSA_OPEN_TO_PAGE), localpalette); greyearth2 = - Open_Animation("E-BWTOCL.WSA", NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), grey2palette); + Open_Animation("E-BWTOCL.WSA", NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_DISK | WSA_OPEN_TO_PAGE), grey2palette); /* ** Load the spinning-globe anim @@ -551,21 +551,21 @@ void Map_Selection(void) const char* const earth_e = (factor == 1) ? "EARTH_E.WSA" : "HEARTH_E.WSA"; const char* const bosnia = (factor == 1) ? "BOSNIA.WSA" : "HBOSNIA.WSA"; - anim = Open_Animation(earth_e, NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), Palette); + anim = Open_Animation(earth_e, NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_DISK | WSA_OPEN_TO_PAGE), Palette); progress = Open_Animation(lastscenario ? bosnia : "EUROPE.WSA", NULL, 0, - (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), + (WSAOpenType)(WSA_OPEN_FROM_DISK | WSA_OPEN_TO_PAGE), progresspalette); } else { const char* const earth_a = (factor == 1) ? "EARTH_A.WSA" : "HEARTH_A.WSA"; const char* const safrica = (factor == 1) ? "S_AFRICA.WSA" : "HSAFRICA.WSA"; - anim = Open_Animation(earth_a, NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), Palette); + anim = Open_Animation(earth_a, NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_DISK | WSA_OPEN_TO_PAGE), Palette); progress = Open_Animation(lastscenario ? safrica : "AFRICA.WSA", NULL, 0, - (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), + (WSAOpenType)(WSA_OPEN_FROM_DISK | WSA_OPEN_TO_PAGE), progresspalette); } diff --git a/tiberiandawn/mplayer.cpp b/tiberiandawn/mplayer.cpp index 7d76feb7..0f01bc2c 100644 --- a/tiberiandawn/mplayer.cpp +++ b/tiberiandawn/mplayer.cpp @@ -46,6 +46,10 @@ #include "common/framelimit.h" #include "common/ini.h" +#ifdef _NDS +#include +#endif + static void Garble_Message(char* buf); int Choose_Internet_Game(void); @@ -527,8 +531,17 @@ void Read_MultiPlayer_Settings(void) if (ini.Load(file)) { // Get the player's last-used Handle +#ifdef _NDS + // Get DS user name as player name. + int i; + for (i = 0; i < PersonalData->nameLen; i++) { + MPlayerName[i] = PersonalData->name[i] & 0xFF; + } + // Ensure that our string is null-terminated. + MPlayerName[i] = '\0'; +#else ini.Get_String("MultiPlayer", "Handle", "Noname", MPlayerName, sizeof(MPlayerName)); - +#endif // Get the player's last-used Color MPlayerPrefColor = (PlayerColorType)ini.Get_Int("MultiPlayer", "Color", 0); diff --git a/tiberiandawn/score.cpp b/tiberiandawn/score.cpp index d1e8c6d3..1742ff7d 100644 --- a/tiberiandawn/score.cpp +++ b/tiberiandawn/score.cpp @@ -653,7 +653,7 @@ void ScoreClass::Presentation(void) /* ** Load the background for the score screen */ - anim = Open_Animation(ScreenNames[house], NULL, 0L, (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), Palette); + anim = Open_Animation(ScreenNames[house], NULL, 0L, (WSAOpenType)(WSA_OPEN_FROM_DISK | WSA_OPEN_TO_PAGE), Palette); unsigned minutes = (unsigned)((ElapsedTime / TIMER_MINUTE)) + 1; @@ -1613,6 +1613,11 @@ void ScoreClass::Input_Name(char str[], int xpos, int ypos, char const pal[]) */ PseudoSeenBuff->Blit(SysMemPage); +#ifdef _NDS + // Nintendo DS doesn't have a keyboard, so we hack something for the user. + strcpy(str, Scen.ScenarioName); +#endif + do { Call_Back(); Animate_Score_Objs(); @@ -1681,7 +1686,7 @@ void ScoreClass::Input_Name(char str[], int xpos, int ypos, char const pal[]) } Frame_Limiter(); - } while (key != KN_RETURN && key != KN_KEYPAD_RETURN); + } while (key != KN_RETURN && key != KN_KEYPAD_RETURN && key != VK_LBUTTON && key != VK_ESCAPE); } void Animate_Cursor(int pos, int ypos) @@ -2032,7 +2037,7 @@ void Multi_Score_Presentation(void) Set_Palette(BlackPalette); - anim = Open_Animation("MLTIPLYR.WSA", NULL, 0L, (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), Palette); + anim = Open_Animation("MLTIPLYR.WSA", NULL, 0L, (WSAOpenType)(WSA_OPEN_FROM_DISK | WSA_OPEN_TO_PAGE), Palette); Hide_Mouse(); /* diff --git a/tiberiandawn/scroll.cpp b/tiberiandawn/scroll.cpp index fd895aef..fba3d74e 100644 --- a/tiberiandawn/scroll.cpp +++ b/tiberiandawn/scroll.cpp @@ -36,6 +36,9 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" +#ifdef _NDS +#include +#endif #define SCROLL_DELAY 1 @@ -88,6 +91,31 @@ void ScrollClass::AI(KeyNumType& input, int x, int y) static DirType direction; bool player_scrolled = false; +#ifdef _NDS + int true_x = x; + int true_y = y; + + // Hack scroll in DPAD + uint32_t keys_current = keysCurrent(); + x = 160; + y = 100; + + if (!(keys_current & KEY_L) && !(keys_current & KEY_B)) { + if (keys_current & KEY_UP) { + y -= 100; + } + if (keys_current & KEY_DOWN) { + y += 99; + } + if (keys_current & KEY_LEFT) { + x -= 160; + } + if (keys_current & KEY_RIGHT) { + x += 159; + } + } +#endif + /* ** If rubber band mode is in progress, then don't allow scrolling of the tactical map. */ @@ -213,6 +241,11 @@ void ScrollClass::AI(KeyNumType& input, int x, int y) } } #endif +#ifdef _NDS + x = true_x; + y = true_y; +#endif + HelpClass::AI(input, x, y); } diff --git a/tiberiandawn/techno.cpp b/tiberiandawn/techno.cpp index 89788ec0..4d5de29f 100644 --- a/tiberiandawn/techno.cpp +++ b/tiberiandawn/techno.cpp @@ -3176,9 +3176,11 @@ void TechnoClass::Record_The_Kill(TechnoClass* source) House->BuildingsLost++; } if (source) { +#ifndef _NDS if (GameToPlay == GAME_INTERNET) { source->House->DestroyedBuildings.Increment_Unit_Total(((BuildingClass*)this)->Class->Type); } +#endif source->House->BuildingsKilled[Owner()]++; } @@ -3194,9 +3196,11 @@ void TechnoClass::Record_The_Kill(TechnoClass* source) case RTTI_AIRCRAFT: House->UnitsLost++; if (source) { +#ifndef _NDS if (GameToPlay == GAME_INTERNET) { source->House->DestroyedAircraft.Increment_Unit_Total(((AircraftClass*)this)->Class->Type); } +#endif source->House->UnitsKilled[Owner()]++; } /* @@ -3211,9 +3215,11 @@ void TechnoClass::Record_The_Kill(TechnoClass* source) case RTTI_INFANTRY: House->UnitsLost++; if (source) { +#ifndef _NDS if (GameToPlay == GAME_INTERNET) { source->House->DestroyedInfantry.Increment_Unit_Total(((InfantryClass*)this)->Class->Type); } +#endif source->House->UnitsKilled[Owner()]++; } /* @@ -3228,9 +3234,11 @@ void TechnoClass::Record_The_Kill(TechnoClass* source) case RTTI_UNIT: House->UnitsLost++; if (source) { +#ifndef _NDS if (GameToPlay == GAME_INTERNET) { source->House->DestroyedUnits.Increment_Unit_Total(((UnitClass*)this)->Class->Type); } +#endif source->House->UnitsKilled[Owner()]++; } diff --git a/tiberiandawn/unit.cpp b/tiberiandawn/unit.cpp index f90f7bff..38a282e8 100644 --- a/tiberiandawn/unit.cpp +++ b/tiberiandawn/unit.cpp @@ -1166,13 +1166,14 @@ UnitClass::UnitClass(UnitType classid, HousesType house) if (Class->IsAnimating) Set_Rate(Options.Normalize_Delay(3)); - /* + /* ** Keep count of the number of units created. */ +#ifndef _NDS if (GameToPlay == GAME_INTERNET) { House->UnitTotals.Increment_Unit_Total((int)classid); } - +#endif #ifdef USE_RA_AI // // Added for RA AI in TD. ST - 7/26/2019 9:12AM diff --git a/tiberiandawn/winstub.cpp b/tiberiandawn/winstub.cpp index 9c32c5da..8135f696 100644 --- a/tiberiandawn/winstub.cpp +++ b/tiberiandawn/winstub.cpp @@ -40,6 +40,11 @@ #include "externs.h" #include "common/wsproto.h" #include "common/vqaaudio.h" +#include "debugstring.h" + +#ifdef _NDS +#include +#endif void output(short, short) { @@ -433,6 +438,14 @@ bool Any_Locked() *=============================================================================================*/ void Memory_Error_Handler(void) { + +#ifdef _NDS + DBG_LOG("Error - out of memory"); + swiWaitForVBlank(); + while (1) + ; +#endif + GlyphX_Debug_Print("Error - out of memory."); VisiblePage.Clear(); Set_Palette(GamePalette);