logic image

betterAPI documentation -

extended lua api for scrap mechanic


this API adds additional methods to the game.
however, it does not provide full access to the lua API.
this mod expands the capabilities of the game API by adding additional features for use by other mods.
betterAPI has a built-in package manager with extensions. to open it, write to the chat /better

manual installation:
1. close scrap mechanic
2. subscribe to the mod
3. open folder: Steam\steamapps\workshop\content\387990\3177944610\content
4. copy all files
5. open folder: Steam\steamapps\common\Scrap Mechanic\Release
6. insert all the files with the replacement

description

this API adds additional methods to the game.
however, it does not provide full access to the lua API.
this mod implements methods such as "loadstring" and "loadfile", however, bytecode loading is not available and the transfer of the user's ENV is mandatory.
if someone has found a vulnerability here, PLEASE let me know.
vulnerability is considered to be any opportunity to execute arbitrary code, as well as to use the filesystem outside the "BetterFS" directory and game mods.
my plans are to implement a framework for creating screens and integrate with the browser and "Visual Studio Code".
betterAPI is also able to speed up the execution of lua code in the game, as it uses a newer version of luajit.
betterAPI itself was written using languages such as: lua, C++, C, C# and python

safety

to use most of the betterAPI methods, you need to get permission to use betterAPI.
this is done so that once you find a vulnerability, you can't just take and make a virus.
if you want to use betterAPI locally, then you can add your mod to the whitelist yourself.
after you post the mod on steam, you should contact me in the comments on betterAPI or discord so that I add your mod to the betterAPI whitelist.
after that, your mod will be guaranteed to be added to the betterAPI whitelist.
however, I will not add you access to the entire betterAPI. you will only get access to the functions you use.
i have the right to refuse to grant you access to certain parts of betterAPI if I consider that you do not need them to solve your problem.
some of the APIs in betterAPI (those that cannot be used for evil purposes) are public and do not require adding mods to the whitelist. study the documentation to understand what is available to you.

installation

after installation, the mod will receive and install updates itself.
installation on Windows (select the drive letter where you have the game installed):
  1. close scrap mechanic
  2. subscribe to the mod in the steam workshop: https://steamcommunity.com/sharedfiles/filedetails/?id=3177944610
  3. open folder: C:\Program Files (x86)\Steam\steamapps\workshop\content\387990\3177944610\content
  4. copy all files
  5. open folder: C:\Program Files (x86)\Steam\steamapps\common\Scrap Mechanic\Release
  6. insert all the files with the replacement

chat commands

  • /revoke - takes away access from all scripts to all APIs that require user confirmation for use
  • /better - opens the "betterAPI setup" program

extensions

betterAPI supports extensions that can extend the functionality of the betterAPI itself and the game.
extensions cannot be distributed through the workshop because they do not work in the sandbox.
the extension must be placed in the "Scrap Mechanic/Release/BetterExtensions" folder.
now it is possible to download extensions from the official repository via "betterAPI setup", where you can also install third-party extensions without opening the game folder.
DLL mods like "BetterPaintTool" work if you put them in the "BetterExtensions" directory.
the extension can be just a lua file, dll, directory, or bext file.
the file in the bext format is an extension format for betterAPI. in fact, this is a zip archive containing all the extension files.
the extension in the bext format will be automatically unpacked into BetterCache, and will be treated as an extension folder.
the following files may be in the extension folder: extension.lua, extension.dll
the extension may also contain other files (at the discretion of the developer)
an extension in lua format or a extension.lua file from the folder extension receives the "better" table available for the extension as an argument.
the extension can add new APIs to the "better" table, these APIs will be available to mods.
if the lua extension file was loaded from the extension folder, then as the second argument it will get the full path to the extension folder, which can be used to load other extension files.
DLL extensions or the extension.dll file from the folder extension are injected into the game process, which will allow you to modify the gameplay.
betterAPI extensions can use the internal betterAPI API. which is not available during normal use from mods.
please note that all extensions only work when entering the world and are unloaded when exiting it!
the folder with your extension is automatically added to package.path and package.cpath so that you can upload your lua and dll files using require.
make sure that the name of your files is unique to avoid conflicts with other extensions!
if you want to extend the functionality of threads, then you should use the "threadExtension.lua" file in the directory with your extension.
other files of your extension will not be automatically loaded in the thread.
please note that not all functions of the "bext" api are available to you in threads.
to debug betterAPI thread extensions, use "print" and read the "Scrap Mechanic/Release/BetterTemp/threadLog.log" file.

