Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add command line frontend and a license file.
- Loading branch information
1 parent
dcd4b48
commit 46f2535
Showing
8 changed files
with
662 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Copyright (c) 2014, Unknown W. Brackets | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted, provided that the above | ||
copyright notice and this permission notice appear in all copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
Description | ||
=========== | ||
|
||
A fast ISO to CSO compression program which uses multiple algorithms for best compression ratio. | ||
|
||
|
||
Basic Usage | ||
=========== | ||
|
||
```sh | ||
maxcso myfile.iso | ||
``` | ||
|
||
Or, drag the iso file into maxcso.exe. | ||
|
||
|
||
Features | ||
=========== | ||
|
||
* Can use as many CPU cores as you want. | ||
* Can use [zlib] (https://github.com/madler/zlib), [7-zip] (http://7-zip.org/)'s deflate, | ||
and [Zopfli] (http://code.google.com/p/zopfli/). | ||
* Processes multiple files in one command. | ||
|
||
|
||
Compression | ||
=========== | ||
|
||
maxcso always uses compression level 9. Decompression speed is about the same regardless of | ||
level, and disk access is faster with smaller files. | ||
|
||
It is possible to leave sectors or files in the iso uncompressed, which could improve speed. | ||
maxcso does not currently support this, however. Benchmarks will need to be made to validate | ||
that the increased disk access time is worth it. | ||
|
||
Using 7-zip's deflate and Zopfli improves compression ratios, but don't expect a lot. Usual | ||
results are between 0.5% to 1.0% smaller. | ||
|
||
|
||
Speed | ||
=========== | ||
|
||
Compared to other tools like ciso and CisoPlus, maxcso can run much faster and achieve the same | ||
compression. Use `--fast` to get the fastest compression, which matches level 9 in other tools. | ||
|
||
Additionally, if you have better than a dual core processor, maxcso will use all of your cores, | ||
and perform even better. | ||
|
||
|
||
Full usage | ||
=========== | ||
|
||
``` | ||
Usage: maxcso [--args] input.iso [-o output.cso] | ||
Multiple files may be specified. Inputs can be iso or cso files. | ||
--threads=N Specify N threads for I/O and compression | ||
--quiet Suppress status output | ||
--fast Use only basic zlib for fastest result | ||
--use-METHOD Use a compression method: zlib, Zopfli, or 7zdeflate | ||
By default, zlib and 7zdeflate are used | ||
--no-METHOD Disable a compression method | ||
``` | ||
|
||
Because Zopfli is significantly slower than the other methods, and uses a lot more memory, it | ||
is disabled by default. Use `--use-zopfli` for maximum compression. | ||
|
||
|
||
Platforms | ||
=========== | ||
|
||
maxcso has only been tested on Windows so far. The code was written to be portable, however. | ||
If you'd like to port it to another platform, pull requests are accepted. It may just compile | ||
out of the box with a Makefile or similar. | ||
|
||
### Windows | ||
|
||
To build on Windows, simply open cli/maxcso.sln and build. Visual Studio 2013 is required, but | ||
Express for Windows Desktop works just fine. | ||
|
||
|
||
Other tools | ||
=========== | ||
|
||
* [CisoPlus] (http://cisoplus.pspgen.com/) | ||
* [CisoMC] (http://wololo.net/talk/viewtopic.php?f=20&t=32659) | ||
* [ciso] (http://sourceforge.net/projects/ciso/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
#include "../src/compress.h" | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <vector> | ||
#include <string> | ||
#include "../libuv/include/uv.h" | ||
|
||
void show_version() { | ||
fprintf(stderr, "maxcso v%s\n", maxcso::VERSION); | ||
} | ||
|
||
void show_help(const char *arg0) { | ||
show_version(); | ||
fprintf(stderr, "Usage: %s [--args] input.iso [-o output.cso]\n", arg0); | ||
fprintf(stderr, "\n"); | ||
fprintf(stderr, "Multiple files may be specified. Inputs can be iso or cso files.\n"); | ||
fprintf(stderr, "\n"); | ||
fprintf(stderr, " --threads=N Specify N threads for I/O and compression\n"); | ||
fprintf(stderr, " --quiet Suppress status output\n"); | ||
fprintf(stderr, " --fast Use only basic zlib for fastest result\n"); | ||
// TODO: Bring this back once it's functional. | ||
//fprintf(stderr, " --smallest Force compression of all sectors for smallest result\n"); | ||
fprintf(stderr, " --use-METHOD Use a compression method: zlib, Zopfli, or 7zdeflate\n"); | ||
fprintf(stderr, " By default, zlib and 7zdeflate are used\n"); | ||
fprintf(stderr, " --no-METHOD Disable a compression method\n"); | ||
} | ||
|
||
bool has_arg_value(int &i, char *argv[], const std::string &arg, const char *&val) { | ||
if (arg.compare(0, arg.npos, argv[i], arg.size()) == 0) { | ||
if (strlen(argv[i]) == arg.size()) { | ||
val = argv[++i]; | ||
return true; | ||
} else if (argv[i][arg.size()] == '=') { | ||
val = argv[i] + arg.size() + 1; | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
bool has_arg(int &i, char *argv[], const std::string &arg) { | ||
if (arg.compare(argv[i]) == 0) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
struct Arguments { | ||
std::vector<std::string> inputs; | ||
std::vector<std::string> outputs; | ||
int threads; | ||
uint32_t flags; | ||
bool quiet; | ||
}; | ||
|
||
void default_args(Arguments &args) { | ||
args.threads = 0; | ||
args.flags = maxcso::TASKFLAG_NO_ZOPFLI; | ||
args.quiet = false; | ||
} | ||
|
||
int parse_args(Arguments &args, int argc, char *argv[]) { | ||
const char *val = nullptr; | ||
int i; | ||
for (i = 1; i < argc; ++i) { | ||
if (argv[i][0] == '-') { | ||
if (has_arg(i, argv, "--help") || has_arg(i, argv, "-h")) { | ||
show_help(argv[0]); | ||
return 1; | ||
} else if (has_arg(i, argv, "--version") || has_arg(i, argv, "-v")) { | ||
show_version(); | ||
return 1; | ||
} else if (has_arg_value(i, argv, "--threads", val)) { | ||
args.threads = atoi(val); | ||
} else if (has_arg(i, argv, "--quiet")) { | ||
args.quiet = true; | ||
} else if (has_arg(i, argv, "--fast")) { | ||
args.flags |= maxcso::TASKFLAG_NO_ZLIB_BRUTE | maxcso::TASKFLAG_NO_ZOPFLI | maxcso::TASKFLAG_NO_7ZIP; | ||
} else if (has_arg(i, argv, "--smallest")) { | ||
args.flags |= maxcso::TASKFLAG_FORCE_ALL; | ||
} else if (has_arg(i, argv, "--use-zopfli")) { | ||
args.flags &= ~maxcso::TASKFLAG_NO_ZOPFLI; | ||
} else if (has_arg(i, argv, "--use-zlib")) { | ||
args.flags &= ~maxcso::TASKFLAG_NO_ZLIB; | ||
} else if (has_arg(i, argv, "--use-7zdeflate")) { | ||
args.flags &= ~maxcso::TASKFLAG_NO_7ZIP; | ||
} else if (has_arg(i, argv, "--no-zopfli")) { | ||
args.flags |= maxcso::TASKFLAG_NO_ZOPFLI; | ||
} else if (has_arg(i, argv, "--no-zlib")) { | ||
args.flags |= maxcso::TASKFLAG_NO_ZLIB; | ||
} else if (has_arg(i, argv, "--no-7zdeflate")) { | ||
args.flags |= maxcso::TASKFLAG_NO_7ZIP; | ||
} else if (has_arg_value(i, argv, "--out", val) || has_arg_value(i, argv, "-o", val)) { | ||
args.outputs.push_back(val); | ||
} else if (has_arg(i, argv, "--")) { | ||
break; | ||
} else { | ||
show_help(argv[0]); | ||
fprintf(stderr, "\nERROR: Unknown argument: %s\n", argv[i]); | ||
return 1; | ||
} | ||
} else { | ||
args.inputs.push_back(argv[i]); | ||
} | ||
} | ||
|
||
for (; i < argc; ++i) { | ||
// If we're here, it means we hit a "--". The rest are inputs, not args. | ||
args.inputs.push_back(argv[i]); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int validate_args(const char *arg0, Arguments &args) { | ||
if (args.threads == 0) { | ||
uv_cpu_info_t *cpus; | ||
uv_cpu_info(&cpus, &args.threads); | ||
uv_free_cpu_info(cpus, args.threads); | ||
} | ||
|
||
if (args.inputs.size() < args.outputs.size()) { | ||
show_help(arg0); | ||
fprintf(stderr, "\nERROR: Too many output files.\n"); | ||
return 1; | ||
} | ||
|
||
// Automatically write to .cso files if not specified. | ||
for (size_t i = args.outputs.size(); i < args.inputs.size(); ++i) { | ||
if (args.inputs[i].size() <= 4) { | ||
continue; | ||
} | ||
|
||
const std::string ext = args.inputs[i].substr(args.inputs[i].size() - 4); | ||
if (ext == ".iso" || ext == ".ISO") { | ||
args.outputs.push_back(args.inputs[i].substr(0, args.inputs[i].size() - 4) + ".cso"); | ||
} | ||
} | ||
|
||
if (args.inputs.empty()) { | ||
show_help(arg0); | ||
fprintf(stderr, "\nERROR: No input files.\n"); | ||
return 1; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
#ifdef _WIN32 | ||
int setenv(const char *name, const char *value, int) { | ||
return _putenv_s(name, value); | ||
} | ||
#endif | ||
|
||
inline uv_buf_t uv_buf_init(const char *str) { | ||
return uv_buf_init(const_cast<char *>(str), static_cast<unsigned int>(strlen(str))); | ||
} | ||
|
||
inline uv_buf_t uv_buf_init(const std::string &str) { | ||
return uv_buf_init(const_cast<char *>(str.c_str()), static_cast<unsigned int>(str.size())); | ||
} | ||
|
||
const std::string ANSI_RESET_LINE = "\033[2K\033[0G"; | ||
|
||
int main(int argc, char *argv[]) { | ||
Arguments args; | ||
default_args(args); | ||
int result = parse_args(args, argc, argv); | ||
if (result != 0) { | ||
return result; | ||
} | ||
result = validate_args(argv[0], args); | ||
if (result != 0) { | ||
return result; | ||
} | ||
|
||
char threadpool_size[32]; | ||
sprintf(threadpool_size, "%d", args.threads); | ||
setenv("UV_THREADPOOL_SIZE", threadpool_size, 1); | ||
|
||
uv_loop_t loop; | ||
uv_tty_t tty; | ||
uv_loop_init(&loop); | ||
uv_tty_init(&loop, &tty, 2, 0); | ||
uv_tty_set_mode(&tty, 0); | ||
bool formatting = uv_guess_handle(2) == UV_TTY && !args.quiet; | ||
|
||
int64_t next = uv_hrtime(); | ||
int64_t lastPos = 0; | ||
// 50ms | ||
static const int64_t interval_ns = 50000000LL; | ||
static const double interval_to_s = 1000.0 / 50.0; | ||
|
||
maxcso::ProgressCallback progress = [&next, &lastPos, formatting, &tty] (const maxcso::Task *task, maxcso::TaskStatus status, int64_t pos, int64_t total, int64_t written) { | ||
if (!formatting) { | ||
return; | ||
} | ||
|
||
std::string statusInfo; | ||
if (status == maxcso::TASK_INPROGRESS) { | ||
int64_t now = uv_hrtime(); | ||
if (now >= next) { | ||
double percent = (pos * 100.0) / total; | ||
double ratio = (written * 100.0) / pos; | ||
double speed = 0.0; | ||
int64_t diff = pos - lastPos; | ||
if (diff > 0) { | ||
speed = (diff / 1024.0 / 1024.0) * interval_to_s; | ||
} | ||
|
||
char temp[128]; | ||
sprintf(temp, "%3.0f%%, ratio=%3.0f%%, speed=%5.2f MB/s", percent, ratio, speed); | ||
statusInfo = temp; | ||
|
||
next = now + interval_ns; | ||
lastPos = pos; | ||
} | ||
} else if (status == maxcso::TASK_SUCCESS) { | ||
statusInfo = "Complete\n"; | ||
} else { | ||
// This shouldn't happen. | ||
statusInfo = "Something went wrong.\n"; | ||
} | ||
|
||
if (statusInfo.empty()) { | ||
return; | ||
} | ||
|
||
uv_buf_t bufs[2]; | ||
uv_write_t write_req; | ||
|
||
unsigned int nbufs = 0; | ||
if (formatting) { | ||
bufs[nbufs++] = uv_buf_init(ANSI_RESET_LINE); | ||
} | ||
|
||
if (task->input.size() > 38) { | ||
statusInfo = "..." + task->input.substr(task->input.size() - 35) + ": " + statusInfo; | ||
} else { | ||
statusInfo = task->input + ": " + statusInfo; | ||
} | ||
|
||
bufs[nbufs++] = uv_buf_init(statusInfo); | ||
uv_write(&write_req, reinterpret_cast<uv_stream_t *>(&tty), bufs, nbufs, nullptr); | ||
}; | ||
maxcso::ErrorCallback error = [&args, formatting, &tty] (const maxcso::Task *task, maxcso::TaskStatus status, const char *reason) { | ||
std::string line; | ||
uv_buf_t buf; | ||
uv_write_t write_req; | ||
|
||
if (args.quiet) { | ||
return; | ||
} | ||
|
||
line = (formatting ? ANSI_RESET_LINE : "") + "Error while processing " + task->input + ": " + reason + "\n"; | ||
buf = uv_buf_init(line); | ||
uv_write(&write_req, reinterpret_cast<uv_stream_t *>(&tty), &buf, 1, nullptr); | ||
}; | ||
|
||
std::vector<maxcso::Task> tasks; | ||
for (size_t i = 0; i < args.inputs.size(); ++i) { | ||
maxcso::Task task; | ||
task.input = args.inputs[i]; | ||
task.output = args.outputs[i]; | ||
task.progress = progress; | ||
task.error = error; | ||
task.flags = args.flags; | ||
tasks.push_back(std::move(task)); | ||
} | ||
|
||
maxcso::Compress(tasks); | ||
|
||
uv_tty_reset_mode(); | ||
uv_loop_close(&loop); | ||
return 0; | ||
} |
Oops, something went wrong.