std::vector<std::string> CTemplateLoader::FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType) { std::vector<std::string> templates; Status ok; VfsPath templatePath; if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES) { templatePath = VfsPath(TEMPLATE_ROOT) / path; if (includeSubdirectories) ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); else ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml"); WARN_IF_ERR(ok); } if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES) { templatePath = VfsPath(ACTOR_ROOT) / path; if (includeSubdirectories) ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); else ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml"); WARN_IF_ERR(ok); } if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES) LOGERROR("Undefined template type (valid: all, simulation, actor)"); return templates; }
/*static*/ OsPath Paths::Root(const OsPath& argv0) { #if OS_ANDROID return OsPath("/sdcard/0ad"); // TODO: this is kind of bogus #else // get full path to executable OsPath pathname = sys_ExecutablePathname(); // safe, but requires OS-specific implementation if(pathname.empty()) // failed, use argv[0] instead { errno = 0; pathname = wrealpath(argv0); if(pathname.empty()) WARN_IF_ERR(StatusFromErrno()); } // make sure it's valid if(!FileExists(pathname)) { LOGERROR(L"Cannot find executable (expected at '%ls')", pathname.string().c_str()); WARN_IF_ERR(StatusFromErrno()); } for(size_t i = 0; i < 2; i++) // remove "system/name.exe" pathname = pathname.Parent(); return pathname; #endif }
void ReadEntry(const VfsPath& pathname, const FileInfo& fileInfo, PIArchiveFile archiveFile) { if (pathname == L"metadata.json" && m_Metadata) { std::string buffer; buffer.resize(fileInfo.Size()); WARN_IF_ERR(archiveFile->Load("", DummySharedPtr((u8*)buffer.data()), buffer.size())); *m_Metadata = m_ScriptInterface.ParseJSON(buffer); } else if (pathname == L"simulation.dat" && m_SavedState) { m_SavedState->resize(fileInfo.Size()); WARN_IF_ERR(archiveFile->Load("", DummySharedPtr((u8*)m_SavedState->data()), m_SavedState->size())); } }
static void call_tls_dtors() { again: bool had_valid_tls = false; // for each registered dtor: (call order unspecified by SUSv3) for(size_t i = 0; i < MAX_DTORS; i++) { // is slot #i in use? void (*dtor)(void*) = dtors[i].dtor; if(!dtor) continue; // clear slot and call dtor with its previous value. const pthread_key_t key = dtors[i].key; void* tls = pthread_getspecific(key); if(tls) { WARN_IF_ERR(pthread_setspecific(key, 0)); dtor(tls); had_valid_tls = true; } } // rationale: SUSv3 says we're allowed to loop infinitely. we do so to // expose any dtor bugs - this shouldn't normally happen. if(had_valid_tls) goto again; }
int pthread_key_create(pthread_key_t* key, void (*dtor)(void*)) { DWORD idx = TlsAlloc(); if(idx == TLS_OUT_OF_INDEXES) return -ENOMEM; ENSURE(idx < TLS_LIMIT); *key = (pthread_key_t)idx; // acquire a free dtor slot size_t i; for(i = 0; i < MAX_DTORS; i++) { if(cpu_CAS((volatile intptr_t*)&dtors[i].dtor, (intptr_t)0, (intptr_t)dtor)) goto have_slot; } // not enough slots; we have a valid key, but its dtor won't be called. WARN_IF_ERR(ERR::LIMIT); return -1; have_slot: dtors[i].key = *key; return 0; }
void in_add_handler(InHandler handler) { ENSURE(handler); if(handler_stack_top >= MAX_HANDLERS) WARN_IF_ERR(ERR::LIMIT); handler_stack[handler_stack_top++] = handler; }
/*static*/ OsPath Paths::Root(const OsPath& argv0) { // get full path to executable OsPath pathname = sys_ExecutablePathname(); // safe, but requires OS-specific implementation if(pathname.empty()) // failed, use argv[0] instead { errno = 0; pathname = wrealpath(argv0); if(pathname.empty()) WARN_IF_ERR(StatusFromErrno()); } // make sure it's valid if(!FileExists(pathname)) WARN_IF_ERR(StatusFromErrno()); for(size_t i = 0; i < 2; i++) // remove "system/name.exe" pathname = pathname.Parent(); return pathname; }
Status sys_cursor_free(sys_cursor cursor) { // bail now to prevent potential confusion below; there's nothing to do. if(!cursor) return INFO::OK; // if the cursor being freed is active, restore the default arrow // (just for safety). if(cursor_from_HCURSOR(GetCursor()) == cursor) WARN_IF_ERR(sys_cursor_set(0)); if(!DestroyIcon(HICON_from_cursor(cursor))) WARN_RETURN(StatusFromWin()); return INFO::OK; }
std::vector<CScriptValRooted> SavedGames::GetSavedGames(ScriptInterface& scriptInterface) { TIMER(L"GetSavedGames"); std::vector<CScriptValRooted> games; Status err; VfsPaths pathnames; err = vfs::GetPathnames(g_VFS, "saves/", L"*.0adsave", pathnames); WARN_IF_ERR(err); for (size_t i = 0; i < pathnames.size(); ++i) { OsPath realPath; err = g_VFS->GetRealPath(pathnames[i], realPath); if (err < 0) { DEBUG_WARN_ERR(err); continue; // skip this file } PIArchiveReader archiveReader = CreateArchiveReader_Zip(realPath); if (!archiveReader) { // Triggered by e.g. the file being open in another program LOGWARNING(L"Failed to read saved game '%ls'", realPath.string().c_str()); continue; // skip this file } CScriptValRooted metadata; CGameLoader loader(scriptInterface, &metadata, NULL); err = archiveReader->ReadEntries(CGameLoader::ReadEntryCallback, (uintptr_t)&loader); if (err < 0) { DEBUG_WARN_ERR(err); continue; // skip this file } CScriptValRooted game; scriptInterface.Eval("({})", game); scriptInterface.SetProperty(game.get(), "id", pathnames[i].Basename()); scriptInterface.SetProperty(game.get(), "metadata", metadata); games.push_back(game); } return games; }
static Status jpg_encode_impl(Tex* t, jpeg_compress_struct* cinfo, DynArray* da) { dst_prepare(cinfo, da); // describe image format // required: cinfo->image_width = (JDIMENSION)t->m_Width; cinfo->image_height = (JDIMENSION)t->m_Height; cinfo->input_components = (int)t->m_Bpp / 8; cinfo->in_color_space = (t->m_Bpp == 8)? JCS_GRAYSCALE : JCS_RGB; // defaults depend on cinfo->in_color_space already having been set! jpeg_set_defaults(cinfo); // (add optional settings, e.g. quality, here) // TRUE ensures that we will write a complete interchange-JPEG file. // don't change unless you are very sure of what you're doing. jpeg_start_compress(cinfo, TRUE); // if BGR, convert to RGB. WARN_IF_ERR(t->transform_to(t->m_Flags & ~TEX_BGR)); const size_t pitch = t->m_Width * t->m_Bpp / 8; u8* data = t->get_data(); std::vector<RowPtr> rows = tex_codec_alloc_rows(data, t->m_Height, pitch, t->m_Flags, TEX_TOP_DOWN); // could use cinfo->output_scanline to keep track of progress, // but we need to count lines_left anyway (paranoia). JSAMPARRAY row = (JSAMPARRAY)&rows[0]; JDIMENSION lines_left = (JDIMENSION)t->m_Height; while(lines_left != 0) { JDIMENSION lines_read = jpeg_write_scanlines(cinfo, row, lines_left); row += lines_read; lines_left -= lines_read; // we've decoded in-place; no need to further process } jpeg_finish_compress(cinfo); Status ret = INFO::OK; if(cinfo->err->num_warnings != 0) ret = WARN::TEX_INVALID_DATA; return ret; }
// Fill in the globals from the config files. static void LoadGlobals() { CFG_GET_USER_VAL("vsync", Bool, g_VSync); CFG_GET_USER_VAL("nos3tc", Bool, g_NoGLS3TC); CFG_GET_USER_VAL("noautomipmap", Bool, g_NoGLAutoMipmap); CFG_GET_USER_VAL("novbo", Bool, g_NoGLVBO); CFG_GET_USER_VAL("pauseonfocusloss", Bool, g_PauseOnFocusLoss); CFG_GET_USER_VAL("shadows", Bool, g_Shadows); CFG_GET_USER_VAL("shadowpcf", Bool, g_ShadowPCF); CFG_GET_USER_VAL("fancywater", Bool, g_FancyWater); CFG_GET_USER_VAL("renderpath", String, g_RenderPath); CFG_GET_USER_VAL("lodbias", Float, g_LodBias); float gain = -1.0f; CFG_GET_USER_VAL("sound.mastergain", Float, gain); if(gain >= 0.0f) WARN_IF_ERR(snd_set_master_gain(gain)); }
// wait until semaphore is locked or a message arrives. non-portable. // // background: on Win32, UI threads must periodically pump messages, or // else deadlock may result (see WaitForSingleObject docs). that entails // avoiding any blocking functions. when event waiting is needed, // one cheap workaround would be to time out periodically and pump messages. // that would work, but either wastes CPU time waiting, or introduces // message latency. to avoid this, we provide an API similar to sem_wait and // sem_timedwait that gives MsgWaitForMultipleObjects functionality. // // return value: 0 if the semaphore has been locked (SUS terminology), // -1 otherwise. errno differentiates what happened: ETIMEDOUT if a // message arrived (this is to ease switching between message waiting and // periodic timeout), or an error indication. int sem_msgwait_np(sem_t* sem) { HANDLE h = HANDLE_from_sem_t(sem); DWORD ret = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_ALLEVENTS); // semaphore is signalled if(ret == WAIT_OBJECT_0) return 0; // something else: // .. message came up if(ret == WAIT_OBJECT_0+1) errno = ETIMEDOUT; // .. error else { errno = EINVAL; WARN_IF_ERR(ERR::FAIL); } return -1; }
// helper function for sem_timedwait - multiple return is convenient. // converts an absolute timeout deadline into a relative length for use with // WaitForSingleObject with the following peculiarity: if the semaphore // could be locked immediately, abs_timeout must be ignored (see SUS). // to that end, we return a timeout of 0 and pass back <valid> = false if // abs_timeout is invalid. static DWORD calc_timeout_length_ms(const struct timespec* abs_timeout, bool& timeout_is_valid) { timeout_is_valid = false; if(!abs_timeout) return 0; // SUS requires we fail if not normalized if(abs_timeout->tv_nsec >= 1000000000) return 0; struct timespec cur_time; if(clock_gettime(CLOCK_REALTIME, &cur_time) != 0) return 0; timeout_is_valid = true; // convert absolute deadline to relative length, rounding up to [ms]. // note: use i64 to avoid overflow in multiply. const i64 ds = abs_timeout->tv_sec - cur_time.tv_sec; const long dn = abs_timeout->tv_nsec - cur_time.tv_nsec; i64 length_ms = ds*1000 + (dn+500000)/1000000; // .. deadline already reached; we'll still attempt to lock once if(length_ms < 0) return 0; // .. length > 49 days => result won't fit in 32 bits. most likely bogus. // note: we're careful to avoid returning exactly -1 since // that's the Win32 INFINITE value. if(length_ms >= 0xFFFFFFFF) { WARN_IF_ERR(ERR::LIMIT); length_ms = 0xfffffffe; } return (DWORD)(length_ms & 0xFFFFFFFF); }
void* pthread_getspecific(pthread_key_t key) { DWORD idx = (DWORD)key; ENSURE(idx < TLS_LIMIT); // TlsGetValue sets last error to 0 on success (boo). // we don't want this to hide previous errors, so it's restored below. DWORD last_err = GetLastError(); void* data = TlsGetValue(idx); // no error if(GetLastError() == 0) { // we care about performance here. SetLastError is low overhead, // but last error = 0 is expected. if(last_err != 0) SetLastError(last_err); } else WARN_IF_ERR(ERR::FAIL); return data; }
// Similar to WriteScreenshot, but generates an image of size 640*tiles x 480*tiles. void WriteBigScreenshot(const VfsPath& extension, int tiles) { // If the game hasn't started yet then use WriteScreenshot to generate the image. if(g_Game == NULL) { WriteScreenshot(L".bmp"); return; } // get next available numbered filename // note: %04d -> always 4 digits, so sorting by filename works correctly. const VfsPath basenameFormat(L"screenshots/screenshot%04d"); const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension); VfsPath filename; vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename); // Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and // hope the screen is actually large enough for that. const int tile_w = 640, tile_h = 480; ENSURE(g_xres >= tile_w && g_yres >= tile_h); const int img_w = tile_w*tiles, img_h = tile_h*tiles; const int bpp = 24; GLenum fmt = GL_RGB; int flags = TEX_BOTTOM_UP; // we want writing BMP to be as fast as possible, // so read data from OpenGL in BMP format to obviate conversion. if(extension == L".bmp") { #if !CONFIG2_GLES // GLES doesn't support BGR fmt = GL_BGR; flags |= TEX_BGR; #endif } const size_t img_size = img_w * img_h * bpp/8; const size_t tile_size = tile_w * tile_h * bpp/8; const size_t hdr_size = tex_hdr_size(filename); void* tile_data = malloc(tile_size); if(!tile_data) { WARN_IF_ERR(ERR::NO_MEM); return; } shared_ptr<u8> img_buf; AllocateAligned(img_buf, hdr_size+img_size, maxSectorSize); Tex t; GLvoid* img = img_buf.get() + hdr_size; if(t.wrap(img_w, img_h, bpp, flags, img_buf, hdr_size) < 0) { free(tile_data); return; } ogl_WarnIfError(); // Resize various things so that the sizes and aspect ratios are correct { g_Renderer.Resize(tile_w, tile_h); SViewPort vp = { 0, 0, tile_w, tile_h }; g_Game->GetView()->GetCamera()->SetViewPort(vp); g_Game->GetView()->SetCameraProjection(); } #if !CONFIG2_GLES // Temporarily move everything onto the front buffer, so the user can // see the exciting progress as it renders (and can tell when it's finished). // (It doesn't just use SwapBuffers, because it doesn't know whether to // call the SDL version or the Atlas version.) GLint oldReadBuffer, oldDrawBuffer; glGetIntegerv(GL_READ_BUFFER, &oldReadBuffer); glGetIntegerv(GL_DRAW_BUFFER, &oldDrawBuffer); glDrawBuffer(GL_FRONT); glReadBuffer(GL_FRONT); #endif // Hide the cursor CStrW oldCursor = g_CursorName; g_CursorName = L""; // Render each tile for (int tile_y = 0; tile_y < tiles; ++tile_y) { for (int tile_x = 0; tile_x < tiles; ++tile_x) { // Adjust the camera to render the appropriate region g_Game->GetView()->GetCamera()->SetProjectionTile(tiles, tile_x, tile_y); RenderLogger(false); RenderGui(false); Render(); RenderGui(true); RenderLogger(true); // Copy the tile pixels into the main image glReadPixels(0, 0, tile_w, tile_h, fmt, GL_UNSIGNED_BYTE, tile_data); for (int y = 0; y < tile_h; ++y) { void* dest = (char*)img + ((tile_y*tile_h + y) * img_w + (tile_x*tile_w)) * bpp/8; void* src = (char*)tile_data + y * tile_w * bpp/8; memcpy(dest, src, tile_w * bpp/8); } } } // Restore the old cursor g_CursorName = oldCursor; #if !CONFIG2_GLES // Restore the buffer settings glDrawBuffer(oldDrawBuffer); glReadBuffer(oldReadBuffer); #endif // Restore the viewport settings { g_Renderer.Resize(g_xres, g_yres); SViewPort vp = { 0, 0, g_xres, g_yres }; g_Game->GetView()->GetCamera()->SetViewPort(vp); g_Game->GetView()->SetCameraProjection(); g_Game->GetView()->GetCamera()->SetProjectionTile(1, 0, 0); } if (tex_write(&t, filename) == INFO::OK) { OsPath realPath; g_VFS->GetRealPath(filename, realPath); LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8()); debug_printf( CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(), realPath.string8().c_str()); } else LOGERROR("Error writing screenshot to '%s'", filename.string8()); free(tile_data); }
std::vector<std::string> CTemplateLoader::FindPlaceableTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType, ScriptInterface& scriptInterface) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); std::vector<std::string> templates; Status ok; VfsPath templatePath; if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES) { JS::RootedValue placeablesFilter(cx); scriptInterface.ReadJSONFile("simulation/data/placeablesFilter.json", &placeablesFilter); JS::RootedObject folders(cx); if (scriptInterface.GetProperty(placeablesFilter, "templates", &folders)) { if (!(JS_IsArrayObject(cx, folders))) { LOGERROR("FindPlaceableTemplates: Argument must be an array!"); return templates; } u32 length; if (!JS_GetArrayLength(cx, folders, &length)) { LOGERROR("FindPlaceableTemplates: Failed to get array length!"); return templates; } templatePath = VfsPath(TEMPLATE_ROOT) / path; //I have every object inside, just run for each for (u32 i=0; i<length; ++i) { JS::RootedValue val(cx); if (!JS_GetElement(cx, folders, i, &val)) { LOGERROR("FindPlaceableTemplates: Failed to read array element!"); return templates; } std::string directoryPath; std::wstring fileFilter; scriptInterface.GetProperty(val, "directory", directoryPath); scriptInterface.GetProperty(val, "file", fileFilter); VfsPaths filenames; if (vfs::GetPathnames(g_VFS, templatePath / (directoryPath + "/"), fileFilter.c_str(), filenames) != INFO::OK) continue; for (const VfsPath& filename : filenames) { // Strip the .xml extension VfsPath pathstem = filename.ChangeExtension(L""); // Strip the root from the path std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT) - 1); templates.emplace_back(name.begin(), name.end()); } } } } if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES) { templatePath = VfsPath(ACTOR_ROOT) / path; if (includeSubdirectories) ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); else ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml"); WARN_IF_ERR(ok); } if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES) LOGERROR("Undefined template type (valid: all, simulation, actor)"); return templates; }