Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - time-killer-games

1
If you happen to want/need OpenAL or FreeType for anything, here's a zip with the required DLL's. Note some of the dependencies of both are licensed under LGPL meaning you cannot statically link with them and release your game closed source, even with a linking exception (while I know this for certain with OpenAL, I'd have to double check with FreeType). Both have missing required static libraries from the MSYS2 pacman packages anyway, so you couldn't easily do it even if you wanted to release your game open source.

TL;DR to use OpenAL/FreeType distribute these DLL's with your game:
https://drive.google.com/file/d/1gagR9bycmIQKRCKQyhMpTvwEk5mXezcE/view?usp=sharing

The "win32" folder for 32-bit games. The "win64" is for 64-bit games.

I will try to update the above link to make sure the DLL's remain up-to-date with the latest versions available intermittently. If they stop working after packages have been updated I recommend letting me know in this topic so I can update the download to have the latest versions.

I won't be updating the DLL's at all until pacman's shipped versions of gRPC/abseil/protobuff are no longer breaking the Windows build.

In the meantime you can use older versions of the Windows installer which actually work.

Technically not sure if you even need libffi but I included it in the download anyway just in case. I added that in case any of the requred DLL's use that to call other dependency functions.

Samuel

2
Function Peer Review / more code ripe for the picking
« on: June 21, 2022, 12:57:11 am »
Code: [Select]
#if defined(_WIN32)
#include <Shlobj.h>
#elif defined(__APPLE__) && defined(__MACH__)
#include <sysdir.h>
#elif defined(__linux__)
#include <unistd.h>
#endif

namespace enigma_user {

  namespace {

    std::string directory_get_special_path(int dtype) {
      std::string result;
      #if defined(_WIN32)
      wchar_t *ptr = nullptr;
      KNOWNFOLDERID fid;
      switch (dtype) {
        case  0: { fid = FOLDERID_Desktop;   break; }
        case  1: { fid = FOLDERID_Documents; break; }
        case  2: { fid = FOLDERID_Downloads; break; }
        case  3: { fid = FOLDERID_Music;     break; }
        case  4: { fid = FOLDERID_Pictures;  break; }
        case  5: { fid = FOLDERID_Videos;    break; }
        default: { fid = FOLDERID_Desktop;   break; }
      }
      if (SUCCEEDED(SHGetKnownFolderPath(fid, KF_FLAG_CREATE | KF_FLAG_DONT_UNEXPAND, nullptr, &ptr))) {
        result = shorten(ptr);
        if (!result.empty() && result.back() != '\\') {
          result.push_back('\\');
        }
      }
      CoTaskMemFree(ptr);
      #elif defined(__APPLE__) && defined(__MACH__)
      char buf[PATH_MAX];
      sysdir_search_path_directory_t fid;
      sysdir_search_path_enumeration_state state;
      switch (dtype) {
        case  0: { fid = SYSDIR_DIRECTORY_DESKTOP;   break; }
        case  1: { fid = SYSDIR_DIRECTORY_DOCUMENT;  break; }
        case  2: { fid = SYSDIR_DIRECTORY_DOWNLOADS; break; }
        case  3: { fid = SYSDIR_DIRECTORY_MUSIC;     break; }
        case  4: { fid = SYSDIR_DIRECTORY_PICTURES;  break; }
        case  5: { fid = SYSDIR_DIRECTORY_MOVIES;    break; }
        default: { fid = SYSDIR_DIRECTORY_DESKTOP;   break; }
      }
      state = sysdir_start_search_path_enumeration(fid, SYSDIR_DOMAIN_MASK_USER);
      while ((state = sysdir_get_next_search_path_enumeration(state, buf))) {
        if (buf[0] == '~') {
          result = buf;
          result.replace(0, 1, environment_get_variable("HOME"));
          if (!result.empty() && result.back() != '/') {
            result.push_back('/');
          }
          break;
        }
      }
      #else
      std::string fid;
      switch (dtype) {
        case  0: { fid = "XDG_DESKTOP_DIR=";   break; }
        case  1: { fid = "XDG_DOCUMENTS_DIR="; break; }
        case  2: { fid = "XDG_DOWNLOAD_DIR=";  break; }
        case  3: { fid = "XDG_MUSIC_DIR=";     break; }
        case  4: { fid = "XDG_PICTURES_DIR=";  break; }
        case  5: { fid = "XDG_VIDEOS_DIR=";    break; }
        default: { fid = "XDG_DESKTOP_DIR=";   break; }
      }
      std::string conf = environment_get_variable("HOME") + "/.config/user-dirs.dirs";
      if (file_exists(conf)) {
        int dirs = file_text_open_read(conf);
        if (dirs != -1) {
          while (!file_text_eof(dirs)) {
            std::string line = file_text_read_string(dirs);
            file_text_readln(dirs);
            std::size_t pos = line.find(fid, 0);
            if (pos != std::string::npos) {
              FILE *fp = popen(("echo " + line.substr(pos + fid.length())).c_str(), "r");
              if (fp) {
                char buf[PATH_MAX];
                if (fgets(buf, PATH_MAX, fp)) {
                  std::string str = buf;
                  std::size_t pos = str.find("\n", strlen(buf) - 1);
                  if (pos != std::string::npos) {
                    str.replace(pos, 1, "");
                  }
                  if (!directory_exists(str)) {
                    directory_create(str);
                  }
                  result = str;
                  if (!result.empty() && result.back() != '/') {
                    result.push_back('/');
                  }
                }
                pclose(fp);
              }
            }
          }
          file_text_close(dirs);
        }
      }
      #endif
      return result;
    }

  } // anonymous namespace

  std::string directory_get_desktop_path() {
    return directory_get_special_path(0);
  }

  string directory_get_documents_path() {
    return directory_get_special_path(1);
  }

  std::string directory_get_downloads_path() {
    return directory_get_special_path(2);
  }

  std::string directory_get_music_path() {
    return directory_get_special_path(3);
  }

  std::string directory_get_pictures_path() {
    return directory_get_special_path(4);
  }

  std::string directory_get_videos_path() {
    return directory_get_special_path(5);
  }

} // namespace enigma_user

I am using popen()/fgets()/pclose() for expanding environment variables which is a requirement for correctly parsing the user-dirs.dirs config file:

Code: [Select]
freebsd@freebsd:~ $ cat ~/.config/user-dirs.dirs
# This file is written by xdg-user-dirs-update
# If you want to change or add directories, just edit the line you're
# interested in. All local changes will be retained on the next run.
# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
# absolute path. No other format is supported.
#
XDG_DESKTOP_DIR="$HOME/Desktop"
XDG_DOWNLOAD_DIR="$HOME/Downloads"
XDG_TEMPLATES_DIR="$HOME/Templates"
XDG_PUBLICSHARE_DIR="$HOME/Public"
XDG_DOCUMENTS_DIR="$HOME/Documents"
XDG_MUSIC_DIR="$HOME/Music"
XDG_PICTURES_DIR="$HOME/Pictures"
XDG_VIDEOS_DIR="$HOME/Videos"

It's important to note these paths will not always be English due to the user-chosen system locale. This goes for all supported platforms. The path may have more than one environment variable as well as escaped quotes within the initial quoted paths on Linux and similar platforms. I don't provide a function for Templates because that is not available on macOS and on Windows it isn't in the home folder (%USERPROFILE% is basically the Windows-equivalent of $HOME on the other platforms) like it is on Linux. I don't have a function for Public because it isn't always in the home folder depending on the platform, thus aren't platform equivalents.

An important note from the Windows implementation which uses SHGetKnownFolderPath():

Quote
When this method returns, contains the address of a pointer to a null-terminated Unicode string that specifies the path of the known folder. The calling process is responsible for freeing this resource once it is no longer needed by calling CoTaskMemFree, whether SHGetKnownFolderPath succeeds or not.

...which is why i am calling CoTaskMemFree() regardless of whether the call to SHGetKnownFolderPath() succeeds or not.

Documentation citation: https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath

The paths returned on all platforms will contain a trailing slash at the end of the string. The paths will be created if they do not already exist on Windows or Linux. Mac is guaranteed to have the paths exist already, as they can't be deleted.

3
Function Peer Review / Embed Windows
« on: May 24, 2022, 06:56:06 am »
I wrote a function for Windows and Xlib platforms which works in ENIGMA. It should also work with SDL minus Mac and Android.

You can use execute_shell() or execute_program() to have a launcher executable launch a game executable. The launcher could serve as a "choose a game" menu or even an updater application.

You can save window_identifier() since it is already a string and make it an environment variable which gets passed down to the child. The child can then plug in the environment variable it inherited as the parentWindowId and its own window_identifier() can be the child windowId parameter passed to these two functions:

Code: [Select]
// embed window identifier into parent window identifier (called once)
void WindowIdSetParentWindowId(wid_t windowId, wid_t parentWindowId) {
  #if defined(_WIN32)
  HWND child  = (HWND)(void *)(uintptr_t)strtoull(windowId.c_str(), nullptr, 10);
  HWND parent = (HWND)(void *)(uintptr_t)strtoull(parentWindowId.c_str(), nullptr, 10);
  RECT wrect; GetWindowRect(parent, &wrect); RECT crect; GetClientRect(parent, &crect);
  POINT lefttop     = { crect.left,  crect.top    }; ClientToScreen(parent, &lefttop);
  POINT bottomright = { crect.right, crect.bottom }; ClientToScreen(parent, &bottomright);
  MoveWindow(child, -(lefttop.x - wrect.left), -(lefttop.y - wrect.top), crect.right + bottomright.x, crect.bottom + bottomright.y, true);
  SetWindowLongPtr(child, GWL_STYLE, GetWindowLongPtr(child, GWL_STYLE) & ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU | WS_POPUP | WS_SIZEBOX) | WS_CHILD);
  SetWindowLongPtr(child, GWL_EXSTYLE, GetWindowLongPtr(child, GWL_EXSTYLE) | WS_EX_TOOLWINDOW);
  SetWindowLongPtr(parent, GWL_STYLE, GetWindowLongPtr(parent, GWL_STYLE) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
  SetParent(child, parent); ShowWindow(child, SW_MAXIMIZE);
  #elif (defined(__linux__) && !defined(__ANDROID__)) || (defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__))
  Window child  = (Window)(uintptr_t)strtoull(windowId.c_str(), nullptr, 10);
  Window parent = (Window)(uintptr_t)strtoull(parentWindowId.c_str(), nullptr, 10);
  Display *display = XOpenDisplay(nullptr); XMapWindow(display, child);
  Window root = 0, rparent = 0, *children = nullptr; unsigned int nchildren = 0;
  XQueryTree(display, child, &root, &rparent, &children, &nchildren);
  XReparentWindow(display, child, parent, 0, 0); if (nchildren > 0) { XFree(children); }
  XFlush(display); std::this_thread::sleep_for(std::chrono::milliseconds(5));
  while (parent != rparent) {
    XSynchronize(display, true);
    XQueryTree(display, child, &root, &rparent, &children, &nchildren);
    XReparentWindow(display, child, parent, 0, 0); if (nchildren > 0) { XFree(children); }
    XFlush(display); std::this_thread::sleep_for(std::chrono::milliseconds(5));
  }
  XCloseDisplay(display);
  #else
  DEBUG_MESSAGE("Unsupported platform for function!", MESSAGE_TYPE::M_INFO);
  #endif
}

// update embed area of window identifier to fill parent window identifier client area
// (must be called every step once a window is embedded inside another to function)
void WindowIdFillParentWindowId(wid_t windowId, wid_t parentWindowId) {
  if (strtoull(parentWindowId.c_str(), nullptr, 10) == 0) return;
  #if defined(_WIN32)
  HWND child  = (HWND)(void *)(uintptr_t)strtoull(windowId.c_str(), nullptr, 10);
  HWND parent = (HWND)(void *)(uintptr_t)strtoull(parentWindowId.c_str(), nullptr, 10);
  RECT crect; GetClientRect(parent, &crect);
  MoveWindow(child, 0, 0, crect.right, crect.bottom, true);
  SetWindowLongPtr(child, GWL_STYLE, GetWindowLongPtr(child, GWL_STYLE) & ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU | WS_POPUP | WS_SIZEBOX) | WS_CHILD);
  SetWindowLongPtr(child, GWL_EXSTYLE, GetWindowLongPtr(child, GWL_EXSTYLE) | WS_EX_TOOLWINDOW);
  SetWindowLongPtr(parent, GWL_STYLE, GetWindowLongPtr(parent, GWL_STYLE) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
  #elif (defined(__linux__) && !defined(__ANDROID__)) || (defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__))
  Window child  = (Window)(uintptr_t)strtoull(windowId.c_str(), nullptr, 10);
  Window parent = (Window)(uintptr_t)strtoull(parentWindowId.c_str(), nullptr, 10);
  Display *display = XOpenDisplay(nullptr); XMapWindow(display, child);
  Window r = 0; int x = 0, y = 0; unsigned w = 0, h = 0, b = 0, d = 0;
  XGetGeometry(display, parent, &r, &x, &y, &w, &h, &b, &d);
  XMoveResizeWindow(display, child, 0, 0, w, h);
  XCloseDisplay(display);
  #else
  DEBUG_MESSAGE("Unsupported platform for function!", MESSAGE_TYPE::M_INFO);
  #endif
}

It's relatively seamless, especially on Windows. It will embed the game window to fill the launcher's client area. I have a means to close the child process when the launcher exits so you don't have the game continuing to run in the background. You can borrow code from my repository to have that automated in enigma internally so you don't risk people writing stuff wrong and terminating the wrong process id which can be avoided if you let enigma handle that for them. You can also make the step event code fire every step automatically too if you want, that will save the user from having to call it manually.

Please note embedding windows is a very popular extension feature that a lot people want on the GMC and have wanted in their projects ever since even pre-GM6 era to the present.  Over time various existing DLL's either lost their compatibility with the newer versions of GM, links to download them went dead, or the solution was closed source and not written with cross-platform in mind. I was paid $275 USD to write the above code, to put it into perspective, they knew it wasn't much work to write, but insisted to pay me that much because that's how much they wanted it. I think you would make a lot of your users happy for adding this.

4
General ENIGMA / got my fork of enigma working on Mac (sort of)
« on: March 18, 2022, 12:12:40 pm »
Mostly everything works, except for some odd reason the string/toString macro under Universal_System/var4.h isn't being defined. Also all functions that return a string aren't being recognized by the compiler.

The good news is, some complete games still do compile. Such as my Key to Success game and hpg678's Open Balloon Pop game.

Screenshots attached. You can also test my fork (requires macports installed before running the install script).

Home-brew doesn't have a way to install universal deps. So anyway that's why I am using Macports. Although, supporting universal binaries is a work in progress.

Macports is missing the alure dependency, so I have temporarily, (and very reluctantly) have the user install hombrew as well until macports adds a proper alure package, so I can finally support universal binaries and add the +universal flag to dependency installer and build script.





More info here:

https://samuel-venable.itch.io/stigma-development-environment

Even though none of our ENIGMA sources are including the C++17 <variant> header, it appears good old apple doesn't like us using the reserved keyword "variant" regardless, which means you will need to find and replace all occurrences of "variant" in enigma and the compiler's sources with something else to call that type, I went with "evariant" in my fork to prevent the name clash, short for "enigma variant".

you can apply this patch simply by doing the following on your enigma-dev folder:

Code: [Select]
LC_CTYPE=C grep -rl variant "/path/to/enigma-dev/" | xargs sed -i 's/variant/evariant/g'
Code: [Select]
LC_CTYPE=C grep -rl multifunction_evariant "/path/to/enigma-dev/" | xargs sed -i 's/multifunction_evariant/multifunction_variant/g'
I am happy to help you guys get macOS working again if you ever unban me from discord and/or github long enough for me to work on that briefly with/for you.

you may download my key to success Mac app here btw:

https://samuel-venable.itch.io/key-to-success

so yeah

5
It's not broken, you just need to distribute dll's with your game and OpenAL will work again.

Here's the files you need:

https://drive.google.com/file/d/1gagR9bycmIQKRCKQyhMpTvwEk5mXezcE/view?usp=sharing

put them in the working directory of your game.

6
Off-Topic / Re: What's your favourite Linux/Unix DE/WM and why?
« on: December 15, 2021, 02:03:39 am »
I like Xfce and don't really like KDE so much anymore.

7
Announcements / Re: Back online, new hardware
« on: November 03, 2021, 08:41:35 pm »
Thank you all the hard work. I know it may not seem like it, but i really do appreciate what you have done to keep this site secure, and for warning everyone to not use the same passwords everywhere. Safety first is a good policy. I'm sorry I haven't been as much of a help around here as I'd like to be. A lot of that is my fault for not prioritizing the right things and being a bit of a hindrance and I hope things get better for you all. Maybe if i post more free-to-use (and free-to-relicense) code snippets I write i could at least help out that way with no access to your repository.

8
I was hoping you guys could review this code.

This is currently under universal, but can be moved to extensions so that we can do platform checks in the makefile and not the sources.

Code: [Select]
  string get_filedescriptor_pathname(int fd) {
    string path;
    #if defined(_WIN32)
    DWORD length; HANDLE file = (HANDLE)_get_osfhandle(fd);
    if ((length = GetFinalPathNameByHandleW(file, nullptr, 0, VOLUME_NAME_DOS))) {
      wstring wpath; wpath.resize(length, '\0'); wchar_t *buffer = wpath.data();
      if ((length = GetFinalPathNameByHandleW(file, buffer, length, VOLUME_NAME_DOS))) {
        path = narrow(wpath); size_t pos = 0; string substr = "\\\\?\\";
        if ((pos = path.find(substr, pos)) != string::npos) {
          path.replace(pos, substr.length(), "");
        }
      }
    }
    #elif (defined(__APPLE__) && defined(__MACH__)) || defined(__DragonFly__)
    char buffer[PATH_MAX];
    if (fcntl(fd, F_GETPATH, buffer) != -1) {
      path = buffer;
    }
    #elif defined(__linux__)
    char *buffer = realpath(("/proc/self/fd/" + std::to_string(fd)).c_str(), nullptr);
    path = buffer ? buffer : "";
    free(buffer);
    #elif defined(__FreeBSD__)
    size_t length;
    int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_FILEDESC, getpid() };
    if (sysctl(mib, 4, nullptr, &length, nullptr, 0) == 0) {
      path.resize(length * 2, '\0'); char *buffer = path.data();
      if (sysctl(mib, 4, buffer, &length, nullptr, 0) == 0) {
        for (char *p = buffer; p < buffer + length;) {
          struct kinfo_file *kif = (struct kinfo_file *)p;
          if (kif->kf_fd == fd) {
            path = kif->kf_path;
          }
          p += kif->kf_structsize;
        }
      }
    }
    #endif
    return path;
  }

  int file_bin_open(string fname, int mode) {
    #if defined(_WIN32)
    wstring wfname = widen(fname);
    FILE *fp = nullptr;
    switch (mode) {
      case  0: { if (!_wfopen_s(&fp, wfname.c_str(), L"rb, ccs=UTF-8" )) break; return -1; }
      case  1: { if (!_wfopen_s(&fp, wfname.c_str(), L"wb, ccs=UTF-8" )) break; return -1; }
      case  2: { if (!_wfopen_s(&fp, wfname.c_str(), L"w+b, ccs=UTF-8")) break; return -1; }
      case  3: { if (!_wfopen_s(&fp, wfname.c_str(), L"ab, ccs=UTF-8" )) break; return -1; }
      case  4: { if (!_wfopen_s(&fp, wfname.c_str(), L"a+b, ccs=UTF-8")) break; return -1; }
      default: return -1;
    }
    if (fp) { int fd = _dup(_fileno(fp));
    fclose(fp); return fd; }
    #else
    FILE *fp = nullptr;
    switch (mode) {
      case  0: { fp = fopen(fname.c_str(), "rb" ); break; }
      case  1: { fp = fopen(fname.c_str(), "wb" ); break; }
      case  2: { fp = fopen(fname.c_str(), "w+b"); break; }
      case  3: { fp = fopen(fname.c_str(), "ab" ); break; }
      case  4: { fp = fopen(fname.c_str(), "a+b"); break; }
      default: return -1;
    }
    if (fp) { int fd = dup(fileno(fp));
    fclose(fp); return fd; }
    #endif
    return -1;
  }

  int file_bin_rewrite(int fd) {
    #if defined(_WIN32)
    _lseek(fd, 0, SEEK_SET);
    return _chsize(fd, 0);
    #else
    lseek(fd, 0, SEEK_SET);
    return ftruncate(fd, 0);
    #endif
  }
 
  int file_bin_close(int fd) {
    #if defined(_WIN32)
    return _close(fd);
    #else
    return close(fd);
    #endif
  }
 
  long file_bin_size(int fd) {
    #if defined(_WIN32)
    struct _stat info = { 0 };
    int result = _fstat(fd, &info);
    #else
    struct stat info = { 0 };
    int result = fstat(fd, &info);
    #endif
    if (result != -1) {
      return info.st_size;
    }
    return 0;
  }

  long file_bin_position(int fd) {
    #if defined(_WIN32)
    return _lseek(fd, 0, SEEK_CUR);
    #else
    return lseek(fd, 0, SEEK_CUR);
    #endif
  }
 
  long file_bin_seek(int fd, long pos) {
    #if defined(_WIN32)
    return _lseek(fd, pos, SEEK_CUR);
    #else
    return lseek(fd, pos, SEEK_CUR);
    #endif
  }

  int file_bin_read_byte(int fd) {
    int byte = 0;
    #if defined(_WIN32)
    int num = (int)_read(fd, &byte, 1);
    #else
    int num = (int)read(fd, &byte, 1);
    #endif
    if (num == -1) return 0;
    return byte;
  }

  int file_bin_write_byte(int fd, int byte) {
    #if defined(_WIN32)
    return (int)_write(fd, &byte, 1);
    #else
    return (int)write(fd, &byte, 1);
    #endif
  }

  int file_text_open_read(string fname) {
    return file_bin_open(fname, 0);
  }

  int file_text_open_write(string fname) {
    return file_bin_open(fname, 1);
  }

  int file_text_open_append(string fname) {
    return file_bin_open(fname, 3);
  }

  long file_text_write_string(int fd, string str) {
    char *buffer = str.data();
    #if defined(_WIN32)
    long result = _write(fd, buffer, (unsigned)str.length());
    #else
    long result = write(fd, buffer, (unsigned)str.length());
    #endif
    return result;
  }

  long file_text_write_real(int fd, double val) {
    string str = std::to_string(val);
    return file_text_write_string(fd, str);
  }

  int file_text_writeln(int fd) {
    return file_bin_write_byte(fd, '\n');
  }

  static unsigned cnt = 0;
  bool file_text_eof(int fd) {
    bool res1 = ((char)file_bin_read_byte(fd) == '\0');
    bool res2 = (file_bin_position(fd) > file_bin_size(fd));
    while (res2 && cnt < 2) {
    file_bin_seek(fd, -1); cnt++; }
    if (!res2) file_bin_seek(fd, -1);
    cnt = 0; return (res1 || res2);
  }

  bool file_text_eoln(int fd) {
    bool res1 = ((char)file_bin_read_byte(fd) == '\n');
    bool res2 = file_text_eof(fd);
    while (res2 && cnt < 2) {
    file_bin_seek(fd, -1); cnt++; }
    if (!res2) file_bin_seek(fd, -1);
    cnt = 0; return (res1 || res2);
  }

  string file_text_read_string(int fd) {
    int byte = file_bin_read_byte(fd); string str;
    str.push_back((char)byte);
    while ((char)byte != '\n') {
      byte = file_bin_read_byte(fd);
      str.push_back((char)byte);
      if (byte == 0) break;
    }
    if (str.length() >= 2) {
      if (str[str.length() - 2] != '\r' && str[str.length() - 1] == '\n') {
        file_bin_seek(fd, -1);
        str = str.substr(0, str.length() - 1);
      }
      if (str[str.length() - 2] == '\r' && str[str.length() - 1] == '\n') {
        file_bin_seek(fd, -2);
        str = str.substr(0, str.length() - 2);
      }
    } else if (str.length() == 1) {
      if (str[str.length() - 1] == '\n') {
        file_bin_seek(fd, -1);
        str = str.substr(0, str.length() - 1);
      }
    }
    return str;
  }

  static bool is_digit(char byte) {
    return (byte == '0' || byte == '1' || byte == '2' || byte == '3' || byte == '4' ||
      byte == '5' || byte == '6' || byte == '7' || byte == '8' || byte == '9');
  }

  double file_text_read_real(int fd) {
    bool dot = false, sign = false;
    string str; char byte = (char)file_bin_read_byte(fd);
    while (byte == '\r' || byte == '\n') byte = (char)file_bin_read_byte(fd);
    if (byte == '.' && !dot) {
      dot = true;
    } else if (!is_digit(byte) && byte != '+' &&
      byte != '-' && byte != '.') {
      return 0;
    } else if (byte == '+' || byte == '-') {
      sign = true;
    }
    if (byte == 0) goto finish;
    str.push_back(byte);
    if (sign) {
      byte = (char)file_bin_read_byte(fd);
      if (byte == '.' && !dot) {
        dot = true;
      } else if (!is_digit(byte) && byte != '.') {
        return strtod(str.c_str(), nullptr);
      }
      if (byte == 0) goto finish;
      str.push_back(byte);
    }
    while (byte != '\n' && !(file_bin_position(fd) > file_bin_size(fd))) {
      byte = (char)file_bin_read_byte(fd);
      if (byte == '.' && !dot) {
        dot = true;
      } else if (byte == '.' && dot) {
        break;
      } else if (!is_digit(byte) && byte != '.') {
        break;
      } else if (byte == '\n' || file_bin_position(fd) > file_bin_size(fd)) {
        break;
      }
      if (byte == 0) goto finish;
      str.push_back(byte);
    }
    finish:
    return strtod(str.c_str(), nullptr);
  }

  string file_text_readln(int fd) {
    int byte = file_bin_read_byte(fd); string str;
    str.push_back((char)byte);
    while ((char)byte != '\n') {
      byte = file_bin_read_byte(fd);
      str.push_back((char)byte);
      if (byte == 0) break;
    }
    return str;
  }

  string file_text_read_all(int fd) {
    string str;
    long sz = file_bin_size(fd);
    char *buffer = new char[sz];
    #if defined(_WIN32)
    long result = _read(fd, buffer, sz);
    #else
    long result = read(fd, buffer, sz);
    #endif
    if (result == -1) {
      delete[] buffer;
      return "";
    }
    str = buffer ? buffer : "";
    delete[] buffer;
    return str;
  }

  int file_text_open_from_string(string str) {
    string fname = get_temp_directory() + "temp.XXXXXX";
    #if defined(_WIN32)
    int fd = -1; wstring wfname = widen(fname);
    wchar_t *buffer = wfname.data(); if (_wmktemp_s(buffer, wfname.length() + 1)) return -1;
    if (_wsopen_s(&fd, buffer, _O_CREAT | _O_RDWR | _O_WTEXT, _SH_DENYNO, _S_IREAD | _S_IWRITE)) {
      return -1;
    }
    #else
    char *buffer = fname.data();
    int fd = mkstemp(buffer);
    #endif
    if (fd == -1) return -1;
    file_text_write_string(fd, str);
    #if defined(_WIN32)
    _lseek(fd, 0, SEEK_SET);
    #else
    lseek(fd, 0, SEEK_SET);
    #endif
    return fd;
  }
 
  int file_text_close(int fd) {
    return file_bin_close(fd);
  }

