initial tests
This commit is contained in:
commit
d40ebbbff5
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
tags
|
||||
*.log
|
||||
*.tar.xz
|
||||
*.tar.gz
|
||||
*.zip
|
||||
/oppai
|
||||
*.json
|
||||
*.obj
|
||||
*.exe
|
||||
*.swp
|
||||
*.so
|
||||
*.dll
|
||||
*.lib
|
||||
*.exp
|
||||
/test/test_suite
|
||||
/test/oppai_test
|
||||
/swig/**/*.c
|
||||
/swig/*/*.i
|
||||
/swig/python/oppai.egg-info
|
||||
/swig/python/build
|
||||
/swig/python/oppai.py
|
||||
*.whl
|
||||
*.pyc
|
29
.travis.yml
Normal file
29
.travis.yml
Normal file
|
@ -0,0 +1,29 @@
|
|||
language: c
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: precise
|
||||
- os: linux
|
||||
dist: trusty
|
||||
- os: osx
|
||||
|
||||
install: true
|
||||
cache:
|
||||
directories:
|
||||
- test/test_suite
|
||||
|
||||
script:
|
||||
- ./build
|
||||
- ./libbuild
|
||||
- cd test
|
||||
- ./download_suite
|
||||
- ./build
|
||||
- DYLD_PRINT_LIBRARIES=1 DYLD_PRINT_LIBRARIES_POST_LAUNCH=1
|
||||
DYLD_PRINT_RPATHS=1 LD_DEBUG=libs ./oppai_test
|
||||
- wc -c ./oppai_test
|
||||
- LDFLAGS="-loppai" ./build -L..
|
||||
- LD_LIBRARY_PATH=.. DYLD_LIBRARY_PATH=..
|
||||
DYLD_PRINT_LIBRARIES=1 DYLD_PRINT_LIBRARIES_POST_LAUNCH=1
|
||||
DYLD_PRINT_RPATHS=1 LD_DEBUG=libs ./oppai_test
|
||||
- wc -c ./oppai_test
|
6
Dockerfile
Normal file
6
Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
|||
ARG PREFIX=
|
||||
FROM ${PREFIX}ubuntu:bionic
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc musl musl-tools musl-dev git-core file
|
||||
WORKDIR /tmp
|
||||
CMD setarch $arch ./release
|
367
README.md
Normal file
367
README.md
Normal file
|
@ -0,0 +1,367 @@
|
|||
[![Build Status](https://travis-ci.org/Francesco149/oppai-ng.svg?branch=master)](https://travis-ci.org/Francesco149/oppai-ng)
|
||||
|
||||
difficulty and pp calculator for osu!
|
||||
|
||||
this is a pure C89 rewrite of
|
||||
[oppai](https://github.com/Francesco149/oppai) with much lower
|
||||
memory usage, smaller and easier to read codebase
|
||||
executable size and better performance.
|
||||
|
||||
experimental taiko support is now available and appears to give
|
||||
correct values for actual taiko maps. converted maps are still
|
||||
unreliable due to incorrect slider conversion and might be
|
||||
completely off (use ```-m1``` or ```-taiko``` to convert a std map
|
||||
to taiko).
|
||||
|
||||
- [installing (linux)](#installing-linux)
|
||||
- [installing (windows)](#installing-windows)
|
||||
- [installing (osx)](#installing-osx)
|
||||
- [usage](#usage)
|
||||
- [implementations for other programming languages](#implementations-for-other-programming-languages)
|
||||
- [bindings for other programming languages](#bindings-for-other-programming-languages)
|
||||
- [oppai-ng vs old oppai](#oppai-ng-vs-old-oppai)
|
||||
- [compile from source (windows)](#compile-from-source-windows)
|
||||
- [using oppai as a library or making bindings](#using-oppai-as-a-library-or-making-bindings)
|
||||
- [other build parameters](#other-build-parameters)
|
||||
|
||||
# installing (linux)
|
||||
```sh
|
||||
wget https://github.com/Francesco149/oppai-ng/archive/HEAD.tar.gz
|
||||
tar xf HEAD.tar.gz
|
||||
cd oppai-*
|
||||
./build
|
||||
sudo install -Dm 755 oppai /usr/bin/oppai
|
||||
|
||||
oppai
|
||||
```
|
||||
|
||||
you can also grab pre-compiled standalone binaries (statically
|
||||
linked against musl libc) from
|
||||
[here](https://github.com/Francesco149/oppai-ng/releases) if you
|
||||
are somehow too scared to run those 5 commands.
|
||||
|
||||
# installing (windows)
|
||||
download and unzip binaries from
|
||||
[here](https://github.com/Francesco149/oppai-ng/releases) and
|
||||
optionally add oppai's folder to your ```PATH``` environment
|
||||
variable for easy access. you can find a guide
|
||||
[here](https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/)
|
||||
if you don't know how.
|
||||
|
||||
# installing (osx)
|
||||
## via homebrew
|
||||
```sh
|
||||
brew install --HEAD pmrowla/homebrew-tap/oppai-ng
|
||||
```
|
||||
Note that installing with ```--HEAD``` is recommended but not required.
|
||||
Installing from homebrew will place the ```oppai``` executable in your homebrew path.
|
||||
|
||||
## manually
|
||||
Follow the same steps as for linux but substitute ```curl -O``` for ```wget``` since wget is not distributed by default in osx.
|
||||
The same caveat applies if you want to run the test suite - you will need to edit the ```download_suite``` script to use curl.
|
||||
|
||||
# usage
|
||||
you can run oppai with no arguments to check the documentation.
|
||||
|
||||
here's some example usages:
|
||||
|
||||
```sh
|
||||
oppai path/to/map.osu +HDHR 98% 500x 1xmiss
|
||||
oppai path/to/map.osu 3x100
|
||||
oppai path/to/map.osu 3x100 OD10
|
||||
oppai path/to/map.osu -ojson
|
||||
```
|
||||
|
||||
you can also pipe maps from standard input by setting the filename
|
||||
to ```-```.
|
||||
|
||||
for example on linux you can do:
|
||||
|
||||
```sh
|
||||
curl https://osu.ppy.sh/osu/774965 | oppai - +HDDT
|
||||
curl https://osu.ppy.sh/osu/774965 | oppai - +HDDT 1200x 1m
|
||||
```
|
||||
|
||||
while on windows it's a bit more verbose (powershell):
|
||||
|
||||
```powershell
|
||||
(New-Object System.Net.WebClient).DownloadString("https://osu.ppy.sh/osu/37658") | ./oppai -
|
||||
(New-Object System.Net.WebClient).DownloadString("https://osu.ppy.sh/osu/37658") | ./oppai - +HDHR
|
||||
(New-Object System.Net.WebClient).DownloadString("https://osu.ppy.sh/osu/37658") | ./oppai - +HDHR 99% 600x 1m
|
||||
```
|
||||
|
||||
I got the .osu file url from "Grab latest .osu file" on the
|
||||
beatmap's page.
|
||||
|
||||
# implementations for other programming languages
|
||||
oppai has been implemented for many other programming languages.
|
||||
If you feel like making your own implementation and want it listed
|
||||
here, open an issue or pull request. the requirement is that it
|
||||
should pass the same test suite that oppai-ng passes.
|
||||
|
||||
* [ojsama (javascript)](https://github.com/Francesco149/ojsama)
|
||||
* [koohii (java)](https://github.com/Francesco149/koohii) . this
|
||||
is currently being used in tillerino.
|
||||
* [pyttanko (python)](https://github.com/Francesco149/pyttanko)
|
||||
* [oppai5 (golang)](https://github.com/flesnuk/oppai5) (by flesnuk)
|
||||
* [OppaiSharp (C#)](https://github.com/HoLLy-HaCKeR/OppaiSharp)
|
||||
(by HoLLy)
|
||||
* [osu-perf (rust)](https://gitlab.com/JackRedstonia/osu-perf/) (by JackRedstonia)
|
||||
|
||||
# bindings for other programming languages
|
||||
thanks to swig it's trivial to generate native bindings for other
|
||||
programming languages. bindings are an interface to the C code, meaning
|
||||
that you get basically the same performance as C by sacrificing some
|
||||
portability
|
||||
|
||||
* [python](https://github.com/Francesco149/oppai-ng/swig/python)
|
||||
|
||||
# oppai-ng vs old oppai
|
||||
executable size is around 7 times smaller:
|
||||
```sh
|
||||
$ cd ~/src/oppai
|
||||
$ ./build.sh -static
|
||||
$ wc -c oppai
|
||||
574648 oppai
|
||||
|
||||
$ cd ~/src/oppai-ng
|
||||
$ ./build -static
|
||||
$ wc -c oppai
|
||||
75512 oppai
|
||||
```
|
||||
|
||||
oppai-ng has proper error output in whatever format you select,
|
||||
while legacy oppai either gives empty output or just dies with
|
||||
a plaintext error.
|
||||
|
||||
oppai-ng has well-defined errno style error codes that you can
|
||||
check for when using it as a library or reading its output.
|
||||
|
||||
the same test suite runs about 45% faster on oppai-ng compared
|
||||
to old oppai, also the peak resident memory size is 4 to 6 times
|
||||
smaller according to various ```time -v``` runs.
|
||||
|
||||
```sh
|
||||
$ cd ~/src/oppai
|
||||
$ ./build_test.sh
|
||||
$ time -v ./oppai_test
|
||||
...
|
||||
Command being timed: "./oppai_test"
|
||||
User time (seconds): 13.89
|
||||
System time (seconds): 0.10
|
||||
Percent of CPU this job got: 99%
|
||||
Elapsed (wall clock) time (h:mm:ss or m:ss): 0m 13.99s
|
||||
Average shared text size (kbytes): 0
|
||||
Average unshared data size (kbytes): 0
|
||||
Average stack size (kbytes): 0
|
||||
Average total size (kbytes): 0
|
||||
Maximum resident set size (kbytes): 45184
|
||||
Average resident set size (kbytes): 0
|
||||
Major (requiring I/O) page faults: 0
|
||||
Minor (reclaiming a frame) page faults: 2143
|
||||
Voluntary context switches: 1
|
||||
Involuntary context switches: 41
|
||||
Swaps: 0
|
||||
File system inputs: 0
|
||||
File system outputs: 0
|
||||
Socket messages sent: 0
|
||||
Socket messages received: 0
|
||||
Signals delivered: 0
|
||||
Page size (bytes): 4096
|
||||
Exit status: 0
|
||||
|
||||
$ cd ~/src/oppai-ng/test/
|
||||
$ ./build
|
||||
$ time -v ./oppai_test
|
||||
...
|
||||
Command being timed: "./oppai_test"
|
||||
User time (seconds): 9.09
|
||||
System time (seconds): 0.06
|
||||
Percent of CPU this job got: 99%
|
||||
Elapsed (wall clock) time (h:mm:ss or m:ss): 0m 9.15s
|
||||
Average shared text size (kbytes): 0
|
||||
Average unshared data size (kbytes): 0
|
||||
Average stack size (kbytes): 0
|
||||
Average total size (kbytes): 0
|
||||
Maximum resident set size (kbytes): 11840
|
||||
Average resident set size (kbytes): 0
|
||||
Major (requiring I/O) page faults: 0
|
||||
Minor (reclaiming a frame) page faults: 304
|
||||
Voluntary context switches: 1
|
||||
Involuntary context switches: 39
|
||||
Swaps: 0
|
||||
File system inputs: 0
|
||||
File system outputs: 0
|
||||
Socket messages sent: 0
|
||||
Socket messages received: 0
|
||||
Signals delivered: 0
|
||||
Page size (bytes): 4096
|
||||
Exit status: 0
|
||||
```
|
||||
|
||||
note that when the test suite is compiled without libcurl, the
|
||||
resident memory usage drops by a flat 4mb, so almost half of that
|
||||
is curl.
|
||||
|
||||
you can expect oppai memory usage to be under 4 mb most of the time
|
||||
with the raw parsed beatmap data not taking more than ~800k even
|
||||
for a 15 minute marathon.
|
||||
|
||||
the codebase has ~3-4x less lines than legacy oppai, making it easy
|
||||
to read and use as a single header library. not only it is smaller,
|
||||
but it now also implements both taiko and osu, so more features
|
||||
than legacy oppai.
|
||||
|
||||
the osu! pp and diff calc alone would be around ~3k LOC including
|
||||
the cli, which would be 5x less lines than legacy oppai for the
|
||||
same functionality.
|
||||
|
||||
```sh
|
||||
$ cd ~/src/oppai
|
||||
$ sloc *.cc
|
||||
|
||||
---------- Result ------------
|
||||
|
||||
Physical : 15310
|
||||
Source : 14406
|
||||
Comment : 301
|
||||
Single-line comment : 289
|
||||
Block comment : 12
|
||||
Mixed : 23
|
||||
Empty : 626
|
||||
To Do : 11
|
||||
|
||||
Number of files read : 10
|
||||
|
||||
------------------------------
|
||||
|
||||
$ cd ~/src/oppai-ng
|
||||
$ sloc *.c
|
||||
|
||||
---------- Result ------------
|
||||
|
||||
Physical : 4123
|
||||
Source : 2906
|
||||
Comment : 492
|
||||
Single-line comment : 1
|
||||
Block comment : 491
|
||||
Mixed : 64
|
||||
Empty : 811
|
||||
To Do : 9
|
||||
|
||||
Number of files read : 2
|
||||
|
||||
------------------------------
|
||||
```
|
||||
|
||||
not to mention it's C89, which will be compatible with many more
|
||||
platforms and old compilers than c++98
|
||||
|
||||
```oppai.c``` alone is only ~2200 LOC (~1500 without comments), and
|
||||
you can compile piece of it out when you don't need them.
|
||||
|
||||
of course, it's not as heavily tested as legacy oppai (which runs
|
||||
24/7 on Tillerino's back-end), however the test suite is a very
|
||||
good test that runs through ~12000 unique scores and I'm confident
|
||||
this rewrite is already very stable.
|
||||
|
||||
# compile from source (windows)
|
||||
oppai should compile even on old versions of msvc dating back to
|
||||
2005, although it was only tested on msvc 2010 and higher.
|
||||
|
||||
have at least [microsoft c++ build tools](http://landinghub.visualstudio.com/visual-cpp-build-tools)
|
||||
installed. visual studio with c/c++ support also works.
|
||||
|
||||
open a visual studio prompt:
|
||||
|
||||
```bat
|
||||
cd path\to\oppai\source
|
||||
build.bat
|
||||
oppai
|
||||
```
|
||||
|
||||
you can also probably set up mingw and cygwin and follow the linux
|
||||
instructions instead, I'm not sure. I don't use windows.
|
||||
|
||||
# using oppai as a library or making bindings
|
||||
the new codebase is much easier to isolate and include in your
|
||||
projects.
|
||||
|
||||
just copy oppai.c into your project, it acts as a single-header
|
||||
library.
|
||||
|
||||
```c
|
||||
#define OPPAI_IMPLEMENTATION
|
||||
#include "../oppai.c"
|
||||
|
||||
int main() {
|
||||
ezpp_t ez = ezpp_new();
|
||||
ezpp_set_mods(ez, MODS_HD | MODS_DT);
|
||||
ezpp(ez, "-");
|
||||
printf("%gpp\n", ezpp_pp(ez));
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
gcc test.c
|
||||
cat /path/to/file.osu | ./a.out
|
||||
```
|
||||
|
||||
read oppai.c, there's documentation for each function at the top.
|
||||
|
||||
see examples directory for detailed examples. you can also read
|
||||
main.c to see how the CLI uses it.
|
||||
|
||||
if you don't feel comfortable writing bindings or using oppai
|
||||
from c code, you can use the -o parameter to output in json or
|
||||
other parsable formats. ```examples/binary.c``` shows how to parse
|
||||
the binary output.
|
||||
|
||||
# shared library
|
||||
you can also build oppai as a shared library with
|
||||
|
||||
```sh
|
||||
./libbuild
|
||||
```
|
||||
|
||||
this will generate a liboppai.so on linux/mac which you can copy to
|
||||
```/usr/local/lib``` or anywhere in your library search paths
|
||||
|
||||
you can then use it by simply not defining ```OPPAI_IMPLEMENTATION``` .
|
||||
this will exclude all the oppai code and just leave the header part
|
||||
|
||||
|
||||
```c
|
||||
#include "oppai.c"
|
||||
|
||||
int main() {
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
then you can compile and run with
|
||||
|
||||
```
|
||||
gcc test.c -lm -loppai
|
||||
cat /path/to/file.osu | ./a.out
|
||||
```
|
||||
|
||||
for windows you can use ```libbuild.bat``` to build (for details see the
|
||||
info on compiling on windows) which will generate a oppai.dll and .lib pair
|
||||
|
||||
and then compile your program with msvc like so
|
||||
|
||||
```
|
||||
cl test.c oppai.lib
|
||||
```
|
||||
|
||||
then you can simply place the dll in the same folder as your executable
|
||||
and run
|
||||
|
||||
# build parameters
|
||||
when you build the oppai cli, you can pass any of these parameters
|
||||
to the build script to disable features:
|
||||
|
||||
* ```-DOPPAI_UTF8GRAPH``` use utf-8 characters for the strains graph
|
||||
|
24
UNLICENSE
Normal file
24
UNLICENSE
Normal file
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
67
appveyor.yml
Normal file
67
appveyor.yml
Normal file
|
@ -0,0 +1,67 @@
|
|||
version: b{build}
|
||||
image: Visual Studio 2017
|
||||
cache: test/test_suite -> test/suite_url
|
||||
build_script:
|
||||
- ps: >-
|
||||
function VcVars {
|
||||
param ([string]$VcPath, [string]$BatName)
|
||||
Push-Location $VcPath
|
||||
cmd /c ($BatName + "&set") |
|
||||
ForEach-Object {
|
||||
if ($_ -match "=") {
|
||||
$v = $_.split("="); set-item -force -path "ENV:\$($v[0])" -value "$($v[1])"
|
||||
}
|
||||
}
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
|
||||
$VcPath = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build"
|
||||
|
||||
|
||||
VcVars $VcPath vcvars32.bat
|
||||
|
||||
.\release.ps1; if (-not $?) { exit $LastExitCode }
|
||||
|
||||
|
||||
VcVars $VcPath vcvars64.bat
|
||||
|
||||
.\release.ps1; if (-not $?) { exit $LastExitCode }
|
||||
|
||||
|
||||
cd .\test
|
||||
|
||||
.\download_suite.ps1
|
||||
|
||||
.\build.bat; if (-not $?) { exit $LastExitCode }
|
||||
|
||||
.\oppai_test.exe; if (-not $?) { exit $LastExitCode }
|
||||
|
||||
Write-Host "bin size: " + (Get-Item '.\oppai_test.exe').length
|
||||
|
||||
dumpbin /dependents .\oppai_test.exe
|
||||
|
||||
.\build.bat ..\oppai.lib; if (-not $?) { exit $LastExitCode }
|
||||
|
||||
cp ..\oppai.dll .
|
||||
|
||||
.\oppai_test.exe; if (-not $?) { exit $LastExitCode }
|
||||
|
||||
Write-Host "bin size: " + (Get-Item '.\oppai_test.exe').length
|
||||
|
||||
dumpbin /dependents .\oppai_test.exe
|
||||
test: off
|
||||
artifacts:
|
||||
- path: oppai-*-windows-*.zip
|
||||
name: windows-binaries
|
||||
deploy:
|
||||
- provider: GitHub
|
||||
tag: $(appveyor_repo_tag_name)
|
||||
release: oppai $(appveyor_repo_tag_name)-$(appveyor_build_version)
|
||||
description: linux binaries are manually uploaded shortly after the windows release and statically linked against musl libc\n\nwindows binaries should not require the c runtime\n\nx64 and x86_64 mean 64-bit i586 and x86 mean 32-bit\n\nthe binary packages include the source code inside the src directory
|
||||
auth_token:
|
||||
secure: k73tV2NZTFp4thujp/KiohNwRwIpWC12gU/qsnfCqlctcC+rqWiDWet3sSAz34gT
|
||||
artifact: windows-binaries
|
||||
force_update: true
|
||||
on:
|
||||
APPVEYOR_REPO_TAG: true
|
6
build
Executable file
6
build
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
dir="$(dirname "$0")"
|
||||
. "$dir"/cflags
|
||||
$cc $cflags "$@" -DOPPAI_IMPLEMENTATION main.c oppai.c $ldflags -o oppai
|
||||
|
12
build.bat
Normal file
12
build.bat
Normal file
|
@ -0,0 +1,12 @@
|
|||
@echo off
|
||||
|
||||
del oppai.exe >nul 2>&1
|
||||
del main.obj >nul 2>&1
|
||||
cl -D_CRT_SECURE_NO_WARNINGS=1 ^
|
||||
-DNOMINMAX=1 ^
|
||||
-O2 -nologo -MT -Gm- -GR- -EHsc -W4 ^
|
||||
-DOPPAI_IMPLEMENTATION ^
|
||||
-DOPPAI_STATIC_HEADER ^
|
||||
main.c oppai.c ^
|
||||
-Feoppai.exe ^
|
||||
|| EXIT /B 1
|
45
cflags
Executable file
45
cflags
Executable file
|
@ -0,0 +1,45 @@
|
|||
#!/bin/sh
|
||||
|
||||
cflags="-std=c89 -pedantic"
|
||||
cflags="$cflags -Os"
|
||||
cflags="$cflags -fno-strict-aliasing"
|
||||
cflags="$cflags -Wall"
|
||||
cflags="$cflags -ffunction-sections -fdata-sections"
|
||||
if [ -z $DBGINFO ]; then
|
||||
cflags="$cflags -g0 -fno-unwind-tables -s"
|
||||
cflags="$cflags -fno-asynchronous-unwind-tables"
|
||||
cflags="$cflags -fno-stack-protector"
|
||||
else
|
||||
cflags="$cflags -g -fsanitize=address -fsanitize=leak "
|
||||
cflags="$cflags -fsanitize=signed-integer-overflow -fsanitize=undefined -static-libasan"
|
||||
fi
|
||||
if [ $(uname) = "Darwin" ]; then
|
||||
cflags="$cflags -Wl,-dead_strip"
|
||||
else
|
||||
cflags="$cflags -Wl,--gc-sections,--build-id=none"
|
||||
fi
|
||||
|
||||
ldflags="-lm"
|
||||
|
||||
cflags="$cflags $CFLAGS"
|
||||
ldflags="$ldflags $LDFLAGS"
|
||||
|
||||
cc="$CC"
|
||||
|
||||
if [ $(uname) = "Darwin" ]; then
|
||||
cc=${cc:-clang}
|
||||
else
|
||||
cc=${cc:-gcc}
|
||||
fi
|
||||
|
||||
uname -a > flags.log
|
||||
echo $cc >> flags.log
|
||||
echo $cflags >> flags.log
|
||||
echo $ldflags >> flags.log
|
||||
$cc --version >> flags.log
|
||||
$cc -dumpmachine >> flags.log
|
||||
|
||||
export cflags="$cflags"
|
||||
export ldflags="$ldflags"
|
||||
export cc="$cc"
|
||||
|
26
examples/FFI.cs
Normal file
26
examples/FFI.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
// csc FFI.cs
|
||||
// FFI.exe /path/to/file.osu
|
||||
// make sure oppai.dll is in the same directory as FFI.exe
|
||||
// see oppai.c for a full list of functions
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public class Program
|
||||
{
|
||||
[DllImport(@"oppai.dll")]
|
||||
public static extern IntPtr ezpp_new();
|
||||
|
||||
[DllImport(@"oppai.dll")]
|
||||
public static extern IntPtr ezpp(IntPtr ez, char[] map);
|
||||
|
||||
[DllImport(@"oppai.dll")]
|
||||
public static extern float ezpp_pp(IntPtr ez);
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
IntPtr ez = ezpp_new();
|
||||
ezpp(ez, args[0].ToCharArray());
|
||||
Console.WriteLine($"{ezpp_pp(ez)} pp");
|
||||
}
|
||||
}
|
133
examples/binary.c
Normal file
133
examples/binary.c
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* example of parsing oppai's binary output
|
||||
*
|
||||
* gcc binary.c
|
||||
* oppai /path/to/file.osu -obinary | ./a.out
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* these are only necessary to ensure endian-ness, if you don't
|
||||
* care about that you can read values like v = *(int*)p
|
||||
*/
|
||||
|
||||
int read2(char** c) {
|
||||
unsigned char* p = (unsigned char*)*c;
|
||||
*c += 2;
|
||||
return p[0] | (p[1] << 8);
|
||||
}
|
||||
|
||||
int read4(char** c) {
|
||||
unsigned char* p = (unsigned char*)*c;
|
||||
*c += 4;
|
||||
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
|
||||
}
|
||||
|
||||
float read_flt(char** p) {
|
||||
int v = read4(p);
|
||||
float* pf = (float*)&v;
|
||||
return *pf;
|
||||
}
|
||||
|
||||
char* read_str(char** p, int* len) {
|
||||
char* res;
|
||||
*len = read2(p);
|
||||
res = *p;
|
||||
*p += *len + 1;
|
||||
return res;
|
||||
}
|
||||
|
||||
#define MODS_NF (1<<0)
|
||||
#define MODS_EZ (1<<1)
|
||||
#define MODS_HD (1<<3)
|
||||
#define MODS_HR (1<<4)
|
||||
#define MODS_DT (1<<6)
|
||||
#define MODS_HT (1<<8)
|
||||
#define MODS_NC (1<<9)
|
||||
#define MODS_FL (1<<10)
|
||||
#define MODS_SO (1<<12)
|
||||
|
||||
int main() {
|
||||
char buf[8192];
|
||||
char* p = buf;
|
||||
int len;
|
||||
int result;
|
||||
int mods;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
/* read stdin in binary mode */
|
||||
if (!freopen(0, "rb", stdin)) {
|
||||
perror("freopen");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!fread(buf, 1, sizeof(buf), stdin)) {
|
||||
perror("fread");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strncmp((char const*)p, "binoppai", 8)) {
|
||||
puts("invalid input");
|
||||
return 1;
|
||||
}
|
||||
p += 8;
|
||||
|
||||
printf("oppai %d.%d.%d\n", p[0], p[1], p[2]);
|
||||
p += 3;
|
||||
puts("");
|
||||
|
||||
/* error code */
|
||||
result = read4(&p);
|
||||
if (result < 0) {
|
||||
printf("error %d\n", result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("artist: %s\n", read_str(&p, &len));
|
||||
printf("artist_unicode: %s\n", read_str(&p, &len));
|
||||
printf("title: %s\n", read_str(&p, &len));
|
||||
printf("title_unicode: %s\n", read_str(&p, &len));
|
||||
printf("version: %s\n", read_str(&p, &len));
|
||||
printf("creator: %s\n", read_str(&p, &len));
|
||||
|
||||
mods = read4(&p);
|
||||
puts("");
|
||||
printf("mods: ");
|
||||
if (mods & MODS_NF) printf("NF");
|
||||
if (mods & MODS_EZ) printf("EZ");
|
||||
if (mods & MODS_HD) printf("HD");
|
||||
if (mods & MODS_HR) printf("HR");
|
||||
if (mods & MODS_DT) printf("DT");
|
||||
if (mods & MODS_HT) printf("HT");
|
||||
if (mods & MODS_NC) printf("NC");
|
||||
if (mods & MODS_FL) printf("FL");
|
||||
if (mods & MODS_SO) printf("SO");
|
||||
puts("");
|
||||
|
||||
printf("OD%g ", read_flt(&p));
|
||||
printf("AR%g ", read_flt(&p));
|
||||
printf("CS%g ", read_flt(&p));
|
||||
printf("HP%g\n", read_flt(&p));
|
||||
printf("%d/%dx\n", read4(&p), read4(&p));
|
||||
printf("%d circles ", read2(&p));
|
||||
printf("%d sliders ", read2(&p));
|
||||
printf("%d spinners\n", read2(&p));
|
||||
printf("scorev%d\n", read4(&p));
|
||||
puts("");
|
||||
printf("%g stars ", read_flt(&p));
|
||||
printf("(%g speed, ", read_flt(&p));
|
||||
printf("%g aim)\n", read_flt(&p));
|
||||
read2(&p); /* legacy */
|
||||
read2(&p); /* legacy */
|
||||
puts("");
|
||||
printf("%g aim pp\n", read_flt(&p));
|
||||
printf("%g speed pp\n", read_flt(&p));
|
||||
printf("%g acc pp\n", read_flt(&p));
|
||||
puts("");
|
||||
printf("%g pp\n", read_flt(&p));
|
||||
|
||||
return 0;
|
||||
}
|
16
examples/min.c
Normal file
16
examples/min.c
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* gcc min.c -lm
|
||||
* cat /path/to/file.osu | ./a.out
|
||||
*/
|
||||
|
||||
#define OPPAI_IMPLEMENTATION
|
||||
#include "../oppai.c"
|
||||
|
||||
int main() {
|
||||
ezpp_t ez = ezpp_new();
|
||||
ezpp_set_mods(ez, MODS_HD | MODS_DT);
|
||||
ezpp(ez, "-");
|
||||
printf("%gpp\n", ezpp_pp(ez));
|
||||
return 0;
|
||||
}
|
||||
|
61
examples/reuse.c
Normal file
61
examples/reuse.c
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* gcc reuse.c -lm
|
||||
* ./a.out /path/to/file.osu
|
||||
*/
|
||||
|
||||
#define OPPAI_IMPLEMENTATION
|
||||
#include "../oppai.c"
|
||||
|
||||
/*
|
||||
* for better performance, the same instance can be reused
|
||||
* settings are remembered and map is only reparsed if mods or cs change
|
||||
*/
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
ezpp_t ez = ezpp_new();
|
||||
ezpp_set_autocalc(ez, 1); /* autorecalc pp when changing any parameter */
|
||||
ezpp(ez, argv[1]);
|
||||
|
||||
puts("---");
|
||||
puts("nomod fc");
|
||||
printf("%gpp\n", ezpp_pp(ez));
|
||||
puts("---");
|
||||
|
||||
puts("nomod 95% fc");
|
||||
ezpp_set_accuracy_percent(ez, 95);
|
||||
printf("%gpp\n", ezpp_pp(ez));
|
||||
puts("---");
|
||||
|
||||
puts("nomod 1x100 fc");
|
||||
ezpp_set_accuracy(ez, 1, 0);
|
||||
printf("%gpp\n", ezpp_pp(ez));
|
||||
puts("---");
|
||||
|
||||
puts("HD 1x100 1miss 300x");
|
||||
ezpp_set_mods(ez, MODS_HD);
|
||||
ezpp_set_nmiss(ez, 1);
|
||||
ezpp_set_combo(ez, 300);
|
||||
printf("%gpp\n", ezpp_pp(ez));
|
||||
puts("---");
|
||||
|
||||
puts("HDDT 1x100 1xmiss 300x");
|
||||
ezpp_set_mods(ez, MODS_HD | MODS_DT);
|
||||
printf("%gpp\n", ezpp_pp(ez));
|
||||
puts("---");
|
||||
|
||||
puts("HDDT 1x100 1xmiss 300x ends at object 300");
|
||||
ezpp_set_end(ez, 300);
|
||||
printf("%gpp\n", ezpp_pp(ez));
|
||||
puts("---");
|
||||
|
||||
puts("HDDT fc");
|
||||
ezpp_set_end(ez, 0);
|
||||
ezpp_set_combo(ez, -1);
|
||||
ezpp_set_accuracy(ez, 0, 0);
|
||||
ezpp_set_nmiss(ez, 0);
|
||||
printf("%gpp\n", ezpp_pp(ez));
|
||||
puts("---");
|
||||
|
||||
ezpp_free(ez);
|
||||
return 0;
|
||||
}
|
42
examples/reuse_mem.c
Normal file
42
examples/reuse_mem.c
Normal file
|
@ -0,0 +1,42 @@
|
|||
#define OPPAI_IMPLEMENTATION
|
||||
#include "oppai.c"
|
||||
|
||||
char buf[1000000];
|
||||
int mods[] = { 0, MODS_HR, MODS_HD | MODS_HR, MODS_DT, MODS_HD | MODS_DT };
|
||||
#define N_MODS (sizeof(mods) / sizeof(mods[0]))
|
||||
|
||||
void print_mods(int mods) {
|
||||
putchar('+');
|
||||
if (!mods) puts("nomod");
|
||||
else {
|
||||
if (mods & MODS_HD) printf("hd");
|
||||
if (mods & MODS_HR) printf("hr");
|
||||
if (mods & MODS_DT) printf("dt");
|
||||
puts("");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int i, j, n, acc;
|
||||
ezpp_t ez = ezpp_new();
|
||||
ezpp_set_autocalc(ez, 1);
|
||||
for (i = 1; i < argc; ++i) {
|
||||
FILE* f = fopen(argv[i], "r");
|
||||
n = fread(buf, 1, sizeof(buf), f);
|
||||
fclose(f);
|
||||
ezpp_data(ez, buf, n);
|
||||
printf("%s - %s [%s]\n", ezpp_artist(ez), ezpp_title(ez),
|
||||
ezpp_version(ez));
|
||||
for (j = 0; j < N_MODS; ++j) {
|
||||
print_mods(mods[j]);
|
||||
ezpp_set_mods(ez, mods[j]);
|
||||
for (acc = 95; acc <= 100; ++acc) {
|
||||
ezpp_set_accuracy_percent(ez, acc);
|
||||
printf("%d%% -> %gpp\n", acc, ezpp_pp(ez));
|
||||
}
|
||||
}
|
||||
puts("");
|
||||
}
|
||||
ezpp_free(ez);
|
||||
return 0;
|
||||
}
|
1360
kradness&Reol - Remote Control (Taeyang) [Max Control!].osu
Normal file
1360
kradness&Reol - Remote Control (Taeyang) [Max Control!].osu
Normal file
File diff suppressed because it is too large
Load Diff
37
libbuild
Executable file
37
libbuild
Executable file
|
@ -0,0 +1,37 @@
|
|||
#!/bin/sh
|
||||
|
||||
dir="$(dirname "$0")"
|
||||
. "$dir"/cflags
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
|
||||
hide_unnecessary_symbols() {
|
||||
[ ! -d "$tmp" ] && echo "W: couldn't find tmp dir" && return
|
||||
gcc_syms="$tmp/gcc_exports.sym"
|
||||
clang_syms="$tmp/clang_exports.list"
|
||||
code="$tmp/main.c"
|
||||
echo "int main() { return 0; }" >> "$code"
|
||||
exports='ezpp ezpp_ errstr oppai_'
|
||||
( printf '{global:'
|
||||
for e in $exports; do
|
||||
printf '%s;' "$e"
|
||||
done
|
||||
printf 'local:*;};' ) | sed 's/_;/_*;/g' >"$gcc_syms"
|
||||
echo "$exports" | tr ' ' '\n' | sed s/_$/_*/g > "$clang_syms"
|
||||
for flags in "-Wl,--version-script=$gcc_syms" \
|
||||
"-Wl,-exported_symbols_list,$clang_syms"
|
||||
do
|
||||
if "$cc" $flags "$code" -o /dev/null >/dev/null 2>&1; then
|
||||
ldflags="$ldflags $flags"
|
||||
return
|
||||
fi
|
||||
done
|
||||
echo "W: can't figure out how to hide unnecessary symbols"
|
||||
}
|
||||
|
||||
hide_unnecessary_symbols
|
||||
|
||||
$cc -shared $cflags -DOPPAI_EXPORT \
|
||||
"$@" oppai.c $ldflags -fpic -o liboppai.so
|
||||
|
||||
[ -d "$tmp" ] && rm -rf "$tmp"
|
14
libbuild.bat
Normal file
14
libbuild.bat
Normal file
|
@ -0,0 +1,14 @@
|
|||
@echo off
|
||||
|
||||
del oppai.dll >nul 2>&1
|
||||
del oppai.obj >nul 2>&1
|
||||
del oppai.exp >nul 2>&1
|
||||
echo compiling
|
||||
cl ^
|
||||
/c /O2 /nologo /Gm- /GR- /EHsc /W4 /Gz ^
|
||||
/D_CRT_SECURE_NO_WARNINGS=1 /DNOMINMAX=1 ^
|
||||
/DOPPAI_EXPORT /D_WINDLL /D_USRDLL ^
|
||||
oppai.c ^
|
||||
|| EXIT /B 1
|
||||
echo making dll
|
||||
link /OUT:oppai.dll /IMPLIB:oppai.lib /NOLOGO /DLL oppai.obj
|
966
main.c
Normal file
966
main.c
Normal file
|
@ -0,0 +1,966 @@
|
|||
/*
|
||||
* this is free and unencumbered software released into the
|
||||
* public domain.
|
||||
*
|
||||
* refer to the attached UNLICENSE or http://unlicense.org/
|
||||
* ----------------------------------------------------------------
|
||||
* command line interface for oppai
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
#undef OPPAI_EXPORT
|
||||
#undef OPPAI_IMPLEMENTATION
|
||||
#include "oppai.c"
|
||||
|
||||
char* me = "oppai";
|
||||
|
||||
#define al_round(x) (float)floor((x) + 0.5f)
|
||||
#define al_min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define al_max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define twodec(x) (al_round((x) * 100.0f) / 100.0f)
|
||||
#define array_len(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
static
|
||||
float get_inf() {
|
||||
static unsigned raw = 0x7F800000;
|
||||
float* p = (float*)&raw;
|
||||
return *p;
|
||||
}
|
||||
|
||||
static
|
||||
int is_nan(float b) {
|
||||
int* p = (int*)&b;
|
||||
return (
|
||||
(*p > 0x7F800000 && *p < 0x80000000) ||
|
||||
(*p > 0x7FBFFFFF && *p <= 0xFFFFFFFF)
|
||||
);
|
||||
}
|
||||
|
||||
static
|
||||
int info(char* fmt, ...) {
|
||||
int res;
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
res = vfprintf(stderr, fmt, va);
|
||||
va_end(va);
|
||||
return res;
|
||||
}
|
||||
|
||||
void usage() {
|
||||
/* logo by flesnuk https://github.com/Francesco149/oppai-ng/issues/10 */
|
||||
|
||||
info(
|
||||
" /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb"
|
||||
"\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ /\xe2\x8e\xbb\xe2"
|
||||
"\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb"
|
||||
"\xe2\x8e\xbb/ /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2"
|
||||
"\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ /\xe2\x8e"
|
||||
"\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2"
|
||||
"\x8e\xbb\xe2\x8e\xbb/ /\xe2\x8e\xbb/ /\xe2\x8e\xbb"
|
||||
"\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\\ /\xe2\x8e\xbb"
|
||||
"\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e"
|
||||
"\xbb\xe2\x8e\xbb/\n / /\xe2\x8e\xbb\xe2\x8e\xbb\xe2"
|
||||
"\x8e\xbb/ / / /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ / "
|
||||
);
|
||||
|
||||
info(
|
||||
"/ /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ / \xe2\x8e\xbb"
|
||||
"\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ / / / "
|
||||
"___ / /\xe2\x8e\xbb\xe2\x8e\xbb\\ \\ / /\xe2\x8e\xbb"
|
||||
"\xe2\x8e\xbb\xe2\x8e\xbb/ /\n / / / / / / / / / /"
|
||||
" / / /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb"
|
||||
"\xe2\x8e\xbb/ / / / /__/ / / / / / / / /\n / /___/ "
|
||||
"/ / /___/ / / /___/ / / /___/ / / / / / / / / /__"
|
||||
"_/ /\n /_______/ / ______/ / ______/ /_______/ /_/ "
|
||||
"/_/ /_/ /_____ /\n / / / / "
|
||||
" / / \n / / /"
|
||||
" / /\xe2\x8e\xbb/___/"
|
||||
" / \n /_/ /_/ "
|
||||
" /_______/"
|
||||
);
|
||||
|
||||
info("\n\n");
|
||||
info("usage: %s /path/to/file.osu parameters\n\n", me);
|
||||
|
||||
info(
|
||||
"set filename to '-' to read from standard input\n"
|
||||
"all parameters are case insensitive\n"
|
||||
"\n"
|
||||
"-o[output_module]\n"
|
||||
" output module. pass ? to list modules (oppai - -o?)\n"
|
||||
" default: text\n"
|
||||
" example: -ojson\n"
|
||||
"\n"
|
||||
"[accuracy]%%\n"
|
||||
" accuracy percentage\n"
|
||||
" default: 100%%\n"
|
||||
" example: 95%%\n"
|
||||
"\n"
|
||||
"[n]x100\n"
|
||||
" amount of 100s\n"
|
||||
" default: 0\n"
|
||||
" example: 2x100\n"
|
||||
"\n"
|
||||
);
|
||||
|
||||
info(
|
||||
"[n]x50\n"
|
||||
" amount of 50s\n"
|
||||
" default: 0\n"
|
||||
" example: 2x50\n"
|
||||
"\n"
|
||||
"[n]xm\n"
|
||||
"[n]xmiss\n"
|
||||
"[n]m\n"
|
||||
" amount of misses\n"
|
||||
" default: 0\n"
|
||||
" example: 1m\n"
|
||||
"\n"
|
||||
"[combo]x\n"
|
||||
" highest combo achieved\n"
|
||||
" default: full combo (calculated from map data)\n"
|
||||
" example: 500x\n"
|
||||
"\n"
|
||||
"scorev[n]\n"
|
||||
" scoring system\n"
|
||||
" default: 1\n"
|
||||
" example: scorev2\n"
|
||||
"\n"
|
||||
);
|
||||
|
||||
info(
|
||||
"ar[n]\n"
|
||||
" base approach rate override\n"
|
||||
" default: map's base approach rate\n"
|
||||
" example: AR5\n"
|
||||
"\n"
|
||||
"od[n]\n"
|
||||
" base overall difficulty override\n"
|
||||
" default: map's base overall difficulty\n"
|
||||
" example: OD10\n"
|
||||
"\n"
|
||||
"cs[n]\n"
|
||||
" base circle size override\n"
|
||||
" default: map's base circle size\n"
|
||||
" example: CS6.5\n"
|
||||
"\n"
|
||||
);
|
||||
|
||||
info(
|
||||
"-m[n]\n"
|
||||
" gamemode id override for converted maps\n"
|
||||
" default: uses the map's gamemode\n"
|
||||
" example: -m1\n"
|
||||
"\n"
|
||||
"-taiko\n"
|
||||
" forces gamemode to taiko for converted maps\n"
|
||||
" default: disabled\n"
|
||||
"\n"
|
||||
"-touch\n"
|
||||
" calculates pp for touchscreen / touch devices. can \n"
|
||||
" also be specified as mod TD\n"
|
||||
"\n"
|
||||
"[n]speed\n"
|
||||
" override speed stars. "
|
||||
"useful for maps with incorrect star rating\n"
|
||||
" default: uses computed speed stars\n"
|
||||
" example: 3.5speed\n"
|
||||
"\n"
|
||||
);
|
||||
|
||||
info(
|
||||
"[n]aim\n"
|
||||
" override aim stars. "
|
||||
"useful for maps with incorrect star rating\n"
|
||||
" default: uses computed aim stars\n"
|
||||
" example: 2.4aim\n"
|
||||
"\n"
|
||||
"-end[n]\n"
|
||||
" cuts map to a certain number of objects\n"
|
||||
);
|
||||
}
|
||||
|
||||
#define output_sig(name) void name(int result, ezpp_t ez, char* mods_str)
|
||||
|
||||
typedef output_sig(fnoutput);
|
||||
|
||||
/* null output --------------------------------------------------------- */
|
||||
|
||||
/* stdout must be left alone, outputting to stderr is fine tho */
|
||||
output_sig(output_null) { (void)result; (void)ez; (void)mods_str; }
|
||||
|
||||
/* text output --------------------------------------------------------- */
|
||||
|
||||
#define ASCIIPLT_W 51
|
||||
|
||||
void asciiplt(float (* getvalue)(void* data, int i), int n, void* data) {
|
||||
static char* charset[] = {
|
||||
#ifdef OPPAI_UTF8GRAPH
|
||||
"\xe2\x96\x81",
|
||||
"\xe2\x96\x82",
|
||||
"\xe2\x96\x83",
|
||||
"\xe2\x96\x84",
|
||||
"\xe2\x96\x85",
|
||||
"\xe2\x96\x86",
|
||||
"\xe2\x96\x87",
|
||||
"\xe2\x96\x88"
|
||||
#else
|
||||
" ", "_", ".", "-", "^"
|
||||
#endif
|
||||
};
|
||||
|
||||
static int charsetsize = array_len(charset);
|
||||
|
||||
float values[ASCIIPLT_W];
|
||||
float minval = (float)get_inf();
|
||||
float maxval = (float)-get_inf();
|
||||
float range;
|
||||
int i;
|
||||
int chunksize;
|
||||
int w = al_min(ASCIIPLT_W, n);
|
||||
|
||||
memset(values, 0, sizeof(values));
|
||||
chunksize = (int)ceil((float)n / w);
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
int chunki = i / chunksize;
|
||||
values[chunki] = al_max(values[chunki], getvalue(data, i));
|
||||
}
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
int chunki = i / chunksize;
|
||||
maxval = al_max(maxval, values[chunki]);
|
||||
minval = al_min(minval, values[chunki]);
|
||||
}
|
||||
|
||||
range = al_max(0.00001f, maxval - minval);
|
||||
|
||||
for (i = 0; i < w; ++i) {
|
||||
int chari = (int)(((values[i] - minval) / range) * charsetsize);
|
||||
chari = al_max(0, al_min(chari, charsetsize - 1));
|
||||
printf("%s", charset[chari]);
|
||||
}
|
||||
|
||||
puts("");
|
||||
}
|
||||
|
||||
float getaim(void* data, int i) {
|
||||
ezpp_t ez = data;
|
||||
return ezpp_strain_at(ez, i, DIFF_AIM);
|
||||
}
|
||||
|
||||
float getspeed(void* data, int i) {
|
||||
ezpp_t ez = data;
|
||||
return ezpp_strain_at(ez, i, DIFF_SPEED);
|
||||
}
|
||||
|
||||
output_sig(output_text) {
|
||||
float ar, od, cs, hp, stars, aim_stars, speed_stars, accuracy_percent;
|
||||
float pp, aim_pp, speed_pp, acc_pp;
|
||||
|
||||
if (result < 0) {
|
||||
puts(errstr(result));
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s - %s ", ezpp_artist(ez), ezpp_title(ez));
|
||||
|
||||
if (strcmp(ezpp_artist(ez), ezpp_artist_unicode(ez)) ||
|
||||
strcmp(ezpp_title(ez), ezpp_title_unicode(ez)))
|
||||
{
|
||||
printf("(%s - %s) ", ezpp_artist_unicode(ez), ezpp_title_unicode(ez));
|
||||
}
|
||||
|
||||
printf("[%s] mapped by %s ", ezpp_version(ez), ezpp_creator(ez));
|
||||
puts("\n");
|
||||
|
||||
ar = twodec(ezpp_ar(ez));
|
||||
od = twodec(ezpp_od(ez));
|
||||
cs = twodec(ezpp_cs(ez));
|
||||
hp = twodec(ezpp_hp(ez));
|
||||
stars = twodec(ezpp_stars(ez));
|
||||
aim_stars = twodec(ezpp_aim_stars(ez));
|
||||
speed_stars = twodec(ezpp_speed_stars(ez));
|
||||
accuracy_percent = twodec(ezpp_accuracy_percent(ez));
|
||||
pp = twodec(ezpp_pp(ez));
|
||||
aim_pp = twodec(ezpp_aim_pp(ez));
|
||||
speed_pp = twodec(ezpp_speed_pp(ez));
|
||||
acc_pp = twodec(ezpp_acc_pp(ez));
|
||||
|
||||
printf("AR%g OD%g ", ar, od);
|
||||
|
||||
if (ezpp_mode(ez) == MODE_STD) {
|
||||
printf("CS%g ", cs);
|
||||
}
|
||||
|
||||
printf("HP%g\n", hp);
|
||||
printf("300 hitwindow: %g ms\n", ezpp_odms(ez));
|
||||
|
||||
printf("%d circles, %d sliders, %d spinners\n",
|
||||
ezpp_ncircles(ez), ezpp_nsliders(ez), ezpp_nspinners(ez));
|
||||
|
||||
if (ezpp_mode(ez) == MODE_STD) {
|
||||
printf("%g stars (%g aim, %g speed)\n", stars, aim_stars, speed_stars);
|
||||
printf("\nspeed strain: ");
|
||||
asciiplt(getspeed, ezpp_nobjects(ez), ez);
|
||||
printf(" aim strain: ");
|
||||
asciiplt(getaim, ezpp_nobjects(ez), ez);
|
||||
} else {
|
||||
printf("%g stars\n", ezpp_stars(ez));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
if (mods_str) {
|
||||
printf("+%s ", mods_str);
|
||||
}
|
||||
|
||||
printf("%d/%dx ", ezpp_combo(ez), ezpp_max_combo(ez));
|
||||
printf("%g%%\n", accuracy_percent);
|
||||
printf("%g pp (", pp);
|
||||
|
||||
if (ezpp_mode(ez) == MODE_STD) {
|
||||
printf("%g aim, ", aim_pp);
|
||||
}
|
||||
|
||||
printf("%g speed, ", speed_pp);
|
||||
printf("%g acc)\n\n", acc_pp);
|
||||
}
|
||||
|
||||
/* json output --------------------------------------------------------- */
|
||||
|
||||
void print_escaped_json_string_ex(char* str, int quotes) {
|
||||
char* chars_to_escape = "\\\"";
|
||||
char* p;
|
||||
if (quotes) {
|
||||
putchar('"');
|
||||
}
|
||||
for (; *str; ++str) {
|
||||
/* escape all characters in chars_to_escape */
|
||||
for (p = chars_to_escape; *p; ++p) {
|
||||
if (*p == *str) {
|
||||
putchar('\\');
|
||||
}
|
||||
}
|
||||
putchar(*str);
|
||||
}
|
||||
if (quotes) {
|
||||
putchar('"');
|
||||
}
|
||||
}
|
||||
|
||||
#define print_escaped_json_string(x) \
|
||||
print_escaped_json_string_ex(x, 1)
|
||||
|
||||
/* https://www.doc.ic.ac.uk/%7Eeedwards/compsys/float/nan.html */
|
||||
|
||||
static int is_inf(float b) {
|
||||
int* p = (int*)&b;
|
||||
return *p == 0x7F800000 || *p == 0xFF800000;
|
||||
}
|
||||
|
||||
/*
|
||||
* json is mentally challenged and can't handle inf and nan so
|
||||
* we're gonna be mathematically incorrect
|
||||
*/
|
||||
void fix_json_flt(float* v) {
|
||||
if (is_inf(*v)) {
|
||||
*v = -1;
|
||||
} else if (is_nan(*v)) {
|
||||
*v = 0;
|
||||
}
|
||||
}
|
||||
|
||||
output_sig(output_json) {
|
||||
float pp, aim_pp, speed_pp, acc_pp, stars, aim_stars, speed_stars;
|
||||
printf("{\"oppai_version\":\"%s\",", oppai_version_str());
|
||||
|
||||
if (result < 0) {
|
||||
printf("\"code\":%d,", result);
|
||||
printf("\"errstr\":");
|
||||
print_escaped_json_string(errstr(result));
|
||||
printf("}");
|
||||
return;
|
||||
}
|
||||
|
||||
pp = ezpp_pp(ez);
|
||||
aim_pp = ezpp_aim_pp(ez);
|
||||
speed_pp = ezpp_speed_pp(ez);
|
||||
acc_pp = ezpp_acc_pp(ez);
|
||||
stars = ezpp_stars(ez);
|
||||
aim_stars = ezpp_aim_stars(ez);
|
||||
speed_stars = ezpp_speed_stars(ez);
|
||||
fix_json_flt(&pp);
|
||||
fix_json_flt(&aim_pp);
|
||||
fix_json_flt(&speed_pp);
|
||||
fix_json_flt(&acc_pp);
|
||||
fix_json_flt(&stars);
|
||||
fix_json_flt(&aim_stars);
|
||||
fix_json_flt(&speed_stars);
|
||||
|
||||
printf("\"code\":200,\"errstr\":\"no error\",");
|
||||
|
||||
printf("\"artist\":");
|
||||
print_escaped_json_string(ezpp_artist(ez));
|
||||
|
||||
if (strcmp(ezpp_artist(ez), ezpp_artist_unicode(ez))) {
|
||||
printf(",\"artist_unicode\":");
|
||||
print_escaped_json_string(ezpp_artist_unicode(ez));
|
||||
}
|
||||
|
||||
printf(",\"title\":");
|
||||
print_escaped_json_string(ezpp_title(ez));
|
||||
|
||||
if (strcmp(ezpp_title(ez), ezpp_title_unicode(ez))) {
|
||||
printf(",\"title_unicode\":");
|
||||
print_escaped_json_string(ezpp_title_unicode(ez));
|
||||
}
|
||||
|
||||
printf(",\"creator\":");
|
||||
print_escaped_json_string(ezpp_creator(ez));
|
||||
|
||||
printf(",\"version\":");
|
||||
print_escaped_json_string(ezpp_version(ez));
|
||||
|
||||
printf(",");
|
||||
|
||||
if (!mods_str) {
|
||||
mods_str = "";
|
||||
}
|
||||
|
||||
printf(
|
||||
"\"mods_str\":\"%s\",\"mods\":%d,"
|
||||
"\"od\":%g,\"ar\":%g,\"cs\":%g,\"hp\":%g,"
|
||||
"\"combo\":%d,\"max_combo\":%d,"
|
||||
"\"num_circles\":%d,\"num_sliders\":%d,"
|
||||
"\"num_spinners\":%d,\"misses\":%d,"
|
||||
"\"score_version\":%d,\"stars\":%.17g,"
|
||||
"\"speed_stars\":%.17g,\"aim_stars\":%.17g,"
|
||||
"\"aim_pp\":%.17g,\"speed_pp\":%.17g,\"acc_pp\":%.17g,"
|
||||
"\"pp\":%.17g}",
|
||||
mods_str, ezpp_mods(ez), ezpp_od(ez), ezpp_ar(ez),
|
||||
ezpp_cs(ez), ezpp_hp(ez), ezpp_combo(ez),
|
||||
ezpp_max_combo(ez), ezpp_ncircles(ez), ezpp_nsliders(ez),
|
||||
ezpp_nspinners(ez), ezpp_nmiss(ez), ezpp_score_version(ez),
|
||||
ezpp_stars(ez), ezpp_speed_stars(ez), ezpp_aim_stars(ez),
|
||||
ezpp_aim_pp(ez), ezpp_speed_pp(ez), ezpp_acc_pp(ez), ezpp_pp(ez)
|
||||
);
|
||||
}
|
||||
|
||||
/* csv output ---------------------------------------------------------- */
|
||||
|
||||
void print_escaped_csv_string(char* str) {
|
||||
char* chars_to_escape = "\\;";
|
||||
char* p;
|
||||
for (; *str; ++str) {
|
||||
/* escape all characters in chars_to_escape */
|
||||
for (p = chars_to_escape; *p; ++p) {
|
||||
if (*p == *str) {
|
||||
putchar('\\');
|
||||
}
|
||||
}
|
||||
putchar(*str);
|
||||
}
|
||||
}
|
||||
|
||||
output_sig(output_csv) {
|
||||
printf("oppai_version;%s\n", oppai_version_str());
|
||||
|
||||
if (result < 0) {
|
||||
printf("code;%d\nerrstr;", result);
|
||||
print_escaped_csv_string(errstr(result));
|
||||
return;
|
||||
}
|
||||
|
||||
printf("code;200\nerrstr;no error\n");
|
||||
|
||||
printf("artist;");
|
||||
print_escaped_csv_string(ezpp_artist(ez));
|
||||
puts("");
|
||||
|
||||
if (strcmp(ezpp_artist(ez), ezpp_artist_unicode(ez))) {
|
||||
printf("artist_unicode;");
|
||||
print_escaped_csv_string(ezpp_artist_unicode(ez));
|
||||
puts("");
|
||||
}
|
||||
|
||||
printf("title;");
|
||||
print_escaped_csv_string(ezpp_title(ez));
|
||||
puts("");
|
||||
|
||||
if (strcmp(ezpp_title(ez), ezpp_title_unicode(ez))) {
|
||||
printf("title_unicode;");
|
||||
print_escaped_csv_string(ezpp_title_unicode(ez));
|
||||
puts("");
|
||||
}
|
||||
|
||||
printf("version;");
|
||||
print_escaped_csv_string(ezpp_version(ez));
|
||||
puts("");
|
||||
|
||||
printf("creator;");
|
||||
print_escaped_csv_string(ezpp_creator(ez));
|
||||
puts("");
|
||||
|
||||
if (!mods_str) {
|
||||
mods_str = "";
|
||||
}
|
||||
|
||||
printf(
|
||||
"mods_str;%s\nmods;%d\nod;%g\nar;%g\ncs;%g\nhp;%g\n"
|
||||
"combo;%d\nmax_combo;%d\nnum_circles;%d\n"
|
||||
"num_sliders;%d\nnum_spinners;%d\nmisses;%d\n"
|
||||
"score_version;%d\nstars;%.17g\nspeed_stars;%.17g\n"
|
||||
"aim_stars;%.17g\naim_pp;%.17g\nspeed_pp;%.17g\nacc_pp;%.17g\npp;%.17g",
|
||||
mods_str, ezpp_mods(ez), ezpp_od(ez), ezpp_ar(ez),
|
||||
ezpp_cs(ez), ezpp_hp(ez), ezpp_combo(ez),
|
||||
ezpp_max_combo(ez), ezpp_ncircles(ez), ezpp_nsliders(ez),
|
||||
ezpp_nspinners(ez), ezpp_nmiss(ez), ezpp_score_version(ez),
|
||||
ezpp_stars(ez), ezpp_speed_stars(ez), ezpp_aim_stars(ez),
|
||||
ezpp_aim_pp(ez), ezpp_speed_pp(ez), ezpp_acc_pp(ez), ezpp_pp(ez)
|
||||
);
|
||||
}
|
||||
|
||||
/* binary output ------------------------------------------------------- */
|
||||
|
||||
void write1(int v) {
|
||||
char buf = (char)(v & 0xFF);
|
||||
fwrite(&buf, 1, 1, stdout);
|
||||
}
|
||||
|
||||
void write2(int v) {
|
||||
char buf[2];
|
||||
buf[0] = (char)(v & 0xFF);
|
||||
buf[1] = (char)(v >> 8);
|
||||
fwrite(buf, 1, 2, stdout);
|
||||
}
|
||||
|
||||
void write4(int v) {
|
||||
char buf[4];
|
||||
buf[0] = (char)(v & 0xFF);
|
||||
buf[1] = (char)((v >> 8) & 0xFF);
|
||||
buf[2] = (char)((v >> 16) & 0xFF);
|
||||
buf[3] = (char)((v >> 24) & 0xFF);
|
||||
fwrite(buf, 1, 4, stdout);
|
||||
}
|
||||
|
||||
void write_flt(float f) {
|
||||
int* p = (int*)&f;
|
||||
write4(*p);
|
||||
}
|
||||
|
||||
void write_str(char* str) {
|
||||
int len = al_min(0xFFFF, (int)strlen(str));
|
||||
write2(len);
|
||||
printf("%s", str);
|
||||
write1(0);
|
||||
}
|
||||
|
||||
output_sig(output_binary) {
|
||||
int major, minor, patch;
|
||||
(void)mods_str;
|
||||
|
||||
if (!freopen(0, "wb", stdout)) {
|
||||
perror("freopen");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("binoppai");
|
||||
oppai_version(&major, &minor, &patch);
|
||||
write1(major);
|
||||
write1(minor);
|
||||
write1(patch);
|
||||
write4(result);
|
||||
|
||||
if (result < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: use varargs to group calls of the same func */
|
||||
write_str(ezpp_artist(ez));
|
||||
write_str(ezpp_artist_unicode(ez));
|
||||
write_str(ezpp_title(ez));
|
||||
write_str(ezpp_title_unicode(ez));
|
||||
write_str(ezpp_version(ez));
|
||||
write_str(ezpp_creator(ez));
|
||||
write4(ezpp_mods(ez));
|
||||
write_flt(ezpp_od(ez));
|
||||
write_flt(ezpp_ar(ez));
|
||||
write_flt(ezpp_cs(ez));
|
||||
write_flt(ezpp_hp(ez));
|
||||
write4(ezpp_combo(ez));
|
||||
write4(ezpp_max_combo(ez));
|
||||
write2(ezpp_ncircles(ez));
|
||||
write2(ezpp_nsliders(ez));
|
||||
write2(ezpp_nspinners(ez));
|
||||
write4(ezpp_score_version(ez));
|
||||
write_flt(ezpp_stars(ez));
|
||||
write_flt(ezpp_speed_stars(ez));
|
||||
write_flt(ezpp_aim_stars(ez));
|
||||
write2(0); /* legacy (nsingles) */
|
||||
write2(0); /* legacy (nsigles_threshold) */
|
||||
write_flt(ezpp_aim_pp(ez));
|
||||
write_flt(ezpp_speed_pp(ez));
|
||||
write_flt(ezpp_acc_pp(ez));
|
||||
write_flt(ezpp_pp(ez));
|
||||
}
|
||||
|
||||
/* gnuplot output ------------------------------------------------------ */
|
||||
|
||||
#define gnuplot_string(x) print_escaped_json_string_ex(x, 0)
|
||||
|
||||
void gnuplot_strains(ezpp_t ez, int type) {
|
||||
int i;
|
||||
for (i = 0; i < ezpp_nobjects(ez); ++i) {
|
||||
printf("%.17g %.17g\n", ezpp_time_at(ez, i),
|
||||
ezpp_strain_at(ez, i, type));
|
||||
}
|
||||
}
|
||||
|
||||
output_sig(output_gnuplot) {
|
||||
if (result < 0 || ezpp_mode(ez) != MODE_STD) {
|
||||
return;
|
||||
}
|
||||
|
||||
puts("set encoding utf8;");
|
||||
|
||||
printf("set title \"");
|
||||
gnuplot_string(ezpp_artist(ez));
|
||||
printf(" - ");
|
||||
gnuplot_string(ezpp_title(ez));
|
||||
|
||||
if (strcmp(ezpp_artist(ez), ezpp_artist_unicode(ez)) ||
|
||||
strcmp(ezpp_title(ez), ezpp_title_unicode(ez)))
|
||||
{
|
||||
printf("(");
|
||||
gnuplot_string(ezpp_artist_unicode(ez));
|
||||
printf(" - ");
|
||||
gnuplot_string(ezpp_title_unicode(ez));
|
||||
printf(")");
|
||||
}
|
||||
|
||||
printf(" [");
|
||||
gnuplot_string(ezpp_version(ez));
|
||||
printf("] mapped by ");
|
||||
gnuplot_string(ezpp_creator(ez));
|
||||
if (mods_str) printf(" +%s", mods_str);
|
||||
puts("\";");
|
||||
|
||||
puts(
|
||||
"set xlabel 'time (ms)';"
|
||||
"set ylabel 'strain';"
|
||||
"set multiplot layout 2,1 rowsfirst;"
|
||||
"plot '-' with lines lc 1 title 'speed'"
|
||||
);
|
||||
gnuplot_strains(ez, DIFF_SPEED);
|
||||
puts("e");
|
||||
puts("unset title;");
|
||||
puts("plot '-' with lines lc 2 title 'aim'");
|
||||
gnuplot_strains(ez, DIFF_AIM);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- */
|
||||
|
||||
#define CODE_DESC "the code and errstr fields " \
|
||||
"should be checked for errors. a negative value for code " \
|
||||
"indicates an error"
|
||||
|
||||
typedef struct output_module {
|
||||
char* name;
|
||||
fnoutput* func;
|
||||
char* description[4];
|
||||
/* null terminated array of strings because of c90 literal limits */
|
||||
} output_module_t;
|
||||
|
||||
output_module_t modules[] = {
|
||||
{ "null", output_null, { "no output", 0 } },
|
||||
{ "text", output_text, { "plain text", 0 } },
|
||||
{
|
||||
"json", output_json,
|
||||
{ "a single utf-8 json object.\n" CODE_DESC, 0 }
|
||||
},
|
||||
{
|
||||
"csv", output_csv,
|
||||
{ "fieldname;value\n"
|
||||
"one value per line. ';' characters in strings will be "
|
||||
"escaped to \"\\;\". utf-8.\n" CODE_DESC, 0 }
|
||||
},
|
||||
{
|
||||
"binary",
|
||||
output_binary,
|
||||
{ "binary stream of values, encoded in little endian.\n"
|
||||
"negative code values indicate an error, which matches "
|
||||
"the error codes defined in oppai.c\n"
|
||||
"for an example on how to read this in C, check out "
|
||||
"examples/binary.c in oppai-ng's source\n"
|
||||
"\n"
|
||||
"floats and floats are represented using whatever "
|
||||
"convention the host machine and compiler use. unless you "
|
||||
"are on a really exotic machine it shouldn't matter\n"
|
||||
"\n"
|
||||
"strings (str) are encoded as a 2-byte integer indicating "
|
||||
"the length in bytes, followed by the string bytes and ",
|
||||
"a null (zero) terminating byte\n"
|
||||
"\n"
|
||||
"binoppai (8-byte magic), "
|
||||
"int8 oppai_ver_major, int8 oppai_ver_minor, "
|
||||
"int8 oppai_ver_patch, int error_code, "
|
||||
"str artist, str artist_utf8, str title, str title_utf8, "
|
||||
"str version, str creator, "
|
||||
"int mods_bitmask, float od, float ar, float cs, "
|
||||
"float hp, int combo, int max_combo, "
|
||||
"int16 ncircles, int16 nsliders, int16 nspinner, "
|
||||
"int score_version, float total_stars, ",
|
||||
"float speed_stars, float aim_stars, int16 nsingles, "
|
||||
"int16 nsingles_threshold, float aim_pp, "
|
||||
"float speed_pp, float acc_pp, float pp",
|
||||
0 }
|
||||
},
|
||||
{ "gnuplot", output_gnuplot, { "gnuplot .gp script", 0 } },
|
||||
};
|
||||
|
||||
output_module_t* output_by_name(char* name) {
|
||||
int i;
|
||||
for (i = 0; i < array_len(modules); ++i) {
|
||||
if (!strcmp(modules[i].name, name)) {
|
||||
return &modules[i];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmpsuffix(char* str, char* suffix) {
|
||||
int sufflen = (int)al_min(strlen(str), strlen(suffix));
|
||||
return strcmp(str + strlen(str) - sufflen, suffix);
|
||||
}
|
||||
|
||||
char lowercase(char c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
return c + ('a' - 'A');
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
char uppercase(char c) {
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
return c - ('a' - 'A');
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int strcmp_nc(char* a, char* b) {
|
||||
for (;; ++a, ++b) {
|
||||
char la = lowercase(*a);
|
||||
char lb = lowercase(*b);
|
||||
if (la > lb) {
|
||||
return 1;
|
||||
}
|
||||
else if (la < lb) {
|
||||
return -1;
|
||||
}
|
||||
if (!*a || *b) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO: split main into smaller funcs for readability? */
|
||||
int main(int argc, char* argv[]) {
|
||||
int i;
|
||||
int result;
|
||||
ezpp_t ez = ezpp_new();
|
||||
output_module_t* m;
|
||||
char* output_name = "text";
|
||||
char* mods_str = 0;
|
||||
int mods = MODS_NOMOD;
|
||||
float tmpf, speed_stars = 0, aim_stars = 0, accuracy_percent = 0;
|
||||
int tmpi, n100 = 0, n50 = 0;
|
||||
|
||||
/* parse arguments ------------------------------------------------- */
|
||||
me = argv[0];
|
||||
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (*argv[1] == '-' && strlen(argv[1]) > 1) {
|
||||
char* a = argv[1] + 1;
|
||||
if (!strcmp_nc(a, "version") || !strcmp_nc(a, "v")) {
|
||||
puts(oppai_version_str());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 2; i < argc; ++i) {
|
||||
char* a = argv[i];
|
||||
char* p;
|
||||
int iswhite = 1;
|
||||
|
||||
for (p = a; *p; ++p) {
|
||||
if (!isspace(*p)) {
|
||||
iswhite = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (iswhite) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (p = a; *p; ++p) {
|
||||
*p = lowercase(*p);
|
||||
}
|
||||
|
||||
if (*a == '-' && a[1] == 'o') {
|
||||
output_name = a + 2;
|
||||
|
||||
if (!strcmp(output_name, "?")) {
|
||||
int j;
|
||||
int nmodules = sizeof(modules) / sizeof(modules[0]);
|
||||
for (j = 0; j < nmodules; ++j) {
|
||||
char** d = modules[j].description;
|
||||
puts(modules[j].name);
|
||||
for (; *d; ++d) {
|
||||
printf("%s", *d);
|
||||
}
|
||||
puts("\n-");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cmpsuffix(a, "%") && sscanf(a, "%f", &accuracy_percent) == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cmpsuffix(a, "x100") && sscanf(a, "%d", &n100) == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cmpsuffix(a, "x50") && sscanf(a, "%d", &n50) == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cmpsuffix(a, "speed") && sscanf(a, "%f", &speed_stars) == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cmpsuffix(a, "aim") && sscanf(a, "%f", &aim_stars) == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cmpsuffix(a, "xm") || !cmpsuffix(a, "xmiss") ||
|
||||
!cmpsuffix(a, "m"))
|
||||
{
|
||||
if (sscanf(a, "%d", &tmpi) == 1) {
|
||||
ezpp_set_nmiss(ez, tmpi);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cmpsuffix(a, "x") && sscanf(a, "%d", &tmpi) == 1) {
|
||||
ezpp_set_combo(ez, tmpi);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(a, "scorev%d", &tmpi)) {
|
||||
ezpp_set_score_version(ez, tmpi);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(a, "ar%f", &tmpf)) {
|
||||
ezpp_set_base_ar(ez, tmpf);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(a, "od%f", &tmpf)) {
|
||||
ezpp_set_base_od(ez, tmpf);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(a, "cs%f", &tmpf)) {
|
||||
ezpp_set_base_cs(ez, tmpf);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(a, "-m%d", &tmpi) == 1) {
|
||||
ezpp_set_mode_override(ez, tmpi);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(a, "-end%d", &tmpi) == 1) {
|
||||
ezpp_set_end(ez, tmpi);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(a, "-taiko")) {
|
||||
ezpp_set_mode_override(ez, MODE_TAIKO);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(a, "-touch")) {
|
||||
mods |= MODS_TOUCH_DEVICE;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* this should be last because it uppercase's the string */
|
||||
if (*a == '+') {
|
||||
mods_str = a + 1;
|
||||
for (p = mods_str; *p; ++p) {
|
||||
*p = uppercase(*p);
|
||||
}
|
||||
|
||||
#define m(mod) \
|
||||
if (!strncmp(p, #mod, strlen(#mod))) { \
|
||||
mods |= MODS_##mod; \
|
||||
p += strlen(#mod); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
for (p = mods_str; *p;) {
|
||||
m(NF) m(EZ) m(TD) m(HD) m(HR) m(SD) m(DT) m(RX) m(HT) m(NC) m(FL)
|
||||
m(AT) m(SO) m(AP) m(PF) m(NOMOD)
|
||||
++p;
|
||||
}
|
||||
|
||||
#undef m
|
||||
continue;
|
||||
}
|
||||
|
||||
info(">%s\n", a);
|
||||
result = ERR_SYNTAX;
|
||||
goto output;
|
||||
}
|
||||
|
||||
if (accuracy_percent) {
|
||||
ezpp_set_accuracy_percent(ez, accuracy_percent);
|
||||
} else {
|
||||
ezpp_set_accuracy(ez, n100, n50);
|
||||
}
|
||||
ezpp_set_mods(ez, mods);
|
||||
ezpp_set_speed_stars(ez, speed_stars);
|
||||
ezpp_set_aim_stars(ez, aim_stars);
|
||||
result = ezpp(ez, argv[1]);
|
||||
|
||||
output:
|
||||
m = output_by_name(output_name);
|
||||
if (!m) {
|
||||
info("output module '%s' does not exist. check 'oppai - -o?'\n",
|
||||
output_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
m->func(result, ez, mods_str);
|
||||
ezpp_free(ez); /* just so valgrind stops crying */
|
||||
return result < 0;
|
||||
}
|
||||
|
16
package
Executable file
16
package
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
|
||||
dir=$(dirname $0)
|
||||
dir=$(readlink -f $dir)
|
||||
prevdir=$(pwd)
|
||||
cd $dir
|
||||
#git pull origin master || exit $?
|
||||
for prefix in i386/ ""; do
|
||||
container="${prefix}oppai-ng:ubuntu"
|
||||
docker build -t "$container" \
|
||||
--build-arg PREFIX="$prefix" . || exit $?
|
||||
docker run --rm -v $dir:/tmp \
|
||||
-e arch=$(echo ${prefix:-x86_64} | tr -d /) \
|
||||
"$container" || exit $?
|
||||
done
|
||||
cd $prevdir
|
49
release
Executable file
49
release
Executable file
|
@ -0,0 +1,49 @@
|
|||
#!/bin/sh
|
||||
|
||||
dir=$(dirname $0)
|
||||
|
||||
olddir=$(pwd)
|
||||
cd $dir
|
||||
|
||||
echo""
|
||||
echo "compiling and stripping"
|
||||
CC=musl-gcc ./build -static -no-pie || exit $?
|
||||
CC=gcc ./libbuild || exit $?
|
||||
|
||||
echo""
|
||||
echo "packaging"
|
||||
folder="oppai-$(./oppai -version)-"
|
||||
folder="${folder}$(gcc -dumpmachine)"
|
||||
|
||||
mkdir -p "$folder" || exit $?
|
||||
mv ./oppai $folder/oppai || exit $?
|
||||
mv ./liboppai.so $folder/liboppai.so || exit $?
|
||||
git archive HEAD --prefix=src/ -o "$folder"/src.tar ||
|
||||
exit $?
|
||||
cd "$folder" || exit $?
|
||||
tar xf src.tar || exit $?
|
||||
cd .. || exit $?
|
||||
|
||||
rm "$folder".tar.xz
|
||||
tar -cvJf "$folder".tar.xz \
|
||||
"$folder"/oppai \
|
||||
"$folder"/liboppai.so \
|
||||
"$folder"/src \
|
||||
|| exit $?
|
||||
|
||||
echo ""
|
||||
file "$folder".tar.xz
|
||||
tar tf "$folder".tar.xz
|
||||
|
||||
echo ""
|
||||
file "$folder"/oppai
|
||||
readelf --dynamic "$folder"/oppai
|
||||
ldd "$folder"/oppai
|
||||
|
||||
echo ""
|
||||
file "$folder"/liboppai.so
|
||||
ldd "$folder"/liboppai.so
|
||||
|
||||
rm -rf "$folder"
|
||||
cd $olddir
|
||||
|
59
release.ps1
Normal file
59
release.ps1
Normal file
|
@ -0,0 +1,59 @@
|
|||
# you must allow script execution by running
|
||||
# 'Set-ExecutionPolicy RemoteSigned' in an admin powershell
|
||||
# this requires vcvars to be already set (see vcvarsall17.ps1)
|
||||
# 7zip is also required (choco install 7zip and add it to path)
|
||||
|
||||
$dir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
Push-Location "$dir"
|
||||
git pull origin master -q
|
||||
|
||||
function Write-Header {
|
||||
param ([string]$Text)
|
||||
Write-Host $Text -Foreground Yellow -Background Black
|
||||
}
|
||||
|
||||
function Header {
|
||||
param ([string]$Title)
|
||||
Write-Header ""
|
||||
Write-Header "##########################################################"
|
||||
Write-Header "> $Title"
|
||||
}
|
||||
|
||||
cmd /c "build.bat"; if (-not $?) { exit $LastExitCode }
|
||||
cmd /c "libbuild.bat"; if (-not $?) { exit $LastExitCode }
|
||||
Header "Packaging"
|
||||
$folder = "oppai-" + $(.\oppai.exe -version) + "-windows-"
|
||||
$clout = & cl 2>&1 | %{ "$_" }
|
||||
"$clout" -match "(Microsoft.*for )([a-z0-9\-_]+)" | Out-Null
|
||||
if (-not $?) {
|
||||
exit $LastExitCode
|
||||
}
|
||||
$folder = $folder + $Matches[2]
|
||||
mkdir $folder; if (-not $?) { exit $LastExitCode }
|
||||
Copy-Item oppai.exe $folder; if (-not $?) { exit $LastExitCode }
|
||||
Copy-Item oppai.dll $folder; if (-not $?) { exit $LastExitCode }
|
||||
Copy-Item oppai.lib $folder; if (-not $?) { exit $LastExitCode }
|
||||
git archive HEAD --prefix=src\ -o $folder\src.zip
|
||||
if (-not $?) {
|
||||
exit $LastExitCode
|
||||
}
|
||||
Set-Location $folder; if (-not $?) { exit $LastExitCode }
|
||||
&7z x src.zip; if (-not $?) { exit $LastExitCode }
|
||||
Set-Location ..; if (-not $?) { exit $LastExitCode }
|
||||
|
||||
if (Test-Path "$folder.zip") {
|
||||
Remove-Item "$folder.zip"
|
||||
}
|
||||
|
||||
&7z a "$folder.zip" $folder\oppai.exe $folder\oppai.dll $folder\oppai.lib `
|
||||
$folder\src
|
||||
if (-not $?) {
|
||||
exit $LastExitCode
|
||||
}
|
||||
|
||||
Header "Result:"
|
||||
&7z l "$folder.zip"
|
||||
|
||||
Remove-Item $folder -Force -Recurse
|
||||
Pop-Location
|
17
swig/README.md
Normal file
17
swig/README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
these are maintainer instructions, check the readme's in the binding
|
||||
directories for user guides
|
||||
|
||||
# requirements
|
||||
* swig
|
||||
* docker
|
||||
* twine (pip)
|
||||
|
||||
# building all bindings
|
||||
```sh
|
||||
./build.sh
|
||||
```
|
||||
|
||||
# publishing all bindings
|
||||
```sh
|
||||
PUBLISH=1 ./build.sh
|
||||
```
|
19
swig/build.sh
Executable file
19
swig/build.sh
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
|
||||
runall() {
|
||||
for d in ./*/; do
|
||||
[ "$d" = "." ] && continue
|
||||
cd "$d"
|
||||
./build.sh || return $?
|
||||
[ ! -z $PUBLISH ] && ./publish.sh
|
||||
cd ..
|
||||
done
|
||||
}
|
||||
|
||||
dir="$(dirname "$0")"
|
||||
olddir="$(pwd)"
|
||||
cd "$dir"
|
||||
runall
|
||||
res=$?
|
||||
cd "$olddir"
|
||||
exit $res
|
11
swig/oppai.i
Normal file
11
swig/oppai.i
Normal file
|
@ -0,0 +1,11 @@
|
|||
%module oppai
|
||||
%feature("autodoc", "3");
|
||||
%apply int *OUTPUT {int*}
|
||||
%begin{
|
||||
#define SWIG_PYTHON_2_UNICODE
|
||||
}
|
||||
%{
|
||||
#define OPPAI_IMPLEMENTATION
|
||||
#include "oppai.c"
|
||||
%}
|
||||
#include "oppai.c"
|
42
swig/python/README.rst
Normal file
42
swig/python/README.rst
Normal file
|
@ -0,0 +1,42 @@
|
|||
osu! pp and difficulty calculator. automatically generated C bindings for
|
||||
https://github.com/Francesco149/oppai-ng
|
||||
|
||||
usage
|
||||
===========
|
||||
.. code-block:: sh
|
||||
|
||||
pip install oppai
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
from oppai import *
|
||||
|
||||
ez = ezpp_new()
|
||||
ezpp(ez, sys.argv[1])
|
||||
print("%g pp" % ezpp_pp(ez))
|
||||
ezpp_free(ez)
|
||||
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
./example.py /path/to/file.osu
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
python -c 'help("oppai")'
|
||||
|
||||
for a list of functions, or just read the top of oppai.c for better doc
|
||||
|
||||
|
||||
limitations
|
||||
===========
|
||||
for some reason, python3 doesn't provide a persisting pointer to strings
|
||||
you pass to c code even if you aren't doing anything with them, so if you
|
||||
want to reuse the handle at all you have to use ezpp_dup and ezpp_data_dup,
|
||||
which create a copy of the strings you pass in. this is inefficient so
|
||||
it's recommended to use autocalc mode and only call ezpp_dup or
|
||||
ezpp_data_dup when you're actually changing map
|
10
swig/python/build.sh
Executable file
10
swig/python/build.sh
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
rm -rf ./dist
|
||||
cp ../../oppai.c .
|
||||
cp ../oppai.i .
|
||||
swig -python -includeall oppai.i || exit
|
||||
for img in quay.io/pypa/manylinux2010_x86_64 quay.io/pypa/manylinux2010_i686; do
|
||||
docker run --user 1000:1000 --rm -v $(pwd):/io -w /io $img \
|
||||
./build_wheels.sh || exit
|
||||
done
|
15
swig/python/build_wheels.sh
Executable file
15
swig/python/build_wheels.sh
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
# this is meant to be used from docker
|
||||
|
||||
for pybin in /opt/python/*/bin
|
||||
do
|
||||
rm *.so
|
||||
"$pybin/pip" wheel . -w dist/ || exit
|
||||
done
|
||||
|
||||
"$pybin/python" ./setup.py sdist || exit
|
||||
|
||||
for w in dist/*linux_*.whl; do
|
||||
auditwheel repair "$w" -w dist/ || exit
|
||||
done
|
||||
rm dist/*linux_*.whl
|
9
swig/python/examples/basic.py
Executable file
9
swig/python/examples/basic.py
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
from oppai import *
|
||||
|
||||
ez = ezpp_new()
|
||||
ezpp(ez, sys.argv[1])
|
||||
print("%g pp" % ezpp_pp(ez))
|
||||
ezpp_free(ez)
|
16
swig/python/examples/reuse.py
Executable file
16
swig/python/examples/reuse.py
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
from oppai import *
|
||||
|
||||
ez = ezpp_new()
|
||||
ezpp_set_autocalc(ez, 1)
|
||||
for osufile in sys.argv[1:]:
|
||||
ezpp_dup(ez, osufile)
|
||||
print("%s - %s [%s]" % (ezpp_artist(ez), ezpp_title(ez), ezpp_version(ez)))
|
||||
print("%g stars" % ezpp_stars(ez))
|
||||
for acc in range(95, 101):
|
||||
ezpp_set_accuracy_percent(ez, acc)
|
||||
print("%g%% -> %g pp" % (acc, ezpp_pp(ez)))
|
||||
print("")
|
||||
ezpp_free(ez)
|
38
swig/python/examples/reuse_mem.py
Executable file
38
swig/python/examples/reuse_mem.py
Executable file
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
from oppai import *
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
# hack to force utf-8 on py < 3
|
||||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
def mods_str(mods):
|
||||
mods_str = "+"
|
||||
if mods == 0:
|
||||
mods_str += "nomod"
|
||||
else:
|
||||
if mods & MODS_HD: mods_str += "hd"
|
||||
if mods & MODS_DT: mods_str += "dt"
|
||||
if mods & MODS_HR: mods_str += "hr"
|
||||
return mods_str
|
||||
|
||||
ez = ezpp_new()
|
||||
ezpp_set_autocalc(ez, 1)
|
||||
for osufile in sys.argv[1:]:
|
||||
# by providing the map in memory we can speed up subsequent re-parses
|
||||
f = open(osufile, 'r')
|
||||
data = f.read()
|
||||
f.close()
|
||||
ezpp_data_dup(ez, data, len(data.encode('utf-8')))
|
||||
print("%s - %s [%s]" % (ezpp_artist(ez), ezpp_title(ez), ezpp_version(ez)))
|
||||
print("%g stars" % ezpp_stars(ez))
|
||||
for mods in [ 0, MODS_HR, MODS_HD | MODS_HR, MODS_DT, MODS_HD | MODS_DT ]:
|
||||
print(mods_str(mods))
|
||||
ezpp_set_mods(ez, mods)
|
||||
for acc in range(95, 101):
|
||||
ezpp_set_accuracy_percent(ez, acc)
|
||||
print("%g%% -> %g pp" % (acc, ezpp_pp(ez)))
|
||||
print("")
|
||||
ezpp_free(ez)
|
14
swig/python/examples/timing.py
Executable file
14
swig/python/examples/timing.py
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
from oppai import *
|
||||
|
||||
# prints timing points (just a test for this interface)
|
||||
ez = ezpp_new()
|
||||
ezpp(ez, sys.argv[1])
|
||||
for i in range(ezpp_ntiming_points(ez)):
|
||||
time = ezpp_timing_time(ez, i)
|
||||
ms_per_beat = ezpp_timing_ms_per_beat(ez, i)
|
||||
change = ezpp_timing_change(ez, i)
|
||||
print("%f | %f beats per ms | change: %d" % (time, ms_per_beat, change))
|
||||
ezpp_free(ez)
|
3
swig/python/publish.sh
Executable file
3
swig/python/publish.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
twine upload dist/*
|
25
swig/python/python.yml
Normal file
25
swig/python/python.yml
Normal file
|
@ -0,0 +1,25 @@
|
|||
environment:
|
||||
TWINE_USERNAME: lolisamurai
|
||||
TWINE_PASSWORD:
|
||||
secure: DTyX4L2loxFxlsbPYAwuga0DyOlGiOnJyEwi/j08gba0NyNx21TvRFMHpITIqcfg
|
||||
cache:
|
||||
- C:\ProgramData\chocolatey\bin
|
||||
- C:\ProgramData\chocolatey\lib
|
||||
- C:\Users\appveyor\AppData\Local\pip\Cache
|
||||
install:
|
||||
- IF NOT EXIST C:\ProgramData\chocolatey\bin\swig.exe choco install swig --version 3.0.12 --yes --limit-output
|
||||
- python -m pip install twine
|
||||
- python -m pip install cibuildwheel==0.10.1
|
||||
build_script:
|
||||
- cd swig/python
|
||||
- copy ..\..\oppai.c .
|
||||
- copy ..\oppai.i .
|
||||
- swig -python -includeall oppai.i
|
||||
- cibuildwheel --output-dir wheelhouse
|
||||
- ps: >-
|
||||
if ($env:APPVEYOR_REPO_TAG -eq "true") {
|
||||
Invoke-Expression "python -m twine upload --skip-existing wheelhouse/*.whl"
|
||||
}
|
||||
artifacts:
|
||||
- path: "swig\\python\\wheelhouse\\*.whl"
|
||||
name: wheels
|
2
swig/python/setup.cfg
Normal file
2
swig/python/setup.cfg
Normal file
|
@ -0,0 +1,2 @@
|
|||
[build_ext]
|
||||
swig-opts=-includeall
|
58
swig/python/setup.py
Normal file
58
swig/python/setup.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
def parse_version_str():
|
||||
import re
|
||||
version_re = re.compile('^#define OPPAI_VERSION_(MAJOR|MINOR|PATCH)')
|
||||
version = []
|
||||
with open('oppai.c', 'r') as f:
|
||||
for line in f:
|
||||
if version_re.match(line):
|
||||
version.append(str(int(line.split(' ')[2])))
|
||||
return '.'.join(version)
|
||||
|
||||
try:
|
||||
from setuptools import setup, Extension
|
||||
except ImportError:
|
||||
from distutils.core import setup, Extension
|
||||
|
||||
try:
|
||||
from oppai import oppai_version_str
|
||||
except Exception:
|
||||
def oppai_version_str():
|
||||
try:
|
||||
return parse_version_str()
|
||||
except Exception:
|
||||
return "INVALID"
|
||||
|
||||
oppai_classifiers = [
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: Public Domain",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Utilities",
|
||||
]
|
||||
|
||||
f = open("README.rst", "r")
|
||||
oppai_readme = f.read()
|
||||
f.close()
|
||||
|
||||
oppai_sources=['oppai.i']
|
||||
if os.system('swig') != 0:
|
||||
oppai_sources=['oppai_wrap.c', 'oppai.c']
|
||||
|
||||
setup(
|
||||
name="oppai",
|
||||
version=oppai_version_str(),
|
||||
author="Franc[e]sco",
|
||||
author_email="lolisamurai@tfwno.gf",
|
||||
url="https://github.com/Francesco149/oppai-ng",
|
||||
ext_modules=[Extension('_oppai', oppai_sources)],
|
||||
py_modules=["oppai"],
|
||||
description="osu! pp and difficulty calculator, C bindings",
|
||||
long_description=oppai_readme,
|
||||
license="Unlicense",
|
||||
classifiers=oppai_classifiers,
|
||||
keywords="osu! osu"
|
||||
)
|
8
test/build
Executable file
8
test/build
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
dir="$(dirname "$0")"
|
||||
. "$dir"/../cflags
|
||||
|
||||
$cc $cflags ${@:--DOPPAI_IMPLEMENTATION} \
|
||||
test.c $ldflags -o oppai_test
|
||||
|
16
test/build.bat
Normal file
16
test/build.bat
Normal file
|
@ -0,0 +1,16 @@
|
|||
@echo off
|
||||
|
||||
set flags="%*"
|
||||
IF "%1"=="" (
|
||||
set flags=-DOPPAI_IMPLEMENTATION
|
||||
)
|
||||
|
||||
del oppai_test.exe >nul 2>&1
|
||||
del test.obj >nul 2>&1
|
||||
cl -D_CRT_SECURE_NO_WARNINGS=1 ^
|
||||
-DNOMINMAX=1 ^
|
||||
-O2 -nologo -MT -Gm- -GR- -EHsc -W4 ^
|
||||
%flags% ^
|
||||
test.c ^
|
||||
-Feoppai_test.exe ^
|
||||
|| EXIT /B 1
|
21
test/download_suite
Executable file
21
test/download_suite
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
|
||||
dir="$(dirname "$0")"
|
||||
if command -v realpath 2>&1 >/dev/null; then
|
||||
wdir="$(realpath "$dir")"
|
||||
else
|
||||
wdir="$dir"
|
||||
fi
|
||||
olddir="$(pwd)"
|
||||
cd "$wdir" || exit $?
|
||||
|
||||
url="$(cat ./suite_url)"
|
||||
|
||||
if [ $(find test_suite 2>/dev/null | tail -n +2 | wc -l) = "0" ]; then
|
||||
curl -LO "$url" || exit $?
|
||||
tar xf "$(basename $url)" || exit $?
|
||||
else
|
||||
echo "using existing test_suite"
|
||||
fi
|
||||
|
||||
cd "$olddir"
|
19
test/download_suite.ps1
Normal file
19
test/download_suite.ps1
Normal file
|
@ -0,0 +1,19 @@
|
|||
# you must allow script execution by running
|
||||
# 'Set-ExecutionPolicy RemoteSigned' in an admin powershell
|
||||
# 7zip is also required (choco install 7zip and add it to path)
|
||||
|
||||
$url = Get-Content .\suite_url -Raw
|
||||
$dir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
Push-Location "$dir"
|
||||
|
||||
if ((Test-Path .\test_suite) -and (Get-ChildItem .\test_suite | Measure-Object).Count -gt 0) {
|
||||
Write-Host "using existing test_suite"
|
||||
} else {
|
||||
# my windows 7 install doesn't support Tls3
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
(New-Object System.Net.WebClient).DownloadFile($url, "$dir\test_suite.tar.gz")
|
||||
&7z x .\test_suite.tar.gz
|
||||
&7z x .\test_suite.tar
|
||||
}
|
||||
|
||||
Pop-Location
|
79
test/download_suite.py
Executable file
79
test/download_suite.py
Executable file
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# very rough script that downloads unique maps from test_suite.json that
|
||||
# gensuite.py generates
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
||||
try:
|
||||
import httplib
|
||||
except ImportError:
|
||||
import http.client as httplib
|
||||
|
||||
try:
|
||||
import urllib
|
||||
except ImportError:
|
||||
import urllib.parse as urllib
|
||||
|
||||
osu = httplib.HTTPSConnection('osu.ppy.sh')
|
||||
|
||||
def osu_get(path):
|
||||
while True:
|
||||
try:
|
||||
osu.request('GET', path)
|
||||
r = osu.getresponse()
|
||||
|
||||
raw = bytes()
|
||||
|
||||
while True:
|
||||
try:
|
||||
raw += r.read()
|
||||
break
|
||||
except httplib.IncompleteRead as e:
|
||||
raw += e.partial
|
||||
|
||||
return raw
|
||||
|
||||
except (httplib.HTTPException, ValueError) as e:
|
||||
sys.stderr.write('%s\n' % (traceback.format_exc()))
|
||||
|
||||
# prevents exceptions on next request if the
|
||||
# response wasn't previously read due to errors
|
||||
try:
|
||||
osu.getresponse().read()
|
||||
except httplib.HTTPException:
|
||||
pass
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
sys.stderr.write('usage: %s test_suite.json\n' % sys.argv[0])
|
||||
sys.exit(1)
|
||||
|
||||
with open(sys.argv[1], 'r') as f:
|
||||
scores = json.loads(f.read())
|
||||
|
||||
unique_maps = set([s['beatmap_id'] for m in [0, 1] for s in scores[m]])
|
||||
i = 1
|
||||
|
||||
for b in unique_maps:
|
||||
sys.stderr.write(
|
||||
"[%.02f%% - %d/%d] %s" % (i / float(len(unique_maps)) * 100, i,
|
||||
len(unique_maps), b)
|
||||
)
|
||||
i += 1
|
||||
|
||||
# TODO: tmp file and rename
|
||||
try:
|
||||
with open(b + '.osu', 'r') as f:
|
||||
sys.stderr.write(' (already exists)\n')
|
||||
continue
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
sys.stderr.write('\n')
|
||||
|
||||
with open(b + '.osu', 'wb') as f:
|
||||
f.write(osu_get('/osu/' + b))
|
292
test/gentest.py
Executable file
292
test/gentest.py
Executable file
|
@ -0,0 +1,292 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import traceback
|
||||
import argparse
|
||||
import hashlib
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
# hack to force utf-8
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf-8')
|
||||
|
||||
try:
|
||||
import httplib
|
||||
except ImportError:
|
||||
import http.client as httplib
|
||||
|
||||
try:
|
||||
import urllib
|
||||
except ImportError:
|
||||
import urllib.parse as urllib
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description = (
|
||||
'generates the oppai test suite. outputs c++ code to ' +
|
||||
'stdout and the json dump to a file.'
|
||||
)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-key',
|
||||
default = None,
|
||||
help = (
|
||||
'osu! api key. required if -input-file is not present. ' +
|
||||
'can also be specified through the OSU_API_KEY ' +
|
||||
'environment variable'
|
||||
)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-output-file',
|
||||
default = 'test_suite.json',
|
||||
help = 'dumps json to this file'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-input-file',
|
||||
default = None,
|
||||
help = (
|
||||
'loads test suite from this json file instead of '
|
||||
'fetching it from osu api. if set to "-", json will be '
|
||||
'read from standard input'
|
||||
)
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.key == None and 'OSU_API_KEY' in os.environ:
|
||||
args.key = os.environ['OSU_API_KEY']
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
osu_treset = time.time() + 60
|
||||
osu_ncalls = 0
|
||||
|
||||
def osu_get(conn, endpoint, paramsdict=None):
|
||||
# GETs /api/endpoint?paramsdict&k=args.key from conn
|
||||
# return json object, exits process on api errors
|
||||
global osu_treset, osu_ncalls, args
|
||||
|
||||
sys.stderr.write('%s %s\n' % (endpoint, str(paramsdict)))
|
||||
|
||||
paramsdict['k'] = args.key
|
||||
path = '/api/%s?%s' % (endpoint, urllib.urlencode(paramsdict))
|
||||
|
||||
while True:
|
||||
while True:
|
||||
if time.time() >= osu_treset:
|
||||
osu_ncalls = 0
|
||||
osu_treset = time.time() + 60
|
||||
sys.stderr.write('\napi ready\n')
|
||||
|
||||
if osu_ncalls < 60:
|
||||
break
|
||||
else:
|
||||
sys.stderr.write('waiting for api cooldown...\r')
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
try:
|
||||
conn.request('GET', path)
|
||||
osu_ncalls += 1
|
||||
r = conn.getresponse()
|
||||
|
||||
raw = ''
|
||||
|
||||
while True:
|
||||
try:
|
||||
raw += r.read()
|
||||
break
|
||||
except httplib.IncompleteRead as e:
|
||||
raw += e.partial
|
||||
|
||||
j = json.loads(raw)
|
||||
|
||||
if 'error' in j:
|
||||
sys.stderr.write('%s\n' % j['error'])
|
||||
sys.exit(1)
|
||||
|
||||
return j
|
||||
|
||||
except (httplib.HTTPException, ValueError) as e:
|
||||
sys.stderr.write('%s\n' % (traceback.format_exc()))
|
||||
|
||||
try:
|
||||
# prevents exceptions on next request if the
|
||||
# response wasn't previously read due to errors
|
||||
conn.getresponse().read()
|
||||
|
||||
except httplib.HTTPException:
|
||||
pass
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def gen_modstr(bitmask):
|
||||
# generates c++ code for a mod combination's bitmask
|
||||
mods = []
|
||||
|
||||
allmods = {
|
||||
(1<< 0, 'nf'), (1<< 1, 'ez'), (1<< 2, 'td'), (1<< 3, 'hd'),
|
||||
(1<< 4, 'hr'), (1<< 6, 'dt'), (1<< 8, 'ht'),
|
||||
(1<< 9, 'nc'), (1<<10, 'fl'), (1<<12, 'so')
|
||||
}
|
||||
|
||||
for bit, string in allmods:
|
||||
if bitmask & bit != 0:
|
||||
mods.append(string)
|
||||
|
||||
if len(mods) == 0:
|
||||
return 'nomod'
|
||||
|
||||
return ' | '.join(mods)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
if args.key == None:
|
||||
sys.stderr.write(
|
||||
'please set OSU_API_KEY or pass it as a parameter\n'
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
scores = []
|
||||
|
||||
top_players = [
|
||||
[ 124493, 4787150, 2558286, 1777162, 2831793, 50265 ],
|
||||
[ 3174184, 8276884, 5991961, 2774767 ]
|
||||
]
|
||||
|
||||
if args.input_file == None:
|
||||
# fetch a fresh test suite from osu api
|
||||
osu = httplib.HTTPSConnection('osu.ppy.sh')
|
||||
|
||||
for m in [0, 1]:
|
||||
for u in top_players[m]:
|
||||
params = { 'u': u, 'limit': 100, 'type': 'id', 'm': m }
|
||||
batch = osu_get(osu, 'get_user_best', params)
|
||||
for s in batch:
|
||||
s['mode'] = m
|
||||
scores += batch
|
||||
|
||||
# temporarily removed, not all std scores are recalc'd
|
||||
#params = { 'm': 0, 'since': '2015-11-26' }
|
||||
#maps = osu_get(osu, 'get_beatmaps', params)
|
||||
|
||||
# no taiko converts here because as explained below, tiny float
|
||||
# errors can lead to completely broken conversions
|
||||
for mode in [1]:
|
||||
params = { 'm': mode, 'since': '2015-11-26' }
|
||||
maps = osu_get(osu, 'get_beatmaps', params)
|
||||
|
||||
for m in maps:
|
||||
params = { 'b': m['beatmap_id'], 'm': mode }
|
||||
map_scores = osu_get(osu, 'get_scores', params)
|
||||
|
||||
if len(map_scores) == 0:
|
||||
sys.stderr.write('W: map has no scores???\n')
|
||||
continue
|
||||
|
||||
# note: api also returns qualified and loved, so ignore
|
||||
# maps that don't have pp in rankings
|
||||
if not 'pp' in map_scores[0] or map_scores[0]['pp'] is None:
|
||||
sys.stderr.write('W: ignoring loved/qualified map\n')
|
||||
continue
|
||||
|
||||
for s in map_scores:
|
||||
s['beatmap_id'] = m['beatmap_id']
|
||||
s['mode'] = mode
|
||||
|
||||
scores += map_scores
|
||||
|
||||
|
||||
with open(args.output_file, 'w+') as f:
|
||||
f.write(json.dumps(scores))
|
||||
|
||||
else:
|
||||
# load existing test suite from json file
|
||||
with open(args.input_file, 'r') as f:
|
||||
scores = json.loads(f.read())
|
||||
# sort by mode by map
|
||||
scores.sort(key=lambda x: int(x['beatmap_id'])<<32|x['mode'],
|
||||
reverse=True)
|
||||
|
||||
|
||||
print('/* this code was automatically generated by gentest.py */')
|
||||
print('')
|
||||
|
||||
# make code a little nicer by shortening mods
|
||||
allmods = {
|
||||
'nf', 'ez', 'td', 'hd', 'hr', 'dt', 'ht', 'nc', 'fl', 'so', 'nomod'
|
||||
}
|
||||
|
||||
for mod in allmods:
|
||||
print('#define %s MODS_%s' % (mod, mod.upper()))
|
||||
|
||||
print('''
|
||||
typedef struct {
|
||||
int mode;
|
||||
int id;
|
||||
int max_combo;
|
||||
int n300, n100, n50, nmiss;
|
||||
int mods;
|
||||
double pp;
|
||||
} score_t;
|
||||
|
||||
score_t suite[] = {''')
|
||||
|
||||
seen_hashes = []
|
||||
|
||||
for s in scores:
|
||||
# due to tiny floating point errors, maps can even double
|
||||
# in combo and not even lazer gets this right, taiko converts are hell
|
||||
# so I'm just gonna exclude them
|
||||
if s['mode'] == 1:
|
||||
is_convert = False
|
||||
with open('test_suite/'+s['beatmap_id']+'.osu') as f:
|
||||
for line in f:
|
||||
split = line.split(':')
|
||||
if len(split) >= 2 and split[0] == 'Mode' and int(split[1]) == 0:
|
||||
is_convert = True
|
||||
break
|
||||
|
||||
if is_convert:
|
||||
continue
|
||||
|
||||
# some taiko maps ignore combo scaling for no apparent reason
|
||||
# so i will only include full combos for now
|
||||
if int(s['countmiss']) != 0:
|
||||
continue
|
||||
|
||||
if s['pp'] is None:
|
||||
continue
|
||||
|
||||
# why is every value returned by osu api a string?
|
||||
line = (
|
||||
' { %d, %s, %s, %s, %s, %s, %s, %s, %s },' %
|
||||
(
|
||||
s['mode'], s['beatmap_id'], s['maxcombo'], s['count300'],
|
||||
s['count100'], s['count50'], s['countmiss'],
|
||||
gen_modstr(int(s['enabled_mods'])), s['pp']
|
||||
)
|
||||
)
|
||||
|
||||
# don't include identical scores by different people
|
||||
s = hashlib.sha1(line).digest()
|
||||
if s in seen_hashes:
|
||||
continue
|
||||
|
||||
print(line)
|
||||
seen_hashes.append(s)
|
||||
|
||||
print('};\n')
|
||||
|
||||
for mod in allmods:
|
||||
print('#undef %s' % (mod))
|
||||
|
11
test/pack_suite
Executable file
11
test/pack_suite
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
dir="$(dirname "$0")"
|
||||
wdir="$(realpath "$dir")"
|
||||
|
||||
olddir="$(pwd)"
|
||||
cd "$wdir" || exit $?
|
||||
tar -zcvf test_suite_$(date +%F).tar.gz test_suite/*.osu
|
||||
res=$?
|
||||
cd "$olddir"
|
||||
exit $res
|
1
test/suite_url
Normal file
1
test/suite_url
Normal file
|
@ -0,0 +1 @@
|
|||
https://github.com/Francesco149/oppai-ng/releases/download/2.3.2/test_suite_2019-02-19.tar.gz
|
119
test/test.c
Normal file
119
test/test.c
Normal file
|
@ -0,0 +1,119 @@
|
|||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../oppai.c"
|
||||
#include "test_suite.c" /* defines suite */
|
||||
|
||||
#define ERROR_MARGIN 0.02f /* pp can be off by +- 2% */
|
||||
|
||||
/*
|
||||
* margin is actually
|
||||
* - 3x for < 100pp
|
||||
* - 2x for 100-200pp
|
||||
* - 1.5x for 200-300pp
|
||||
*/
|
||||
|
||||
void print_score(score_t* s) {
|
||||
char mods_str_buf[20];
|
||||
char* mods_str = mods_str_buf;
|
||||
|
||||
strcpy(mods_str, "nomod");
|
||||
|
||||
#define m(mod) \
|
||||
if (s->mods & MODS_##mod) { \
|
||||
mods_str += sprintf(mods_str, #mod); \
|
||||
} \
|
||||
|
||||
m(HR) m(NC) m(HT) m(SO) m(NF) m(EZ) m(DT) m(FL) m(HD)
|
||||
#undef m
|
||||
|
||||
printf("m=%d %u +%s %dx %dx300 %dx100 %dx50 %dxmiss %g pp\n",
|
||||
s->mode, s->id, mods_str_buf, s->max_combo, s->n300, s->n100,
|
||||
s->n50, s->nmiss, s->pp);
|
||||
}
|
||||
|
||||
int main() {
|
||||
char fname_buf[4096];
|
||||
char* fname = fname_buf;
|
||||
int i, n = (int)(sizeof(suite) / sizeof(suite[0]));
|
||||
float max_err[2] = { 0, 0 };
|
||||
int max_err_map[2] = { 0, 0 };
|
||||
float avg_err[2] = { 0, 0 };
|
||||
int count[2] = { 0, 0 };
|
||||
float error = 0;
|
||||
float error_percent = 0;
|
||||
ezpp_t ez = ezpp_new();
|
||||
int err;
|
||||
int last_id = 0;
|
||||
|
||||
fname += sprintf(fname, "test_suite/");
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
score_t* s = &suite[i];
|
||||
float margin;
|
||||
float pptotal;
|
||||
|
||||
print_score(s);
|
||||
if (s->id != last_id) {
|
||||
sprintf(fname, "%u.osu", s->id);
|
||||
last_id = s->id;
|
||||
/* force reparse and don't reuse prev map ar/cs/od/hp */
|
||||
ezpp_set_base_cs(ez, -1);
|
||||
ezpp_set_base_ar(ez, -1);
|
||||
ezpp_set_base_od(ez, -1);
|
||||
ezpp_set_base_hp(ez, -1);
|
||||
}
|
||||
ezpp_set_mods(ez, s->mods);
|
||||
ezpp_set_accuracy(ez, s->n100, s->n50);
|
||||
ezpp_set_nmiss(ez, s->nmiss);
|
||||
ezpp_set_combo(ez, s->max_combo);
|
||||
ezpp_set_mode_override(ez, s->mode);
|
||||
err = ezpp(ez, fname_buf);
|
||||
if (err < 0) {
|
||||
printf("%s\n", errstr(err));
|
||||
exit(1);
|
||||
}
|
||||
pptotal = ezpp_pp(ez);
|
||||
++count[s->mode];
|
||||
|
||||
margin = (float)s->pp * ERROR_MARGIN;
|
||||
if (s->pp < 100) {
|
||||
margin *= 3;
|
||||
} else if (s->pp < 200) {
|
||||
margin *= 2;
|
||||
} else if (s->pp < 300) {
|
||||
margin *= 1.5f;
|
||||
}
|
||||
|
||||
error = (float)fabs(pptotal - (float)s->pp);
|
||||
error_percent = error / (float)s->pp;
|
||||
avg_err[s->mode] += error_percent;
|
||||
if (error_percent > max_err[s->mode]) {
|
||||
max_err[s->mode] = error_percent;
|
||||
max_err_map[s->mode] = s->id;
|
||||
}
|
||||
|
||||
if (error >= margin) {
|
||||
printf("failed test: got %g pp, expected %g\n", pptotal, s->pp);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 2; ++i) {
|
||||
switch (i) {
|
||||
case MODE_STD: puts("osu"); break;
|
||||
case MODE_TAIKO: puts("taiko"); break;
|
||||
}
|
||||
avg_err[i] /= count[i];
|
||||
printf("%d scores\n", count[i]);
|
||||
printf("avg err %f\n", avg_err[i]);
|
||||
printf("max err %f on %d\n", max_err[i], max_err_map[i]);
|
||||
}
|
||||
|
||||
ezpp_free(ez);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
14745
test/test_suite.c
Normal file
14745
test/test_suite.c
Normal file
File diff suppressed because it is too large
Load Diff
3
valgrind
Executable file
3
valgrind
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
valgrind --leak-check=full --track-origins=yes --show-leak-kinds=all "$@"
|
15
vcvarsall17.ps1
Normal file
15
vcvarsall17.ps1
Normal file
|
@ -0,0 +1,15 @@
|
|||
# https://stackoverflow.com/questions/2124753/how-can-i-use-powershell-with-the-visual-studio-command-prompt
|
||||
|
||||
param (
|
||||
[string]$arch = "amd64"
|
||||
)
|
||||
|
||||
Push-Location "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools"
|
||||
cmd /c "VsDevCmd.bat -arch=$arch&set" |
|
||||
ForEach-Object {
|
||||
if ($_ -match "=") {
|
||||
$v = $_.split("="); set-item -force -path "ENV:\$($v[0])" -value "$($v[1])"
|
||||
}
|
||||
}
|
||||
Pop-Location
|
||||
Write-Host "`nVisual Studio 2017 Command Prompt variables set." -ForegroundColor Yellow
|
Loading…
Reference in New Issue
Block a user