port: grp.SaveScreenShot via next-frame swapchain readback + JPEG
CPythonGraphic::SaveScreenShot queues a readback of the next presented frame (ScreenShotHost -> VulkanWindow::request_frame); WriteScreenShot runs the rest of 40250's function verbatim (X8R8G8B8 rows, SaveJPEG, EXIF tag). jpeg_save is encoded with stb_image_write. SHGetSpecialFolderPath returns $HOME/Documents (else cwd); CreateDirectory/_access/jpeg_save accept '\\'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c21e012965
commit
bda85eea50
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "platform/PackBackend.h"
|
||||
#include "platform/ScriptLib/PythonBoot.h"
|
||||
#include "platform/EterPythonLib/ScreenShotHost.h"
|
||||
#include "platform/UserInterface/ServerClock.h"
|
||||
#include "platform/EterBase/TraceErrorObserver.h"
|
||||
#include "platform/EterBase/FrameRateMode.h"
|
||||
@@ -188,6 +189,11 @@ int run_live_client(
|
||||
SDL_Log("live stage: PythonBoot started");
|
||||
#endif
|
||||
|
||||
ScreenShotHost::SetRequest([&renderer](const std::string& path) {
|
||||
return renderer.request_frame([path](const std::uint8_t* bgra, std::uint32_t width, std::uint32_t height) {
|
||||
ScreenShotHost::WriteScreenShot(path.c_str(), bgra, width, height, width * 4);
|
||||
});
|
||||
});
|
||||
SetPlatformServerTime(123456789);
|
||||
PythonBoot::SetUISafeInset(renderer.ui_safe_inset());
|
||||
PythonBoot::SetUISize(static_cast<int>(renderer.logical_width()), static_cast<int>(renderer.logical_height()));
|
||||
@@ -505,6 +511,7 @@ int run_live_client(
|
||||
const auto wall_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
print_summary(renderer, completed, wall_ms, update_ms, std::move(frame_ms));
|
||||
|
||||
ScreenShotHost::SetRequest(nullptr);
|
||||
PythonBoot::Stop();
|
||||
#ifdef __ANDROID__
|
||||
SDL_Log("live stage: PythonBoot stopped");
|
||||
|
||||
@@ -139,6 +139,12 @@ void VulkanWindow::request_screenshot(std::string path, std::uint64_t frame) {
|
||||
screenshot_frame_ = frame;
|
||||
}
|
||||
|
||||
bool VulkanWindow::request_frame(FrameSink sink) {
|
||||
if (!swapchain_readable_) return false;
|
||||
frame_sinks_.push_back(std::move(sink));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t VulkanWindow::logical_width() const {
|
||||
int w = 0, h = 0;
|
||||
if (window_) SDL_GetWindowSize(window_, &w, &h);
|
||||
|
||||
@@ -120,6 +120,7 @@ void VulkanWindow::create_swapchain() {
|
||||
info.imageFormat = format.format; info.imageColorSpace = format.colorSpace; info.imageExtent = extent_;
|
||||
info.imageArrayLayers = 1; info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
|
||||
(capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
|
||||
swapchain_readable_ = (info.imageUsage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0;
|
||||
info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
// Android surfaces can report a quarter-turn transform even when SDL's window is
|
||||
// already landscape. Rendering in SDL window coordinates and requesting that transform
|
||||
|
||||
+13
-3
@@ -493,7 +493,9 @@ void VulkanWindow::record_passes(std::uint32_t image_index, const FrameDraws& fr
|
||||
|
||||
VulkanWindow::Readback VulkanWindow::record_readbacks(std::uint32_t image_index) {
|
||||
Readback readback;
|
||||
readback.screenshot = !screenshot_path_.empty() && frame_number_ == screenshot_frame_;
|
||||
readback.bmp = !screenshot_path_.empty() && frame_number_ == screenshot_frame_;
|
||||
readback.sinks.swap(frame_sinks_);
|
||||
readback.screenshot = readback.bmp || !readback.sinks.empty();
|
||||
if (readback.screenshot) {
|
||||
const VkDeviceSize bytes = VkDeviceSize(extent_.width) * extent_.height * 4;
|
||||
VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
@@ -530,7 +532,7 @@ VulkanWindow::Readback VulkanWindow::record_readbacks(std::uint32_t image_index)
|
||||
}
|
||||
// Debug: MT_DUMP_RT=prefix writes each render target of the screenshot frame to <prefix>_<n>.pgm.
|
||||
const char* dump_rt = std::getenv("MT_DUMP_RT");
|
||||
if (readback.screenshot && dump_rt && *dump_rt) {
|
||||
if (readback.bmp && dump_rt && *dump_rt) {
|
||||
readback.dump_rt = dump_rt;
|
||||
const std::uint32_t texel = offscreen_format_ == VK_FORMAT_R5G6B5_UNORM_PACK16 ? 2 : 4;
|
||||
for (auto& entry : render_targets_) {
|
||||
@@ -578,7 +580,15 @@ void VulkanWindow::finish_readbacks(Readback& readback) {
|
||||
check(vkWaitForFences(device_, 1, &f_->fence, VK_TRUE, UINT64_MAX), "vkWaitForFences readback");
|
||||
void* mapped = nullptr;
|
||||
check(vkMapMemory(device_, readback.memory, 0, VK_WHOLE_SIZE, 0, &mapped), "vkMapMemory readback");
|
||||
write_bmp(screenshot_path_, static_cast<const std::uint8_t*>(mapped));
|
||||
if (readback.bmp) write_bmp(screenshot_path_, static_cast<const std::uint8_t*>(mapped));
|
||||
if (!readback.sinks.empty()) {
|
||||
const std::uint32_t w = extent_.width, h = extent_.height;
|
||||
std::vector<std::uint8_t> bgra(static_cast<const std::uint8_t*>(mapped),
|
||||
static_cast<const std::uint8_t*>(mapped) + std::size_t(w) * h * 4);
|
||||
if (swapchain_format_ == VK_FORMAT_R8G8B8A8_UNORM || swapchain_format_ == VK_FORMAT_R8G8B8A8_SRGB)
|
||||
for (std::size_t i = 0; i < bgra.size(); i += 4) std::swap(bgra[i], bgra[i + 2]);
|
||||
for (auto& sink : readback.sinks) sink(bgra.data(), w, h);
|
||||
}
|
||||
vkUnmapMemory(device_, readback.memory);
|
||||
const std::uint32_t texel = offscreen_format_ == VK_FORMAT_R5G6B5_UNORM_PACK16 ? 2 : 4;
|
||||
for (std::size_t n = 0; n < readback.rt_dumps.size(); ++n) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -133,6 +134,10 @@ public:
|
||||
double input_idle_seconds() const;
|
||||
|
||||
void request_screenshot(std::string path, std::uint64_t frame);
|
||||
// Hands the next presented frame to `sink` as top-down BGRA rows (grp.SaveScreenShot). False when the
|
||||
// swapchain images cannot be copied from.
|
||||
using FrameSink = std::function<void(const std::uint8_t* bgra, std::uint32_t width, std::uint32_t height)>;
|
||||
bool request_frame(FrameSink sink);
|
||||
std::uint64_t frame_number() const { return frame_number_; }
|
||||
|
||||
int msaa_samples() const { return int(samples_); }
|
||||
@@ -215,7 +220,9 @@ private:
|
||||
// Host copies of the swapchain image (--screenshot-out) and, with MT_DUMP_RT, of every render target.
|
||||
struct Readback {
|
||||
struct RtDump { VkBuffer buffer; VkDeviceMemory memory; std::uint32_t w, h; };
|
||||
bool screenshot = false;
|
||||
bool screenshot = false; // the swapchain image is copied (a BMP or a frame sink wants it)
|
||||
bool bmp = false; // --screenshot-out
|
||||
std::vector<FrameSink> sinks;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
const char* dump_rt = nullptr;
|
||||
@@ -398,6 +405,8 @@ private:
|
||||
// --screenshot-out: read back the swapchain image of one frame into a BMP file.
|
||||
std::string screenshot_path_;
|
||||
std::uint64_t screenshot_frame_ = 0;
|
||||
std::vector<FrameSink> frame_sinks_; // request_frame, served by the next frame's readback
|
||||
bool swapchain_readable_ = false; // the swapchain images have TRANSFER_SRC usage
|
||||
VkImage depth_image_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory depth_memory_ = VK_NULL_HANDLE;
|
||||
VkImageView depth_view_ = VK_NULL_HANDLE;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Platform implementation of EterLib/JpegFile.h. 40250 writes it with libjpeg-9a (JpegFile.cpp: a stdio
|
||||
// destination manager, JCS_RGB, jpeg_set_defaults + jpeg_set_quality); here stb_image_write encodes the
|
||||
// same input (top-down RGB rows, 3 bytes per pixel) at the same quality into a baseline JFIF file.
|
||||
//
|
||||
// Only jpeg_save has a caller in 40250 (CPythonGraphic::SaveJPEG, the screenshot key); the other
|
||||
// declarations of JpegFile.h stay undefined.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/JpegFile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_STATIC
|
||||
#define STBI_WRITE_NO_STDIO
|
||||
#include "../third_party/stb_image_write.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
void write_to_file(void* context, void* data, int size)
|
||||
{
|
||||
fwrite(data, 1, size_t(size), static_cast<FILE*>(context));
|
||||
}
|
||||
}
|
||||
|
||||
int jpeg_save(unsigned char*data_d, int width, int height, int quality, const char*filename)
|
||||
{
|
||||
// The Win32 CRT takes '\\' as a separator (grp.SaveScreenShot builds "<Documents>\\METIN2\\<time>.jpg").
|
||||
std::string path(filename);
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
FILE* fi = fopen(path.c_str(), "wb");
|
||||
if (fi == NULL)
|
||||
return 0;
|
||||
const int ok = stbi_write_jpg_to_func(write_to_file, fi, width, height, 3, data_d, quality);
|
||||
const bool closed = fclose(fi) == 0;
|
||||
return ok && closed ? 1 : 0;
|
||||
}
|
||||
@@ -9,6 +9,9 @@
|
||||
#include "EterPythonLib/StdAfx.h"
|
||||
#include "EterPythonLib/PythonGraphic.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
#include "EterLib/JpegFile.h"
|
||||
#include "EterBase/CRC32.h"
|
||||
#include "ScreenShotHost.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "../EterLib/UIRenderCommands.h"
|
||||
@@ -163,7 +166,162 @@ void CPythonGraphic::RenderUpButton(float sx, float sy, float ex, float ey)
|
||||
RenderLine2d(sx, ey, ex, ey);
|
||||
RenderLine2d(ex, sy, ex, ey);
|
||||
}
|
||||
bool CPythonGraphic::SaveScreenShot(const char*) { MT_PLATFORM_STUB(); return false; }
|
||||
|
||||
// 40250 PythonGraphic.cpp:6
|
||||
bool g_isScreenShotKey = false;
|
||||
|
||||
// 40250 PythonGraphic.cpp:157
|
||||
void GenScreenShotTag(const char* src, DWORD crc32, char* leaf, size_t leafLen)
|
||||
{
|
||||
const char* p = src;
|
||||
const char* n = p;
|
||||
while (n = strchr(p, '\\'))
|
||||
p = n + 1;
|
||||
|
||||
_snprintf(leaf, leafLen, "YMIR_METIN2:%s:0x%.8x", p, crc32);
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:167
|
||||
bool CPythonGraphic::SaveJPEG(const char * pszFileName, LPBYTE pbyBuffer, UINT uWidth, UINT uHeight)
|
||||
{
|
||||
return jpeg_save(pbyBuffer, uWidth, uHeight, 100, pszFileName) != 0;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
ScreenShotHost::Request g_screenShotRequest;
|
||||
}
|
||||
|
||||
void ScreenShotHost::SetRequest(Request request)
|
||||
{
|
||||
g_screenShotRequest = std::move(request);
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:172. PORT: the back-buffer lock becomes a request for the next presented frame
|
||||
// (ScreenShotHost.h); the rest of the function runs in ScreenShotHost::WriteScreenShot when it arrives, so
|
||||
// a true return means the capture was queued.
|
||||
bool CPythonGraphic::SaveScreenShot(const char * c_pszFileName)
|
||||
{
|
||||
if (!g_screenShotRequest || !g_screenShotRequest(c_pszFileName))
|
||||
{
|
||||
TraceError("Failed to get back buffer (0x%08x)", E_FAIL);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:172, from the locked surface on (the swapchain reads back as D3DFMT_X8R8G8B8).
|
||||
bool ScreenShotHost::WriteScreenShot(const char* c_pszFileName, const std::uint8_t* bgrx, unsigned uWidth, unsigned uHeight, unsigned pitch)
|
||||
{
|
||||
BYTE* pbyBuffer = new BYTE[uWidth * uHeight * 3];
|
||||
const BYTE* pbySource = bgrx;
|
||||
BYTE* pbyDestination = (BYTE*) pbyBuffer;
|
||||
for(UINT y = 0; y < uHeight; ++y) {
|
||||
const BYTE *pRow = pbySource;
|
||||
|
||||
for(UINT x = 0; x < uWidth; ++x) {
|
||||
*pbyDestination++ = pRow[2]; // Blue
|
||||
*pbyDestination++ = pRow[1]; // Green
|
||||
*pbyDestination++ = pRow[0]; // Red
|
||||
pRow += 4;
|
||||
}
|
||||
|
||||
// increase by one line
|
||||
pbySource += pitch;
|
||||
}
|
||||
|
||||
bool bSaved = CPythonGraphic::Instance().SaveJPEG(c_pszFileName, pbyBuffer, uWidth, uHeight);
|
||||
|
||||
if(pbyBuffer) {
|
||||
delete [] pbyBuffer;
|
||||
pbyBuffer = NULL;
|
||||
}
|
||||
|
||||
if(bSaved == false) {
|
||||
TraceError("Failed to save JPEG file. (%s, %d, %d)", c_pszFileName, uWidth, uHeight);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_isScreenShotKey)
|
||||
{
|
||||
FILE* srcFilePtr = fopen(c_pszFileName, "rb");
|
||||
if (srcFilePtr)
|
||||
{
|
||||
fseek(srcFilePtr, 0, SEEK_END);
|
||||
size_t fileSize = ftell(srcFilePtr);
|
||||
fseek(srcFilePtr, 0, SEEK_SET);
|
||||
|
||||
char head[21];
|
||||
size_t tailSize = fileSize - sizeof(head);
|
||||
char* tail = (char*)malloc(tailSize);
|
||||
|
||||
fread(head, sizeof(head), 1, srcFilePtr);
|
||||
fread(tail, tailSize, 1, srcFilePtr);
|
||||
fclose(srcFilePtr);
|
||||
|
||||
char imgDesc[64];
|
||||
GenScreenShotTag(c_pszFileName, GetCRC32(tail, tailSize), imgDesc, sizeof(imgDesc));
|
||||
|
||||
int imgDescLen = strlen(imgDesc) + 1;
|
||||
|
||||
unsigned char exifHeader[] = {
|
||||
0xe1,
|
||||
0, // blockLen[1],
|
||||
0, // blockLen[0],
|
||||
0x45,
|
||||
0x78,
|
||||
0x69,
|
||||
0x66,
|
||||
0x0,
|
||||
0x0,
|
||||
0x49,
|
||||
0x49,
|
||||
0x2a,
|
||||
0x0,
|
||||
0x8,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x1,
|
||||
0x0,
|
||||
0xe,
|
||||
0x1,
|
||||
0x2,
|
||||
0x0,
|
||||
static_cast<unsigned char>(imgDescLen), // textLen[0],
|
||||
0, // textLen[1],
|
||||
0, // textLen[2],
|
||||
0, // textLen[3],
|
||||
0x1a,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
};
|
||||
|
||||
exifHeader[2] = sizeof(exifHeader) + imgDescLen;
|
||||
|
||||
FILE* dstFilePtr = fopen(c_pszFileName, "wb");
|
||||
//FILE* dstFilePtr = fopen("temp.jpg", "wb");
|
||||
if (dstFilePtr)
|
||||
{
|
||||
fwrite(head, sizeof(head), 1, dstFilePtr);
|
||||
fwrite(exifHeader, sizeof(exifHeader), 1, dstFilePtr);
|
||||
fwrite(imgDesc, imgDescLen, 1, dstFilePtr);
|
||||
fputc(0x00, dstFilePtr);
|
||||
fputc(0xff, dstFilePtr);
|
||||
fwrite(tail, tailSize, 1, dstFilePtr);
|
||||
fclose(dstFilePtr);
|
||||
}
|
||||
|
||||
free(tail);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// 40250 PythonGraphic.cpp:52.
|
||||
void CPythonGraphic::SetCursorPosition(int x, int y)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
// The frame readback behind CPythonGraphic::SaveScreenShot (grp.SaveScreenShot / SaveScreenShotToPath).
|
||||
//
|
||||
// 40250 locks the D3D8 back buffer inside the call and writes the JPEG before returning. Here the script
|
||||
// thread and the host take turns, so the call cannot wait for the host to read the frame back: it asks the
|
||||
// host for the next presented frame and returns true; the host hands the pixels to WriteScreenShot, which
|
||||
// runs the rest of 40250's SaveScreenShot (X8R8G8B8 -> RGB rows, SaveJPEG, the g_isScreenShotKey EXIF tag)
|
||||
// one frame later.
|
||||
//
|
||||
// Plain std types only: the SDL host includes this without the port headers.
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace ScreenShotHost
|
||||
{
|
||||
// Asks the host to call WriteScreenShot(path, ...) with the next presented frame. False when the host
|
||||
// cannot read the swapchain back.
|
||||
using Request = std::function<bool(const std::string& path)>;
|
||||
void SetRequest(Request request);
|
||||
|
||||
// `bgrx`: top-down rows, 4 bytes per pixel in D3DFMT_X8R8G8B8 memory order, `pitch` bytes apart.
|
||||
bool WriteScreenShot(const char* path, const std::uint8_t* bgrx, unsigned width, unsigned height, unsigned pitch);
|
||||
}
|
||||
+1724
File diff suppressed because it is too large
Load Diff
@@ -209,6 +209,11 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
|
||||
target_link_libraries(port_spherelib_test PRIVATE port_platform)
|
||||
add_test(NAME port.spherelib COMMAND $<TARGET_FILE:port_spherelib_test>)
|
||||
|
||||
# grp.SaveScreenShot: the host frame request and the 40250 JPEG write behind it.
|
||||
add_executable(port_screenshot_test ${PROJECT_SOURCE_DIR}/tests/port/port_screenshot_test.cpp)
|
||||
target_link_libraries(port_screenshot_test PRIVATE port_platform)
|
||||
add_test(NAME port.screenshot COMMAND $<TARGET_FILE:port_screenshot_test>)
|
||||
|
||||
# 批次 2V1: Packet.h struct sizes against the classic wire layouts.
|
||||
add_executable(port_packet_test ${PROJECT_SOURCE_DIR}/tests/port/port_packet_test.cpp
|
||||
${PROJECT_SOURCE_DIR}/tests/port/port_packet_test_wire.cpp)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int jpeg_save(unsigned char*data_d, int width, int height, int quality, const char*filename);
|
||||
int jpeg_save_to_file(unsigned char*data_d, int width, int height, int quality, FILE*fi);
|
||||
int jpeg_save_to_mem(unsigned char*data_d, int width, int height, int quality, unsigned char*dest, int destsize);
|
||||
int jpeg_load(const char*filename, unsigned char**dest, int*width, int*height);
|
||||
int jpeg_load_from_mem(unsigned char*_data, int size_d, unsigned char*dest, int width, int height);
|
||||
@@ -32,9 +32,18 @@ void DeleteCriticalSection(LPCRITICAL_SECTION cs) { cs_mutex(cs).~recursive_mute
|
||||
void EnterCriticalSection(LPCRITICAL_SECTION cs) { cs_mutex(cs).lock(); }
|
||||
void LeaveCriticalSection(LPCRITICAL_SECTION cs) { cs_mutex(cs).unlock(); }
|
||||
|
||||
// Win32 takes '\\' and '/' as separators alike.
|
||||
static std::string native_path(const char* path)
|
||||
{
|
||||
std::string normalized(path ? path : "");
|
||||
for (char& ch : normalized)
|
||||
if (ch == '\\') ch = '/';
|
||||
return normalized;
|
||||
}
|
||||
|
||||
BOOL CreateDirectory(LPCSTR path, void*)
|
||||
{
|
||||
return mkdir(path, 0777) == 0 ? TRUE : FALSE;
|
||||
return mkdir(native_path(path).c_str(), 0777) == 0 ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL DeleteFile(LPCSTR path)
|
||||
@@ -49,7 +58,7 @@ int _access(const char* path, int mode)
|
||||
m |= W_OK;
|
||||
if (mode & 4)
|
||||
m |= R_OK;
|
||||
return access(path, m);
|
||||
return access(native_path(path).c_str(), m);
|
||||
}
|
||||
|
||||
int _unlink(const char* path)
|
||||
|
||||
@@ -2,11 +2,33 @@
|
||||
// Win32 shell-folder API used only by the screenshot command in grp.
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define CSIDL_PERSONAL 0x0005
|
||||
|
||||
// CSIDL_PERSONAL ("My Documents"): $HOME/Documents where it exists (desktop), otherwise the working
|
||||
// directory, which is the writable Client directory on Android. grp.SaveScreenShot appends
|
||||
// "\\METIN2\\"; CreateDirectory and jpeg_save take '\\' as a separator like Win32 does.
|
||||
inline BOOL SHGetSpecialFolderPath(void*, char* path, int, BOOL)
|
||||
{
|
||||
if (path)
|
||||
if (!path)
|
||||
return FALSE;
|
||||
path[0] = '\0';
|
||||
struct stat st;
|
||||
const char* home = std::getenv("HOME");
|
||||
if (home && home[0])
|
||||
{
|
||||
std::snprintf(path, MAX_PATH, "%s/Documents", home);
|
||||
if (::stat(path, &st) == 0 && S_ISDIR(st.st_mode))
|
||||
return TRUE;
|
||||
}
|
||||
if (!::getcwd(path, MAX_PATH))
|
||||
{
|
||||
path[0] = '\0';
|
||||
return FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user