This enables UTF-8 support on Windows as well as makes use of real file descriptors instead of utilizing the asset array, both of which are improvements if you ask me. All of these functions may be used interchangeably. So that means you can return a file descriptor from file_bin_open(), file_text_open_read(), file_text_open_write(), file_text_open_append(), or file_text_open_from_string(), and pass that return value to any of the other file_bin_* and/or file_text_* functions and they work together seamlessly.

You can open a file with file_bin_open() for example and write to it with file_text_write_string(), or use the returned value of file_text_open_from_string() stored in a variable and change the seek position while you read from it with file_bin_seek(). The file created by GameMaker's implementation of file_text_open_from_string() is read-only which I found to be a bit silly, and GameMaker has no means to retrieve the filename of this temp file, and it gets deleted automatically when the file is closed with file_text_close(). I changed it so that you have both read and write permissions to the file upon creation, and you may the file doesn't get deleted when closed automatically with my implementation, to give the user more control over the lifetime of the file. You may get the absolute path filename from the returned file descriptor using the get_filedescriptor_pathname() function, which will allow you to delete that file from your temporary directory.

I also changed the behavior of file_text_read_all() slightly so that it really just reads the contents left of the file based on the current seek position. If you are at the very beginning of the file it will still read the entire contents so this is unlikely to break existing games. If it does break a game they can just seek the file to the beginning before reading all the contents with that function.

