Add Graphics.screenshot

This commit is contained in:
Inori 2019-08-19 08:59:35 -04:00 committed by Inori
parent 35311fb768
commit 28b60eb9fb
13 changed files with 129 additions and 25 deletions

4
.gitmodules vendored Normal file
View file

@ -0,0 +1,4 @@
[submodule "cwalk"]
path = cwalk
url = https://github.com/likle/cwalk
branch = stable

View file

@ -1,25 +1,24 @@
# mkxp-z
This is a work-in-progress fork of mkxp that is intended to run as similarly to RPG Maker XP (RGSS1) as possible, specifically with the target of running games based on Pokemon Essentials, ideally without having to change a single line of code. Once this goal can be accomplished, it's possible that optional enhancements (such as Discord integration) can be written for fangame developers (you poor souls) to take advantage of.
This is a work-in-progress fork of mkxp that is intended to run and alleviate the difficulty of porting games based on Pokemon Essentials. It's not necessarily intended to be a byte-for-byte copy of RGSS though, so non-standard extensions and optional enhancements (such as Discord integration) can/will be written for fangame developers (you poor souls) to take advantage of.
## Prebuilt binaries
> None yet!
## Bindings
Bindings provide the glue code for an interpreted language environment to run game scripts in. mkxp-z focuses on Ruby 1.8 and as such the mruby and null bindings are not included. The original MRI bindings remain for the time being. Please see the original README for more details.
Bindings provide the glue code for an interpreted language environment to run game scripts in. mkxp-z focuses on Ruby 1.8 and as such the mruby and null bindings are not included. The original MRI bindings remain for the time being, with the possible intent of working with >=1.9 to better support RGSS3. Please see the original README for more details.
### MRI
Website: https://www.ruby-lang.org/en/
Matz's Ruby Interpreter, also called CRuby, is the most widely deployed version of ruby. MRI 1.8.1 is what was used in RPG Maker XP, and 1.8.7 is what mkxp-z is written around (at least for now). 1.8.1 and 1.8.7 are for the most part identical, though there are a few differences that need to be ironed out before Essentials can be loaded.
This binding supports RGSS1, RGSS2 and RGSS3, though I've only tested it with RGSS1.
This binding should support RGSS1, RGSS2 and RGSS3, though I've only tested it with RGSS1.
## Dependencies / Building
* Boost.Unordered (headers only)
* Boost.Program_options
* Boost.Filesystem
* libsigc++ 2.0
* PhysFS (latest hg)
* OpenAL
@ -87,23 +86,39 @@ If a requested font is not found, no error is generated. Instead, a built-in fon
* Win32API calls outside of Windows (Win32API is just an alias to the MiniFFI class, which *does* work with other operating systems, but you can obviously only load libraries made for the platform you're on)*
* Some Win32API calls don't play nicely with SDL. Building with the `fix_essentials` option will attempt to fix this.
<<<<<<< HEAD
* `rubyscreen.dll` doesn't work, and `Graphics.snap_to_bitmap` is unconditionally overridden by the SpriteResizer script. This can't be worked around without modifying Ruby in ways I don't have any desire to. When built with `fix_essentials` enabled, RGSS2 Graphics functions are bound, so wrap the definition of the Win32API-based `snap_to_bitmap` in an if statement. Or just delete the thing if you don't care about being able to swap executables in and out. As another consequence of `rubyscreen.dll` not being compatible, screenshots also don't work -- but the Graphics module now has a `screenshot` method to compensate for this.
=======
* The current implementation of `load_data` is case-sensitive. If you try to load `Data/MapXXX`, you will not find `Data/mapXXX`.
>>>>>>> e930e6a34505916f8f563e4846493c72aa3ee7e9
* `load_data` is slow. In fact, it's too slow to handle `pbResolveBitmap` firing a million times a second, so if `fix_essentials` is used Graphics files can only be loaded from outside of the game's archive. You could remove that code if you want, but you'll lag. Very hard.
* `FileSystem::openRead` is not compatible with absolute paths in Windows (this affects `Bitmap.new` or `Audio.bgm_play`, for instance, another reason why Win32API `Graphics.snap_to_bitmap` breaks).
* Movie playback
* wma audio files
* Creating Bitmaps with sizes greater than the OpenGL texture size limit (around 8192 on modern cards)^
\* Once games can be played comfortably on Windows, I may try to have a 'fake' Win32API class written for other operating systems which intercepts and interprets some of the common calls that get used, a bit like what's already being done with the `fix_essentials` option. SDL2 can handle a lot of the things that are performed with WinAPI.
^ There is an exception to this, called *mega surface*. When a Bitmap bigger than the texture limit is created from a file, it is not stored in VRAM, but regular RAM. Its sole purpose is to be used as a tileset bitmap. Any other operation to it (besides blitting to a regular Bitmap) will result in an error. (This breaks SLLD after the professor's speech due to an issue with the tilemaps, but Pokemon Uranium seems to be okay)
^ There is an exception to this, called *mega surface*. When a Bitmap bigger than the texture limit is created from a file, it is not stored in VRAM, but regular RAM. Its sole purpose is to be used as a tileset bitmap. Any other operation to it (besides blitting to a regular Bitmap) will result in an error. (This breaks SLLD after the professor's speech, Zeta after you leave the cave, but Pokemon Uranium seems to be fine as far as I've tested)
## Win32API
Win32API exists in mkxp-z, objectively functioning nearly the same as before for better or worse. The third and fourth arguments are now optional (if you just want a function that takes no arguments and returns nothing, for instance), and Win32API objects will yield themselves to any blocks given to `initialize`.
However, the Win32API class is also avaiable when built for Linux and macOS, under the name `MiniFFI` instead (Win32API is just an extra name for it in Windows). It will only load shared libraries built for your platform.
Being simple as it is, it remains mostly as the lazy option/last resort if you can't/don't want to build MKXP yourself.
## Nonstandard RGSS extensions
To alleviate possible porting of heavily Win32API reliant scripts, Ancurio added certain functionality that you won't find in the RGSS spec. Currently this amounts to the following:
To alleviate possible porting of heavily Win32API reliant scripts, certain functionality that you won't find in the RGSS spec has been added. Currently this amounts to the following:
* The `Input.press?` family of functions accepts three additional button constants: `::MOUSELEFT`, `::MOUSEMIDDLE` and `::MOUSERIGHT` for the respective mouse buttons.
* The `Input` module has two additional functions, `#mouse_x` and `#mouse_y` to query the mouse pointer position relative to the game screen.
* The `Graphics` module has two additional properties: `fullscreen` represents the current fullscreen mode (`true` = fullscreen, `false` = windowed), `show_cursor` hides the system cursor inside the game window when `false`.
* Calling `Graphics.screenshot(path)` will save a screenshot to `path` in BMP format.
Commonly used Win32API routines will eventually have equivalent functions directly bound, like `Graphics.screenshot` for `Win32API.new('rubyscreen.dll,'TakeScreenshot','p','i')`.
## Building on Windows (MSYS+MinGW)
@ -154,7 +169,7 @@ cd ..
5. Build mkxp-z:
```sh
git clone https://github.com/inori-z/mkxp-z
git clone --recursive https://github.com/inori-z/mkxp-z
cd mkxp-z
# Ruby 1.8 doesn’t support pkg-config (Might add it in, since this
@ -217,7 +232,7 @@ cd ..
4. Build mkxp-z:
```sh
git clone https://github.com/inori-z/mkxp-z
git clone --recursive https://github.com/inori-z/mkxp-z
cd mkxp-z
# Ruby 1.8 doesn’t support pkg-config (Might add it in, since this
@ -292,7 +307,7 @@ cd ..
4. Build mkxp-z:
```sh
git clone https://github.com/inori-z/mkxp-z
git clone --recursive https://github.com/inori-z/mkxp-z
cd mkxp-z
# Ruby 1.8 doesn’t support pkg-config (Might add it in, since this

View file

@ -24,6 +24,7 @@
#include "binding-util.h"
#include "binding-types.h"
#include "exception.h"
#include <SDL.h>
RB_METHOD(graphicsUpdate)
{
@ -195,6 +196,24 @@ RB_METHOD(graphicsPlayMovie)
return Qnil;
}
RB_METHOD(graphicsScreenshot)
{
RB_UNUSED_PARAM;
VALUE filename;
rb_scan_args(argc, argv, "1", &filename);
SafeStringValue(filename);
try
{
shState->graphics().screenshot(RSTRING_PTR(filename));
}
catch(const Exception &e)
{
raiseRbExc(e);
}
return Qnil;
}
DEF_GRA_PROP_I(FrameRate)
DEF_GRA_PROP_I(FrameCount)
DEF_GRA_PROP_I(Brightness)
@ -216,6 +235,7 @@ void graphicsBindingInit()
_rb_define_module_function(module, "freeze", graphicsFreeze);
_rb_define_module_function(module, "transition", graphicsTransition);
_rb_define_module_function(module, "frame_reset", graphicsFrameReset);
_rb_define_module_function(module, "screenshot", graphicsScreenshot);
_rb_define_module_function(module, "__reset__", graphicsReset);

View file

@ -45,10 +45,10 @@ MiniFFI_alloc(VALUE self)
// Probably should do something else for macOS and Linux if I get around to them,
// this would probably be a *very* long list for those
static void*
MiniFFI_GetFunctionHandle(void *lib, const char *func)
MiniFFI_GetFunctionHandle(void *libhandle, const char *func)
{
#if defined(__WIN32__) && defined(USE_ESSENTIALS_FIXES)
#define CAPTURE(name) if (!strcmp(#name, func)) return (void*)&MKXP_##name
#define CAPTURE(n) if (!strcmp(#n,func)) return (void*)&MKXP_##n
CAPTURE(GetCurrentThreadId);
CAPTURE(GetWindowThreadProcessId);
CAPTURE(FindWindowEx);
@ -62,7 +62,7 @@ MiniFFI_GetFunctionHandle(void *lib, const char *func)
CAPTURE(RegisterHotKey);
CAPTURE(GetKeyboardState);
#endif
return SDL_LoadFunction(lib, func);
return SDL_LoadFunction(libhandle, func);
}
// MiniFFI.new(library, function[, imports[, exports]])

1
cwalk Submodule

@ -0,0 +1 @@
Subproject commit fe3b37eb3cbcc0f45fb3f1bed0a40f1cfb234ce4

View file

@ -1,4 +1,4 @@
project('mkxp-z', 'cpp', version: '1.0', default_options: ['cpp_std=c++11'])
project('mkxp-z', 'cpp', 'c', version: '1.0', default_options: ['cpp_std=c++11'])
xxd = find_program('xxd', native: true)
host_system = host_machine.system()
@ -24,6 +24,10 @@ subdir('assets')
all_sources = [main, bindings, processed_shaders, processed_assets]
include_dirs = [include_directories('src', 'binding')]
# Use cwalk
all_sources += files('cwalk/src/cwalk.c')
include_dirs += include_directories('cwalk/include')
linker_args = []
if host_system == 'windows'

View file

@ -34,7 +34,6 @@
#include <SDL_mutex.h>
#include <SDL_thread.h>
#include <SDL_timer.h>
#include <boost/filesystem/path.hpp>
ALStream::ALStream(LoopMode loopMode,
const std::string &threadId)
@ -258,12 +257,7 @@ struct ALStreamOpenHandler : FileSystem::OpenHandler
void ALStream::openSource(const std::string &filename)
{
ALStreamOpenHandler handler(srcOps, looped);
// The path must be normalized or ridiculous things like
// `Audio/BGM/../../Audio/ME/001-Victory01` will break
std::string normal = boost::filesystem::path(filename).lexically_normal().generic_string();
shState->fileSystem().openRead(handler, normal.c_str());
shState->fileSystem().openRead(handler, filename.c_str());
source = handler.source;
needsRewind.clear();

View file

@ -37,6 +37,11 @@
// library made for your platform or, better yet, just write the
// functionality into MKXP and don't use MiniFFI/Win32API at all.
// A lot of functions here will probably be bound directly to Ruby
// eventually (so that you can just call Graphics.save_screenshot
// or something instead of having to make a messy Win32API call
// that *now* has to run entirely different function to even work)
// No macOS/Linux support yet.

View file

@ -29,6 +29,12 @@
#include "boost-hash.h"
#include "debugwriter.h"
// boost::filesystem::path and std::filesystem::path
// are too troublesome
extern "C"{
#include "cwalk.h"
}
#include <physfs.h>
#include <SDL_sound.h>
@ -633,8 +639,12 @@ openReadEnumCB(void *d, const char *dirpath, const char *filename)
void FileSystem::openRead(OpenHandler &handler, const char *filename)
{
// FIXME: Paths with Windows drive letters don't
// hecking work, apparently never did
char *filename_nm = normalize(filename, false, false);
char buffer[512];
size_t len = strcpySafe(buffer, filename, sizeof(buffer), -1);
size_t len = strcpySafe(buffer, filename_nm, sizeof(buffer), -1);
delete filename_nm;
char *delim;
if (p->havePathCache)
@ -684,6 +694,8 @@ void FileSystem::openRead(OpenHandler &handler, const char *filename)
throw Exception(Exception::NoFileError, "%s", filename);
}
// FIXME: This is (a) slower than RGSS and (b) case-sensitive
void FileSystem::openReadRaw(SDL_RWops &ops,
const char *filename,
bool freeOnClose)
@ -697,6 +709,33 @@ void FileSystem::openReadRaw(SDL_RWops &ops,
initReadOps(handle, ops, freeOnClose);
}
// RGSS normalizes paths for at least audio
// Essentials kind of takes this for granted
// (`Audio/BGM/../../Audio/ME/[file]`)
// SDL_SaveBMP wants absolute paths
char* FileSystem::normalize(const char *pathname, bool preferred, bool absolute)
{
char *path_nml = new char[512];
char *path_abs = 0;
#ifdef __WIN32__
cwk_path_set_style((preferred) ? CWK_STYLE_WINDOWS : CWK_STYLE_UNIX);
#endif
if (cwk_path_is_relative(pathname) && absolute)
{
path_abs = new char[512];
char *bp = SDL_GetBasePath();
cwk_path_join(bp, pathname, path_abs, 512);
SDL_free(bp);
}
cwk_path_normalize((path_abs) ? path_abs : (char*)pathname, path_nml, 512);
Debug() << pathname << ":" << path_nml;
return path_nml;
}
bool FileSystem::exists(const char *filename)
{
return PHYSFS_exists(filename);

View file

@ -23,6 +23,7 @@
#define FILESYSTEM_H
#include <SDL_rwops.h>
#include <string>
struct FileSystemPrivate;
class SharedFontState;
@ -64,6 +65,8 @@ public:
const char *filename,
bool freeOnClose = false);
char *normalize(const char *pathname, bool preferred, bool absolute);
/* Does not perform extension supplementing */
bool exists(const char *filename);

View file

@ -23,6 +23,7 @@
#include "util.h"
#include "gl-util.h"
#include "gl-fun.h"
#include "sharedstate.h"
#include "config.h"
#include "glstate.h"
@ -31,6 +32,7 @@
#include "quad.h"
#include "eventthread.h"
#include "texpool.h"
#include "filesystem.h"
#include "bitmap.h"
#include "etc-internal.h"
#include "disposable.h"
@ -951,6 +953,20 @@ void Graphics::playMovie(const char *filename)
Debug() << "Graphics.playMovie(" << filename << ") not implemented";
}
void Graphics::screenshot(const char *filename)
{
int w = width();
int h = height();
const char *fn_normalized = shState->fileSystem().normalize(filename, true, true);
unsigned char *pixbuf = new unsigned char[w*h*4];
glReadPixels(0,0,w,h,GL_BGRA,GL_UNSIGNED_BYTE,pixbuf);
SDL_Surface *s = SDL_CreateRGBSurfaceFrom(pixbuf,w,h,8*4,w*4,0,0,0,0);
int rc = SDL_SaveBMP(s, fn_normalized);
delete fn_normalized;
delete pixbuf;
if (rc) throw Exception(Exception::SDLError, "%s", SDL_GetError());
}
DEF_ATTR_RD_SIMPLE(Graphics, Brightness, int, p->brightness)
void Graphics::setBrightness(int value)

View file

@ -55,6 +55,7 @@ public:
int height() const;
void resizeScreen(int width, int height);
void playMovie(const char *filename);
void screenshot(const char *filename);
void reset();

View file

@ -6,21 +6,23 @@ sdl2 = dependency('sdl2')
sdl2_ttf = dependency('SDL2_ttf')
sdl2_image = dependency('SDL2_image')
sdl_sound = dependency('SDL_sound')
opengl = dependency('GL')
boost = dependency('boost', version: '>=1.49', modules: ['system', 'program_options', 'filesystem'])
openal = dependency('openal')
zlib = dependency('zlib')
main_dependencies = [sigcxx, openal, boost, zlib, pixman, physfs, vorbisfile, sdl2, sdl2_ttf, sdl2_image, sdl_sound]
main_dependencies = [sigcxx, openal, opengl, boost, zlib, pixman, physfs, vorbisfile, sdl2, sdl2_ttf, sdl2_image, sdl_sound]
if host_system == 'darwin'
main_dependencies += compiler.find_library('iconv')
if openal.type_name() != 'pkgconfig'
add_project_arguments('-DUSE_MAC_OPENAL', language: 'cpp')
endif
elif host_system == 'windows' and get_option('fix_essentials')
main_dependencies += compiler.find_library('wsock32')
elif host_system == 'windows'
if get_option('fix_essentials') == true
main_dependencies += compiler.find_library('wsock32')
endif
endif
if get_option('shared_fluid') == true