diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..b2a079f1
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
+
+ - package-ecosystem: "gitsubmodule"
+ directory: "/"
+ schedule:
+ interval: "daily"
\ No newline at end of file
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..776413e8
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,168 @@
+name: CLEO 5 Release Build
+
+on:
+ push:
+ tags:
+ - 'v[0-9]+.[0-9]+.[0-9]+**'
+
+jobs:
+ build:
+ runs-on: windows-2022
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: "recursive"
+
+ - name: Add msbuild to PATH
+ uses: microsoft/setup-msbuild@v2
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: lts/*
+
+ - name: Read Version Tag
+ id: read_version
+ run: node.exe .github/workflows/version.js
+
+ - name: Core - Build
+ shell: cmd
+ run: |
+ set PLUGIN_SDK_DIR=%GITHUB_WORKSPACE%\third-party\plugin-sdk
+ msbuild -m CLEO5.sln /property:Configuration=Release /property:Platform=GTASA
+
+# - name: Core - Sign
+# uses: x87/code-sign-action@develop
+# with:
+# certificate: '${{ secrets.DIG_KEY_CERT }}'
+# password: '${{ secrets.DIG_KEY_PWD }}'
+# certificatename: 'Seemann'
+# description: 'CLEO 5'
+# timestampUrl: 'http://timestamp.digicert.com'
+# filename: './.output/Release/cleo.asi'
+
+ - name: Core - VirusTotal Scan
+ uses: crazy-max/ghaction-virustotal@v4
+ with:
+ vt_api_key: ${{ secrets.VT_KEY }}
+ files: './.output/Release/CLEO.asi'
+
+ - name: Plugins - Build
+ shell: cmd
+ run: |
+ set PLUGIN_SDK_DIR=%GITHUB_WORKSPACE%\third-party\plugin-sdk
+ msbuild -m cleo_plugins/CLEO_Plugins.sln /property:Configuration=Release /property:Platform=x86
+
+# - name: Plugins - Sign
+# uses: x87/code-sign-action@develop
+# with:
+# certificate: '${{ secrets.DIG_KEY_CERT }}'
+# password: '${{ secrets.DIG_KEY_PWD }}'
+# certificatename: 'Seemann'
+# description: 'CLEO 5 Plugin'
+# timestampUrl: 'http://timestamp.digicert.com'
+# folder: './cleo_plugins/.output'
+
+ - name: Plugins - VirusTotal Scan
+ uses: crazy-max/ghaction-virustotal@v4
+ with:
+ vt_api_key: ${{ secrets.VT_KEY }}
+ files: './cleo_plugins/.output/*.cleo'
+
+ - name: Gather Output Files
+ id: prepare_archive
+ shell: cmd
+ run: |
+ @REM create output directory
+ mkdir .output\Release\cleo
+ mkdir .output\Release\cleo\.config
+ mkdir .output\Release\cleo\cleo_modules
+ mkdir .output\Release\cleo\cleo_plugins
+ mkdir .output\Release\cleo\cleo_saves
+ mkdir .output\Release\cleo\cleo_text
+ mkdir .output\Release\cleo_readme
+ mkdir .output\Release\cleo_readme\examples
+
+ @REM copy files
+ copy source\cleo_config.ini .output\Release\cleo\.cleo_config.ini
+ copy cleo_plugins\.output\*.cleo .output\Release\cleo\cleo_plugins
+ copy cleo_plugins\.output\*.ini .output\Release\cleo\cleo_plugins
+ copy cleo_plugins\Audio\bass\bass.dll .output\Release\bass.dll
+ xcopy /E /I tests .output\Release\cleo
+ xcopy /E /I examples .output\Release\cleo_readme\examples
+
+ @REM copy SDK
+ copy .output\Release\CLEO.lib cleo_sdk\CLEO.lib
+
+ @REM download Sanny Builder Library json
+ curl https://raw.githubusercontent.com/sannybuilder/library/master/sa/sa.json -o .output\Release\cleo\.config\sa.json
+
+ - name: Convert Markdown to HTML
+ id: md_to_html
+ run: |
+ npm install showdown
+ node.exe .github/workflows/markdown.js
+ move README.html .output\Release\cleo_readme\README.html
+ move CHANGELOG.html .output\Release\cleo_readme\CHANGELOG.html
+
+ - name: Download ASI Loaders
+ id: download_asi_loaders
+ shell: cmd
+ run: |
+ xcopy /E /I .output\Release .output\Release_with_Silent_ASI_Loader
+ xcopy /E /I .output\Release .output\Release_with_Ultimate_ASI_Loader
+
+ @REM install Silent's ASI Loader
+ curl https://silent.rockstarvision.com/uploads/silents_asi_loader_13.zip -o silents_asi_loader_13.zip
+ powershell.exe -NoP -NonI -Command "Expand-Archive '.\silents_asi_loader_13.zip' '.\.output\Release_with_Silent_ASI_Loader'"
+ move .output\Release_with_Silent_ASI_Loader\ReadMe.txt ".output\Release_with_Silent_ASI_Loader\cleo_readme\ASI Loader Readme.txt"
+ rmdir /s /q .output\Release_with_Silent_ASI_Loader\advanced_plugin_management_example
+ rmdir /s /q .output\Release_with_Silent_ASI_Loader\scripts
+
+ @REM install Ultimate ASI Loader
+ curl https://github.com/ThirteenAG/Ultimate-ASI-Loader/releases/download/Win32-latest/vorbisFile-Win32.zip -L -o ual.zip
+ powershell.exe -NoP -NonI -Command "Expand-Archive '.\ual.zip' '.\.output\Release_with_Ultimate_ASI_Loader'"
+ rm .\.output\Release_with_Ultimate_ASI_Loader\vorbisFile-Win32.SHA512
+
+ - name: Pack Base Archive
+ uses: ThirteenAG/zip-release@master
+ with:
+ path: ./.output/Release/*
+ type: "zip"
+ filename: SA.CLEO_${{ github.ref_name }}.zip
+ exclusions: "*.pdb *.lib *.exp"
+
+ - name: Pack Base + Silent's ASI Loader
+ uses: ThirteenAG/zip-release@master
+ with:
+ path: ./.output/Release_with_Silent_ASI_Loader/*
+ type: "zip"
+ filename: SA.CLEO_${{ github.ref_name }}+Silent_ASI_Loader.zip
+ exclusions: "*.pdb *.lib *.exp"
+
+ - name: Pack Base + UAL
+ uses: ThirteenAG/zip-release@master
+ with:
+ path: ./.output/Release_with_Ultimate_ASI_Loader/*
+ type: "zip"
+ filename: SA.CLEO_${{ github.ref_name }}+Ultimate_ASI_Loader.zip
+ exclusions: "*.pdb *.lib *.exp"
+
+ - name: CLEO SDK
+ uses: ThirteenAG/zip-release@master
+ with:
+ path: ./cleo_sdk/*
+ type: "zip"
+ filename: SA.CLEO_${{ github.ref_name }}_SDK.zip
+ exclusions: "*.pdb *.exp"
+
+ - name: Upload Release
+ uses: ncipollo/release-action@main
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ name: ${{ steps.read_version.outputs.version }}
+ bodyFile: 'changes.txt' # generated in read_version
+ tag: ${{ github.ref_name }}
+ prerelease: ${{ contains(github.ref_name, 'beta') || contains(github.ref_name, 'alpha') }}
+ artifacts: "SA.CLEO_*.zip"
+ allowUpdates: true
diff --git a/.github/workflows/markdown.js b/.github/workflows/markdown.js
new file mode 100644
index 00000000..445c159a
--- /dev/null
+++ b/.github/workflows/markdown.js
@@ -0,0 +1,16 @@
+const showdown = require('showdown');
+const {readFileSync, writeFileSync} = require('fs');
+
+const md = new showdown.Converter({
+ literalMidWordUnderscores: true,
+ disableForced4SpacesIndentedSublists: true,
+ noHeaderId: true,
+ completeHTMLDocument: true,
+ simplifiedAutoLink: true,
+});
+
+const readme = md.makeHtml(readFileSync('README.md', 'utf8'));
+const changelog = md.makeHtml(readFileSync('CHANGELOG.md', 'utf8'));
+
+writeFileSync('README.html', readme, 'utf8');
+writeFileSync('CHANGELOG.html', changelog, 'utf8');
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..5ea22bac
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,91 @@
+name: CLEO 5 Test Build
+
+on:
+ push:
+ paths-ignore:
+ - ".github/*"
+ - "*.md"
+ pull_request:
+ types: [opened, reopened]
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: windows-2022
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: "recursive"
+
+ - name: Add msbuild to PATH
+ uses: microsoft/setup-msbuild@v2
+
+ - name: Core - Build
+ shell: cmd
+ run: |
+ set PLUGIN_SDK_DIR=%GITHUB_WORKSPACE%\third-party\plugin-sdk
+ msbuild -m CLEO5.sln /property:Configuration=Release /property:Platform=GTASA
+
+# - name: Core - Sign
+# uses: x87/code-sign-action@develop
+# with:
+# certificate: '${{ secrets.DIG_KEY_CERT }}'
+# password: '${{ secrets.DIG_KEY_PWD }}'
+# certificatename: 'Seemann'
+# description: 'CLEO 5'
+# timestampUrl: 'http://timestamp.digicert.com'
+# filename: './.output/Release/cleo.asi'
+
+ - name: Core - VirusTotal Scan
+ uses: crazy-max/ghaction-virustotal@v4
+ with:
+ vt_api_key: ${{ secrets.VT_KEY }}
+ files: './.output/Release/CLEO.asi'
+
+ - name: Plugins - Build
+ shell: cmd
+ run: |
+ set PLUGIN_SDK_DIR=%GITHUB_WORKSPACE%\third-party\plugin-sdk
+ msbuild -m cleo_plugins/CLEO_Plugins.sln /property:Configuration=Release /property:Platform=x86
+
+# - name: Plugins - Sign
+# uses: x87/code-sign-action@develop
+# with:
+# certificate: '${{ secrets.DIG_KEY_CERT }}'
+# password: '${{ secrets.DIG_KEY_PWD }}'
+# certificatename: 'Seemann'
+# description: 'CLEO 5 Plugin'
+# timestampUrl: 'http://timestamp.digicert.com'
+# folder: './cleo_plugins/.output'
+
+ - name: Plugins - VirusTotal Scan
+ uses: crazy-max/ghaction-virustotal@v4
+ with:
+ vt_api_key: ${{ secrets.VT_KEY }}
+ files: './cleo_plugins/.output/*.cleo'
+
+ - name: Gather Output Files
+ id: prepare_archive
+ shell: cmd
+ run: |
+ @REM create output directory
+ mkdir .output\Release\cleo
+ mkdir .output\Release\cleo\cleo_plugins
+
+ @REM copy files
+ copy third-party\bass\bass.dll .output\Release\bass.dll
+ copy source\cleo_config.ini .output\Release\cleo\.cleo_config.ini
+ copy cleo_plugins\.output\*.cleo .output\Release\cleo\cleo_plugins
+ copy cleo_plugins\.output\*.ini .output\Release\cleo\cleo_plugins
+
+ - name: Upload Result
+ uses: actions/upload-artifact@v4
+ with:
+ compression-level: 0
+ name: SA.CLEO5
+ path: |
+ .output\Release\*
+ !.output\Release\*.pdb
+ !.output\Release\*.lib
+ !.output\Release\*.exp
diff --git a/.github/workflows/version.js b/.github/workflows/version.js
new file mode 100644
index 00000000..0063d40a
--- /dev/null
+++ b/.github/workflows/version.js
@@ -0,0 +1,59 @@
+const { appendFileSync, readFileSync, writeFileSync } = require("fs");
+const { EOL } = require("os");
+const { GITHUB_OUTPUT, GITHUB_REF_NAME } = process.env;
+
+if (GITHUB_REF_NAME) {
+ const version = GITHUB_REF_NAME.startsWith("v") ? GITHUB_REF_NAME.slice(1) : GITHUB_REF_NAME;
+ addOutput("version", version);
+
+ // update cleo.h to replace version
+ const cleoH = readFileSync("cleo_sdk/cleo.h", { encoding: "utf-8" });
+
+ const [, main, major, minor] = version.match(/(\d+)\.(\d+)\.(\d+).*/);
+
+ const newCleoH = cleoH
+ .replace(/#define\s+CLEO_VERSION_MAIN\s+.*/, `#define CLEO_VERSION_MAIN ${main}`)
+ .replace(/#define\s+CLEO_VERSION_MAJOR\s+.*/, `#define CLEO_VERSION_MAJOR ${major}`)
+ .replace(/#define\s+CLEO_VERSION_MINOR\s+.*/, `#define CLEO_VERSION_MINOR ${minor}`)
+ .replace(/#define\s+CLEO_VERSION_STR\s+.*/, `#define CLEO_VERSION_STR "${version}"`);
+ writeFileSync("cleo_sdk/cleo.h", newCleoH, { encoding: "utf-8" });
+}
+
+const changelog = readFileSync("CHANGELOG.md", { encoding: "utf-8" });
+writeFileSync("changes.txt", getChanges().join(EOL), { encoding: "utf-8" });
+
+function addOutput(key, value) {
+ appendFileSync(GITHUB_OUTPUT, `${key}=${value}${EOL}`, { encoding: "utf-8" });
+}
+
+function getChanges() {
+ const lines = changelog.split(EOL);
+ const result = [
+ `## Download Instructions`,
+ `An ASI loader is required for CLEO 5 to work. CLEO 5 comes pre-packaged with several popular ASI Loaders ([Silent's ASI Loader](https://cookieplmonster.github.io/mods/gta-sa/#asiloader) and [Ultimate ASI Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader)).`,
+ `#### If you don't have an ASI loader installed already or unsure which one to download:`,
+ `- Download [CLEO ${GITHUB_REF_NAME} with Silent's ASI Loader](https://github.com/cleolibrary/CLEO5/releases/download/${GITHUB_REF_NAME}/SA.CLEO_${GITHUB_REF_NAME}+Silent_ASI_Loader.zip)`,
+ `#### If you prefer Ultimate ASI Loader:`,
+ `- Download [CLEO ${GITHUB_REF_NAME} with Ultimate ASI Loader](https://github.com/cleolibrary/CLEO5/releases/download/${GITHUB_REF_NAME}/SA.CLEO_${GITHUB_REF_NAME}+Ultimate_ASI_Loader.zip)`,
+ `#### If you have an ASI loader installed already:`,
+ `- Download [this archive](https://github.com/cleolibrary/CLEO5/releases/download/${GITHUB_REF_NAME}/SA.CLEO_${GITHUB_REF_NAME}.zip) which contains ONLY CLEO 5 library and plugins.`,
+ `## Installation`,
+ `- Unzip the archive to GTA San Andreas game directory.`,
+ `## Changelog`,
+ ];
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ if (line.trimStart().startsWith("## ")) {
+ for (let j = i + 1; j < lines.length; j++) {
+ const line = lines[j];
+ if (line.trimStart().startsWith("## ")) {
+ return result;
+ }
+ result.push(line);
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/.gitignore b/.gitignore
index 5a024441..7f88dfcf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
build/
-output/
+.output/
*.APS
*.VC.db
*.VC.opendb
@@ -38,4 +38,8 @@ output/
Debug/*
Release/*
ipch/
-.vs/
\ No newline at end of file
+.vs/
+*.zip
+*.lib
+node_modules/
+tests/**/*.s
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..4cab7a0d
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "plugin-sdk"]
+ path = third-party/plugin-sdk
+ url = https://github.com/DK22Pac/plugin-sdk
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45d899ec..9fc877dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,187 +1,157 @@
-## 4.4.4
-
-- added string arguments support to 0AB1 (cleo_call)
-- fix an issue when PRINT_STRING and PRINT_BIG_STRING would overwrite each other (see https://github.com/cleolibrary/CLEO4/issues/80)
-- update BASS.dll to the latest to solve some issues with audio in game (see https://github.com/cleolibrary/CLEO4/issues/70)
-- added support of variable length arguments longer than 128 characters
-
-## 4.4.3
-
-- added correct support of condition result to opcodes 0AB1 0AB2. Fixes possible bugs when 0AB1 are used in multi conditional if statements.
-- set condition result in 0ADA and 0AD8
-
-## 4.4.2
-
-- fix eventual crash when using the game's user track player radio station (see https://github.com/cleolibrary/CLEO4/issues/38 for details)
-- fix 0AAA opcode not working with scripts from main.scm
-
-## 4.4.1
-
-- fix some issues with audio stream output #61 (by @GeTechG)
-- compatibility with latest plugin-sdk
-
-## 4.4.0
-
-- Dropped Windows XP support
-
-## 4.3.24
-
-- Added the export of some functions required for new version of the CLEO+ plugin, and can be used in other plugins: CLEO_GetScriptTextureById, CLEO_GetInternalAudioStream, CLEO_CreateCustomScript, CLEO_GetLastCreatedCustomScript, CLEO_AddScriptDeleteDelegate, CLEO_RemoveScriptDeleteDelegate.
-- Fixed sounds not pausing when unfocus (thanks to dkluin).
-- Opcodes for finding entities (0AE1, 0AE2, 0AE3) now use a distance check with optimized performance, and ignore the distance limitation if the argument sent is greater than 1000.0.
-- Opcode for finding peds (0AE1) now makes it possible to send "-1" in the "pass_deads" parameter to ignore all checks and return literally all peds.
-- Opcode for car number of gears (0AB7) now returns from vehicle class itself instead of using model and handling arrays — now compatible with f92la and IndieVehicles.
-- Opcodes for blip target coordinates (0AB6), car name (0ADB) and spawn (0ADD) are now compatible with f92la.
-- Now the full version is shown in the SDK and menu text.
-
-## 4.3.23
-
-- Now you can use string pointer in the file address parameter for .ini files opcodes.
-- Fixed the 0ABA opcode causing heap corruption.
-- Fixed shared variables not reset correctly. Which caused malfunctions in mods that use them and you play a new game or load game in a slot without the variables.
-
-## 4.3.22
-
-- Now creates cleo, cleo/cleo_saves and cleo/cleo_text directories on startup if they do not exist
-- Fix to issue with 0AE9 not returning result
-
-## 4.3.21
-
-- Fixed operand type IDs in CLEO.h
-- Added 'extern' to variables declared in CLEO.h
-- Fix to issue with 0AB1 in missions not storing mission locals
-
-## 4.3.19-20
-
-- Fixed issue with 0AB1 passing incorrect variable scope in missions
-- Updated SDK version
-
-## 4.3.17-18
-
-- Fixed potential future problem with 0AB0 which used methods with undefined behaviour
-- Fixed incorrect method used for 0AB7
-
-## 4.3.16
-
-- Fixed bugs with CLEO saves when saved scripts ended
-- Prevented crashing when invalid audiostream handles are used
-
-## 4.3.15
-
-- Improvemed compatibility fix for opcodes 0AE1, 0AE2 and 0AE3 with incorrect find_next usage
-
-## 4.3.14
-
-- Fixed 0AAA only returning custom scripts
-- Fixed many things which use the 'SCM Block' or 'Mission Local Storage' space
-- Fixed parameters being passed to script local storage instead of mission local storage through 0A94
-- Fixed potential problems with iteration through the script queues (may cause rare and hard to trace bugs)
-
-## 4.3.13
-
-- Fixed crashing when starting a new game after a game has already started with CLEO scripts installed
-- Possibly fixed other issues with starting a game with CLEO scripts installed
-
-## 4.3.12.1
-
-- Un-did the 'Scripts no longer load prematurely' fix as it caused scripts to not load certain circumstances (like before CLEO 4)
-- Included 'cleo_text' folder in installation
-
-## 4.3.12
-
-- Fixed string parameter skipping in 'SkipOpcodeParams' used by CLEO plugins
-- 0AC8 now returns a NULL value to the output var if allocation failed (as it did before 4.3a)
-- 0AC9 now checks the memory was allocated by 0AC8 before attempting to free it
-- FXT references are now case insensitive (as they were before 4.3a)
-- File operations now check the input handle isn't null (as it seems was the way before 4.3a)
-- 'Loaded mission' status now reset on new/loaded game (as it was before 4.3a)
-- Scripts no longer load prematurely (like before 4.3a)
-- Resolved conflicts with other menu hooks such as 'HUME'
-- Other minor tweaks
-
-## 4.3.11
-
-- Fixed crash with 0ADA in scripts beginning with an opcode ending in '00'
-
-## 4.3.10
-
-- Improvements to opcodes 0AE1, 0AE2 and 0AE3 - now loops around the pool even when the 'find_next' flag isn't used correctly
-- Fixed 0AD2 not returning peds targetted with the mouse, while targetting with a pad worked
-
-## 4.3.9
-
-- Will now be able to start a CLEO mission after recently finishing a standard mission
-- Will no longer error & terminate when scripts fail to open and instead simply log the error
-- Will no longer terminate on warnings
-- No longer includes paths in automatically generated script names (e.g. cleo\dir\demo.cs is now named 'demo.cs' and not 'dir\dem')
-- Improved handling of script load errors
-
-## 4.3.8
-
-- Fixed crash which would occur when missions were ended with 004E
-
-## 4.3.7
-
-- Custom missions launched by CLEO scripts now inherit their compatibility mode - possibly fixing incompatibilities with mods using custom missions
-- The current directory set by 0A99 is now script-dependant and only affects running CLEO scripts (not the entire game or the main.scm)
-- Text and texture/sprite draws are now script-dependant (doesn't affect main.scm scripts)
-
-## 4.3.0
-
-- Replaced code which dynamically allocated and deallocated memory for script parameters every time 0AA5-0AA8 were called with static arrays
-- Removed a script execution loop replacement which wasn't used for anything important and weirdly only worked with 1.0US that caused crashes with script logging plugins
-- Added support for Steam (v3) versions of gta_sa.exe
-- Prevented the local storage from being initialized in SCM functions when the script is in CLEO 3 compatibility mode ('.cs3' extension)
-
-### Updates to behaviour of following opcodes:
-
-#### 0A99
-
-CHANGE_DIRECTORY can now correctly change to the program directory
-
-#### 0A9A
-
-OPEN_FILE now uses a 'legacy' mode when passing an integer as the mode parameter for compatibility of CLEO file handles and SA file handles
-Note that you should really not pass CLEO file handles to game functions. However, this legacy mode now ensures that the handles are compatible.
-Other file functions have also been updated ensuring that game file handles are passed to relevant game functions.
-It is recommended to not rely on passing files to game functions and instead use CLEO 4's in-built file functions in future.
-
-#### 0AD1
-
-CALL now accepts string input, which is passed as a string pointer following string convention
-
-#### 0AD4
-
-SCAN_STRING now returns a condition result
-
-#### 0AE6
-
-FIND_FIRST_FILE now accepts string array output
-
-#### 0AE3
-
-FIND_ALL_RANDOM_OBJECTS_IN_SPHERE now ensures no fading objects are returned and returns -1 instead of 0 on failure
-
-#### 0AE2
-
-FIND_ALL_RANDOM_CARS_IN_SPHERE now ensures no script vehicles or fading vehicle are returned and returns -1 instead of 0 on failure
-
-#### 0AE1
-
-FIND_ALL_RANDOM_CHARS_IN_SPHERE now ensures no script characters or fading characters are returned and returns -1 instead of 0 on failure
-
-#### 0ADF
-
-ADD_TEXT_LABEL now updates existing text labels if they already exist
-
-#### 0AD6
-
-IS_END_OF_FILE_REACHED now returns true if a file error occured
-
-#### 0AD2
-
-GET_CHAR_PLAYER_IS_TARGETING now returns -1 instead of 0 when no target is found
-
-#### 0AB5
-
-STORE_CLOSEST_ENTITIES now ensures no script entities or fading entities are returned and ensures the player ped is not returned
+## 5.0.0
+
+- support for CLEO modules feature https://github.com/sannybuilder/dev/issues/264
+- new [Audio](https://github.com/cleolibrary/CLEO5/tree/master/cleo_plugins/Audio) plugin
+ - audio related opcodes moved from CLEO core into separated plugin
+ - CLEO's audio now obey game's volume settings
+ - implemented Doppler effect for 3d audio streams (fast moving sound sources)
+ - CLEO's audio now follows game speed changes
+ - sound device can be now manually selected in .ini file
+ - new opcode **2500 ([is_audio_stream_playing](https://library.sannybuilder.com/#/sa/audio/2500))**
+ - new opcode **2501 ([get_audio_stream_duration](https://library.sannybuilder.com/#/sa/audio/2501))**
+ - new opcode **2502 ([get_audio_stream_speed](https://library.sannybuilder.com/#/sa/audio/2502))**
+ - new opcode **2503 ([set_audio_stream_speed](https://library.sannybuilder.com/#/sa/audio/2503))**
+ - new opcode **2504 ([set_audio_stream_volume_with_transition](https://library.sannybuilder.com/#/sa/audio/2504))**
+ - new opcode **2505 ([set_audio_stream_speed_with_transition](https://library.sannybuilder.com/#/sa/audio/2505))**
+ - new opcode **2506 ([set_audio_stream_source_size](https://library.sannybuilder.com/#/sa/audio/2506))**
+ - new opcode **2507 ([get_audio_stream_progress](https://library.sannybuilder.com/#/sa/audio/2507))**
+ - new opcode **2508 ([set_audio_stream_progress](https://library.sannybuilder.com/#/sa/audio/2508))**
+ - new opcode **2509 ([get_audio_stream_type](https://library.sannybuilder.com/#/sa/audio/2509))**
+ - new opcode **250A ([set_audio_stream_type](https://library.sannybuilder.com/#/sa/audio/250A))**
+- new [DebugUtils](https://github.com/cleolibrary/CLEO5/tree/master/cleo_plugins/DebugUtils) plugin
+ - new opcode **00C3 ([debug_on](https://library.sannybuilder.com/#/sa/debug/00C3))**
+ - new opcode **00C4 ([debug_off](https://library.sannybuilder.com/#/sa/debug/00C4))**
+ - new opcode **2100 ([breakpoint](https://library.sannybuilder.com/#/sa/debug/2100))**
+ - new opcode **2101 ([trace](https://library.sannybuilder.com/#/sa/debug/2101))**
+ - new opcode **2102 ([log_to_file](https://library.sannybuilder.com/#/sa/debug/2102))**
+ - implemented support of opcodes **0662**, **0663** and **0664** (original Rockstar's script debugging opcodes. See DebugUtils.ini)
+- new [FileSystemOperations](https://github.com/cleolibrary/CLEO5/tree/master/cleo_plugins/FileSystemOperations) plugin
+ - forbidden scripts from accessing and changing any files outside game root or game settings directory
+ - file related opcodes moved from CLEO core into separated plugin
+ - opcode **0A9E ([write_to_file](https://library.sannybuilder.com/#/sa/file/0A9E))** now supports literal numbers and strings
+ - fixed bug causing file stream opcodes not working correctly when read-write modes are used
+ - fixed buffer overflows in file stream read opcodes
+ - added/fixed support of all file stream opcodes in legacy mode (Cleo3)
+ - new opcode **2300 ([get_file_position](https://library.sannybuilder.com/#/sa/file/2300))**
+ - new opcode **2301 ([read_block_from_file](https://library.sannybuilder.com/#/sa/file/2301))**
+ - new opcode **2302 ([write_block_to_file](https://library.sannybuilder.com/#/sa/file/2302))**
+ - new opcode **2303 ([resolve_filepath](https://library.sannybuilder.com/#/sa/file/2303))**
+ - new opcode **2304 ([get_script_filename](https://library.sannybuilder.com/#/sa/file/2304))**
+- new [Math](https://github.com/cleolibrary/CLEO5/tree/master/cleo_plugins/Math) plugin
+ - math related opcodes moved from CLEO core into separated plugin
+ - new opcode **2700 ([is_bit_set](https://library.sannybuilder.com/#/sa/math/2700))**
+ - new opcode **2701 ([set_bit](https://library.sannybuilder.com/#/sa/math/2701))**
+ - new opcode **2702 ([clear_bit](https://library.sannybuilder.com/#/sa/math/2702))**
+ - new opcode **2703 ([toggle_bit](https://library.sannybuilder.com/#/sa/math/2703))**
+ - new opcode **2704 ([is_truthy](https://library.sannybuilder.com/#/sa/math/2704))**
+- new [MemoryOperations](https://github.com/cleolibrary/CLEO5/tree/master/cleo_plugins/MemoryOperations) plugin
+ - memory related opcodes moved from CLEO core into separated plugin
+ - validation of input and output parameters for all opcodes
+ - opcode **0A8C ([write_memory](https://library.sannybuilder.com/#/sa/memory/0A8C))** now supports strings
+ - new opcode **2400 ([copy_memory](https://library.sannybuilder.com/#/sa/memory/2400))**
+ - new opcode **2401 ([read_memory_with_offset](https://library.sannybuilder.com/#/sa/memory/2401))**
+ - new opcode **2402 ([write_memory_with_offset](https://library.sannybuilder.com/#/sa/memory/2402))**
+ - new opcode **2403 ([forget_memory](https://library.sannybuilder.com/#/sa/memory/2403))**
+ - new opcode **2404 ([get_script_struct_just_created](https://library.sannybuilder.com/#/sa/memory/2404))**
+ - new opcode **2405 ([is_script_running](https://library.sannybuilder.com/#/sa/memory/2405))**
+ - new opcode **2406 ([get_script_struct_from_filename](https://library.sannybuilder.com/#/sa/memory/2406))**
+ - new opcode **2407 ([is_memory_equal](https://library.sannybuilder.com/#/sa/memory/2407))**
+ - new opcode **2408 ([terminate_script](https://library.sannybuilder.com/#/sa/memory/2408))**
+- new [Text](https://github.com/cleolibrary/CLEO5/tree/master/cleo_plugins/Text) plugin
+ - text related opcodes moved from CLEO core into separated plugin
+ - new opcode **2600 ([is_text_empty](https://library.sannybuilder.com/#/sa/text/2600))**
+ - new opcode **2601 ([is_text_equal](https://library.sannybuilder.com/#/sa/text/2601))**
+ - new opcode **2602 ([is_text_in_text](https://library.sannybuilder.com/#/sa/text/2602))**
+ - new opcode **2603 ([is_text_prefix](https://library.sannybuilder.com/#/sa/text/2603))**
+ - new opcode **2604 ([is_text_suffix](https://library.sannybuilder.com/#/sa/text/2604))**
+ - new opcode **2605 ([display_text_formatted](https://library.sannybuilder.com/#/sa/text/2605))**
+ - new opcode **2606 ([load_fxt](https://library.sannybuilder.com/#/sa/text/2606))**
+ - new opcode **2607 ([unload_fxt](https://library.sannybuilder.com/#/sa/text/2607))**
+ - new opcode **2608 ([get_text_length](https://library.sannybuilder.com/#/sa/text/2608))**
+- new and updated opcodes
+ - implemented support for **memory pointer string** arguments for all game's native opcodes
+ - **0B1E ([sign_extend](https://library.sannybuilder.com/#/sa/bitwise/0B1E))**
+ - **0DD5 ([get_game_platform](https://library.sannybuilder.com/#/sa/CLEO/0DD5))**
+ - **2000 ([get_cleo_arg_count](https://library.sannybuilder.com/#/sa/CLEO/2000))**
+ - **2002 ([cleo_return_with](https://library.sannybuilder.com/#/sa/CLEO/2002))**
+ - **2003 ([cleo_return_fail](https://library.sannybuilder.com/#/sa/CLEO/2003))**
+ - 'argument count' parameter of **0AB1 (cleo_call)** is now optional. `cleo_call @LABEL args 0` can be written as `cleo_call @LABEL`
+ - 'argument count' parameter of **0AB2 (cleo_return)** is now optional. `cleo_return 0` can be written as `cleo_return`
+ - SCM functions can return string literals and string variables
+ - SCM functions **(0AB1)** now keep their own GOSUB's call stack
+ - fixed bug in **0AD4 ([scan_string](https://library.sannybuilder.com/#/sa/text/2604))** causing data overruns when reading strings longer than target variable
+ - fixed result register not being cleared before function call in opcodes **0AA7** and **0AA8**
+ - fixed **0ABA ([terminate_all_custom_scripts_with_this_name](https://library.sannybuilder.com/#/sa/CLEO/0ABA))** terminating only first found script
+- changes in file operations
+ - file paths can now use 'virtual absolute paths'. Use prefix in file path strings to access predefined locations:
+ - `root:\` for _game root_ directory
+ - `user:\` for _game save files_ directory
+ - `.\` for _this script file_ directory
+ - `cleo:\` for _CLEO_ directory
+ - `modules:\` for _CLEO\cleo_modules_ directory
+ - rewritten opcode **0A99 (set_current_directory)**. Now it no longer affects internal game state or current directory in other scripts
+- improved error handling
+ - more detailed error messages in multiple scenarios
+ - some errors now cause the script to pause, instead of crashing the game
+- updated included Silent's ASI Loader to version 1.3
+
+### Bug Fixes
+
+- fixed error in **004E (terminate_this_script)** allowing to run multiple missions
+- fixed handling of strings longer than 128 characters causing errors in some cases
+- fixed error in handling of first string argument in **0AF5 (write_string to_ini_file)**
+- fixed resolution dependent aspect ratio of CLEO text in main menu
+- fixed clearing mission locals when new CLEO mission is started
+- when reading less than 4 bytes with **0A9D (readfile)** now remaining bytes of the target variable are set to zero
+- fixed invalid 7 characters length limit of **0AAA (get_script_struct_named)**
+
+#### SDK AND PLUGINS
+
+- now all opcodes in range **0-7FFF** can be registered by plugins
+- plugins moved to _cleo\cleo_plugins_ directory
+- new SDK methods:
+ - CLEO_RegisterCommand
+ - CLEO_RegisterCallback
+ - CLEO_GetVarArgCount
+ - CLEO_PeekIntOpcodeParam
+ - CLEO_PeekFloatOpcodeParam
+ - CLEO_PeekPointerToScriptVariable
+ - CLEO_SkipUnusedVarArgs
+ - CLEO_ReadParamsFormatted
+ - CLEO_ReadStringParamWriteBuffer
+ - CLEO_GetOpcodeParamsArray
+ - CLEO_GetParamsHandledCount
+ - CLEO_IsScriptRunning
+ - CLEO_TerminateScript
+ - CLEO_GetScriptVersion
+ - CLEO_GetScriptInfoStr
+ - CLEO_GetScriptFilename
+ - CLEO_GetScriptWorkDir
+ - CLEO_SetScriptWorkDir
+ - CLEO_ResolvePath
+ - CLEO_ListDirectory
+ - CLEO_ListDirectoryFree
+ - CLEO_GetGameDirectory
+ - CLEO_GetUserDirectory
+ - CLEO_GetScriptByName
+ - CLEO_GetScriptByFilename
+ - CLEO_GetScriptDebugMode
+ - CLEO_SetScriptDebugMode
+ - CLEO_Log
+
+#### CLEO internal
+
+- introduced unit test scripts
+- project migrated to VS 2022
+- configured game debugging settings
+- plugins moved into single solution
+- configured automatic releases on GitHub
+- added setup_env.bat script
+
+#### Special Thanks
+
+- **123nir** for the alpha-testing, troubleshooting and valuable bug reports
+
+## Older
+
+For previous changes, see [CLEO4 changelog](https://github.com/cleolibrary/CLEO4/blob/master/CHANGELOG.md)
diff --git a/CLEO4.vcxproj b/CLEO4.vcxproj
deleted file mode 100644
index 343d34f5..00000000
--- a/CLEO4.vcxproj
+++ /dev/null
@@ -1,160 +0,0 @@
-
-
-
-
- Release GTASA
- Win32
-
-
- Debug GTASA
- Win32
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Create
- Create
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}
- true
- Win32Proj
- CLEO4
- 10.0.18362.0
-
-
-
- DynamicLibrary
- false
- MultiByte
- v142
- true
-
-
- DynamicLibrary
- true
- MultiByte
- v142
-
-
-
-
-
-
-
-
-
-
-
-
- $(SolutionDir)output\Release\
- $(SolutionDir)output\.obj\Release\
- CLEO
- .asi
-
-
- $(SolutionDir)output\Debug\
- $(SolutionDir)output\.obj\Debug\
- CLEO
- .asi
- $(VC_IncludePath);$(WindowsSdk_71A_IncludePath);E:\Documents\Para GTA\plugin-sdk-master\shared
-
-
-
- Level3
- MaxSpeed
- true
- true
- true
- MultiThreaded
- $(PLUGIN_SDK_DIR)\plugin_sa\;$(PLUGIN_SDK_DIR)\plugin_sa\game_sa\;$(PLUGIN_SDK_DIR)\shared\;$(PLUGIN_SDK_DIR)\shared\game\;$(SolutionDir)\third-party\bass;%(AdditionalIncludeDirectories)
- _NDEBUG;_USING_V110_SDK71_;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;GTASA;GTAGAME_NAME="San Andreas";GTAGAME_ABBR="SA";GTAGAME_ABBRLOW="sa";GTAGAME_PROTAGONISTNAME="CJ";GTAGAME_CITYNAME="San Andreas";CLEO4_EXPORTS;%(PreprocessorDefinitions)
- /Zc:threadSafeInit- %(AdditionalOptions)
- Create
-
-
- true
- true
- No
- UseLinkTimeCodeGeneration
- $(SolutionDir)\third-party\bass;%(AdditionalLibraryDirectories)
- bass.lib;%(AdditionalDependencies)
- Windows
- $(SolutionDir)source\cleo.def
- false
-
-
- xcopy /Y "$(SolutionDir)output\Release\CLEO.lib" "$(SolutionDir)cleo_sdk\"
-
-
-
-
- Level3
- Disabled
- true
- MultiThreadedDebug
- $(PLUGIN_SDK_DIR)\plugin_sa\;$(PLUGIN_SDK_DIR)\plugin_sa\game_sa\;$(SolutionDir)\third-party\bass;%(AdditionalIncludeDirectories)
- _DEBUG;_USING_V110_SDK71_;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;_SCL_SECURE_NO_WARNINGS;GTASA;GTAGAME_NAME="San Andreas";GTAGAME_ABBR="SA";GTAGAME_ABBRLOW="sa";GTAGAME_PROTAGONISTNAME="CJ";GTAGAME_CITYNAME="San Andreas";CLEO4_EXPORTS;%(PreprocessorDefinitions);
- /Zc:threadSafeInit- %(AdditionalOptions)
- Create
-
-
- Debug
- Default
- $(SolutionDir)\third-party\bass;%(AdditionalLibraryDirectories)
- bass.lib;%(AdditionalDependencies)
- Windows
- $(SolutionDir)source\cleo.def
- false
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CLEO4.vcxproj.filters b/CLEO4.vcxproj.filters
deleted file mode 100644
index 35b0037e..00000000
--- a/CLEO4.vcxproj.filters
+++ /dev/null
@@ -1,127 +0,0 @@
-
-
-
-
- {3f2ebf48-96a5-4129-887c-706e10368922}
-
-
- {d188d452-fbc6-48b5-bd49-d4036c989109}
-
-
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- source
-
-
- cleo_sdk
-
-
- source
-
-
-
-
- source
-
-
-
-
- source
-
-
-
\ No newline at end of file
diff --git a/CLEO4.sln b/CLEO5.sln
similarity index 76%
rename from CLEO4.sln
rename to CLEO5.sln
index 7b85ecbb..00b71c93 100644
--- a/CLEO4.sln
+++ b/CLEO5.sln
@@ -1,9 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27004.2002
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33213.308
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CLEO4", "CLEO4.vcxproj", "{B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CLEO5", "CLEO5.vcxproj", "{B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -11,10 +11,10 @@ Global
Release|GTASA = Release|GTASA
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Debug|GTASA.ActiveCfg = Debug GTASA|Win32
- {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Debug|GTASA.Build.0 = Debug GTASA|Win32
- {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Release|GTASA.ActiveCfg = Release GTASA|Win32
- {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Release|GTASA.Build.0 = Release GTASA|Win32
+ {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Debug|GTASA.ActiveCfg = Debug|Win32
+ {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Debug|GTASA.Build.0 = Debug|Win32
+ {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Release|GTASA.ActiveCfg = Release|Win32
+ {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Release|GTASA.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/CLEO5.vcxproj b/CLEO5.vcxproj
new file mode 100644
index 00000000..3634d5bb
--- /dev/null
+++ b/CLEO5.vcxproj
@@ -0,0 +1,237 @@
+
+
+
+
+ Release
+ Win32
+
+
+ Debug
+ Win32
+
+
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+ Create
+
+
+ NotUsing
+
+
+ NotUsing
+ NotUsing
+
+
+ NotUsing
+ NotUsing
+
+
+ NotUsing
+ NotUsing
+
+
+ NotUsing
+ NotUsing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}
+ true
+ Win32Proj
+ CLEO5
+ 10.0
+ CLEO5
+
+
+
+ DynamicLibrary
+ false
+ Unicode
+ v143
+ true
+
+
+ DynamicLibrary
+ true
+ Unicode
+ v143
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir).output\$(Configuration)\
+ $(SolutionDir).output\.obj\$(Configuration)\
+ CLEO
+ .asi
+ $(PLUGIN_SDK_DIR)\shared\;$(PLUGIN_SDK_DIR)\shared\game\;$(SolutionDir)third-party\simdjson;$(IncludePath)
+ false
+ false
+
+
+ $(SolutionDir).output\$(Configuration)\
+ $(SolutionDir).output\.obj\$(Configuration)\
+ CLEO
+ .asi
+ $(PLUGIN_SDK_DIR)\shared\;$(PLUGIN_SDK_DIR)\shared\game\;$(SolutionDir)third-party\simdjson;$(IncludePath)
+
+
+ $(GTA_SA_DIR)\gta_sa.exe
+ $(GTA_SA_DIR)
+ false
+ WindowsLocalDebugger
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ MultiThreaded
+ $(PLUGIN_SDK_DIR)\plugin_sa\;$(PLUGIN_SDK_DIR)\plugin_sa\game_sa\;%(AdditionalIncludeDirectories)
+ _NDEBUG;_USING_V110_SDK71_;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;GTASA;%(PreprocessorDefinitions);TARGET_NAME=R"($(TargetName))"
+ /Zc:threadSafeInit- %(AdditionalOptions)
+ Create
+ stdcpp17
+ None
+ Speed
+ false
+ false
+ false
+
+
+ true
+ true
+ false
+ UseLinkTimeCodeGeneration
+ %(AdditionalDependencies)
+ Windows
+ $(SolutionDir)source\cleo.def
+ false
+ false
+ false
+
+
+ xcopy /Y "$(OutDir)$(TargetName).lib" "$(SolutionDir)cleo_sdk\"
+if defined GTA_SA_DIR (
+taskkill /IM gta_sa.exe /F /FI "STATUS eq RUNNING"
+xcopy /Y "$(OutDir)$(TargetName).asi" "$(GTA_SA_DIR)\"
+)
+
+
+
+
+ Level3
+ Disabled
+ true
+ MultiThreadedDebug
+ $(PLUGIN_SDK_DIR)\plugin_sa\;$(PLUGIN_SDK_DIR)\plugin_sa\game_sa\;%(AdditionalIncludeDirectories)
+ _DEBUG;_USING_V110_SDK71_;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;_SCL_SECURE_NO_WARNINGS;GTASA;%(PreprocessorDefinitions);;TARGET_NAME=R"($(TargetName))"
+ /Zc:threadSafeInit- %(AdditionalOptions)
+ Create
+ stdcpp17
+
+
+ true
+ Default
+
+
+ %(AdditionalDependencies)
+ Windows
+ $(SolutionDir)source\cleo.def
+ false
+
+
+ xcopy /Y "$(OutDir)$(TargetName).lib" "$(SolutionDir)cleo_sdk\"
+if defined GTA_SA_DIR (
+taskkill /IM gta_sa.exe /F /FI "STATUS eq RUNNING"
+xcopy /Y "$(OutDir)$(TargetName).asi" "$(GTA_SA_DIR)\"
+xcopy /Y "$(OutDir)$(TargetName).pdb" "$(GTA_SA_DIR)\"
+)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CLEO5.vcxproj.filters b/CLEO5.vcxproj.filters
new file mode 100644
index 00000000..ca3d4e19
--- /dev/null
+++ b/CLEO5.vcxproj.filters
@@ -0,0 +1,202 @@
+
+
+
+
+ {3f2ebf48-96a5-4129-887c-706e10368922}
+
+
+ {d188d452-fbc6-48b5-bd49-d4036c989109}
+
+
+ {3104a2cb-d9c5-4eb5-9910-cf77d903db30}
+
+
+ {1b4011a1-a8c2-4ab1-a38b-b0e28120d24d}
+
+
+ {01a17d56-e9bb-4315-a808-63e91baaafaa}
+
+
+ {9c8be703-c930-47b1-b0cb-7c4b80922a48}
+
+
+ {d2916069-8bff-46e6-9a07-6b845c4361dd}
+
+
+ {2fba67c2-5ab9-4f75-82ed-e1024b272094}
+
+
+ {5cead5cc-9a75-4d2e-99b5-ebbc8f9d6d86}
+
+
+
+
+ source
+
+
+ source
+
+
+ third_party\plugin_sdk
+
+
+ third_party\plugin_sdk
+
+
+ third_party\plugin_sdk
+
+
+ third_party\plugin_sdk
+
+
+ third_party\plugin_sdk
+
+
+ third_party\plugin_sdk
+
+
+ third_party\plugin_sdk
+
+
+ third_party\plugin_sdk
+
+
+ third_party\plugin_sdk
+
+
+ source\utils
+
+
+ source\utils
+
+
+ source\utils
+
+
+ source\game_sa
+
+
+ source\game_sa
+
+
+ source\game_sa
+
+
+ source\core
+
+
+ source\extensions
+
+
+ source\extensions
+
+
+ source\core
+
+
+ source\utils
+
+
+ source\extensions
+
+
+ third_party\plugin_sdk
+
+
+ source\utils
+
+
+ third_party\plugin_sdk
+
+
+ source\extensions
+
+
+ third_party\plugin_sdk
+
+
+ third_party\plugin_sdk
+
+
+ third_party\simdjson
+
+
+
+
+ source
+
+
+ cleo_sdk
+
+
+ source\utils
+
+
+ source\utils
+
+
+ source\utils
+
+
+ source\game_sa
+
+
+ source\game_sa
+
+
+ source\game_sa
+
+
+ source\core
+
+
+ source\extensions
+
+
+ source\extensions
+
+
+ source\extensions
+
+
+ source\core
+
+
+ source\utils
+
+
+ source\utils
+
+
+ source
+
+
+ source\utils
+
+
+ source\extensions
+
+
+ cleo_sdk
+
+
+ source\utils
+
+
+ third_party\simdjson
+
+
+
+
+ source
+
+
+ source
+
+
+
+
+ source
+
+
+
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
index c788f4df..cb79f086 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2007-2022, CLEO Library by Seemann, Alien and Deji
+Copyright (c) 2007-2023, CLEO Library by Seemann, Alien, Deji and Miran
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index a0d02c5d..0632c761 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,29 @@
-# CLEO Library for GTA San Andreas
+# CLEO Library for GTA San Andreas (Windows PC)
CLEO is a hugely popular extensible library plugin which brings new possibilities in scripting for the game Grand Theft Auto: San Andreas by Rockstar Games, allowing the use of thousands of unique mods which change or expand the gameplay. You may find more information about CLEO on the official website https://cleo.li
## Installation
-CLEO requires an 'ASI Loader' installed to run which is provided with the release. The ASI Loader requires overwriting one original game file: vorbisFile.dll - be sure to make a backup of this file.
-No additional files are replaced, however the following files and folders are added:
+An ASI loader is required for CLEO 5 to work. CLEO 5 is bundled with several popular ASI Loaders ([Silent's ASI Loader](https://cookieplmonster.github.io/mods/gta-sa/#asiloader) and [Ultimate ASI Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader/)).
+
+Follow the instructions on the [release page](https://github.com/cleolibrary/CLEO5/releases) to choose a bundle that works best for you.
+
+The ASI Loader replaces one original game file: `vorbisFile.dll` - be sure to make a backup of this file.
+CLEO itself does not replace any game file, however the following files and folders are added:
- cleo\ (CLEO script directory)
-- cleo\FileSystemOperations.cleo (file system plugin)
-- cleo\IniFiles.cleo (INI config plugin)
-- cleo\IntOperations.cleo (INT operations plugin)
+- cleo\\.config\sa.json (opcodes info file)
+- cleo\cleo_plugins\SA.Audio.cleo (audio playback utilities powered by BASS.dll library)
+- cleo\cleo_plugins\SA.DebugUtils.cleo (script debugging utilities plugin)
+- cleo\cleo_plugins\SA.FileSystemOperations.cleo (disk drive files related operations plugin)
+- cleo\cleo_plugins\SA.IniFiles.cleo (.ini config files handling plugin)
+- cleo\cleo_plugins\SA.Math.cleo (additional math operations plugin)
+- cleo\cleo_plugins\SA.MemoryOperations.cleo (memory and .dll libraries utilities plugin)
+- cleo\cleo_plugins\SA.Text.cleo (text processing plugin)
- cleo\cleo_saves\ (CLEO save directory)
- cleo\cleo_text\ (CLEO text directory)
- cleo.asi (core library)
- bass.dll (audio engine library)
-- vorbisHooked.dll (Silent's ASI Loader)
All plugins are optional, however they may be required by various CLEO scripts.
@@ -31,19 +39,20 @@ CLEO scripts can be found on Grand Theft Auto fansites and modding sites such as
## Compatibility Mode
-CLEO is continually being improved and extended over time. In very rare circumstances, some scripts written for CLEO 3 may not work while using CLEO 4. However, since CLEO 4.3 you are able to enable a 'legacy mode' to increase compatibility with CLEO 3 scripts by naming them with the extension '.cs3'. CLEO 4.3 will load '.cs' and '.cs4' scripts normally and load '.cs3' scripts in CLEO 3 compatibility mode, in which certain small behaviours of the CLEO library will change to achieve better compatibility with that script. However, most CLEO 3 scripts will work without the need for compatibility mode being set as CLEO 4.3 also detects certain necessary CLEO 3 behaviours. Specifically, scripts which use the uninitialized storage data after a SCM function call to work.
+CLEO is continually being improved and extended over time. In very rare circumstances, new major releases may break some older scripts. To fix this, CLEO provides a 'compatibility mode' to closely emulate behavior of previous versions and improve stability of old scripts.
+- To run a script with maximum compatibility with 'CLEO 4', change the script extension from `.cs` to `.cs4`.
+- To run a script with maximum compatibility with 'CLEO 3', change the script extension from `.cs` to `.cs3`.
## Credits
-The author and original developer of the CLEO library is Seemann. Development of CLEO 4 was led by Alien and Deji. Today the CLEO library is an open-source project being maintained at https://github.com/cleolibrary
-
-The author of the ASI Loader is Silent. Find out more at: https://gtaforums.com/topic/523982-relopensrc-silents-asi-loader/
+The author and original developer of the CLEO library is Seemann. Development of CLEO 4 was led by Alien and Deji, later turned into CLEO 5 by Miran. Today the CLEO library is an open-source project being maintained at https://github.com/cleolibrary
Special thanks to:
- Stanislav Golovin (a.k.a. listener) for his great work in exploration of the GTA series.
- NTAuthority and LINK/2012 for additional support with CLEO 4.3.
- mfisto for the alpha-testing of CLEO 4, his support and advices.
+- 123nir for the alpha-testing of CLEO 5.0.0, troubleshooting and valuable bug reports.
-The developers have no connection with Take 2 Interactive or Rockstar Games.
+The developers are not affiliated with Take 2 Interactive or Rockstar Games.
By using this product or any of the additional products included you take your own personal responsibility for any negative consequences should they arise.
diff --git a/SETUP.md b/SETUP.md
new file mode 100644
index 00000000..f03cd252
--- /dev/null
+++ b/SETUP.md
@@ -0,0 +1,7 @@
+# CLEO project configuration
+
+This project depends on Plugin SDK (https://github.com/DK22Pac/plugin-sdk). Using SDK's installer results in creation of PLUGIN_SDK_DIR envinroment variable in operating system. If installer is not used then please manually enter path to the sdk directory in setup_env.bat.
+If GTA SA is installed in different than default location please open setup_env.bat file and configure correct path.
+Run setup_env.bat to setup required envinroment variables.
+
+After opening project solution in Visual Studio it should be possible to build as well as debug CLEO in game.
diff --git a/cleo_plugins/Audio/Audio.cpp b/cleo_plugins/Audio/Audio.cpp
new file mode 100644
index 00000000..192c66d6
--- /dev/null
+++ b/cleo_plugins/Audio/Audio.cpp
@@ -0,0 +1,441 @@
+#include "CLEO.h"
+#include "CLEO_Utils.h"
+#include "plugin.h"
+#include "CTheScripts.h"
+#include "CSoundSystem.h"
+#include "CAudioStream.h"
+
+using namespace CLEO;
+using namespace plugin;
+
+#define VALIDATE_STREAM() if(stream != nullptr && !soundSystem.HasStream(stream)) { SHOW_ERROR("Invalid or already closed '0x%X' audio stream handle param in script %s \nScript suspended.", stream, ScriptInfoStr(thread).c_str()); return thread->Suspend(); }
+
+class Audio
+{
+public:
+ static CSoundSystem soundSystem;
+
+ enum eStreamAction
+ {
+ Stop,
+ Play,
+ Pause,
+ Resume,
+ };
+
+ Audio()
+ {
+ auto cleoVer = CLEO_GetVersion();
+ if (cleoVer < CLEO_VERSION)
+ {
+ auto err = StringPrintf("This plugin requires version %X or later! \nCurrent version of CLEO is %X.", CLEO_VERSION >> 8, cleoVer >> 8);
+ MessageBox(HWND_DESKTOP, err.c_str(), TARGET_NAME, MB_SYSTEMMODAL | MB_ICONERROR);
+ return;
+ }
+
+ // register opcodes
+ CLEO_RegisterOpcode(0x0AAC, opcode_0AAC); // load_audiostream
+ CLEO_RegisterOpcode(0x0AAD, opcode_0AAD); // set_audio_stream_state
+ CLEO_RegisterOpcode(0x0AAE, opcode_0AAE); // remove_audio_stream
+ CLEO_RegisterOpcode(0x0AAF, opcode_0AAF); // get_audiostream_length
+
+ CLEO_RegisterOpcode(0x0AB9, opcode_0AB9); // get_audio_stream_state
+
+ CLEO_RegisterOpcode(0x0ABB, opcode_0ABB); // get_audio_stream_volume
+ CLEO_RegisterOpcode(0x0ABC, opcode_0ABC); // set_audio_stream_volume
+
+ CLEO_RegisterOpcode(0x0AC0, opcode_0AC0); // loop_audiostream
+ CLEO_RegisterOpcode(0x0AC1, opcode_0AC1); // load_audiostream_with_3d_support
+ CLEO_RegisterOpcode(0x0AC2, opcode_0AC2); // set_play_3d_audio_stream_at_coords
+ CLEO_RegisterOpcode(0x0AC3, opcode_0AC3); // set_play_3d_audio_stream_at_object
+ CLEO_RegisterOpcode(0x0AC4, opcode_0AC4); // set_play_3d_audio_stream_at_char
+ CLEO_RegisterOpcode(0x0AC5, opcode_0AC5); // set_play_3d_audio_stream_at_vehicle
+
+ CLEO_RegisterOpcode(0x2500, opcode_2500); // is_audio_stream_playing
+ CLEO_RegisterOpcode(0x2501, opcode_2501); // get_audiostream_duration
+ CLEO_RegisterOpcode(0x2502, opcode_2502); // get_audio_stream_speed
+ CLEO_RegisterOpcode(0x2503, opcode_2503); // set_audio_stream_speed
+ CLEO_RegisterOpcode(0x2504, opcode_2504); // set_audio_stream_volume_with_transition
+ CLEO_RegisterOpcode(0x2505, opcode_2505); // set_audio_stream_speed_with_transition
+ CLEO_RegisterOpcode(0x2506, opcode_2506); // set_audio_stream_source_size
+ CLEO_RegisterOpcode(0x2507, opcode_2507); // get_audio_stream_progress
+ CLEO_RegisterOpcode(0x2508, opcode_2508); // set_audio_stream_progress
+
+ CLEO_RegisterOpcode(0x2509, opcode_2509); // get_audio_stream_type
+ CLEO_RegisterOpcode(0x250A, opcode_250A); // set_audio_stream_type
+
+ // register event callbacks
+ CLEO_RegisterCallback(eCallbackId::GameBegin, OnGameBegin);
+ CLEO_RegisterCallback(eCallbackId::GameProcess, OnGameProcess);
+ CLEO_RegisterCallback(eCallbackId::GameEnd, OnGameEnd);
+ CLEO_RegisterCallback(eCallbackId::DrawingFinished, OnDrawingFinished);
+ CLEO_RegisterCallback(eCallbackId::MainWindowFocus, OnMainWindowFocus);
+ }
+
+ ~Audio()
+ {
+ CLEO_UnregisterCallback(eCallbackId::GameBegin, OnGameBegin);
+ CLEO_UnregisterCallback(eCallbackId::GameProcess, OnGameProcess);
+ CLEO_UnregisterCallback(eCallbackId::GameEnd, OnGameEnd);
+ CLEO_UnregisterCallback(eCallbackId::DrawingFinished, OnDrawingFinished);
+ CLEO_UnregisterCallback(eCallbackId::MainWindowFocus, OnMainWindowFocus);
+ }
+
+ static void __stdcall OnGameBegin(DWORD saveSlot)
+ {
+ soundSystem.Init();
+ }
+
+ static void __stdcall OnGameProcess()
+ {
+ soundSystem.Process();
+ }
+
+ static void __stdcall OnGameEnd()
+ {
+ soundSystem.Clear();
+ }
+
+ static void __stdcall OnDrawingFinished()
+ {
+ if (CTimer::m_UserPause) // main menu visible
+ soundSystem.Process();
+ }
+
+ static void __stdcall OnMainWindowFocus(bool active)
+ {
+ if (active)
+ soundSystem.Resume();
+ else
+ soundSystem.Pause();
+ }
+
+
+ //0AAC=2, %2d% = load_audiostream %1d% // IF and SET
+ static OpcodeResult __stdcall opcode_0AAC(CScriptThread* thread)
+ {
+ OPCODE_READ_PARAM_STRING_LEN(path, 511);
+
+ if (!isNetworkSource(path))
+ CLEO_ResolvePath(thread, _buff_path, sizeof(_buff_path));
+
+ auto ptr = soundSystem.CreateStream(path);
+
+ if (ptr != nullptr && IsLegacyScript(thread))
+ {
+ ptr->SetType(CLEO::CSoundSystem::LegacyModeDefaultStreamType);
+ }
+
+ OPCODE_WRITE_PARAM_PTR(ptr);
+ OPCODE_CONDITION_RESULT(ptr != nullptr);
+ return OR_CONTINUE;
+ }
+
+ //0AAD=2,set_audiostream %1d% perform_action %2d%
+ static OpcodeResult __stdcall opcode_0AAD(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM()
+ auto action = OPCODE_READ_PARAM_INT();
+
+ if (stream)
+ {
+ switch (action)
+ {
+ case eStreamAction::Stop: stream->Stop(); break;
+ case eStreamAction::Play: stream->Play(); break;
+ case eStreamAction::Pause: stream->Pause(); break;
+ case eStreamAction::Resume: stream->Resume(); break;
+ default:
+ LOG_WARNING(thread, "Unknown AudioStreamAction (%d) in script %s", action, ScriptInfoStr(thread).c_str());
+ }
+ }
+
+ return OR_CONTINUE;
+ }
+
+ //0AAE=1,release_audiostream %1d%
+ static OpcodeResult __stdcall opcode_0AAE(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+
+ if (stream) soundSystem.DestroyStream(stream);
+
+ return OR_CONTINUE;
+ }
+
+ //0AAF=2,%2d% = get_audiostream_length %1d%
+ static OpcodeResult __stdcall opcode_0AAF(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+
+ auto length = 0.0f;
+ if (stream) length = stream->GetLength();
+
+ OPCODE_WRITE_PARAM_INT((int)length);
+ return OR_CONTINUE;
+ }
+
+ //0AB9=2,get_audio_stream_state %1d% store_to %2d%
+ static OpcodeResult __stdcall opcode_0AB9(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+
+ auto state = CAudioStream::eStreamState::Stopped;
+ if (stream) state = stream->GetState();
+
+ OPCODE_WRITE_PARAM_INT(state);
+ return OR_CONTINUE;
+ }
+
+ //0ABB=2,%2d% = get_audio_stream_volume %1d%
+ static OpcodeResult __stdcall opcode_0ABB(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+
+ auto volume = 0.0f;
+ if (stream) volume = stream->GetVolume();
+
+ OPCODE_WRITE_PARAM_FLOAT(volume);
+ return OR_CONTINUE;
+ }
+
+ //0ABC=2,set_audiostream %1d% volume %2d%
+ static OpcodeResult __stdcall opcode_0ABC(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto volume = OPCODE_READ_PARAM_FLOAT();
+
+ if (stream) stream->SetVolume(volume);
+
+ return OR_CONTINUE;
+ }
+
+ //0AC0=2,loop_audiostream %1d% flag %2d%
+ static OpcodeResult __stdcall opcode_0AC0(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto loop = OPCODE_READ_PARAM_BOOL();
+
+ if (stream) stream->SetLooping(loop);
+
+ return OR_CONTINUE;
+ }
+
+ //0AC1=2,%2d% = load_audiostream_with_3d_support %1d% //IF and SET
+ static OpcodeResult __stdcall opcode_0AC1(CScriptThread* thread)
+ {
+ OPCODE_READ_PARAM_STRING_LEN(path, 511);
+
+ if (!isNetworkSource(path))
+ CLEO_ResolvePath(thread, _buff_path, sizeof(_buff_path));
+
+ auto ptr = soundSystem.CreateStream(path, true);
+
+ if (ptr != nullptr && IsLegacyScript(thread))
+ {
+ ptr->SetType(CLEO::CSoundSystem::LegacyModeDefaultStreamType);
+ }
+
+ OPCODE_WRITE_PARAM_PTR(ptr);
+ OPCODE_CONDITION_RESULT(ptr != nullptr);
+ return OR_CONTINUE;
+ }
+
+ //0AC2=4,set_3d_audiostream %1d% position %2d% %3d% %4d%
+ static OpcodeResult __stdcall opcode_0AC2(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ CVector pos;
+ pos.x = OPCODE_READ_PARAM_FLOAT();
+ pos.y = OPCODE_READ_PARAM_FLOAT();
+ pos.z = OPCODE_READ_PARAM_FLOAT();
+
+ if (stream) stream->Set3dPosition(pos);
+
+ return OR_CONTINUE;
+ }
+
+ //0AC3=2,link_3d_audiostream %1d% to_object %2d%
+ static OpcodeResult __stdcall opcode_0AC3(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto handle = OPCODE_READ_PARAM_OBJECT_HANDLE();
+
+ if (stream)
+ {
+ auto object = CPools::GetObject(handle);
+ stream->Link(object);
+ }
+
+ return OR_CONTINUE;
+ }
+
+ //0AC4=2,link_3d_audiostream %1d% to_actor %2d%
+ static OpcodeResult __stdcall opcode_0AC4(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto handle = OPCODE_READ_PARAM_PED_HANDLE();
+
+ if (stream)
+ {
+ auto ped = CPools::GetPed(handle);
+ stream->Link(ped);
+ }
+
+ return OR_CONTINUE;
+ }
+
+ //0AC5=2,link_3d_audiostream %1d% to_vehicle %2d%
+ static OpcodeResult __stdcall opcode_0AC5(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto handle = OPCODE_READ_PARAM_VEHICLE_HANDLE();
+
+ if (stream)
+ {
+ auto vehicle = CPools::GetVehicle(handle);
+ stream->Link(vehicle);
+ }
+
+ return OR_CONTINUE;
+ }
+
+ //2500=1, is_audio_stream_playing %1d%
+ static OpcodeResult __stdcall opcode_2500(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+
+ auto state = CAudioStream::eStreamState::Stopped;
+ if (stream) state = stream->GetState();
+
+ OPCODE_CONDITION_RESULT(state == CAudioStream::eStreamState::Playing);
+ return OR_CONTINUE;
+ }
+
+ //2501=2,%2d% = get_audiostream_duration %1d%
+ static OpcodeResult __stdcall opcode_2501(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+
+ auto length = 0.0f;
+ if (stream)
+ {
+ auto length = stream->GetLength();
+
+ auto speed = stream->GetSpeed();
+ if (speed <= 0.0f)
+ length = FLT_MAX; // it would take forever to play paused
+ else
+ length /= speed; // speed corrected
+ }
+
+ OPCODE_WRITE_PARAM_FLOAT(length);
+ return OR_CONTINUE;
+ }
+
+ //2502=2,get_audio_stream_speed %1d% store_to %2d%
+ static OpcodeResult __stdcall opcode_2502(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+
+ auto speed = 0.0f;
+ if (stream) speed = stream->GetSpeed();
+
+ OPCODE_WRITE_PARAM_FLOAT(speed);
+ return OR_CONTINUE;
+ }
+
+ //2503=2,set_audio_stream_speed %1d% speed %2d%
+ static OpcodeResult __stdcall opcode_2503(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto speed = OPCODE_READ_PARAM_FLOAT();
+
+ if (stream) stream->SetSpeed(speed);
+
+ return OR_CONTINUE;
+ }
+
+ //2504=3,set_audio_stream_volume_with_transition %1d% volume %2d% time_ms %2d%
+ static OpcodeResult __stdcall opcode_2504(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto volume = OPCODE_READ_PARAM_FLOAT();
+ auto time = OPCODE_READ_PARAM_INT();
+
+ if (stream) stream->SetVolume(volume, 0.001f * time);
+
+ return OR_CONTINUE;
+ }
+
+ //2505=3,set_audio_stream_speed_with_transition %1d% speed %2d% time_ms %2d%
+ static OpcodeResult __stdcall opcode_2505(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto speed = OPCODE_READ_PARAM_FLOAT();
+ auto time = OPCODE_READ_PARAM_INT();
+
+ if (stream) stream->SetSpeed(speed, 0.001f * time);
+
+ return OR_CONTINUE;
+ }
+
+ //2506=2,set_audio_stream_source_size %1d% radius %2d%
+ static OpcodeResult __stdcall opcode_2506(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto radius = OPCODE_READ_PARAM_FLOAT();
+
+ if (stream) stream->Set3dSourceSize(radius);
+
+ return OR_CONTINUE;
+ }
+
+ //2507=2,get_audio_stream_progress %1d% store_to %2d%
+ static OpcodeResult __stdcall opcode_2507(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+
+ auto progress = 0.0f;
+ if (stream) progress = stream->GetProgress();
+
+ OPCODE_WRITE_PARAM_FLOAT(progress);
+ return OR_CONTINUE;
+ }
+
+ //2508=2,set_audio_stream_progress %1d% speed %2d%
+ static OpcodeResult __stdcall opcode_2508(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto speed = OPCODE_READ_PARAM_FLOAT();
+
+ if (stream) stream->SetProgress(speed);
+
+ return OR_CONTINUE;
+ }
+
+ //2509=2,get_audio_stream_type %1d% store_to %2d%
+ static OpcodeResult __stdcall opcode_2509(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+
+ auto type = eStreamType::None;
+ if (stream) type = stream->GetType();
+
+ OPCODE_WRITE_PARAM_INT(type);
+ return OR_CONTINUE;
+ }
+
+ //250A=2,set_audio_stream_type %1d% type %2d%
+ static OpcodeResult __stdcall opcode_250A(CScriptThread* thread)
+ {
+ auto stream = (CAudioStream*)OPCODE_READ_PARAM_UINT(); VALIDATE_STREAM();
+ auto type = OPCODE_READ_PARAM_INT();
+
+ if (stream) stream->SetType((eStreamType)type);
+
+ return OR_CONTINUE;
+ }
+} audioInstance;
+
+CSoundSystem Audio::soundSystem;
diff --git a/cleo_plugins/Audio/Audio.filters b/cleo_plugins/Audio/Audio.filters
new file mode 100644
index 00000000..757b525e
--- /dev/null
+++ b/cleo_plugins/Audio/Audio.filters
@@ -0,0 +1,43 @@
+
+
+
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+
+
+ {1903661c-d3a7-4f51-8910-54b32282a46d}
+
+
+ {0c8900ae-85e5-4dc1-9d7b-173b6f8cd435}
+
+
+
+
+ cleo_sdk
+
+
+ cleo_sdk
+
+
+
\ No newline at end of file
diff --git a/cleo_plugins/Audio/Audio.vcxproj b/cleo_plugins/Audio/Audio.vcxproj
new file mode 100644
index 00000000..7893f49b
--- /dev/null
+++ b/cleo_plugins/Audio/Audio.vcxproj
@@ -0,0 +1,173 @@
+
+
+
+
+ Release
+ Win32
+
+
+ Debug
+ Win32
+
+
+
+ {897344A5-1AF1-493A-8B0B-196C0423D5DA}
+ true
+ Win32Proj
+ Audio
+ 10.0
+ Audio
+
+
+
+ DynamicLibrary
+ false
+ MultiByte
+ v143
+ true
+
+
+ DynamicLibrary
+ true
+ MultiByte
+ v143
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir).output\
+ $(ProjectDir).obj\$(Configuration)\
+ SA.Audio
+ .cleo
+ $(ProjectDir)bass;$(LibraryPath)
+
+
+ $(SolutionDir).output\
+ $(ProjectDir).obj\$(Configuration)\
+ SA.Audio
+ .cleo
+ $(ProjectDir)bass;$(LibraryPath)
+
+
+ $(GTA_SA_DIR)\gta_sa.exe
+ $(GTA_SA_DIR)
+ false
+ WindowsLocalDebugger
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ MultiThreaded
+ _NDEBUG;_USING_V110_SDK71_;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;GTASA;GTAGAME_NAME="San Andreas";GTAGAME_ABBR="SA";GTAGAME_ABBRLOW="sa";GTAGAME_PROTAGONISTNAME="CJ";GTAGAME_CITYNAME="San Andreas";%(PreprocessorDefinitions);TARGET_NAME=R"($(TargetName))"
+ /Zc:threadSafeInit- %(AdditionalOptions)
+ $(PLUGIN_SDK_DIR)\plugin_sa\;$(PLUGIN_SDK_DIR)\plugin_sa\game_sa\;$(PLUGIN_SDK_DIR)\shared;$(PLUGIN_SDK_DIR)\shared\game;$(SolutionDir)..\cleo_sdk;$(ProjectDir)\bass\;%(AdditionalIncludeDirectories)
+ stdcpp17
+ None
+
+
+ true
+ true
+ false
+ UseLinkTimeCodeGeneration
+ $(PLUGIN_SDK_DIR)\output\lib\;$(SolutionDir)..\cleo_sdk\;$(ProjectDir)bass\;%(AdditionalLibraryDirectories)
+ cleo.lib;bass.lib;%(AdditionalDependencies)
+ Windows
+
+
+ taskkill /IM gta_sa.exe /F /FI "STATUS eq RUNNING"
+xcopy /Y "$(ProjectDir)*.ini" "$(OutDir)"
+xcopy /Y "$(OutDir)$(TargetName).*" "$(GTA_SA_DIR)\cleo\cleo_plugins\"
+
+
+ TARGET_NAME=$(TargetFileName)
+
+
+
+
+ Level3
+ Disabled
+ true
+ MultiThreadedDebug
+ _DEBUG;_USING_V110_SDK71_;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;GTASA;GTAGAME_NAME="San Andreas";GTAGAME_ABBR="SA";GTAGAME_ABBRLOW="sa";GTAGAME_PROTAGONISTNAME="CJ";GTAGAME_CITYNAME="San Andreas";%(PreprocessorDefinitions);TARGET_NAME=R"($(TargetName))"
+ /Zc:threadSafeInit- %(AdditionalOptions)
+ $(PLUGIN_SDK_DIR)\plugin_sa\;$(PLUGIN_SDK_DIR)\plugin_sa\game_sa\;$(PLUGIN_SDK_DIR)\shared\;$(PLUGIN_SDK_DIR)\shared\game\;$(SolutionDir)..\cleo_sdk\;$(ProjectDir)\bass\;%(AdditionalIncludeDirectories)
+ stdcpp17
+
+
+ true
+ Default
+ $(PLUGIN_SDK_DIR)\output\lib\;$(SolutionDir)..\cleo_sdk\;$(ProjectDir)bass\;%(AdditionalLibraryDirectories)
+ cleo.lib;bass.lib;%(AdditionalDependencies)
+ Windows
+
+
+ taskkill /IM gta_sa.exe /F /FI "STATUS eq RUNNING"
+xcopy /Y "$(ProjectDir)*.ini" "$(OutDir)"
+xcopy /Y "$(OutDir)$(TargetName).*" "$(GTA_SA_DIR)\cleo\cleo_plugins\"
+
+
+ TARGET_NAME=$(TargetFileName)
+
+
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cleo_plugins/Audio/Audio.vcxproj.filters b/cleo_plugins/Audio/Audio.vcxproj.filters
new file mode 100644
index 00000000..dfa912cc
--- /dev/null
+++ b/cleo_plugins/Audio/Audio.vcxproj.filters
@@ -0,0 +1,76 @@
+
+
+
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+ plugin_sdk
+
+
+
+
+
+
+ cleo_sdk
+
+
+ cleo_sdk
+
+
+
+ bass
+
+
+
+
+
+
+ {cd3f4d0b-2948-4ccd-87c9-c05ebb973b25}
+
+
+ {995941cc-43c5-43e9-a674-5916cf70a5f7}
+
+
+ {06b76bd2-09a7-4369-b83b-0298428ebb4f}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cleo_plugins/Audio/C3DAudioStream.cpp b/cleo_plugins/Audio/C3DAudioStream.cpp
new file mode 100644
index 00000000..88861175
--- /dev/null
+++ b/cleo_plugins/Audio/C3DAudioStream.cpp
@@ -0,0 +1,82 @@
+#include "C3DAudioStream.h"
+#include "CSoundSystem.h"
+#include "CLEO_Utils.h"
+
+using namespace CLEO;
+
+C3DAudioStream::C3DAudioStream(const char* filepath) : CAudioStream()
+{
+ if (isNetworkSource(filepath) && !CSoundSystem::allowNetworkSources)
+ {
+ TRACE("Loading of 3d-audiostream '%s' failed. Support of network sources was disabled in SA.Audio.ini", filepath);
+ return;
+ }
+
+ unsigned flags = BASS_SAMPLE_3D | BASS_SAMPLE_MONO | BASS_SAMPLE_SOFTWARE;
+ if (CSoundSystem::useFloatAudio) flags |= BASS_SAMPLE_FLOAT;
+
+ if (!(streamInternal = BASS_StreamCreateFile(FALSE, filepath, 0, 0, flags)) &&
+ !(streamInternal = BASS_StreamCreateURL(filepath, 0, flags, nullptr, nullptr)))
+ {
+ LOG_WARNING(0, "Loading of 3d-audiostream '%s' failed. Error code: %d", filepath, BASS_ErrorGetCode());
+ return;
+ }
+
+ BASS_ChannelGetAttribute(streamInternal, BASS_ATTRIB_FREQ, &rate);
+ BASS_ChannelSet3DAttributes(streamInternal, BASS_3DMODE_NORMAL, 3.0f, 1E+12f, -1, -1, -1.0f);
+ ok = true;
+}
+
+void C3DAudioStream::Set3dPosition(const CVector& pos)
+{
+ link = nullptr;
+ position.x = pos.y;
+ position.y = pos.z;
+ position.z = pos.x;
+ BASS_3DVECTOR vel = { 0.0f, 0.0f, 0.0f };
+
+ BASS_ChannelSet3DPosition(streamInternal, &position, nullptr, &vel);
+}
+
+void C3DAudioStream::Set3dSourceSize(float radius)
+{
+ BASS_ChannelSet3DAttributes(streamInternal, BASS_3DMODE_NORMAL, radius, 1E+12f, -1, -1, -1.0f);
+}
+
+void C3DAudioStream::Link(CPlaceable* placable)
+{
+ link = placable;
+}
+
+void C3DAudioStream::Process()
+{
+ CAudioStream::Process();
+
+ if (state != Playing) return; // done
+
+ UpdatePosition();
+}
+
+void C3DAudioStream::UpdatePosition()
+{
+ if (link) // attached to entity
+ {
+ auto prevPos = position;
+ CVector* pVec = link->m_matrix ? &link->m_matrix->pos : &link->m_placement.m_vPosn;
+ position = BASS_3DVECTOR(pVec->y, pVec->z, pVec->x);
+
+
+ // calculate velocity
+ BASS_3DVECTOR vel = position;
+ vel.x -= prevPos.x;
+ vel.y -= prevPos.y;
+ vel.z -= prevPos.z;
+ auto timeDelta = 0.001f * (CTimer::m_snTimeInMillisecondsNonClipped - CTimer::m_snPreviousTimeInMillisecondsNonClipped);
+ vel.x *= timeDelta;
+ vel.y *= timeDelta;
+ vel.z *= timeDelta;
+
+ BASS_ChannelSet3DPosition(streamInternal, &position, nullptr, &vel);
+ }
+}
+
diff --git a/cleo_plugins/Audio/C3DAudioStream.h b/cleo_plugins/Audio/C3DAudioStream.h
new file mode 100644
index 00000000..cbef37e5
--- /dev/null
+++ b/cleo_plugins/Audio/C3DAudioStream.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "CAudioStream.h"
+
+namespace CLEO
+{
+ class C3DAudioStream : public CAudioStream
+ {
+ public:
+ C3DAudioStream(const char* filepath);
+
+ // overloaded actions
+ virtual void Set3dPosition(const CVector& pos);
+ virtual void Set3dSourceSize(float radius);
+ virtual void Link(CPlaceable* placable = nullptr);
+ virtual void Process();
+
+ protected:
+ CPlaceable* link = nullptr;
+ BASS_3DVECTOR position = { 0.0f, 0.0f, 0.0f };
+
+ C3DAudioStream(const C3DAudioStream&) = delete; // no copying!
+ void UpdatePosition();
+ };
+}
+
diff --git a/cleo_plugins/Audio/CAudioStream.cpp b/cleo_plugins/Audio/CAudioStream.cpp
new file mode 100644
index 00000000..03dae03b
--- /dev/null
+++ b/cleo_plugins/Audio/CAudioStream.cpp
@@ -0,0 +1,250 @@
+#include "CAudioStream.h"
+#include "CSoundSystem.h"
+#include "CLEO_Utils.h"
+
+using namespace CLEO;
+
+CAudioStream::CAudioStream(const char* filepath)
+{
+ if (isNetworkSource(filepath) && !CSoundSystem::allowNetworkSources)
+ {
+ TRACE("Loading of audiostream '%s' failed. Support of network sources was disabled in SA.Audio.ini", filepath);
+ return;
+ }
+
+ unsigned flags = BASS_SAMPLE_SOFTWARE;
+ if (CSoundSystem::useFloatAudio) flags |= BASS_SAMPLE_FLOAT;
+
+ if (!(streamInternal = BASS_StreamCreateFile(FALSE, filepath, 0, 0, flags)) &&
+ !(streamInternal = BASS_StreamCreateURL(filepath, 0, flags, 0, nullptr)))
+ {
+ LOG_WARNING(0, "Loading of audiostream '%s' failed. Error code: %d", filepath, BASS_ErrorGetCode());
+ return;
+ }
+
+ BASS_ChannelGetAttribute(streamInternal, BASS_ATTRIB_FREQ, &rate);
+ ok = true;
+}
+
+CAudioStream::~CAudioStream()
+{
+ if (streamInternal) BASS_StreamFree(streamInternal);
+}
+
+void CAudioStream::Play()
+{
+ if (state == Stopped) BASS_ChannelSetPosition(streamInternal, 0, BASS_POS_BYTE); // rewind
+ state = PlayingInactive; // needs to be processed
+}
+
+void CAudioStream::Pause(bool changeState)
+{
+ if (GetState() == Playing)
+ {
+ BASS_ChannelPause(streamInternal);
+ state = changeState ? Paused : PlayingInactive;
+ }
+}
+
+void CAudioStream::Stop()
+{
+ BASS_ChannelPause(streamInternal);
+ state = Stopped;
+
+ // cancel ongoing transitions
+ speed = speedTarget;
+ volume = volumeTarget;
+}
+
+void CAudioStream::Resume()
+{
+ Play();
+}
+
+float CAudioStream::GetLength() const
+{
+ return (float)BASS_ChannelBytes2Seconds(streamInternal, BASS_ChannelGetLength(streamInternal, BASS_POS_BYTE));
+}
+
+void CAudioStream::SetProgress(float value)
+{
+ value = std::clamp(value, 0.0f, 1.0f);
+ auto total = BASS_ChannelGetLength(streamInternal, BASS_POS_BYTE);
+ auto bytePos = QWORD(value * total);
+ BASS_ChannelSetPosition(streamInternal, bytePos, BASS_POS_BYTE);
+}
+
+float CAudioStream::GetProgress() const
+{
+ auto total = BASS_ChannelGetLength(streamInternal, BASS_POS_BYTE); // returns -1 on error
+ auto bytePos = BASS_ChannelGetPosition(streamInternal, BASS_POS_BYTE); // returns -1 on error
+
+ if (bytePos == -1) bytePos = 0; // error or not available yet
+
+ float progress = (float)bytePos / total;
+ progress = std::clamp(progress, 0.0f, 1.0f);
+ return progress;
+}
+
+CAudioStream::eStreamState CAudioStream::GetState() const
+{
+ return (state == PlayingInactive) ? Playing : state;
+}
+
+void CAudioStream::SetLooping(bool enable)
+{
+ BASS_ChannelFlags(streamInternal, enable ? BASS_SAMPLE_LOOP : 0, BASS_SAMPLE_LOOP);
+}
+
+bool CLEO::CAudioStream::GetLooping() const
+{
+ return (BASS_ChannelFlags(streamInternal, 0, 0) & BASS_SAMPLE_LOOP) != 0;
+}
+
+void CAudioStream::SetVolume(float value, float transitionTime)
+{
+ if (transitionTime > 0.0f) Resume();
+
+ value = max(value, 0.0f);
+ volumeTarget = value;
+
+ if (transitionTime <= 0.0)
+ volume = value; // instant
+ else
+ volumeTransitionStep = (volumeTarget - volume) / (1000.0 * transitionTime);
+}
+
+float CAudioStream::GetVolume() const
+{
+ return (float)volume;
+}
+
+void CAudioStream::SetSpeed(float value, float transitionTime)
+{
+ if (transitionTime > 0.0f) Resume();
+
+ value = max(value, 0.0f);
+ speedTarget = value;
+
+ if (transitionTime <= 0.0)
+ speed = value; // instant
+ else
+ speedTransitionStep = (speedTarget - speed) / (1000.0 * transitionTime);
+}
+
+float CAudioStream::GetSpeed() const
+{
+ return (float)speed;
+}
+
+void CLEO::CAudioStream::SetType(eStreamType value)
+{
+ switch(value)
+ {
+ case eStreamType::SoundEffect:
+ case eStreamType::Music:
+ type = value;
+ break;
+
+ default:
+ type = None;
+ }
+}
+
+eStreamType CLEO::CAudioStream::GetType() const
+{
+ return type;
+}
+
+void CAudioStream::UpdateVolume()
+{
+ if (volume != volumeTarget)
+ {
+ auto timeDelta = CTimer::m_snTimeInMillisecondsNonClipped - CTimer::m_snPreviousTimeInMillisecondsNonClipped;
+ volume += volumeTransitionStep * (double)timeDelta; // animate the transition
+
+ // check progress
+ auto remaining = volumeTarget - volume;
+ remaining *= (volumeTransitionStep > 0.0) ? 1.0 : -1.0;
+ if (remaining < 0.0) // overshoot
+ {
+ volume = volumeTarget;
+ if (volume <= 0.0f) Pause();
+ }
+ }
+
+ float masterVolume = 1.0f;
+ switch(type)
+ {
+ case SoundEffect: masterVolume = CSoundSystem::masterVolumeSfx; break;
+ case Music: masterVolume = CSoundSystem::masterVolumeMusic; break;
+ }
+
+ BASS_ChannelSetAttribute(streamInternal, BASS_ATTRIB_VOL, (float)volume * masterVolume);
+}
+
+void CAudioStream::UpdateSpeed()
+{
+ if (speed != speedTarget)
+ {
+ auto timeDelta = CTimer::m_snTimeInMillisecondsNonClipped - CTimer::m_snPreviousTimeInMillisecondsNonClipped;
+ speed += speedTransitionStep * (double)timeDelta; // animate the transition
+
+ // check progress
+ auto remaining = speedTarget - speed;
+ remaining *= (speedTransitionStep > 0.0) ? 1.0 : -1.0;
+ if (remaining < 0.0) // overshoot
+ {
+ speed = speedTarget; // done
+ if (speed <= 0.0f) Pause();
+ }
+ }
+
+ float freq = rate * (float)speed * CSoundSystem::masterSpeed;
+ freq = max(freq, 0.000001f); // 0 results in original speed
+ BASS_ChannelSetAttribute(streamInternal, BASS_ATTRIB_FREQ, freq);
+}
+
+bool CAudioStream::IsOk() const
+{
+ return ok;
+}
+
+HSTREAM CAudioStream::GetInternal()
+{
+ return streamInternal;
+}
+
+void CAudioStream::Process()
+{
+ if (state == PlayingInactive)
+ {
+ BASS_ChannelPlay(streamInternal, FALSE);
+ state = Playing;
+ }
+
+ if (!GetLooping() && GetProgress() >= 1.0f) // end reached
+ {
+ state = Stopped;
+ }
+
+ if (state != Playing) return; // done
+
+ UpdateSpeed();
+ UpdateVolume();
+}
+
+void CAudioStream::Set3dPosition(const CVector& pos)
+{
+ // not applicable for 2d audio
+}
+
+void CAudioStream::Set3dSourceSize(float radius)
+{
+ // not applicable for 2d audio
+}
+
+void CAudioStream::Link(CPlaceable* placable)
+{
+ // not applicable for 2d audio
+}
diff --git a/cleo_plugins/Audio/CAudioStream.h b/cleo_plugins/Audio/CAudioStream.h
new file mode 100644
index 00000000..fdfa2b60
--- /dev/null
+++ b/cleo_plugins/Audio/CAudioStream.h
@@ -0,0 +1,76 @@
+#pragma once
+#include "CSoundSystem.h"
+#include "plugin.h"
+#include "bass.h"
+
+namespace CLEO
+{
+ class CAudioStream
+ {
+ public:
+ enum eStreamState
+ {
+ Stopped = -1,
+ PlayingInactive, // internal: playing, but not processed yet or the sound system is silenced right now
+ Playing,
+ Paused,
+ };
+
+ CAudioStream(const char* filepath); // filesystem or URL
+ virtual ~CAudioStream();
+
+ bool IsOk() const;
+ HSTREAM GetInternal(); // get BASS stream
+
+ eStreamState GetState() const;
+ void Play();
+ void Pause(bool changeState = true);
+ void Stop();
+ void Resume();
+
+ void SetLooping(bool enable);
+ bool GetLooping() const;
+
+ float GetLength() const;
+
+ void SetProgress(float value);
+ float GetProgress() const;
+
+ void SetSpeed(float value, float transitionTime = 0.0f);
+ float GetSpeed() const;
+
+ void SetType(eStreamType value);
+ eStreamType GetType() const;
+
+ void SetVolume(float value, float transitionTime = 0.0f);
+ float GetVolume() const;
+
+ // 3d
+ virtual void Set3dPosition(const CVector& pos);
+ virtual void Set3dSourceSize(float radius);
+ virtual void Link(CPlaceable* placable = nullptr);
+
+ virtual void Process();
+
+ protected:
+ HSTREAM streamInternal = 0;
+ eStreamState state = Paused;
+ eStreamType type = eStreamType::SoundEffect;
+ bool ok = false;
+ float rate = 44100.0f; // file's sampling rate
+ double speed = 1.0f;
+ double volume = 1.0f;
+
+ // transitions
+ double volumeTarget = 1.0f;
+ double volumeTransitionStep = 1.0f;
+ double speedTarget = 1.0f;
+ double speedTransitionStep = 1.0f;
+
+ CAudioStream() = default;
+ CAudioStream(const CAudioStream&) = delete; // no copying!
+
+ void UpdateVolume();
+ void UpdateSpeed();
+ };
+}
diff --git a/cleo_plugins/Audio/CSoundSystem.cpp b/cleo_plugins/Audio/CSoundSystem.cpp
new file mode 100644
index 00000000..8c7171f7
--- /dev/null
+++ b/cleo_plugins/Audio/CSoundSystem.cpp
@@ -0,0 +1,251 @@
+#include "CSoundSystem.h"
+#include "CAudioStream.h"
+#include "C3dAudioStream.h"
+#include "CLEO_Utils.h"
+#include "CAEAudioHardware.h"
+#include "CCamera.h"
+
+namespace CLEO
+{
+ bool CSoundSystem::useFloatAudio = false;
+ bool CSoundSystem::allowNetworkSources = true;
+ BASS_3DVECTOR CSoundSystem::pos(0.0, 0.0, 0.0);
+ BASS_3DVECTOR CSoundSystem::vel(0.0, 0.0, 0.0);
+ BASS_3DVECTOR CSoundSystem::front(0.0, -1.0, 0.0);
+ BASS_3DVECTOR CSoundSystem::top(0.0, 0.0, 1.0);
+ eStreamType CSoundSystem::LegacyModeDefaultStreamType = eStreamType::None;
+ float CSoundSystem::masterSpeed = 1.0f;
+ float CSoundSystem::masterVolumeSfx = 1.0f;
+ float CSoundSystem::masterVolumeMusic = 1.0f;
+
+ void EnumerateBassDevices(int& total, int& enabled, int& default_device)
+ {
+ TRACE("Listing audio devices:");
+
+ BASS_DEVICEINFO info;
+ enabled = 0;
+ default_device = -1;
+ for (total = 0; BASS_GetDeviceInfo(total, &info); ++total)
+ {
+ if (info.flags & BASS_DEVICE_DEFAULT) default_device = total;
+
+ bool isEnabled = info.flags & BASS_DEVICE_ENABLED;
+ if (isEnabled) ++enabled;
+
+ TRACE(" %d: %s%s", total, info.name, isEnabled ? "" : " (disabled)");
+ }
+ TRACE(" Default device index: %d", default_device);
+ }
+
+ bool isNetworkSource(const char* path)
+ {
+ return _strnicmp("http:", path, 5) == 0 ||
+ _strnicmp("https:", path, 6) == 0;
+ }
+
+ CSoundSystem::~CSoundSystem()
+ {
+ TRACE(""); // seaprator
+ TRACE("Finalizing SoundSystem...");
+ Clear();
+
+ if (initialized)
+ {
+ //TRACE("Freeing BASS library");
+ //std::thread(BASS_Free); // causes deadlock with ModLoader
+ initialized = false;
+ }
+ TRACE("SoundSystem finalized");
+ }
+
+ bool CSoundSystem::Init()
+ {
+ if (initialized) return true; // already done
+
+ TRACE(""); // separator
+ TRACE("Initializing SoundSystem...");
+
+ auto ver = HIWORD(BASS_GetVersion());
+ TRACE("BASS library version is %d (required %d or newer)", ver, BASSVERSION);
+ if (ver < BASSVERSION)
+ {
+ SHOW_ERROR("Invalid BASS library version! Expected at least %d, found %d.", BASSVERSION, ver);
+ }
+
+ auto config = GetConfigFilename();
+ LegacyModeDefaultStreamType = (eStreamType)GetPrivateProfileInt("General", "LegacyModeDefaultStreamType", 0, config.c_str());
+ allowNetworkSources = GetPrivateProfileInt("General", "AllowNetworkSources", 1, config.c_str()) != 0;
+
+ int deviceIndex, total_devices, enabled_devices;
+ EnumerateBassDevices(total_devices, enabled_devices, deviceIndex);
+
+ BASS_DEVICEINFO info = { "Unknown device", nullptr, 0 };
+ BASS_GetDeviceInfo(deviceIndex, &info);
+
+ int forceIndex = GetPrivateProfileInt("General", "AudioDevice", -1, config.c_str());
+ if (forceIndex != -1)
+ {
+ BASS_DEVICEINFO forceInfo = { "Unknown device", nullptr, 0 };
+ if (BASS_GetDeviceInfo(forceIndex, &forceInfo) && forceInfo.flags & BASS_DEVICE_ENABLED)
+ {
+ TRACE("Force selecting audio device #%d: %s", forceIndex, forceInfo.name);
+ deviceIndex = forceIndex;
+ }
+ else
+ {
+ LOG_WARNING(0, "Failed to force select device #%d! Selecting default audio device #%d: %s", forceIndex, deviceIndex, info.name);
+ }
+ }
+ else
+ {
+ TRACE("Selecting default audio device #%d: %s", deviceIndex, info.name);
+ }
+
+ if (BASS_Init(deviceIndex, 44100, BASS_DEVICE_3D, RsGlobal.ps->window, nullptr) &&
+ BASS_Set3DFactors(1.0f, 3.0f, 80.0f) &&
+ BASS_Set3DPosition(&pos, &vel, &front, &top))
+ {
+ TRACE("SoundSystem initialized");
+
+ // Can we use floating-point (HQ) audio streams?
+ DWORD floatable = BASS_StreamCreate(44100, 1, BASS_SAMPLE_FLOAT, NULL, NULL); // floating-point channel support? 0 = no, else yes
+ if (floatable)
+ {
+ TRACE("Floating-point audio supported!");
+ useFloatAudio = true;
+ BASS_StreamFree(floatable);
+ }
+ else TRACE("Floating-point audio not supported!");
+
+ if (BASS_GetInfo(&SoundDevice))
+ {
+ if (SoundDevice.flags & DSCAPS_EMULDRIVER)
+ TRACE("Audio drivers not installed - using DirectSound emulation");
+ if (!SoundDevice.eax)
+ TRACE("Audio hardware acceleration disabled (no EAX)");
+ }
+
+ initialized = true;
+ BASS_Apply3D();
+ return true;
+ }
+
+ LOG_WARNING(0, "Could not initialize BASS sound system. Error code: %d", BASS_ErrorGetCode());
+ return false;
+ }
+
+ bool CSoundSystem::Initialized()
+ {
+ return initialized;
+ }
+
+ CAudioStream* CSoundSystem::CreateStream(const char *filename, bool in3d)
+ {
+ CAudioStream* result = in3d ? new C3DAudioStream(filename) : new CAudioStream(filename);
+ if (!result->IsOk())
+ {
+ delete result;
+ return nullptr;
+ }
+
+ streams.insert(result);
+ return result;
+ }
+
+ void CSoundSystem::DestroyStream(CAudioStream *stream)
+ {
+ if (streams.erase(stream))
+ delete stream;
+ else
+ TRACE("Unloading of stream that is not in list of loaded streams");
+ }
+
+ bool CSoundSystem::HasStream(CAudioStream* stream)
+ {
+ return streams.find(stream) != streams.end();
+ }
+
+ void CSoundSystem::Clear()
+ {
+ for (auto stream : streams)
+ {
+ delete stream;
+ };
+ streams.clear();
+ }
+
+ void CSoundSystem::Resume()
+ {
+ paused = false;
+ for (auto stream : streams)
+ {
+ if(stream->GetState() == CAudioStream::Playing) stream->Resume();
+ }
+ }
+
+ void CSoundSystem::Pause()
+ {
+ paused = true;
+ for (auto stream : streams)
+ {
+ stream->Pause(false);
+ };
+ }
+
+ void CSoundSystem::Process()
+ {
+ if (CTimer::m_UserPause || CTimer::m_CodePause) // covers menu pausing, no disc in drive pausing, etc.
+ {
+ if (!paused) Pause();
+ }
+ else // not in menu
+ {
+ if (paused) Resume();
+
+ // get game globals
+ masterSpeed = CTimer::ms_fTimeScale;
+ masterVolumeSfx = AEAudioHardware.m_fEffectMasterScalingFactor * 0.5f; // fit to game's sfx volume
+ masterVolumeMusic = AEAudioHardware.m_fMusicMasterScalingFactor * 0.5f;
+
+ // camera movements
+ CMatrixLink * pMatrix = nullptr;
+ CVector * pVec = nullptr;
+ if (TheCamera.m_matrix)
+ {
+ pMatrix = TheCamera.m_matrix;
+ pVec = &pMatrix->pos;
+ }
+ else pVec = &TheCamera.m_placement.m_vPosn;
+
+ BASS_3DVECTOR prevPos = pos;
+ pos = BASS_3DVECTOR(pVec->y, pVec->z, pVec->x);
+
+ // calculate velocity
+ vel = prevPos;
+ vel.x -= pos.x;
+ vel.y -= pos.y;
+ vel.z -= pos.z;
+ auto timeDelta = 0.001f * (CTimer::m_snTimeInMillisecondsNonClipped - CTimer::m_snPreviousTimeInMillisecondsNonClipped);
+ vel.x *= timeDelta;
+ vel.y *= timeDelta;
+ vel.z *= timeDelta;
+
+ // setup the ears
+ if (!TheCamera.m_bJust_Switched && !TheCamera.m_bCameraJustRestored) // avoid camera change/jump cut velocity glitches
+ {
+ BASS_Set3DPosition(
+ &pos,
+ &vel,
+ pMatrix ? &BASS_3DVECTOR(pMatrix->at.y, pMatrix->at.z, pMatrix->at.x) : nullptr,
+ pMatrix ? &BASS_3DVECTOR(pMatrix->up.y, pMatrix->up.z, pMatrix->up.x) : nullptr
+ );
+ }
+
+ // process streams
+ for(auto stream : streams) stream->Process();
+
+ // apply above changes
+ BASS_Apply3D();
+ }
+ }
+}
diff --git a/cleo_plugins/Audio/CSoundSystem.h b/cleo_plugins/Audio/CSoundSystem.h
new file mode 100644
index 00000000..956141c8
--- /dev/null
+++ b/cleo_plugins/Audio/CSoundSystem.h
@@ -0,0 +1,59 @@
+#pragma once
+#include "bass.h"
+#include
+
+namespace CLEO
+{
+ class CAudioStream;
+ class C3DAudioStream;
+
+ enum eStreamType
+ {
+ None = 0,
+ SoundEffect,
+ Music,
+ };
+
+ class CSoundSystem
+ {
+ friend class CAudioStream;
+ friend class C3DAudioStream;
+
+ std::set streams;
+ BASS_INFO SoundDevice = { 0 };
+ bool initialized = false;
+ bool paused = false;
+
+ static bool useFloatAudio;
+ static bool CSoundSystem::allowNetworkSources;
+
+ static BASS_3DVECTOR pos;
+ static BASS_3DVECTOR vel;
+ static BASS_3DVECTOR front;
+ static BASS_3DVECTOR top;
+ static float masterSpeed; // game simulation speed
+ static float masterVolumeSfx;
+ static float masterVolumeMusic;
+
+ public:
+ static eStreamType LegacyModeDefaultStreamType;
+
+ CSoundSystem() = default; // TODO: give to user an ability to force a sound device to use (ini-file or cmd-line?)
+ ~CSoundSystem();
+
+ bool Init();
+ bool Initialized();
+
+ CAudioStream* CreateStream(const char *filename, bool in3d = false);
+ void DestroyStream(CAudioStream *stream);
+
+ bool HasStream(CAudioStream* stream);
+ void Clear(); // destroy all created streams
+
+ void Pause();
+ void Resume();
+ void Process();
+ };
+
+ bool isNetworkSource(const char* path);
+}
diff --git a/cleo_plugins/Audio/SA.Audio.ini b/cleo_plugins/Audio/SA.Audio.ini
new file mode 100644
index 00000000..73d0bcd4
--- /dev/null
+++ b/cleo_plugins/Audio/SA.Audio.ini
@@ -0,0 +1,10 @@
+[General]
+; Manually select audio device. See `.cleo.log` file to check list of available options. -1 for automatic
+AudioDevice=-1
+
+; Allow playing streams from http(s) locations
+AllowNetworkSources=1
+
+; Sounds created from scripts in legacy mode (*.cs3 or *.cs4) should use by default game's volume settings: 0 - None, 1 - SFX, 2 - Music
+; Select 1 if you have older mods that play sounds too loud
+LegacyModeDefaultStreamType=0
diff --git a/third-party/bass/bass.dll b/cleo_plugins/Audio/bass/bass.dll
similarity index 100%
rename from third-party/bass/bass.dll
rename to cleo_plugins/Audio/bass/bass.dll
diff --git a/third-party/bass/bass.h b/cleo_plugins/Audio/bass/bass.h
similarity index 100%
rename from third-party/bass/bass.h
rename to cleo_plugins/Audio/bass/bass.h
diff --git a/third-party/bass/bass.lib b/cleo_plugins/Audio/bass/bass.lib
similarity index 100%
rename from third-party/bass/bass.lib
rename to cleo_plugins/Audio/bass/bass.lib
diff --git a/cleo_plugins/CLEO_Plugins.sln b/cleo_plugins/CLEO_Plugins.sln
new file mode 100644
index 00000000..ee05f7ed
--- /dev/null
+++ b/cleo_plugins/CLEO_Plugins.sln
@@ -0,0 +1,61 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33213.308
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileSystemOperations", "FileSystemOperations\FileSystemOperations.vcxproj", "{B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IniFiles", "IniFiles\IniFiles.vcxproj", "{6831362D-5226-4634-9DB4-266A1B6C3E6C}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DebugUtils", "DebugUtils\DebugUtils.vcxproj", "{481896C4-0C19-4992-9602-729537774B32}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MemoryOperations", "MemoryOperations\MemoryOperations.vcxproj", "{35C80F79-8B18-4925-8C32-94B320DBE76F}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Audio", "Audio\Audio.vcxproj", "{897344A5-1AF1-493A-8B0B-196C0423D5DA}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Text", "Text\Text.vcxproj", "{BD19AEFD-626B-40AE-8D83-6D444D2EFBF8}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Math", "Math\Math.vcxproj", "{68A434CF-6390-4FDF-9A15-36A8A9ECEAA9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Debug|x86.ActiveCfg = Debug|Win32
+ {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Debug|x86.Build.0 = Debug|Win32
+ {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Release|x86.ActiveCfg = Release|Win32
+ {B212DDA4-2A8E-45B2-914D-7BEEB31D06B1}.Release|x86.Build.0 = Release|Win32
+ {6831362D-5226-4634-9DB4-266A1B6C3E6C}.Debug|x86.ActiveCfg = Debug|Win32
+ {6831362D-5226-4634-9DB4-266A1B6C3E6C}.Debug|x86.Build.0 = Debug|Win32
+ {6831362D-5226-4634-9DB4-266A1B6C3E6C}.Release|x86.ActiveCfg = Release|Win32
+ {6831362D-5226-4634-9DB4-266A1B6C3E6C}.Release|x86.Build.0 = Release|Win32
+ {481896C4-0C19-4992-9602-729537774B32}.Debug|x86.ActiveCfg = Debug|Win32
+ {481896C4-0C19-4992-9602-729537774B32}.Debug|x86.Build.0 = Debug|Win32
+ {481896C4-0C19-4992-9602-729537774B32}.Release|x86.ActiveCfg = Release|Win32
+ {481896C4-0C19-4992-9602-729537774B32}.Release|x86.Build.0 = Release|Win32
+ {35C80F79-8B18-4925-8C32-94B320DBE76F}.Debug|x86.ActiveCfg = Debug|Win32
+ {35C80F79-8B18-4925-8C32-94B320DBE76F}.Debug|x86.Build.0 = Debug|Win32
+ {35C80F79-8B18-4925-8C32-94B320DBE76F}.Release|x86.ActiveCfg = Release|Win32
+ {35C80F79-8B18-4925-8C32-94B320DBE76F}.Release|x86.Build.0 = Release|Win32
+ {897344A5-1AF1-493A-8B0B-196C0423D5DA}.Debug|x86.ActiveCfg = Debug|Win32
+ {897344A5-1AF1-493A-8B0B-196C0423D5DA}.Debug|x86.Build.0 = Debug|Win32
+ {897344A5-1AF1-493A-8B0B-196C0423D5DA}.Release|x86.ActiveCfg = Release|Win32
+ {897344A5-1AF1-493A-8B0B-196C0423D5DA}.Release|x86.Build.0 = Release|Win32
+ {BD19AEFD-626B-40AE-8D83-6D444D2EFBF8}.Debug|x86.ActiveCfg = Debug|Win32
+ {BD19AEFD-626B-40AE-8D83-6D444D2EFBF8}.Debug|x86.Build.0 = Debug|Win32
+ {BD19AEFD-626B-40AE-8D83-6D444D2EFBF8}.Release|x86.ActiveCfg = Release|Win32
+ {BD19AEFD-626B-40AE-8D83-6D444D2EFBF8}.Release|x86.Build.0 = Release|Win32
+ {68A434CF-6390-4FDF-9A15-36A8A9ECEAA9}.Debug|x86.ActiveCfg = Debug|Win32
+ {68A434CF-6390-4FDF-9A15-36A8A9ECEAA9}.Debug|x86.Build.0 = Debug|Win32
+ {68A434CF-6390-4FDF-9A15-36A8A9ECEAA9}.Release|x86.ActiveCfg = Release|Win32
+ {68A434CF-6390-4FDF-9A15-36A8A9ECEAA9}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {A9E58D83-82BC-453A-95B3-2AE3449FE59F}
+ EndGlobalSection
+EndGlobal
diff --git a/cleo_plugins/DebugUtils/DebugUtils.cpp b/cleo_plugins/DebugUtils/DebugUtils.cpp
new file mode 100644
index 00000000..f96e700b
--- /dev/null
+++ b/cleo_plugins/DebugUtils/DebugUtils.cpp
@@ -0,0 +1,384 @@
+#include // keyboard
+#include
+#include