The above code is licensed under MIT and is pulled from my GitHub account, however you are free to relicense it however you like when implementing any portion of it modified or not into enigma. I recommend relicensing under GPLv3 (with a linking exception) to match the rest of the software. The full code may be viewed here: https://github.com/time-killer-games/libfilesystem/blob/master/filesystem.cpp

9
General ENIGMA / Re: Enigma and The Raspberry PI
« on: March 20, 2021, 05:20:31 pm »
This topic was a painful read.

No - you don't need to install manjaro. It will work with any Linux distro if you actually know what you are doing.

To top it all off, if you know how to copy and paste a script from our Ubuntu/Debian wiki, that should work just fine on Raspbian.

I did that just now. My pi came in the mail today. Instructions didn't need to be modified. It 'just works'.



10
General ENIGMA / Josh appreciation topic
« on: March 18, 2021, 11:06:47 am »
Thank you Josh for creating enigma and putting up with me and the rest of this acid trip of a community every day. We are all very thankful for your patience with our bullshit.

Love,
Your Significant Other

11
ENIGMA port of this GameMaker asset: https://marketplace.yoyogames.com/assets/9730/virtual-visit-maker





Requires installing ENIGMA with a custom script so you may use third-party extensions not included in ENIGMA base. You can get the curstom script for your platform here: https://github.com/time-killer-games/enigma-dev be sure to read the ReadMe first at that link. On top of whatever dependencies your game uses, Ubnntu/Linux users will also need:

