Working unpacker!
This commit is contained in:
commit
1b25329769
|
@ -0,0 +1,2 @@
|
|||
.zig-cache/
|
||||
zig-out/
|
|
@ -0,0 +1,70 @@
|
|||
Licence Libre du Québec – Permissive (LiLiQ-P)
|
||||
|
||||
Version 1.1
|
||||
|
||||
1. Préambule
|
||||
Cette licence s'applique à tout logiciel distribué dont le titulaire du droit d'auteur précise qu'il est sujet aux termes de la Licence Libre du Québec – Permissive (LiLiQ-P) (ci-après appelée la « licence »).
|
||||
|
||||
2. Définitions
|
||||
Dans la présente licence, à moins que le contexte n'indique un sens différent, on entend par:
|
||||
|
||||
« concédant » : le titulaire du droit d'auteur sur le logiciel, ou toute personne dûment autorisée par ce dernier à accorder la présente licence;
|
||||
« contributeur » : le titulaire du droit d'auteur ou toute personne autorisée par ce dernier à soumettre au concédant une contribution. Un contributeur dont sa contribution est incorporée au logiciel est considéré comme un concédant en regard de sa contribution;
|
||||
« contribution » : tout logiciel original, ou partie de logiciel original soumis et destiné à être incorporé dans le logiciel;
|
||||
« distribution » : le fait de délivrer une copie du logiciel;
|
||||
« licencié » : toute personne qui possède une copie du logiciel et qui exerce les droits concédés par la licence;
|
||||
« logiciel » : une œuvre protégée par le droit d'auteur, telle qu'un programme d'ordinateur et sa documentation, pour laquelle le titulaire du droit d'auteur a précisé qu'elle est sujette aux termes de la présente licence;
|
||||
« logiciel dérivé » : tout logiciel original réalisé par un licencié, autre que le logiciel ou un logiciel modifié, qui produit ou reproduit la totalité ou une partie importante du logiciel;
|
||||
« logiciel modifié » : toute modification par un licencié de l'un des fichiers source du logiciel ou encore tout nouveau fichier source qui incorpore le logiciel ou une partie importante de ce dernier.
|
||||
|
||||
3. Licence de droit d'auteur
|
||||
Sous réserve des termes de la licence, le concédant accorde au licencié une licence non exclusive et libre de redevances lui permettant d’exercer les droits suivants sur le logiciel :
|
||||
|
||||
1 Produire ou reproduire la totalité ou une partie importante;
|
||||
2 Exécuter ou représenter la totalité ou une partie importante en public;
|
||||
3 Publier la totalité ou une partie importante;
|
||||
4 Sous-licencier sous une autre licence libre, approuvée ou certifiée par la Free Software Foundation ou l'Open Source Initiative.
|
||||
|
||||
Cette licence est accordée sans limite territoriale et sans limite de temps.
|
||||
|
||||
L'exercice complet de ces droits est sujet à la distribution par le concédant du code source du logiciel, lequel doit être sous une forme permettant d'y apporter des modifications. Le concédant peut aussi distribuer le logiciel accompagné d'une offre de distribuer le code source du logiciel, sans frais supplémentaires, autres que ceux raisonnables afin de permettre la livraison du code source. Cette offre doit être valide pendant une durée raisonnable.
|
||||
|
||||
4. Distribution
|
||||
Le licencié peut distribuer des copies du logiciel, d'un logiciel modifié ou dérivé, sous réserve de respecter les conditions suivantes :
|
||||
|
||||
1 Le logiciel doit être accompagné d'un exemplaire de cette licence;
|
||||
2 Si le logiciel a été modifié, le licencié doit en faire la mention, de préférence dans chacun des fichiers modifiés dont la nature permet une telle mention;
|
||||
3 Les étiquettes ou mentions faisant état des droits d'auteur, des marques de commerce, des garanties ou de la paternité concernant le logiciel ne doivent pas être modifiées ou supprimées, à moins que ces étiquettes ou mentions ne soient inapplicables à un logiciel modifié ou dérivé donné.
|
||||
|
||||
5. Contributions
|
||||
Sous réserve d'une entente distincte, toute contribution soumise par un contributeur au concédant pour inclusion dans le logiciel sera soumise aux termes de cette licence.
|
||||
|
||||
6. Marques de commerce
|
||||
La licence n'accorde aucune permission particulière qui permettrait d'utiliser les marques de commerce du concédant, autre que celle requise permettant d'identifier la provenance du logiciel.
|
||||
|
||||
7. Garanties
|
||||
Sauf mention contraire, le concédant distribue le logiciel sans aucune garantie, aux risques et périls de l'acquéreur de la copie du logiciel, et ce, sans assurer que le logiciel puisse répondre à un besoin particulier ou puisse donner un résultat quelconque.
|
||||
|
||||
Sans lier le concédant d'une quelconque manière, rien n'empêche un licencié d'offrir ou d'exclure des garanties ou du support.
|
||||
|
||||
8. Responsabilité
|
||||
Le licencié est responsable de tout préjudice résultant de l'exercice des droits accordés par la licence.
|
||||
|
||||
Le concédant ne saurait être tenu responsable de dommages subis par le licencié ou par des tiers, pour quelque cause que ce soit en lien avec la licence et les droits qui y sont accordés.
|
||||
|
||||
9. Résiliation
|
||||
La présente licence est automatiquement résiliée dès que les droits qui y sont accordés ne sont pas exercés conformément aux termes qui y sont stipulés.
|
||||
|
||||
Toutefois, si le défaut est corrigé dans un délai de 30 jours de sa prise de connaissance par la personne en défaut, et qu'il s'agit du premier défaut, la licence est accordée de nouveau.
|
||||
|
||||
Pour tout défaut subséquent, le consentement exprès du concédant est nécessaire afin que la licence soit accordée de nouveau.
|
||||
|
||||
10. Version de la licence
|
||||
Le Centre de services partagés du Québec, ses ayants cause ou toute personne qu'il désigne, peuvent diffuser des versions révisées ou modifiées de cette licence. Chaque version recevra un numéro unique. Si un logiciel est déjà soumis aux termes d'une version spécifique, c'est seulement cette version qui liera les parties à la licence.
|
||||
|
||||
Le concédant peut aussi choisir de concéder la licence sous la version actuelle ou toute version ultérieure, auquel cas le licencié peut choisir sous quelle version la licence lui est accordée.
|
||||
|
||||
11. Divers
|
||||
Dans la mesure où le concédant est un ministère, un organisme public ou une personne morale de droit public, créés en vertu d'une loi de l'Assemblée nationale du Québec, la licence est régie par le droit applicable au Québec et en cas de contestation, les tribunaux du Québec seront seuls compétents.
|
||||
|
||||
La présente licence peut être distribuée sans conditions particulières. Toutefois, une version modifiée doit être distribuée sous un nom différent. Toute référence au Centre de services partagés du Québec, et, le cas échéant, ses ayant cause, doit être retirée, autre que celle permettant d'identifier la provenance de la licence.
|
|
@ -0,0 +1,40 @@
|
|||
# mpar: Mozaïc Pack Archiver
|
||||
|
||||
A featureful archiver for the .pak file format, as found in the game Mozaïc, by Ingenio, a Loto-Québec subsidiary, and probably a few of their other games.
|
||||
|
||||
## Known to work with
|
||||
|
||||
- Mozaïc
|
||||
|
||||
## Build
|
||||
|
||||
Use Zig 0.14.
|
||||
|
||||
## Usage
|
||||
|
||||
If you intend to mod the game, you will want to repack the files using our JSON metadata format that looks like this:
|
||||
|
||||
```
|
||||
{
|
||||
"description": "",
|
||||
"entries": [
|
||||
{
|
||||
"name": "media\\En\\Img\\Hd\\aide_bkg_explic_EN.bmp",
|
||||
"value": 39
|
||||
},
|
||||
// ...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can generate one from an existing pack using this command:
|
||||
|
||||
```
|
||||
mpar l -f json Mozaic_EN_hd.pak
|
||||
```
|
||||
|
||||
The `description` field can be freely changed, and as far as we know isn't used by the game. We highly recommend to put something there. It has a limit of 32 bytes.
|
||||
|
||||
The `value` field, every file has one, we still don't know what it is for, how it's generated and how it correlates to anything, but it seems important for the game - it's going to crash right away if you change any of these values.
|
||||
|
||||
The format can change at any time, so you may want to regenerate a fresh one if you upgrade this tool.
|
|
@ -0,0 +1,40 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "mpar",
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const yazap = b.dependency("yazap", .{});
|
||||
exe.root_module.addImport("yazap", yazap.module("yazap"));
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
const exe_unit_tests = b.addTest(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_exe_unit_tests.step);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
.{
|
||||
.name = .mpar,
|
||||
.version = "0.0.1",
|
||||
.fingerprint = 0x7e14d2647bed0f3a,
|
||||
.dependencies = .{
|
||||
.yazap = .{
|
||||
.url = "git+https://github.com/prajwalch/yazap#71490491b8f1b0741218160b88cf0a87d2a941ce",
|
||||
.hash = "yazap-0.6.3-Z1t-EiLlAQCR5mUSOlF1PovnwyPcIUGeV02lAIVMUEOc",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
},
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
const std = @import("std");
|
||||
const yazap = @import("yazap");
|
||||
const root = @import("root.zig");
|
||||
|
||||
const allocator = std.heap.page_allocator;
|
||||
const log = std.log;
|
||||
const App = yazap.App;
|
||||
const Arg = yazap.Arg;
|
||||
|
||||
pub fn main() !u8 {
|
||||
var app = App.init(allocator, "mpar", "Mozaic pack archiver");
|
||||
defer app.deinit();
|
||||
|
||||
var mpar = app.rootCommand();
|
||||
mpar.setProperty(.help_on_empty_args);
|
||||
|
||||
var list_cmd = app.createCommand("l", "List files from a pack");
|
||||
list_cmd.setProperty(.help_on_empty_args);
|
||||
list_cmd.setProperty(.positional_arg_required);
|
||||
|
||||
try list_cmd.addArgs(&[_]Arg{
|
||||
Arg.singleValueOptionWithValidValues("format", 'f', "Output format", &[_][]const u8{ "none", "json" }),
|
||||
Arg.positional("filename", "Path to .pak file", null),
|
||||
});
|
||||
|
||||
try mpar.addSubcommand(list_cmd);
|
||||
|
||||
var extract_cmd = app.createCommand("x", "Extract files from a pack");
|
||||
extract_cmd.setProperty(.help_on_empty_args);
|
||||
extract_cmd.setProperty(.positional_arg_required);
|
||||
|
||||
try extract_cmd.addArgs(&[_]Arg{
|
||||
Arg.singleValueOption("output", 'o', "Output directory"),
|
||||
Arg.booleanOption("quiet", 'q', "Be quiet"),
|
||||
Arg.positional("filename", "Path to .pak file", null),
|
||||
});
|
||||
|
||||
try mpar.addSubcommand(extract_cmd);
|
||||
|
||||
const matches = try app.parseProcess();
|
||||
|
||||
if (matches.subcommandMatches("x")) |submatches| {
|
||||
return root.extract(submatches);
|
||||
}
|
||||
|
||||
if (matches.subcommandMatches("l")) |submatches| {
|
||||
return root.list(submatches);
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub const PackError = error{InvalidPack};
|
||||
|
||||
pub const Entry = struct {
|
||||
size: u32,
|
||||
offset: u32,
|
||||
value: u32,
|
||||
oname: []const u8,
|
||||
name: []const u8,
|
||||
};
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
handle: std.fs.File,
|
||||
description: []const u8,
|
||||
entries: []Entry,
|
||||
|
||||
pub fn open(allocator: std.mem.Allocator, name: []const u8) !@This() {
|
||||
const f = try std.fs.cwd().openFile(name, .{});
|
||||
const reader = f.reader();
|
||||
|
||||
const magic = try reader.readInt(u32, .big);
|
||||
if (magic != 0x5041434b) return PackError.InvalidPack;
|
||||
|
||||
const size = try reader.readInt(u32, .little);
|
||||
const desc = try reader.readBoundedBytes(32);
|
||||
const a: []const u8 = try allocator.dupe(u8, &desc.buffer);
|
||||
const d = std.mem.trimRight(u8, a, "\x00");
|
||||
const entries = try allocator.alloc(Entry, size);
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < size) : (i += 1) {
|
||||
const fsize = try reader.readInt(u32, .little);
|
||||
const foffset = try reader.readInt(u32, .little);
|
||||
const fvalue = try reader.readInt(u32, .little);
|
||||
const foname = try reader.readUntilDelimiterAlloc(allocator, 0, 64);
|
||||
const fname = try allocator.dupe(u8, foname);
|
||||
std.mem.replaceScalar(u8, fname, std.fs.path.sep_windows, std.fs.path.sep);
|
||||
entries[i] = .{
|
||||
.size = fsize,
|
||||
.offset = foffset,
|
||||
.value = fvalue,
|
||||
.oname = foname,
|
||||
.name = fname,
|
||||
};
|
||||
}
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.handle = f,
|
||||
.description = d,
|
||||
.entries = entries,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: @This()) void {
|
||||
for (self.entries) |entry| {
|
||||
self.allocator.free(entry.name);
|
||||
}
|
||||
self.allocator.free(self.entries);
|
||||
self.handle.close();
|
||||
}
|
||||
|
||||
pub fn copy(self: @This(), entry: Entry, out: std.fs.File, offset: u64) !u64 {
|
||||
return self.handle.copyRangeAll(entry.offset, out, offset, entry.size);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
const std = @import("std");
|
||||
const yazap = @import("yazap");
|
||||
const Pack = @import("pack.zig");
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
pub fn list(matches: yazap.ArgMatches) !u8 {
|
||||
const name = matches.getSingleValue("filename").?;
|
||||
const pack = Pack.open(allocator, name) catch |err| switch (err) {
|
||||
error.InvalidPack => {
|
||||
std.log.err("invalid pack", .{});
|
||||
return 1;
|
||||
},
|
||||
else => {
|
||||
std.log.err("read error", .{});
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
defer pack.close();
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
|
||||
if (matches.getSingleValue("format")) |format| {
|
||||
if (std.mem.eql(u8, format, "json")) {
|
||||
try stdout.print("{{\n \"description\": \"", .{});
|
||||
try std.zig.stringEscape(pack.description, "", .{}, stdout);
|
||||
try stdout.print("\",\n \"entries\": [\n", .{});
|
||||
var i: usize = 0;
|
||||
for (pack.entries) |entry| {
|
||||
i += 1;
|
||||
try stdout.print(" {{\n \"name\": \"", .{});
|
||||
try std.zig.stringEscape(entry.oname, "", .{}, stdout);
|
||||
try stdout.print("\",\n \"value\": {d}\n", .{entry.value});
|
||||
if (i == pack.entries.len) {
|
||||
try stdout.print(" }}\n", .{});
|
||||
} else {
|
||||
try stdout.print(" }},\n", .{});
|
||||
}
|
||||
}
|
||||
try stdout.print(" ]\n}}\n", .{});
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!std.mem.eql(u8, pack.description, ""))
|
||||
try stdout.print("{s}\n\n", .{pack.description});
|
||||
try stdout.print(" size val name\n", .{});
|
||||
try stdout.print("{s:-<48}\n", .{""});
|
||||
var size: u32 = 0;
|
||||
var n: u32 = 0;
|
||||
for (pack.entries) |entry| {
|
||||
size += entry.size;
|
||||
n += 1;
|
||||
try std.io.getStdOut().writer().print("{d: >10} {d: >3} {s}\n", .{ entry.size, entry.value, entry.name });
|
||||
}
|
||||
try stdout.print("{s:-<48}\n", .{""});
|
||||
try stdout.print("{d: >10} bytes in {} files\n", .{ size, n });
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn extract(matches: yazap.ArgMatches) !u8 {
|
||||
const name = matches.getSingleValue("filename").?;
|
||||
const pack = Pack.open(allocator, name) catch |err| switch (err) {
|
||||
error.InvalidPack => {
|
||||
std.log.err("invalid pack", .{});
|
||||
return 1;
|
||||
},
|
||||
else => {
|
||||
std.log.err("read error", .{});
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
defer pack.close();
|
||||
|
||||
var currentDir = std.fs.cwd();
|
||||
if (matches.getSingleValue("output")) |path| {
|
||||
try std.fs.cwd().makePath(path);
|
||||
currentDir = try std.fs.cwd().openDir(path, .{});
|
||||
}
|
||||
|
||||
for (pack.entries) |entry| {
|
||||
if (std.fs.path.dirname(entry.name)) |dir| {
|
||||
try currentDir.makePath(dir);
|
||||
}
|
||||
const f = try currentDir.createFile(entry.name, .{});
|
||||
defer f.close();
|
||||
|
||||
_ = try pack.copy(entry, f, 0);
|
||||
|
||||
if (!matches.containsArg("quiet")) {
|
||||
try std.io.getStdOut().writer().print("{s}\n", .{entry.name});
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue