|
|
|
|
#include "./sdl2_to_1_2_backports.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
|
|
|
|
#include "./console.h"
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
|
#define NOMINMAX 1
|
|
|
|
|
#define UNICODE 1
|
|
|
|
|
#include <shlobj.h>
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#define DEFAULT_PRIORITY SDL_LOG_PRIORITY_CRITICAL
|
|
|
|
|
#define DEFAULT_ASSERT_PRIORITY SDL_LOG_PRIORITY_WARN
|
|
|
|
|
#define DEFAULT_APPLICATION_PRIORITY SDL_LOG_PRIORITY_INFO
|
|
|
|
|
#define DEFAULT_TEST_PRIORITY SDL_LOG_PRIORITY_VERBOSE
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
// We use the same names of these structs as the SDL2 implementation:
|
|
|
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
|
|
|
struct SDL_LogLevel {
|
|
|
|
|
int category;
|
|
|
|
|
SDL_LogPriority priority;
|
|
|
|
|
SDL_LogLevel *next;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SDL_LogLevel *SDL_loglevels; // NOLINT(readability-identifier-naming)
|
|
|
|
|
SDL_LogPriority SDL_default_priority = DEFAULT_PRIORITY; // NOLINT(readability-identifier-naming)
|
|
|
|
|
SDL_LogPriority SDL_assert_priority = DEFAULT_ASSERT_PRIORITY; // NOLINT(readability-identifier-naming)
|
|
|
|
|
SDL_LogPriority SDL_application_priority = DEFAULT_APPLICATION_PRIORITY; // NOLINT(readability-identifier-naming)
|
|
|
|
|
SDL_LogPriority SDL_test_priority = DEFAULT_TEST_PRIORITY; // NOLINT(readability-identifier-naming)
|
|
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
|
|
|
const char *const SDL_priority_prefixes[SDL_NUM_LOG_PRIORITIES] = {
|
|
|
|
|
nullptr,
|
|
|
|
|
"VERBOSE",
|
|
|
|
|
"DEBUG",
|
|
|
|
|
"INFO",
|
|
|
|
|
"WARN",
|
|
|
|
|
"ERROR",
|
|
|
|
|
"CRITICAL"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void SDL_LogSetAllPriority(SDL_LogPriority priority)
|
|
|
|
|
{
|
|
|
|
|
for (SDL_LogLevel *entry = SDL_loglevels; entry != nullptr; entry = entry->next) {
|
|
|
|
|
entry->priority = priority;
|
|
|
|
|
}
|
|
|
|
|
SDL_default_priority = priority;
|
|
|
|
|
SDL_assert_priority = priority;
|
|
|
|
|
SDL_application_priority = priority;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_LogSetPriority(int category, SDL_LogPriority priority)
|
|
|
|
|
{
|
|
|
|
|
SDL_LogLevel *entry;
|
|
|
|
|
for (entry = SDL_loglevels; entry != nullptr; entry = entry->next) {
|
|
|
|
|
if (entry->category == category) {
|
|
|
|
|
entry->priority = priority;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entry = static_cast<SDL_LogLevel *>(SDL_malloc(sizeof(*entry)));
|
|
|
|
|
if (entry != nullptr) {
|
|
|
|
|
entry->category = category;
|
|
|
|
|
entry->priority = priority;
|
|
|
|
|
entry->next = SDL_loglevels;
|
|
|
|
|
SDL_loglevels = entry;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SDL_LogPriority SDL_LogGetPriority(int category)
|
|
|
|
|
{
|
|
|
|
|
for (SDL_LogLevel *entry = SDL_loglevels; entry != nullptr; entry = entry->next) {
|
|
|
|
|
if (entry->category == category) {
|
|
|
|
|
return entry->priority;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (category) {
|
|
|
|
|
case SDL_LOG_CATEGORY_TEST:
|
|
|
|
|
return SDL_test_priority;
|
|
|
|
|
case SDL_LOG_CATEGORY_APPLICATION:
|
|
|
|
|
return SDL_application_priority;
|
|
|
|
|
case SDL_LOG_CATEGORY_ASSERT:
|
|
|
|
|
return SDL_assert_priority;
|
|
|
|
|
default:
|
|
|
|
|
return SDL_default_priority;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_Log(const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_LogVerbose(int category, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
SDL_LogMessageV(category, SDL_LOG_PRIORITY_VERBOSE, fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_LogDebug(int category, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
SDL_LogMessageV(category, SDL_LOG_PRIORITY_DEBUG, fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_LogInfo(int category, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
SDL_LogMessageV(category, SDL_LOG_PRIORITY_INFO, fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_LogWarn(int category, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
SDL_LogMessageV(category, SDL_LOG_PRIORITY_WARN, fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_LogError(int category, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
SDL_LogMessageV(category, SDL_LOG_PRIORITY_ERROR, fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_LogCritical(int category, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
SDL_LogMessageV(category, SDL_LOG_PRIORITY_CRITICAL, fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_LogMessage(int category, SDL_LogPriority priority, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
SDL_LogMessageV(category, priority, fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SDL_LogMessageV(int category, SDL_LogPriority priority, const char *fmt, va_list ap)
|
|
|
|
|
{
|
|
|
|
|
if (static_cast<int>(priority) < 0 || priority >= SDL_NUM_LOG_PRIORITIES || priority < SDL_LogGetPriority(category))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
::devilution::printfInConsole("%s: ", SDL_priority_prefixes[priority]);
|
|
|
|
|
::devilution::vprintfInConsole(fmt, ap);
|
|
|
|
|
::devilution::printNewlineInConsole();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
#define DEFINE_COPY_ROW(name, type) \
|
|
|
|
|
void name(type *src, int src_w, type *dst, int dst_w) \
|
|
|
|
|
{ \
|
|
|
|
|
type pixel = 0; \
|
|
|
|
|
\
|
|
|
|
|
int pos = 0x10000; \
|
|
|
|
|
int inc = (src_w << 16) / dst_w; \
|
|
|
|
|
for (int i = dst_w; i > 0; i--) { \
|
|
|
|
|
while (pos >= 0x10000L) { \
|
|
|
|
|
pixel = *src++; \
|
|
|
|
|
pos -= 0x10000L; \
|
|
|
|
|
} \
|
|
|
|
|
*dst++ = pixel; \
|
|
|
|
|
pos += inc; \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
DEFINE_COPY_ROW(copy_row1, Uint8)
|
|
|
|
|
DEFINE_COPY_ROW(copy_row2, Uint16)
|
|
|
|
|
DEFINE_COPY_ROW(copy_row4, Uint32)
|
|
|
|
|
|
|
|
|
|
void copy_row3(Uint8 *src, int src_w, Uint8 *dst, int dst_w)
|
|
|
|
|
{
|
|
|
|
|
Uint8 pixel[3] = { 0, 0, 0 };
|
|
|
|
|
|
|
|
|
|
int pos = 0x10000;
|
|
|
|
|
int inc = (src_w << 16) / dst_w;
|
|
|
|
|
for (int i = dst_w; i > 0; --i) {
|
|
|
|
|
while (pos >= 0x10000L) {
|
|
|
|
|
pixel[0] = *src++;
|
|
|
|
|
pixel[1] = *src++;
|
|
|
|
|
pixel[2] = *src++;
|
|
|
|
|
pos -= 0x10000L;
|
|
|
|
|
}
|
|
|
|
|
*dst++ = pixel[0];
|
|
|
|
|
*dst++ = pixel[1];
|
|
|
|
|
*dst++ = pixel[2];
|
|
|
|
|
pos += inc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
int SDL_SoftStretch(SDL_Surface *src, const SDL_Rect *srcrect,
|
|
|
|
|
SDL_Surface *dst, const SDL_Rect *dstrect)
|
|
|
|
|
{
|
|
|
|
|
// All the ASM support has been removed, as the platforms that the ASM
|
|
|
|
|
// implementation exists for support SDL2 anyway.
|
|
|
|
|
int src_locked;
|
|
|
|
|
int dst_locked;
|
|
|
|
|
int pos, inc;
|
|
|
|
|
int dst_maxrow;
|
|
|
|
|
int src_row, dst_row;
|
|
|
|
|
Uint8 *srcp = NULL;
|
|
|
|
|
Uint8 *dstp;
|
|
|
|
|
SDL_Rect full_src;
|
|
|
|
|
SDL_Rect full_dst;
|
|
|
|
|
const int bpp = dst->format->BytesPerPixel;
|
|
|
|
|
|
|
|
|
|
if (!SDLBackport_PixelFormatFormatEq(src->format, dst->format)) {
|
|
|
|
|
SDL_SetError("Only works with same format surfaces");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Verify the blit rectangles */
|
|
|
|
|
if (srcrect) {
|
|
|
|
|
if ((srcrect->x < 0) || (srcrect->y < 0) || ((srcrect->x + srcrect->w) > src->w) || ((srcrect->y + srcrect->h) > src->h)) {
|
|
|
|
|
SDL_SetError("Invalid source blit rectangle");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
full_src.x = 0;
|
|
|
|
|
full_src.y = 0;
|
|
|
|
|
full_src.w = src->w;
|
|
|
|
|
full_src.h = src->h;
|
|
|
|
|
srcrect = &full_src;
|
|
|
|
|
}
|
|
|
|
|
if (dstrect) {
|
|
|
|
|
if ((dstrect->x < 0) || (dstrect->y < 0) || ((dstrect->x + dstrect->w) > dst->w) || ((dstrect->y + dstrect->h) > dst->h)) {
|
|
|
|
|
SDL_SetError("Invalid destination blit rectangle");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
full_dst.x = 0;
|
|
|
|
|
full_dst.y = 0;
|
|
|
|
|
full_dst.w = dst->w;
|
|
|
|
|
full_dst.h = dst->h;
|
|
|
|
|
dstrect = &full_dst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Lock the destination if it's in hardware */
|
|
|
|
|
dst_locked = 0;
|
|
|
|
|
if (SDL_MUSTLOCK(dst)) {
|
|
|
|
|
if (SDL_LockSurface(dst) < 0) {
|
|
|
|
|
SDL_SetError("Unable to lock destination surface");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
dst_locked = 1;
|
|
|
|
|
}
|
|
|
|
|
/* Lock the source if it's in hardware */
|
|
|
|
|
src_locked = 0;
|
|
|
|
|
if (SDL_MUSTLOCK(src)) {
|
|
|
|
|
if (SDL_LockSurface(src) < 0) {
|
|
|
|
|
if (dst_locked) {
|
|
|
|
|
SDL_UnlockSurface(dst);
|
|
|
|
|
}
|
|
|
|
|
SDL_SetError("Unable to lock source surface");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
src_locked = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Set up the data... */
|
|
|
|
|
pos = 0x10000;
|
|
|
|
|
inc = (srcrect->h << 16) / dstrect->h;
|
|
|
|
|
src_row = srcrect->y;
|
|
|
|
|
dst_row = dstrect->y;
|
|
|
|
|
|
|
|
|
|
/* Perform the stretch blit */
|
|
|
|
|
for (dst_maxrow = dst_row + dstrect->h; dst_row < dst_maxrow; ++dst_row) {
|
|
|
|
|
dstp = (Uint8 *)dst->pixels + (dst_row * dst->pitch)
|
|
|
|
|
+ (dstrect->x * bpp);
|
|
|
|
|
while (pos >= 0x10000L) {
|
|
|
|
|
srcp = (Uint8 *)src->pixels + (src_row * src->pitch)
|
|
|
|
|
+ (srcrect->x * bpp);
|
|
|
|
|
++src_row;
|
|
|
|
|
pos -= 0x10000L;
|
|
|
|
|
}
|
|
|
|
|
switch (bpp) {
|
|
|
|
|
case 1:
|
|
|
|
|
copy_row1(srcp, srcrect->w, dstp, dstrect->w);
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
copy_row2((Uint16 *)srcp, srcrect->w,
|
|
|
|
|
(Uint16 *)dstp, dstrect->w);
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
copy_row3(srcp, srcrect->w, dstp, dstrect->w);
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
copy_row4((Uint32 *)srcp, srcrect->w,
|
|
|
|
|
(Uint32 *)dstp, dstrect->w);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
pos += inc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* We need to unlock the surfaces if they're locked */
|
|
|
|
|
if (dst_locked) {
|
|
|
|
|
SDL_UnlockSurface(dst);
|
|
|
|
|
}
|
|
|
|
|
if (src_locked) {
|
|
|
|
|
SDL_UnlockSurface(src);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SDL_BlitScaled(SDL_Surface *src, SDL_Rect *srcrect,
|
|
|
|
|
SDL_Surface *dst, SDL_Rect *dstrect)
|
|
|
|
|
{
|
|
|
|
|
if (!SDLBackport_PixelFormatFormatEq(src->format, dst->format) || SDLBackport_IsPixelFormatIndexed(src->format)) {
|
|
|
|
|
return SDL_BlitSurface(src, srcrect, dst, dstrect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double src_x0, src_y0, src_x1, src_y1;
|
|
|
|
|
double dst_x0, dst_y0, dst_x1, dst_y1;
|
|
|
|
|
SDL_Rect final_src, final_dst;
|
|
|
|
|
double scaling_w, scaling_h;
|
|
|
|
|
int src_w, src_h;
|
|
|
|
|
int dst_w, dst_h;
|
|
|
|
|
|
|
|
|
|
/* Make sure the surfaces aren't locked */
|
|
|
|
|
if (!src || !dst) {
|
|
|
|
|
SDL_SetError("SDL_UpperBlitScaled: passed a NULL surface");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (src->locked || dst->locked) {
|
|
|
|
|
SDL_SetError("Surfaces must not be locked during blit");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NULL == srcrect) {
|
|
|
|
|
src_w = src->w;
|
|
|
|
|
src_h = src->h;
|
|
|
|
|
} else {
|
|
|
|
|
src_w = srcrect->w;
|
|
|
|
|
src_h = srcrect->h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NULL == dstrect) {
|
|
|
|
|
dst_w = dst->w;
|
|
|
|
|
dst_h = dst->h;
|
|
|
|
|
} else {
|
|
|
|
|
dst_w = dstrect->w;
|
|
|
|
|
dst_h = dstrect->h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dst_w == src_w && dst_h == src_h) {
|
|
|
|
|
/* No scaling, defer to regular blit */
|
|
|
|
|
return SDL_BlitSurface(src, srcrect, dst, dstrect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scaling_w = (double)dst_w / src_w;
|
|
|
|
|
scaling_h = (double)dst_h / src_h;
|
|
|
|
|
|
|
|
|
|
if (NULL == dstrect) {
|
|
|
|
|
dst_x0 = 0;
|
|
|
|
|
dst_y0 = 0;
|
|
|
|
|
dst_x1 = dst_w - 1;
|
|
|
|
|
dst_y1 = dst_h - 1;
|
|
|
|
|
} else {
|
|
|
|
|
dst_x0 = dstrect->x;
|
|
|
|
|
dst_y0 = dstrect->y;
|
|
|
|
|
dst_x1 = dst_x0 + dst_w - 1;
|
|
|
|
|
dst_y1 = dst_y0 + dst_h - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NULL == srcrect) {
|
|
|
|
|
src_x0 = 0;
|
|
|
|
|
src_y0 = 0;
|
|
|
|
|
src_x1 = src_w - 1;
|
|
|
|
|
src_y1 = src_h - 1;
|
|
|
|
|
} else {
|
|
|
|
|
src_x0 = srcrect->x;
|
|
|
|
|
src_y0 = srcrect->y;
|
|
|
|
|
src_x1 = src_x0 + src_w - 1;
|
|
|
|
|
src_y1 = src_y0 + src_h - 1;
|
|
|
|
|
|
|
|
|
|
/* Clip source rectangle to the source surface */
|
|
|
|
|
|
|
|
|
|
if (src_x0 < 0) {
|
|
|
|
|
dst_x0 -= src_x0 * scaling_w;
|
|
|
|
|
src_x0 = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (src_x1 >= src->w) {
|
|
|
|
|
dst_x1 -= (src_x1 - src->w + 1) * scaling_w;
|
|
|
|
|
src_x1 = src->w - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (src_y0 < 0) {
|
|
|
|
|
dst_y0 -= src_y0 * scaling_h;
|
|
|
|
|
src_y0 = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (src_y1 >= src->h) {
|
|
|
|
|
dst_y1 -= (src_y1 - src->h + 1) * scaling_h;
|
|
|
|
|
src_y1 = src->h - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Clip destination rectangle to the clip rectangle */
|
|
|
|
|
|
|
|
|
|
/* Translate to clip space for easier calculations */
|
|
|
|
|
dst_x0 -= dst->clip_rect.x;
|
|
|
|
|
dst_x1 -= dst->clip_rect.x;
|
|
|
|
|
dst_y0 -= dst->clip_rect.y;
|
|
|
|
|
dst_y1 -= dst->clip_rect.y;
|
|
|
|
|
|
|
|
|
|
if (dst_x0 < 0) {
|
|
|
|
|
src_x0 -= dst_x0 / scaling_w;
|
|
|
|
|
dst_x0 = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dst_x1 >= dst->clip_rect.w) {
|
|
|
|
|
src_x1 -= (dst_x1 - dst->clip_rect.w + 1) / scaling_w;
|
|
|
|
|
dst_x1 = dst->clip_rect.w - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dst_y0 < 0) {
|
|
|
|
|
src_y0 -= dst_y0 / scaling_h;
|
|
|
|
|
dst_y0 = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dst_y1 >= dst->clip_rect.h) {
|
|
|
|
|
src_y1 -= (dst_y1 - dst->clip_rect.h + 1) / scaling_h;
|
|
|
|
|
dst_y1 = dst->clip_rect.h - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Translate back to surface coordinates */
|
|
|
|
|
dst_x0 += dst->clip_rect.x;
|
|
|
|
|
dst_x1 += dst->clip_rect.x;
|
|
|
|
|
dst_y0 += dst->clip_rect.y;
|
|
|
|
|
dst_y1 += dst->clip_rect.y;
|
|
|
|
|
|
|
|
|
|
final_src.x = static_cast<Sint16>(SDL_floor(src_x0 + 0.5));
|
|
|
|
|
final_src.y = static_cast<Sint16>(SDL_floor(src_y0 + 0.5));
|
|
|
|
|
src_w = std::max(static_cast<int>(SDL_floor(src_x1 + 1 + 0.5)) - static_cast<int>(SDL_floor(src_x0 + 0.5)), 0);
|
|
|
|
|
src_h = std::max(static_cast<int>(SDL_floor(src_y1 + 1 + 0.5)) - static_cast<int>(SDL_floor(src_y0 + 0.5)), 0);
|
|
|
|
|
|
|
|
|
|
final_src.w = static_cast<Uint16>(src_w);
|
|
|
|
|
final_src.h = static_cast<Uint16>(src_h);
|
|
|
|
|
|
|
|
|
|
final_dst.x = static_cast<Sint16>(SDL_floor(dst_x0 + 0.5));
|
|
|
|
|
final_dst.y = static_cast<Sint16>(SDL_floor(dst_y0 + 0.5));
|
|
|
|
|
dst_w = std::max(static_cast<int>(SDL_floor(dst_x1 - dst_x0 + 1.5)), 0);
|
|
|
|
|
dst_h = std::max(static_cast<int>(SDL_floor(dst_y1 - dst_y0 + 1.5)), 0);
|
|
|
|
|
|
|
|
|
|
final_dst.w = static_cast<Uint16>(dst_w);
|
|
|
|
|
final_dst.h = static_cast<Uint16>(dst_h);
|
|
|
|
|
|
|
|
|
|
if (dstrect)
|
|
|
|
|
*dstrect = final_dst;
|
|
|
|
|
|
|
|
|
|
if (final_dst.w == 0 || final_dst.h == 0 || final_src.w == 0 || final_src.h == 0) {
|
|
|
|
|
/* No-op. */
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SDL_SoftStretch(src, &final_src, dst, &final_dst);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// = Filesystem
|
|
|
|
|
|
|
|
|
|
Sint64 SDL_RWsize(SDL_RWops *context)
|
|
|
|
|
{
|
|
|
|
|
const int current = SDL_RWtell(context);
|
|
|
|
|
if (current == -1)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
const int begin = SDL_RWseek(context, 0, RW_SEEK_SET);
|
|
|
|
|
if (begin == -1)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
const int end = SDL_RWseek(context, 0, RW_SEEK_END);
|
|
|
|
|
if (end == -1)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
if (SDL_RWseek(context, current, RW_SEEK_SET) == -1)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
return end - begin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
// From sdl2-2.0.9/src/core/windows/SDL_windows.h
|
|
|
|
|
#define WIN_StringToUTF8(S) SDL_iconv_string("UTF-8", "UTF-16LE", (char *)(S), (wcslen(S) + 1) * sizeof(WCHAR))
|
|
|
|
|
#define WIN_UTF8ToString(S) (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (char *)(S), SDL_strlen(S) + 1)
|
|
|
|
|
|
|
|
|
|
/* Sets an error message based on an HRESULT */
|
|
|
|
|
int WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr)
|
|
|
|
|
{
|
|
|
|
|
// From sdl2-2.0.9/src/core/windows/SDL_windows.c
|
|
|
|
|
|
|
|
|
|
TCHAR buffer[1024];
|
|
|
|
|
char *message;
|
|
|
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, 0,
|
|
|
|
|
buffer, SDL_arraysize(buffer), NULL);
|
|
|
|
|
message = WIN_StringToUTF8(buffer);
|
|
|
|
|
SDL_SetError("%s%s%s", prefix ? prefix : "", prefix ? ": " : "", message);
|
|
|
|
|
SDL_free(message);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Sets an error message based on GetLastError() */
|
|
|
|
|
int WIN_SetError(const char *prefix)
|
|
|
|
|
{
|
|
|
|
|
// From sdl2-2.0.9/src/core/windows/SDL_windows.c
|
|
|
|
|
|
|
|
|
|
return WIN_SetErrorFromHRESULT(prefix, GetLastError());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
char *SDL_GetBasePath(void)
|
|
|
|
|
{
|
|
|
|
|
// From sdl2-2.0.9/src/filesystem/windows/SDL_sysfilesystem.c
|
|
|
|
|
|
|
|
|
|
typedef DWORD(WINAPI * GetModuleFileNameExW_t)(HANDLE, HMODULE, LPWSTR, DWORD);
|
|
|
|
|
GetModuleFileNameExW_t pGetModuleFileNameExW;
|
|
|
|
|
DWORD buflen = 128;
|
|
|
|
|
WCHAR *path = NULL;
|
|
|
|
|
HMODULE psapi = LoadLibrary(L"psapi.dll");
|
|
|
|
|
char *retval = NULL;
|
|
|
|
|
DWORD len = 0;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
if (!psapi) {
|
|
|
|
|
WIN_SetError("Couldn't load psapi.dll");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pGetModuleFileNameExW = (GetModuleFileNameExW_t)GetProcAddress(psapi, "GetModuleFileNameExW");
|
|
|
|
|
if (!pGetModuleFileNameExW) {
|
|
|
|
|
WIN_SetError("Couldn't find GetModuleFileNameExW");
|
|
|
|
|
FreeLibrary(psapi);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (SDL_TRUE) {
|
|
|
|
|
void *ptr = SDL_realloc(path, buflen * sizeof(WCHAR));
|
|
|
|
|
if (!ptr) {
|
|
|
|
|
SDL_free(path);
|
|
|
|
|
FreeLibrary(psapi);
|
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
path = (WCHAR *)ptr;
|
|
|
|
|
|
|
|
|
|
len = pGetModuleFileNameExW(GetCurrentProcess(), NULL, path, buflen);
|
|
|
|
|
if (len != buflen) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* buffer too small? Try again. */
|
|
|
|
|
buflen *= 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FreeLibrary(psapi);
|
|
|
|
|
|
|
|
|
|
if (len == 0) {
|
|
|
|
|
SDL_free(path);
|
|
|
|
|
WIN_SetError("Couldn't locate our .exe");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = len - 1; i > 0; i--) {
|
|
|
|
|
if (path[i] == '\\') {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
path[i + 1] = '\0'; /* chop off filename. */
|
|
|
|
|
|
|
|
|
|
retval = WIN_StringToUTF8(path);
|
|
|
|
|
SDL_free(path);
|
|
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *SDL_GetPrefPath(const char *org, const char *app)
|
|
|
|
|
{
|
|
|
|
|
// From sdl2-2.0.9/src/filesystem/windows/SDL_sysfilesystem.c
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Vista and later has a new API for this, but SHGetFolderPath works there,
|
|
|
|
|
* and apparently just wraps the new API. This is the new way to do it:
|
|
|
|
|
*
|
|
|
|
|
* SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE,
|
|
|
|
|
* NULL, &wszPath);
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
WCHAR path[MAX_PATH];
|
|
|
|
|
char *retval = NULL;
|
|
|
|
|
WCHAR *worg = NULL;
|
|
|
|
|
WCHAR *wapp = NULL;
|
|
|
|
|
size_t new_wpath_len = 0;
|
|
|
|
|
BOOL api_result = FALSE;
|
|
|
|
|
|
|
|
|
|
if (!app) {
|
|
|
|
|
SDL_InvalidParamError("app");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
if (!org) {
|
|
|
|
|
org = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, path))) {
|
|
|
|
|
WIN_SetError("Couldn't locate our prefpath");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
worg = WIN_UTF8ToString(org);
|
|
|
|
|
if (worg == NULL) {
|
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wapp = WIN_UTF8ToString(app);
|
|
|
|
|
if (wapp == NULL) {
|
|
|
|
|
SDL_free(worg);
|
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_wpath_len = lstrlenW(worg) + lstrlenW(wapp) + lstrlenW(path) + 3;
|
|
|
|
|
|
|
|
|
|
if ((new_wpath_len + 1) > MAX_PATH) {
|
|
|
|
|
SDL_free(worg);
|
|
|
|
|
SDL_free(wapp);
|
|
|
|
|
WIN_SetError("Path too long.");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (*worg) {
|
|
|
|
|
lstrcatW(path, L"\\");
|
|
|
|
|
lstrcatW(path, worg);
|
|
|
|
|
}
|
|
|
|
|
SDL_free(worg);
|
|
|
|
|
|
|
|
|
|
api_result = CreateDirectoryW(path, NULL);
|
|
|
|
|
if (api_result == FALSE) {
|
|
|
|
|
if (GetLastError() != ERROR_ALREADY_EXISTS) {
|
|
|
|
|
SDL_free(wapp);
|
|
|
|
|
WIN_SetError("Couldn't create a prefpath.");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lstrcatW(path, L"\\");
|
|
|
|
|
lstrcatW(path, wapp);
|
|
|
|
|
SDL_free(wapp);
|
|
|
|
|
|
|
|
|
|
api_result = CreateDirectoryW(path, NULL);
|
|
|
|
|
if (api_result == FALSE) {
|
|
|
|
|
if (GetLastError() != ERROR_ALREADY_EXISTS) {
|
|
|
|
|
WIN_SetError("Couldn't create a prefpath.");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lstrcatW(path, L"\\");
|
|
|
|
|
|
|
|
|
|
retval = WIN_StringToUTF8(path);
|
|
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
#if !defined(__QNXNTO__)
|
|
|
|
|
char *readSymLink(const char *path)
|
|
|
|
|
{
|
|
|
|
|
// From sdl2-2.0.9/src/filesystem/unix/SDL_sysfilesystem.c
|
|
|
|
|
char *retval = NULL;
|
|
|
|
|
ssize_t len = 64;
|
|
|
|
|
ssize_t rc = -1;
|
|
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
|
char *ptr = (char *)SDL_realloc(retval, (size_t)len);
|
|
|
|
|
if (ptr == NULL) {
|
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retval = ptr;
|
|
|
|
|
|
|
|
|
|
rc = readlink(path, retval, len);
|
|
|
|
|
if (rc == -1) {
|
|
|
|
|
break; /* not a symlink, i/o error, etc. */
|
|
|
|
|
} else if (rc < len) {
|
|
|
|
|
retval[rc] = '\0'; /* readlink doesn't null-terminate. */
|
|
|
|
|
return retval; /* we're good to go. */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
len *= 2; /* grow buffer, try again. */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SDL_free(retval);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
char *SDL_GetBasePath()
|
|
|
|
|
{
|
|
|
|
|
// From sdl2-2.0.9/src/filesystem/unix/SDL_sysfilesystem.c
|
|
|
|
|
|
|
|
|
|
char *retval = NULL;
|
|
|
|
|
|
|
|
|
|
#if defined(__FREEBSD__)
|
|
|
|
|
char fullpath[PATH_MAX];
|
|
|
|
|
size_t buflen = sizeof(fullpath);
|
|
|
|
|
const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
|
|
|
|
|
if (sysctl(mib, SDL_arraysize(mib), fullpath, &buflen, NULL, 0) != -1) {
|
|
|
|
|
retval = SDL_strdup(fullpath);
|
|
|
|
|
if (!retval) {
|
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
#if defined(__OPENBSD__)
|
|
|
|
|
char **retvalargs;
|
|
|
|
|
size_t len;
|
|
|
|
|
const int mib[] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };
|
|
|
|
|
if (sysctl(mib, 4, NULL, &len, NULL, 0) != -1) {
|
|
|
|
|
retvalargs = SDL_malloc(len);
|
|
|
|
|
if (!retvalargs) {
|
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
sysctl(mib, 4, retvalargs, &len, NULL, 0);
|
|
|
|
|
retval = SDL_malloc(PATH_MAX + 1);
|
|
|
|
|
if (retval)
|
|
|
|
|
realpath(retvalargs[0], retval);
|
|
|
|
|
|
|
|
|
|
SDL_free(retvalargs);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
#if defined(__SOLARIS__)
|
|
|
|
|
const char *path = getexecname();
|
|
|
|
|
if ((path != NULL) && (path[0] == '/')) { /* must be absolute path... */
|
|
|
|
|
retval = SDL_strdup(path);
|
|
|
|
|
if (!retval) {
|
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
#if defined(__3DS__)
|
|
|
|
|
retval = SDL_strdup("file:sdmc:/3ds/devilutionx/");
|
|
|
|
|
return retval;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* is a Linux-style /proc filesystem available? */
|
|
|
|
|
if (!retval && (access("/proc", F_OK) == 0)) {
|
|
|
|
|
/* !!! FIXME: after 2.0.6 ships, let's delete this code and just
|
|
|
|
|
use the /proc/%llu version. There's no reason to have
|
|
|
|
|
two copies of this plus all the #ifdefs. --ryan. */
|
|
|
|
|
#if defined(__FREEBSD__)
|
|
|
|
|
retval = readSymLink("/proc/curproc/file");
|
|
|
|
|
#elif defined(__NETBSD__)
|
|
|
|
|
retval = readSymLink("/proc/curproc/exe");
|
|
|
|
|
#elif defined(__QNXNTO__)
|
|
|
|
|
retval = SDL_LoadFile("/proc/self/exefile", NULL);
|
|
|
|
|
#else
|
|
|
|
|
retval = readSymLink("/proc/self/exe"); /* linux. */
|
|
|
|
|
if (retval == NULL) {
|
|
|
|
|
/* older kernels don't have /proc/self ... try PID version... */
|
|
|
|
|
char path[64];
|
|
|
|
|
const int rc = (int)SDL_snprintf(path, sizeof(path),
|
|
|
|
|
"/proc/%llu/exe",
|
|
|
|
|
(unsigned long long)getpid());
|
|
|
|
|
if ((rc > 0) && (static_cast<std::size_t>(rc) < sizeof(path))) {
|
|
|
|
|
retval = readSymLink(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we had access to argv[0] here, we could check it for a path,
|
|
|
|
|
or troll through $PATH looking for it, too. */
|
|
|
|
|
|
|
|
|
|
if (retval != NULL) { /* chop off filename. */
|
|
|
|
|
char *ptr = SDL_strrchr(retval, '/');
|
|
|
|
|
if (ptr != NULL) {
|
|
|
|
|
*(ptr + 1) = '\0';
|
|
|
|
|
} else { /* shouldn't happen, but just in case... */
|
|
|
|
|
SDL_free(retval);
|
|
|
|
|
retval = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (retval != NULL) {
|
|
|
|
|
/* try to shrink buffer... */
|
|
|
|
|
char *ptr = (char *)SDL_realloc(retval, strlen(retval) + 1);
|
|
|
|
|
if (ptr != NULL)
|
|
|
|
|
retval = ptr; /* oh well if it failed. */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *SDL_GetPrefPath(const char *org, const char *app)
|
|
|
|
|
{
|
|
|
|
|
// From sdl2-2.0.9/src/filesystem/unix/SDL_sysfilesystem.c
|
|
|
|
|
/*
|
|
|
|
|
* We use XDG's base directory spec, even if you're not on Linux.
|
|
|
|
|
* This isn't strictly correct, but the results are relatively sane
|
|
|
|
|
* in any case.
|
|
|
|
|
*
|
|
|
|
|
* https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
|
|
|
|
*/
|
|
|
|
|
const char *envr = SDL_getenv("XDG_DATA_HOME");
|
|
|
|
|
const char *append;
|
|
|
|
|
char *retval = NULL;
|
|
|
|
|
char *ptr = NULL;
|
|
|
|
|
size_t len = 0;
|
|
|
|
|
|
|
|
|
|
#if defined(__3DS__)
|
|
|
|
|
retval = SDL_strdup("sdmc:/3ds/devilutionx/");
|
|
|
|
|
return retval;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (!app) {
|
|
|
|
|
SDL_InvalidParamError("app");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
if (!org) {
|
|
|
|
|
org = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!envr) {
|
|
|
|
|
/* You end up with "$HOME/.local/share/Game Name 2" */
|
|
|
|
|
envr = SDL_getenv("HOME");
|
|
|
|
|
if (!envr) {
|
|
|
|
|
/* we could take heroic measures with /etc/passwd, but oh well. */
|
|
|
|
|
SDL_SetError("neither XDG_DATA_HOME nor HOME environment is set");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
#if defined(__unix__) || defined(__unix)
|
|
|
|
|
append = "/.local/share/";
|
|
|
|
|
#else
|
|
|
|
|
append = "/";
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
|
|
|
|
append = "/";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
len = SDL_strlen(envr);
|
|
|
|
|
if (envr[len - 1] == '/')
|
|
|
|
|
append += 1;
|
|
|
|
|
|
|
|
|
|
len += SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3;
|
|
|
|
|
retval = (char *)SDL_malloc(len);
|
|
|
|
|
if (!retval) {
|
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (*org) {
|
|
|
|
|
SDL_snprintf(retval, len, "%s%s%s/%s", envr, append, org, app);
|
|
|
|
|
} else {
|
|
|
|
|
SDL_snprintf(retval, len, "%s%s%s", envr, append, app);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (ptr = retval + 1; *ptr; ptr++) {
|
|
|
|
|
if (*ptr == '/') {
|
|
|
|
|
*ptr = '\0';
|
|
|
|
|
if (mkdir(retval, 0700) != 0 && errno != EEXIST)
|
|
|
|
|
goto error;
|
|
|
|
|
*ptr = '/';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (mkdir(retval, 0700) != 0 && errno != EEXIST) {
|
|
|
|
|
error:
|
|
|
|
|
SDL_SetError("Couldn't create directory '%s': '%s'", retval, strerror(errno));
|
|
|
|
|
SDL_free(retval);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Append trailing /
|
|
|
|
|
size_t final_len = SDL_strlen(retval);
|
|
|
|
|
if (final_len + 1 < len) {
|
|
|
|
|
retval[final_len++] = '/';
|
|
|
|
|
retval[final_len] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|