Code: [Select]
sudo apt-get install libsdl2-dev libglu1-mesa-dev freeglut3-dev mesa-common-dev libpthread-stubs0-dev libx11-dev libprocps-dev
Ubuntu/Linux users will need to run the build64.sh script in the cmdline folder of the downloadable *.tar.gz on this page. If you run Ubuntu 20.04 LTS binary compatiblity specifically, no build needed. Windows/Mac users don't need extra deps nor should you need to build from source code on those platforms, instead, download and extract the exe in the release zip from and here: https://github.com/time-killer-games/panoview/releases/tag/v1.0.0.0 and put that file directly into the cmdline folder found in the *.tar.gz file.

Designed for games to run in headless mode, Windows users need to set the window invisible and Liuux/Mac can simply set the platform to None. Download *.tar.gz: https://drive.google.com/file/d/1FsPONo1jrNHvzdlZHyq46Gf0107_yXb-/view?usp=sharing

12
Announcements / Re: ENIGMOS - The Operating System for Attractive Women!
« on: January 29, 2021, 09:17:22 pm »
The non-VirtualBox image for USB installation has been discontinued, mainly because I can't afford to pay for online storage right now. But there are other reasons on top of that. The good news is I managed to upgrade the VM from 12.1-RELEASE to 12.2 RELEASE on literally the last day I was able to do so before 12.1 EOL.