betterAPI setup

the betterAPI setup program is included in betterAPI
it can be found through a search in the OS
this program allows you to turn betterAPI on and off, as well as download and install extensions
you can also use this program to update/restore betterAPI from the workshop or from the repository.
you can open this program while in the game using the /better command
all extensions that are installed by this program are installed in the "MgrBetterExtensions" directory
betterAPI setup will automatically update all extensions downloaded from the betterAPI repository.

rules for publishing extensions

in order for your extension to be hosted in the betterAPI repository and for users to download it through the betterAPI setup, it must meet several criteria:
  1. no binary files (if you need a DLL or exe file for your expansion, then send me the source code and I will compile it myself)
  2. no exits from the sandbox (your extension should not provide mods with the ability to crawl through the user's system or execute any native code)
  3. limit the functions that can theoretically be used for malicious purposes (if your extension has something more than the acceleration of any mathematical algorithm, limit the function to the mods in which you are going to use it using bext.modCheckEx)
even if all these rules are followed, your extension will be thoroughly checked before I add it to the repository! I can ask you to make some edits during the moderation process.

asynchronous

all asynchronous functions in the mod work as follows:
they return a lambda(function) that needs to be called until it returns the result

registration

first you need to get into the white list of betterAPI mods, to do this, contact me on steam or discord
for debugging, you can add your mod there yourself. the file with the white list of mods is called modsWhitelist.json
in general, I am ready to add anyone to this whitelist who will not use betterAPI for bad purposes
before using the better api, you need to register your mod in betterapi
to do this, call the better.registration method (it won't be a big deal if you call it several times in a row, subsequent calls will simply be ignored (for example, each time the block is installed))

example

                        --please note that the third argument is the FOLDER name of your mod (it may differ from the name of the mod on steam)
better.registration("dcf0ddb5-5585-4e10-8302-7dc58b2aba50", "1234567890", "testmod")
                    
                        --in this case, the function will read the missing parameters by itself
better.registration("testmod")
                    
                        --it only works if the name of your mod and its directory are the same
--it will be the same if you created your mod through mod tool and did not change the name
better.registration()
                    

filesystem

the paths in mod work like this: $CONTENT_MODUUID/Scripts/test.lua
you cannot use $CONTENT_DATA
so the best solution would be to declare a variable with the UUID of the mod in your mod and access the files like this:
local selfuuid = "dcf0ddb5-5585-4e10-8302-7dc58b2aba50"
better.loadfile("$CONTENT_" .. selfuuid .. "/Scripts/test.lua", nil, _G)
if you start the path with "/" then you will be accessing the isolated betterAPI filesystem.
the better API filesystem allows you to save any files in any format, delete them, rename them, and do WHATEVER you want with them.
however, you cannot execute files (so the filesystem remains secure).
the betterAPI filesystem is restricted in the AppData\Roaming\Axolot Games\Scrap Mechanic\User\User_steamid\BetterFS directory.
all files in this filesystem are available to any mods using betterAPI and the user.
please note that when using "better.filesystem" only the paths in the betterAPI filesystem are available to you
(that is, the paths of $CONTENT_UUID are not available) this is done to prevent overwriting scripts of other mods
(readFile/exists/isDirectory/list exceptions, they can use the paths $CONTENT_UUID).

notes

  • note that the debug library in the better api is severely restricted (restrictions are identical to opencomputers)
  • the default volume of the audio API is set to 0 percent!! don't forget to increase it: audioObject:setVolume(0.5)

thread environment

  • _G - link to your environment
  • print - the logs from the thread goes only to "Scrap Mechanic/Release/BetterTemp/threadLog.log" and this file exists for one game session
  • type
  • assert
  • error
  • ipairs
  • next
  • pairs
  • pcall
  • select
  • tonumber
  • tostring
  • unpack
  • xpcall
  • setmetatable
  • getmetatable
  • loadstring(chunk:string, chunkname:string, env:table):function, error - loads the code of their string. the ENV argument is required. does not load bytecode
  • coroutine (full library)
  • string (full library)
  • table (full library)
  • math (full library)
  • bit (full library)
  • os {clock = os.clock, difftime = os.difftime, time = os.time, date = os.date} does not have access to os.execute and other dangerous
  • threadTunnelSet(index:number, value:string|number|boolean|nil) - writes the value to the tunnel table of the thread
  • threadTunnelGet(index:number):value - reads the value from the tunnel table of the thread
  • sleep(time in milliseconds:number) - stops the thread for the specified time

api

main

  • better.version - the number with the betterAPI version
  • better.isAvailable():boolean - returns true if your mod has the right to use betterAPI
  • better.isAvailable(api:string):boolean - checks if your mod has access to a specific part of the betterAPI
  • better.registration(uuid, steamid, foldername):uuid, steamid, foldername - registers your mod in the better api for paths to work
  • better.registration(foldername:string|nil):uuid, steamid, foldername - this method registers the mod automatically (reads the parameters itself) you only need to specify the name of the folder with your mod. also, the parameters that this function reads, it returns in the form of strings. if the name of your mod and its directory match, then you can omit the foldername parameter
  • better.registrationBlueprint(uuid, steamid) - registers your blueprint in betterAPI so that you can read files from it using filesystem
  • better.tick() - it must be called at least once per tick on client and server. it's okay if you call it more often (you can just put the call in client_onFixedUpdate and server_onFixedUpdate)
  • better.fast():boolean - enables additional luajit optimizations, returns true if it was possible to do it now or earlier. It applies to all scripts in the game (available from threads)

extension

  • better.extension.isLoaded(name:string):boolean - returns true if the specified extension was downloaded by the user from betterAPI setup and it is currently activated (only extensions from the betterAPI repository)
  • better.extension.require(name:string):boolean - requests the user to download an extension with the specified name from the repository. when called, the user is given a choice whether he wants to activate the specified extension, if the user confirms that the extension will be activated even without re-entering the world, if the extension has not been downloaded, it will be downloaded from the betterAPI repository. this function CANNOT be used to download third-party extensions that are not published in the betterAPI repository. It does not allow you to set extensions without the user's permission. returns false if the installation request has been successfully completed and true if extensions have already been installed and nothing has happened

functions

  • better.getSteamID():string - returns the steam user ID as a string
  • better.loadstring(chunk, chunkname, env):code,err - loads the code from the string. does not load bytecode. also, the transfer of your ENV is mandatory
  • better.loadfile(path, env):code,err - load the code from a file. does not load bytecode. also, the transfer of your ENV is mandatory
  • better.setmetatable - control of metatables from lua
  • better.getmetatable - control of metatables from lua
  • better.date - the os.date function, which for some reason was deprived of the game
  • better.textEditor_txt(text):function(delete:boolean):text - opens the visual studio code that is installed for the extension .txt with your text, returns a function that returns the current state of the text file. pass true to the function to delete the file
  • better.textEditor_lua(text):function(delete:boolean):text - opens the visual studio code that is installed for the extension .lua with your text, returns a function that returns the current state of the text file. pass true to the function to delete the file

filesystem api (restricted by the BetterFS directory)

  • better.filesystem.show(pathOrNil):boolean - opens a folder from better FS in the user's explorer
  • better.filesystem.readFile(path):content,err - reads a file from the betterAPI filesystem
  • better.filesystem.writeFile(path, content):ok,err - writes a file to the betterAPI filesystem
  • better.filesystem.makeDirectory(path):boolean - creates a directory
  • better.filesystem.isDirectory(path):boolean - checks whether the object is a directory
  • better.filesystem.exists(path):boolean - checks if the object exists
  • better.filesystem.list(path):tbl - retrieves a list of files and folders by a specific path
  • better.filesystem.rename(fromPath, toPath):boolean - renames a folder or file
  • better.filesystem.remove(path):boolean - deletes a folder or file
  • better.filesystem.copy(fromPath, toPath):boolean - copy a folder or file
  • better.filesystem.size(path):number - returns the file size in bytes. measures only the size of files (not directories)

coroutine api

  • better.nativeCoroutine - native coroutine, if you call the API methods of the game from it, then the game crashes
  • better.coroutine - this is lua coroutine, but it has been changed so that the game api methods work fine from it (currently it does not work with the game API and crashes the game. when will I be able to fix this without wild performance losses, fixed=true will appear in this table)

debug api (restricted)

  • better.debug.traceback - full-fledged debug.traceback
  • better.debug.getinfo - gives incomplete information
  • better.debug.getlocal - provides only the variable name (not the variable itself)
  • better.debug.getupvalue - provides only the variable name (not the variable itself)

audio api

  • better.audio.createFromFile(path:string):audioObject - creates an audio object from a file
  • better.audio.createFromUrl(path:string):audioObject - creates an audio object from a url
  • better.audio.createFromString(binstr:string):audioObject - loads an audio file from RAM (for example mp3 or wav) automatically detects the format
  • better.audio.createFromPcm(pcmContent:string, sampleRate:number|nil(8000), byteRate:number|nil(1), channelsCount:number|nil(1)):audioObject - loads audio from a PCM fragment
  • better.audio.createPcmStream(sampleRate:number|nil(8000), byteRate:number|nil(1), channelsCount:number|nil(1)):audioObject - creates a PCM stream. It can be used to create musical instruments, play sound from game console emulators, etc.
  • better.audio.destroy(audioObject) - destroys the audio object
  • better.audio.stop(audioObject) - stop the sound (does not work for PCM stream)
  • better.audio.start(audioObject) - start the sound (does not work for PCM stream)
  • better.audio.pause(audioObject) - pause the sound (does not work for PCM stream)
  • better.audio.setVolume(audioObject, volume) - sets the volume (0 - math.huge)
  • better.audio.setBalance(audioObject, balance) - sets the channels balance (-1 - 1)
  • better.audio.setRate(audioObject, rate) - sets the rate (0 - math.huge) (It works as a speed setting that does not distort the tone)
  • better.audio.setPitch(audioObject, pitch) - sets the pitch (-60....0....+60 semitones) (changes the pitch without changing the speed)
  • better.audio.setSpeed(audioObject, speed) - sets the speed (0 - math.huge) (it changes both pitch and speed)
  • better.audio.setLoop(audioObject, state) - enables or disables loop playback (default: false)
  • better.audio.setPosition(audioObject, second) - starts the playback from a certain point (does not work for PCM stream)
  • better.audio.seek(audioObject, seconds) - rewinds the track (does not work for PCM stream)
  • better.audio.updateSpatialSound(earpos:vec3, speaker's {{pos:vec3, dist:number}}, eardir:vec3) - updates spatial sound variables (for spatial sound, it is better to use mono recordings)
  • better.audio.noSpatialSound() - turns off spatial sound
  • better.audio.getState():number - 0 - stopped, 1 - playing, 2 - paused, 3 - loading (does not work for PCM stream)
  • better.audio.getPosition():number - returns the current position in the track (does not work for PCM stream)
  • better.audio.getLength():number - returns the length of the track in seconds (does not work for PCM stream)
  • better.audio.update(audioObject) - applies sound settings right now. It often doesn't make sense to use
  • better.audio.flush(audioObject, pcm:string|nil) - updates the PCM stream. if the loop mode is enabled, the new fragment will be played cyclically until the stream is destroyed or until this value is equated to nil.
  • better.audio.fork(audioObject):audioObject - creates a new instance of this sound. It can be used to start one sound at the same time several times with different parameters. it does not copy audio settings (speed, pitch, etc), only its contents

mouse api

  • better.mouse.isLeft():boolean
  • better.mouse.isRight():boolean
  • better.mouse.isCenter():boolean

keyboard api

  • better.keyboard.keys - keys table
  • better.keyboard.isKey(keycode):boolean

openAI api

  • better.openAI.textRequest(apikey:string|nil, model:string|nil(default: gpt-3.5-turbo), prompt:string, request:string):asyncfunction():string - sends a text request to openAI over the internet. if you have not specified a token, then duckAI will be used, iIn such cases, the GPT_4_MINI model will always be used

thread api

  • better.thread.new(code:string):threadObj - creates a thread. after creating the object, call result from time to time, if you get true, then thread execution is terminated, if the second argument exists, then this is an error string. after the end of the thread, BE SURE to call thread:free() to free up the memory allocated for the thread. threads DO NOT HAVE ACCESS TO GAME API. you have to pass the data through the tunnel. betterAPI is only partially accessible from the threads
  • better.thread.threadTunnelSet(threadObj, index:number, value:string|number|boolean|nil) - writes the value to the tunnel table of the thread. so that in the future it can be read from the thread using threadTunnelGet
  • better.thread.threadTunnelGet(threadObj, index:number):value - reads the value from the tunnel table of the thread. the thread can write data there via threadTunnelSet. you can still read values from here after the thread is finished, but before calling thread:free()
  • better.thread.result():end:boolean, err:string - returns true if the thread was completed, and the error text if it ended with an error. after receiving the execution result, you still have access to the data from the tunnel, after you have received the result, call thread:free() to free the memory
  • better.thread.free(threadObj):boolean - free the memory that was occupied by the thread. you should call this after thread:result() returns true. you can't call this while the thread is running, you need to wait for it to finish. returns true if it was possible to make free, returns false if thread is still active and free failed
  • better.thread.kill(threadObj):boolean - stops the thread. returns true if it was successful. after the thread ends, but before calling free, you can still read data from the tunnel. after th:kill(), you will call th:free()

videoCapture api (client API) (confirmation from the user is required)

  • better.videoCapture.trigger(shapeClass, callback(self, key):string) - if you want your block to act as a screen capture, put this at the end of your script. when the user presses E, this function will call your callback with the API access key. the access key will be revoked immediately as soon as this block is destroy!
  • better.videoCapture.isAvailable(key):boolean - returns true if your API access key is valid
  • better.videoCapture.getSize(key):width, height - returns the size of the captured area
  • better.videoCapture.cancel(key) - revokes access
  • better.videoCapture.capture(key, width, height, x, y, sizeX, sizeY):table - captures an image from a user-selected source. first, the resolution of the source you want to work with is transmitted, followed by the position and size of the area in this resolution. the output table is one-dimensional, and the pixels in it are arranged from top-bottom/left-right. the color format is 0xRRGGBB
  • better.videoCapture.isControlAvailable(key):boolean - returns true if the user has allowed the captured area to be control (the control API has not been implemented yet)
  • better.videoCapture.touch(apikey, width, height, x, y, action:(press, drop, drag), button:(1, 2)) - simulates mouse input in the capture field. first, the virtual resolution of the captured area is transmitted. it only works if the user has given permission to control

tts api

  • better.tts.textToSpeech(text:string):audioObject - makes tts for further playback via audio API

algorithm api (public API. it is available to all mods and does not require to be added to the betterAPI whitelist)

  • better.algorithm.base64_encode(string):string - converts text to a base64 string (available from threads)
  • better.algorithm.base64_decode(string):string - converts a base64 string to a text string (available from threads)
  • better.algorithm.sha256_text(string):string - converts a string to sha256 in text format (available from threads)
  • better.algorithm.sha256_binary(string):string - converts a string to binary sha256 (available from threads)
  • better.algorithm.sha256_file_text(path:string):string - return the file's checksum. to use it, you need to have access to the "filesystem" API
  • better.algorithm.sha256_file_binary(path:string):string - return the file's checksum. to use it, you need to have access to the "filesystem" API

array api

creates an array of the specified length with the specified data type
it does not contain additional checks, so be careful when using
  • better.array.create_uint8(length):array
  • better.array.create_uint16(length):array
  • better.array.create_uint32(length):array
  • better.array.create_uint64(length):array

  • better.array.create_int8(length):array
  • better.array.create_int16(length):array
  • better.array.create_int32(length):array
  • better.array.create_int64(length):array

  • better.array.create_double(length):array
  • better.array.create_float(length):array

internal api (available only for betterAPI extensions)

  • bext.checkArg(argnum, arg, ...) - checks the correctness of the arguments (available from threads)
  • bext.loadDLL(path) - injects a DLL into the game process (available from threads)
  • bext.modCheck(...) - this method should be called at the beginning of each function that the extension exports to betterAPI and makes available to mods. as arguments, pass all the additional API permissions that the mod must have in order to use this feature. for example: bext.modCheck("audio", "http"). if you place this at the beginning of your function, the mod will need to have access to both audio and http in order to invoke your function (available from threads)
  • bext.modCheckEx(mods:table, ...) - this function is similar to bext.modCheck. except that it accepts a table with additional mods that should have access to your function. the structure of the table is identical to the one presented in the "modsWhitelist.json" file in betterAPI. please note that the mods table can only contain tables, each of which is a list of description requirements.json in a defiant mod. the mod must satisfy at least one of these tables, all keys in these tables will be compared with the same ones in the description.json and if at least one does not match, then it is assumed that the description.json does not meet the requirements of this table and the check proceeds to the next one. exceptions the "apis" key is an array with a list of APIs available for this mod. if there is no api key, then all "apis" are available to the mod.. note that since the check is implemented in this way, it means that if there is an empty table in the mods table, the function will become available to all mods, as any description.json is able to meet completely missing requirements (available from threads)
  • bext.modCheckCEx(mods:table, ...) - the name is short for "custom extended" and works the same way as bext.modCheckEx, but your feature will only be available to mods from your list, and will not be available to mods from the standard list (available from threads)
  • bext.registerHandler_exit(function) - registers a callback function for an exit event from the world (available from threads) (from the thread is called at the end of the stream)
  • bext.registerHandler_tick(function) - registers the callback function for betterAPI tick events
  • bext.isThread - this value is set to true if your extension is loaded from the stream. note that in order for the extension to work in a thread, you must create a "threadExtension.lua" file as an entry point