13
Issues Help Desk / Who is the smartest person in all of enigma-dev?
« on: January 04, 2021, 07:13:46 pm »
i cast 3 votes, 1 to each of them

14
Off-Topic / Converting a Linux user to FreeBSD be like.
« on: December 25, 2020, 08:07:04 pm »


I know what you are thinking: "Why not just use Arch?" My answer: The less GPL, the better. After all, FreeBSD is what all modern Sony and Nintendo consoles and handhelds (even the Switch) are based on for the permissive *BSD license. These companies produce stable and respected products with huge followings. What can be said about Linux? Android and Chromebook. From literally exploding Samsungs to constantly breaking external storage, to having everything stored in the cloud and monitored by Google, what's not to hate? It's obvious why these products suck. "Just anyone" can contribute and break anything they want to without testing the code manually.

If it passes the imperfect and ever-growing CI, they'll probably accept it. Bad idea when money and making a living is a non-motivator to hobbyist GPL devs. This prevents quality products on being delivered, which results in severe bugs, and a rather poopy OS as a whole. FreeBSD needs to be stable, otherwise companies with the tiniest bit of self respect won't look to them. Even apple shares some of FreeBSD ancestry in common for the same licensing debate. Money talks, even if you aren't the one making it but others who are relying on you are, you'll be much likelier to be motivated to deliver what they need and keep up the reputation as being reliable.

Additionally, the process filesystem is optional on FreeBSD. Linux has it mandatory, which results in slower applications that need to read and write to files for any kind of process related interaction, even something as simple as getting the current executable directory and filename, it requires creating, reading, and writing directories, text files, and symlinks for practically everything. The more processes you run, all the more will your computer be unnecessarily slow because of it. More processes = less performance regardless, but now we're just adding to that slowness for no good reason.

Another myth: commercial developers don't give back to the open source permissively licensed OS. Some of that is covered in this video.
https://www.youtube.com/watch?v=cofKxtIO3Is

Greetings from Brazil,
Samuel

15
Tips, Tutorials, Examples / Running NGINX Server in GameMaker and ENIGMA
« on: December 20, 2020, 03:55:52 pm »
Running NGINX Server in GameMaker and ENIGMA

ENIGMA | GM Version: GameMaker Sudio 2.x or later (ENIGMA and GMS 1.4.x too but w/ different string handling)
Target Platform: Windows, (macOS and Linux/Ubuntu need slightly modified instructions)
LinksCLICK HERE FOR PROCESS INFO'S EXTENSION DOCUMENTATION
Downloads (Required): NGINX, FILE MANAGER, PROCESS INFO

Introduction:

ENIGMA users can ignore the download links for File Manager and Process Info above. Server and client interaction is very common in games and my Process Info extension allows you to do much more with that. For the time being, these extensions are not available in ENIGMA's main repository. File Manager is virtually the same as the upcoming official support for std::filesystem in ENIGMA, however I have several bells and whistles added in my repository such as the ability to pass environment variables directly to string arguments when in the form of, for example, ${VARIABLE}.

TL;DR ENIGMA users have to install ENIGMA from the instructions on my own repository instead to get the required extensions:

https://github.com/time-killer-games/enigma-dev

Then you may proceed with this tutorial.

Summary:

Someone added me on Discord asking me how to do this with my Execute Shell extension, so I used that as an excuse to educate everyone here on a practical use case for my much more powerful extension called Process Info, hoping more people switch to using that extension instead of my Execute Shell and Evaluate Shell extensions, which are much less powerful and deprecated in favor of Process Info. They are also no longer actively developed and won't receive any feature updates. Use Process Info instead, it has a learning curve that is steeper, but you will learn to appreciate it with time as you learn what it can do.

Here I am explaining how to use Process Info more specifically on the topic of how to properly run your own NGINX server.

Tutorial:

Download NGINX and extract the ZIP archive. Drag and drop all the files in the extracted contents, (if extracted to a new folder, the contents of that folder), into your GameMaker Studio 2.x window to add them as included files for your project to make use of them. Not just the nginx executable, but also the folders it depends on which are also included in the ZIP you extracted. Create a controller object obj_conttroller, create a blank room with a black background, and then drag your controller object from the resource tree into your room. Now your body is ready.



In the Create Event of your controller object...

General Initialization and copying all your files to where they need to be is the first thing we need to take care of before we can do anything else.

1) First, choose a process index, similar to a resource id like for a sprite or other stuff, but this will represent a unique instance of a running application so we can later get various information from it such as output that would show in the terminal or command prompt if something goes wrong you can further debug it with ease. I chose the variable name to be nginx because that is the name of the executable we are running here, and a value of 0 stored in the variable to uniquely identify the process index:
Code: (gml) [Select]
globalvar nginx; nginx = 0;
2) Next, we create the sandbox directory, using the game_save_id constant, which is a writable directory, the working directory won't always be writable, thus we are using the sandbox directory for that reason alone:
Code: (gml) [Select]
directory_create(game_save_id);
3) Then, copy the included files we have in our project (nginx and its associated files and folders) using my File Manager extension's directory_copy function:
Code: (gml) [Select]
directory_copy(working_directory, game_save_id + "files");
4) We need to set a new working directory to where we copied the included files to, using File Manager's set_working_directory(dname) function, like so:
Code: (gml) [Select]
set_working_directory(game_save_id + "files");and that will set the new working directory to the "files" subdirectory we copied into the sandbox folder game_save_id from the working_directory in step 3. It will also copy everything else the game depends on, not just the included files you previously drag and dropped onto the GameMaker Studio 2 IDE window.

Now we can write a batch file that will run nginx and we will write it directly to our sandbox, (not to be confused with what we set the working _directory to, which is the "files" subfolder within the sandbox folder game_save_id):
Code: (gml) [Select]
f = file_text_open_write(game_save_id + "nginx.bat");
file_text_write_string(f, @'cd "' + working_directory + @'" & "' + working_directory + @'nginx.exe"');
file_text_close(f);

Notice above I also set the working directory of the batch file using the cd command, which is necessary to get this stuff working. Now for the good part; we are going to execute asynchronously an instance of a nginx web server process, but first, please notice I created a temp folder:
Code: (gml) [Select]
directory_create(working_directory + "temp");
process_execute_async(nginx, @'cmd /c @ECHO OFF & "' + game_save_id + @'nginx.bat"');

That temp folder is needed by nginx, otherwise it will complain in the process standard output that the folder doesn't exist. Notice there are two arguments to our process creation function, the second argument is the command itself, not the first. In the second argument we are running the batch file we created earlier found in our sandbox directory game_save_id.

The first argument of process_execute_asnyc(ind, command), on-the-other-hand, should be the global variable we set in the very first step of this tutorial, nginx as the variable's name and zero as its value to represent an index for the specific running application instance, which will be used to get process output for debugging purposes like mentioned earlier. That leads to our next step major step in this tutorial.

Done with Create Event. Now Draw Event / Draw GUI.

Now we are going to draw the process output to our game window in case something went wrong. If you followed this tutorial perfectly as described, no text should be drawn. In the code below you'll see I am setting a custom font named fnt_example, you will either need to create a font yourself that has this name or omit that line completely. The key thing is the text needs to not be cropped when drawn, so make the text small enough it is readable but will still fit in your game window or view-port for easy viewing. If you want to program a camera system to view the output text more power to you but that I won't be covering and it is a lot of unnecessary extra effort. The second line is where we actually draw the process output, should you have messed up following any steps thus far, text will draw and you will need to go back and review this tutorial to debug what the issue is based on what info is drawn.
Code: (gml) [Select]
draw_set_font(fnt_example);
draw_text(0, 0, process_output(nginx));

process_output(ind) will return the process output for the specified process index, that is, the running application process that was started using the exact same process index ind argument. To clear process output when you no longer need it and wish to free memory, call process_clear_out(ind) on the same exact process index value, in this case the value we have stored in the nginx global variable.

Done with Draw Event / Draw GUI. Now for Game End Event.

Now, create a Game End event in your controller object, and give it the following code to execute when triggered:
Code: (gml) [Select]
process_execute(999, @'"' + working_directory + @'nginx.exe" -s stop');
This will cause the nginx server to shut down automatically when you close the game, to prevent it running in the background after you are done with it. Notice I used process_execute(ind, command) which will run synchronously, unlike process_execute_async(ind, command) which is what we used earlier. For the process index ind argument I used 999 as the reserved value to represent this process to make room for other process indexes to be used throughout your program should you ever have the need to run 999 other processes (counting zero) simultaneously, this allows for that many if you count up without skipping a reserved value for each process you create. Though 999 is not required, you may use any ridiculously high number if you wish that is less than ULONG_MAX(=4294967295). The key thing is that every process you have running from your game or application must have a unique index when run and held in memory simultaneously. A process index also can not be negative nor can it have a decimal value.

To free a process index from memory, call process_clear_pid(ind) to get rid of the pid's (process identifer, not the same thing as an index) held in memory, as well as call process_clear_out(ind) to get rid of the process index's standard output strings held in memory.

In other words, for example:
Code: (gml) [Select]
process_clear_pid(nginx);
process_clear_out(nginx);
process_clear_pid(999);
process_clear_out(999);

The above can be put in your Game End event after the process_execute(ind, command) call for general code cleanup.

If you followed everything correctly, you should have the following code in your controller object:

Create Event:
Code: (gml) [Select]
globalvar nginx; nginx = 0;
directory_create(game_save_id);
directory_copy(working_directory, game_save_id + "files");
set_working_directory(game_save_id + "files");
f = file_text_open_write(game_save_id + "nginx.bat");
file_text_write_string(f, @'cd "' + working_directory + @'" & "' + working_directory + @'nginx.exe"');
file_text_close(f);
directory_create(working_directory + "temp");
process_execute_async(nginx, @'cmd /c @ECHO OFF & "' + game_save_id + @'nginx.bat"');
Draw Event (or Draw GUI, either one):
Code: (gml) [Select]
draw_set_font(fnt_example); // optional line
draw_text(0, 0, process_output(nginx));
Game End Event:
Code: (gml) [Select]
process_execute(999, @'"' + working_directory + @'nginx.exe" -s stop');
process_clear_pid(nginx);
process_clear_out(nginx);
process_clear_pid(999);
process_clear_out(999);

Like normal when running the NGINX server executable, it will ask you for permission to access your local network, and which you will need to accept. You can view the server by typing in "localhost" in your favorite internet browser.

Happy Coding!
Samuel