|
diff --git a/libraries/vfs.h b/libraries/vfs.h
|
|
|
1 |
#ifndef VFS_H |
|
|
2 |
#define VFS_H |
|
|
3 |
|
|
|
4 |
#include <stddef.h> |
|
|
5 |
#include <stdint.h> |
|
|
6 |
#include <stdio.h> |
|
|
7 |
#include <stdlib.h> |
|
|
8 |
#include <string.h> |
|
|
9 |
|
|
|
10 |
typedef struct { |
|
|
11 |
char path[256]; |
|
|
12 |
uint64_t offset; |
|
|
13 |
uint64_t size; |
|
|
14 |
} VfsEntry; |
|
|
15 |
|
|
|
16 |
typedef struct { |
|
|
17 |
char magic[4]; |
|
|
18 |
uint32_t num_files; |
|
|
19 |
} VfsHeader; |
|
|
20 |
|
|
|
21 |
typedef struct { |
|
|
22 |
FILE* f; |
|
|
23 |
uint64_t start; |
|
|
24 |
uint64_t size; |
|
|
25 |
uint64_t pos; |
|
|
26 |
int is_pak; |
|
|
27 |
} VfsFile; |
|
|
28 |
|
|
|
29 |
void vfs_init(const char* pak_path); |
|
|
30 |
void vfs_shutdown(void); |
|
|
31 |
|
|
|
32 |
// Basic API |
|
|
33 |
void* vfs_read(const char* path, size_t* out_size); |
|
|
34 |
void vfs_free(void* data); |
|
|
35 |
|
|
|
36 |
// Extended API for VFS-like behavior |
|
|
37 |
VfsFile* vfs_open(const char* path); |
|
|
38 |
size_t vfs_fread(void* ptr, size_t size, size_t nmemb, VfsFile* file); |
|
|
39 |
int vfs_fseek(VfsFile* file, int64_t offset, int whence); |
|
|
40 |
int64_t vfs_ftell(VfsFile* file); |
|
|
41 |
void vfs_fclose(VfsFile* file); |
|
|
42 |
|
|
|
43 |
#ifdef VFS_IMPLEMENTATION |
|
|
44 |
|
|
|
45 |
#include <errno.h> |
|
|
46 |
|
|
|
47 |
// Note: Standard printf is used for logging. |
|
|
48 |
|
|
|
49 |
static VfsEntry* g_entries = NULL; |
|
|
50 |
static uint32_t g_num_entries = 0; |
|
|
51 |
static char g_pak_path[512] = {0}; |
|
|
52 |
static int g_disk_mode = 0; |
|
|
53 |
|
|
|
54 |
void vfs_init(const char* pak_path) { |
|
|
55 |
const char* debug_env = getenv("DEBUG"); |
|
|
56 |
if (debug_env && (strcmp(debug_env, "1") == 0 || strcmp(debug_env, "true") == 0)) { |
|
|
57 |
g_disk_mode = 1; |
|
|
58 |
printf("VFS INFO: Operating in Disk Mode (DEBUG enabled)\n"); |
|
|
59 |
fflush(stdout); |
|
|
60 |
return; |
|
|
61 |
} |
|
|
62 |
|
|
|
63 |
FILE* f = fopen(pak_path, "rb"); |
|
|
64 |
if (!f) { |
|
|
65 |
printf("VFS ERROR: Failed to open pak file: %s (Error: %s)\n", pak_path, strerror(errno)); |
|
|
66 |
printf("VFS WARNING: Falling back to Disk Mode\n"); |
|
|
67 |
fflush(stdout); |
|
|
68 |
g_disk_mode = 1; |
|
|
69 |
return; |
|
|
70 |
} |
|
|
71 |
|
|
|
72 |
VfsHeader header; |
|
|
73 |
if (fread(&header, sizeof(VfsHeader), 1, f) != 1) { |
|
|
74 |
printf("VFS ERROR: Failed to read header from %s\n", pak_path); |
|
|
75 |
fflush(stdout); |
|
|
76 |
fclose(f); |
|
|
77 |
g_disk_mode = 1; |
|
|
78 |
return; |
|
|
79 |
} |
|
|
80 |
|
|
|
81 |
if (memcmp(header.magic, "DRP1", 4) != 0) { |
|
|
82 |
printf("VFS ERROR: Invalid magic in %s\n", pak_path); |
|
|
83 |
fflush(stdout); |
|
|
84 |
fclose(f); |
|
|
85 |
g_disk_mode = 1; |
|
|
86 |
return; |
|
|
87 |
} |
|
|
88 |
|
|
|
89 |
g_num_entries = header.num_files; |
|
|
90 |
g_entries = (VfsEntry*)malloc(sizeof(VfsEntry) * g_num_entries); |
|
|
91 |
if (fread(g_entries, sizeof(VfsEntry), g_num_entries, f) != g_num_entries) { |
|
|
92 |
printf("VFS ERROR: Failed to read index table from %s\n", pak_path); |
|
|
93 |
fflush(stdout); |
|
|
94 |
free(g_entries); |
|
|
95 |
g_entries = NULL; |
|
|
96 |
fclose(f); |
|
|
97 |
g_disk_mode = 1; |
|
|
98 |
return; |
|
|
99 |
} |
|
|
100 |
|
|
|
101 |
fclose(f); |
|
|
102 |
strncpy(g_pak_path, pak_path, sizeof(g_pak_path) - 1); |
|
|
103 |
printf("VFS INFO: Loaded %u files from %s\n", g_num_entries, pak_path); |
|
|
104 |
fflush(stdout); |
|
|
105 |
} |
|
|
106 |
|
|
|
107 |
void vfs_shutdown(void) { |
|
|
108 |
if (g_entries) { |
|
|
109 |
free(g_entries); |
|
|
110 |
g_entries = NULL; |
|
|
111 |
} |
|
|
112 |
} |
|
|
113 |
|
|
|
114 |
VfsFile* vfs_open(const char* path) { |
|
|
115 |
if (g_disk_mode) { |
|
|
116 |
FILE* f = fopen(path, "rb"); |
|
|
117 |
if (!f) return NULL; |
|
|
118 |
VfsFile* vf = (VfsFile*)malloc(sizeof(VfsFile)); |
|
|
119 |
vf->f = f; |
|
|
120 |
fseek(f, 0, SEEK_END); |
|
|
121 |
vf->size = ftell(f); |
|
|
122 |
fseek(f, 0, SEEK_SET); |
|
|
123 |
vf->start = 0; |
|
|
124 |
vf->pos = 0; |
|
|
125 |
vf->is_pak = 0; |
|
|
126 |
return vf; |
|
|
127 |
} |
|
|
128 |
|
|
|
129 |
for (uint32_t i = 0; i < g_num_entries; i++) { |
|
|
130 |
if (strcmp(g_entries[i].path, path) == 0) { |
|
|
131 |
FILE* f = fopen(g_pak_path, "rb"); |
|
|
132 |
if (!f) return NULL; |
|
|
133 |
VfsFile* vf = (VfsFile*)malloc(sizeof(VfsFile)); |
|
|
134 |
vf->f = f; |
|
|
135 |
vf->start = g_entries[i].offset; |
|
|
136 |
vf->size = g_entries[i].size; |
|
|
137 |
vf->pos = 0; |
|
|
138 |
vf->is_pak = 1; |
|
|
139 |
fseek(f, (long)vf->start, SEEK_SET); |
|
|
140 |
return vf; |
|
|
141 |
} |
|
|
142 |
} |
|
|
143 |
|
|
|
144 |
printf("VFS WARNING: File not found: %s\n", path); |
|
|
145 |
fflush(stdout); |
|
|
146 |
return NULL; |
|
|
147 |
} |
|
|
148 |
|
|
|
149 |
size_t vfs_fread(void* ptr, size_t size, size_t nmemb, VfsFile* file) { |
|
|
150 |
size_t total_to_read = size * nmemb; |
|
|
151 |
if (file->pos + total_to_read > file->size) { |
|
|
152 |
total_to_read = (size_t)(file->size - file->pos); |
|
|
153 |
} |
|
|
154 |
|
|
|
155 |
if (total_to_read <= 0) return 0; |
|
|
156 |
|
|
|
157 |
size_t read_bytes = fread(ptr, 1, total_to_read, file->f); |
|
|
158 |
file->pos += read_bytes; |
|
|
159 |
return read_bytes / size; |
|
|
160 |
} |
|
|
161 |
|
|
|
162 |
int vfs_fseek(VfsFile* file, int64_t offset, int whence) { |
|
|
163 |
int64_t new_pos; |
|
|
164 |
switch (whence) { |
|
|
165 |
case SEEK_SET: new_pos = offset; break; |
|
|
166 |
case SEEK_CUR: new_pos = (int64_t)file->pos + offset; break; |
|
|
167 |
case SEEK_END: new_pos = (int64_t)file->size + offset; break; |
|
|
168 |
default: return -1; |
|
|
169 |
} |
|
|
170 |
|
|
|
171 |
if (new_pos < 0) new_pos = 0; |
|
|
172 |
if (new_pos > (int64_t)file->size) new_pos = (int64_t)file->size; |
|
|
173 |
|
|
|
174 |
file->pos = (uint64_t)new_pos; |
|
|
175 |
return fseek(file->f, (long)(file->start + file->pos), SEEK_SET); |
|
|
176 |
} |
|
|
177 |
|
|
|
178 |
int64_t vfs_ftell(VfsFile* file) { |
|
|
179 |
return (int64_t)file->pos; |
|
|
180 |
} |
|
|
181 |
|
|
|
182 |
void vfs_fclose(VfsFile* file) { |
|
|
183 |
if (file->f) fclose(file->f); |
|
|
184 |
free(file); |
|
|
185 |
} |
|
|
186 |
|
|
|
187 |
void* vfs_read(const char* path, size_t* out_size) { |
|
|
188 |
VfsFile* f = vfs_open(path); |
|
|
189 |
if (!f) return NULL; |
|
|
190 |
|
|
|
191 |
void* data = malloc((size_t)f->size + 1); |
|
|
192 |
if (!data) { |
|
|
193 |
vfs_fclose(f); |
|
|
194 |
return NULL; |
|
|
195 |
} |
|
|
196 |
|
|
|
197 |
if (vfs_fread(data, 1, (size_t)f->size, f) != f->size) { |
|
|
198 |
free(data); |
|
|
199 |
vfs_fclose(f); |
|
|
200 |
return NULL; |
|
|
201 |
} |
|
|
202 |
|
|
|
203 |
((char*)data)[f->size] = '\0'; |
|
|
204 |
|
|
|
205 |
if (out_size) *out_size = (size_t)f->size; |
|
|
206 |
vfs_fclose(f); |
|
|
207 |
return data; |
|
|
208 |
} |
|
|
209 |
|
|
|
210 |
void vfs_free(void* data) { |
|
|
211 |
free(data); |
|
|
212 |
} |
|
|
213 |
|
|
|
214 |
#endif // VFS_IMPLEMENTATION |
|
|
215 |
|
|
|
216 |
#endif // VFS_H |
|
diff --git a/tools/packer.c b/tools/packer.c
|
|
|
1 |
#define _DEFAULT_SOURCE |
|
|
2 |
#include <stdio.h> |
|
|
3 |
#include <stdlib.h> |
|
|
4 |
#include <string.h> |
|
|
5 |
#include <stdint.h> |
|
|
6 |
#include <dirent.h> |
|
|
7 |
#include <sys/stat.h> |
|
|
8 |
#include <getopt.h> |
|
|
9 |
#include <fnmatch.h> |
|
|
10 |
#include <ctype.h> |
|
|
11 |
|
|
|
12 |
#include "../libraries/vfs.h" |
|
|
13 |
|
|
|
14 |
static void format_size(char* buf, size_t buf_size, uint64_t size) { |
|
|
15 |
const char* units[] = {"B", "KB", "MB", "GB", "TB"}; |
|
|
16 |
int i = 0; |
|
|
17 |
double d_size = (double)size; |
|
|
18 |
while (d_size >= 1024 && i < 4) { |
|
|
19 |
d_size /= 1024.0; |
|
|
20 |
i++; |
|
|
21 |
} |
|
|
22 |
if (i == 0) { |
|
|
23 |
snprintf(buf, buf_size, "%llu B", (unsigned long long)size); |
|
|
24 |
} else { |
|
|
25 |
snprintf(buf, buf_size, "%.2f %s", d_size, units[i]); |
|
|
26 |
} |
|
|
27 |
} |
|
|
28 |
|
|
|
29 |
#define MAX_FILES 4096 |
|
|
30 |
|
|
|
31 |
typedef struct { |
|
|
32 |
char path[256]; |
|
|
33 |
long size; |
|
|
34 |
void* data; |
|
|
35 |
} PendingFile; |
|
|
36 |
|
|
|
37 |
static PendingFile g_pending[MAX_FILES]; |
|
|
38 |
static int g_count = 0; |
|
|
39 |
|
|
|
40 |
static char* g_ignore_patterns[MAX_FILES]; |
|
|
41 |
static int g_ignore_count = 0; |
|
|
42 |
|
|
|
43 |
void load_gitignore() { |
|
|
44 |
FILE* f = fopen(".gitignore", "r"); |
|
|
45 |
if (!f) return; |
|
|
46 |
|
|
|
47 |
char line[1024]; |
|
|
48 |
while (fgets(line, sizeof(line), f)) { |
|
|
49 |
// Strip whitespace and newline |
|
|
50 |
char* p = line; |
|
|
51 |
while (isspace(*p)) p++; |
|
|
52 |
char* end = p + strlen(p) - 1; |
|
|
53 |
while (end >= p && isspace(*end)) { |
|
|
54 |
*end = '\0'; |
|
|
55 |
end--; |
|
|
56 |
} |
|
|
57 |
|
|
|
58 |
if (*p == '\0' || *p == '#') continue; |
|
|
59 |
|
|
|
60 |
if (g_ignore_count < MAX_FILES) { |
|
|
61 |
char* s = strdup(p); |
|
|
62 |
if (s) g_ignore_patterns[g_ignore_count++] = s; |
|
|
63 |
} |
|
|
64 |
} |
|
|
65 |
fclose(f); |
|
|
66 |
} |
|
|
67 |
|
|
|
68 |
void free_gitignore() { |
|
|
69 |
for (int i = 0; i < g_ignore_count; i++) { |
|
|
70 |
free(g_ignore_patterns[i]); |
|
|
71 |
} |
|
|
72 |
g_ignore_count = 0; |
|
|
73 |
} |
|
|
74 |
|
|
|
75 |
int should_ignore(const char* path, int is_dir) { |
|
|
76 |
// Normalize path for matching: remove leading "./" if present |
|
|
77 |
const char* p = path; |
|
|
78 |
if (strncmp(p, "./", 2) == 0) p += 2; |
|
|
79 |
|
|
|
80 |
for (int i = 0; i < g_ignore_count; i++) { |
|
|
81 |
const char* pattern = g_ignore_patterns[i]; |
|
|
82 |
int pattern_is_dir_only = (pattern[strlen(pattern) - 1] == '/'); |
|
|
83 |
|
|
|
84 |
char clean_pattern[1024]; |
|
|
85 |
strncpy(clean_pattern, pattern, 1023); |
|
|
86 |
clean_pattern[1023] = '\0'; |
|
|
87 |
if (pattern_is_dir_only) { |
|
|
88 |
clean_pattern[strlen(clean_pattern) - 1] = '\0'; |
|
|
89 |
} |
|
|
90 |
|
|
|
91 |
// If pattern has a slash (not at the end), it's relative to root |
|
|
92 |
int has_slash = (strchr(clean_pattern, '/') != NULL); |
|
|
93 |
|
|
|
94 |
if (has_slash) { |
|
|
95 |
// Match against full path from root |
|
|
96 |
if (fnmatch(clean_pattern, p, FNM_PATHNAME) == 0) { |
|
|
97 |
if (!pattern_is_dir_only || is_dir) return 1; |
|
|
98 |
} |
|
|
99 |
} else { |
|
|
100 |
// Match against filename only |
|
|
101 |
const char* filename = strrchr(p, '/'); |
|
|
102 |
if (filename) filename++; |
|
|
103 |
else filename = p; |
|
|
104 |
|
|
|
105 |
if (fnmatch(clean_pattern, filename, 0) == 0) { |
|
|
106 |
if (!pattern_is_dir_only || is_dir) return 1; |
|
|
107 |
} |
|
|
108 |
} |
|
|
109 |
} |
|
|
110 |
return 0; |
|
|
111 |
} |
|
|
112 |
|
|
|
113 |
void add_file(const char* path) { |
|
|
114 |
if (g_count >= MAX_FILES) { |
|
|
115 |
fprintf(stderr, "Too many files!\n"); |
|
|
116 |
return; |
|
|
117 |
} |
|
|
118 |
|
|
|
119 |
FILE* f = fopen(path, "rb"); |
|
|
120 |
if (!f) { |
|
|
121 |
fprintf(stderr, "Could not open %s\n", path); |
|
|
122 |
return; |
|
|
123 |
} |
|
|
124 |
|
|
|
125 |
fseek(f, 0, SEEK_END); |
|
|
126 |
long size = ftell(f); |
|
|
127 |
fseek(f, 0, SEEK_SET); |
|
|
128 |
|
|
|
129 |
void* data = malloc(size); |
|
|
130 |
if (!data) { |
|
|
131 |
fprintf(stderr, "Memory allocation failed for %s\n", path); |
|
|
132 |
fclose(f); |
|
|
133 |
return; |
|
|
134 |
} |
|
|
135 |
if (fread(data, 1, size, f) != (size_t)size) { |
|
|
136 |
fprintf(stderr, "Failed to read %s\n", path); |
|
|
137 |
free(data); |
|
|
138 |
fclose(f); |
|
|
139 |
return; |
|
|
140 |
} |
|
|
141 |
fclose(f); |
|
|
142 |
|
|
|
143 |
strncpy( g_pending[g_count].path, path, 255); |
|
|
144 |
g_pending[g_count].size = size; |
|
|
145 |
g_pending[g_count].data = data; |
|
|
146 |
g_count++; |
|
|
147 |
|
|
|
148 |
char size_str[32]; |
|
|
149 |
format_size(size_str, sizeof(size_str), (uint64_t)size); |
|
|
150 |
printf("Added: %s (%s)\n", path, size_str); |
|
|
151 |
} |
|
|
152 |
|
|
|
153 |
|
|
|
154 |
void traverse(const char* dir_path) { |
|
|
155 |
if (should_ignore(dir_path, 1)) return; |
|
|
156 |
|
|
|
157 |
char clean_dir[1024]; |
|
|
158 |
strncpy(clean_dir, dir_path, 1023); |
|
|
159 |
clean_dir[1023] = '\0'; |
|
|
160 |
size_t len = strlen(clean_dir); |
|
|
161 |
if (len > 1 && clean_dir[len - 1] == '/') { |
|
|
162 |
clean_dir[len - 1] = '\0'; |
|
|
163 |
} |
|
|
164 |
|
|
|
165 |
DIR* dir = opendir(clean_dir); |
|
|
166 |
if (!dir) { |
|
|
167 |
struct stat st; |
|
|
168 |
if (stat(clean_dir, &st) == 0 && !S_ISDIR(st.st_mode)) { |
|
|
169 |
add_file(clean_dir); |
|
|
170 |
} |
|
|
171 |
return; |
|
|
172 |
} |
|
|
173 |
|
|
|
174 |
struct dirent* entry; |
|
|
175 |
while ((entry = readdir(dir)) != NULL) { |
|
|
176 |
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) |
|
|
177 |
continue; |
|
|
178 |
|
|
|
179 |
char path[2048]; |
|
|
180 |
snprintf(path, sizeof(path), "%s/%s", clean_dir, entry->d_name); |
|
|
181 |
|
|
|
182 |
struct stat st; |
|
|
183 |
if (stat(path, &st) == 0) { |
|
|
184 |
int is_dir = S_ISDIR(st.st_mode); |
|
|
185 |
if (should_ignore(path, is_dir)) continue; |
|
|
186 |
|
|
|
187 |
if (is_dir) { |
|
|
188 |
traverse(path); |
|
|
189 |
} else { |
|
|
190 |
add_file(path); |
|
|
191 |
} |
|
|
192 |
} |
|
|
193 |
} |
|
|
194 |
closedir(dir); |
|
|
195 |
} |
|
|
196 |
|
|
|
197 |
void cmd_pack(const char* output_name, int num_folders, char** folders) { |
|
|
198 |
load_gitignore(); |
|
|
199 |
|
|
|
200 |
for (int i = 0; i < num_folders; i++) { |
|
|
201 |
traverse(folders[i]); |
|
|
202 |
} |
|
|
203 |
|
|
|
204 |
if (g_count == 0) { |
|
|
205 |
fprintf(stderr, "No files to pack.\n"); |
|
|
206 |
free_gitignore(); |
|
|
207 |
return; |
|
|
208 |
} |
|
|
209 |
|
|
|
210 |
FILE* out = fopen(output_name, "wb"); |
|
|
211 |
if (!out) { |
|
|
212 |
fprintf(stderr, "Failed to open output file %s\n", output_name); |
|
|
213 |
free_gitignore(); |
|
|
214 |
return; |
|
|
215 |
} |
|
|
216 |
|
|
|
217 |
VfsHeader header; |
|
|
218 |
memcpy(header.magic, "DRP1", 4); |
|
|
219 |
header.num_files = (uint32_t)g_count; |
|
|
220 |
fwrite(&header, sizeof(VfsHeader), 1, out); |
|
|
221 |
|
|
|
222 |
VfsEntry* entries = malloc(sizeof(VfsEntry) * g_count); |
|
|
223 |
uint64_t current_offset = sizeof(VfsHeader) + sizeof(VfsEntry) * g_count; |
|
|
224 |
|
|
|
225 |
for (int i = 0; i < g_count; i++) { |
|
|
226 |
strncpy(entries[i].path, g_pending[i].path, 255); |
|
|
227 |
entries[i].size = (uint64_t)g_pending[i].size; |
|
|
228 |
entries[i].offset = current_offset; |
|
|
229 |
current_offset += entries[i].size; |
|
|
230 |
} |
|
|
231 |
|
|
|
232 |
fwrite(entries, sizeof(VfsEntry), g_count, out); |
|
|
233 |
|
|
|
234 |
for (int i = 0; i < g_count; i++) { |
|
|
235 |
fwrite(g_pending[i].data, 1, g_pending[i].size, out); |
|
|
236 |
free(g_pending[i].data); |
|
|
237 |
} |
|
|
238 |
|
|
|
239 |
fclose(out); |
|
|
240 |
free(entries); |
|
|
241 |
free_gitignore(); |
|
|
242 |
|
|
|
243 |
printf("Successfully packed %d files into %s\n", g_count, output_name); |
|
|
244 |
} |
|
|
245 |
|
|
|
246 |
int read_pak(const char* path, VfsHeader* header, VfsEntry** entries) { |
|
|
247 |
FILE* f = fopen(path, "rb"); |
|
|
248 |
if (!f) return 0; |
|
|
249 |
|
|
|
250 |
if (fread(header, sizeof(VfsHeader), 1, f) != 1) { |
|
|
251 |
fclose(f); |
|
|
252 |
return 0; |
|
|
253 |
} |
|
|
254 |
|
|
|
255 |
if (memcmp(header->magic, "DRP1", 4) != 0) { |
|
|
256 |
fclose(f); |
|
|
257 |
return 0; |
|
|
258 |
} |
|
|
259 |
|
|
|
260 |
*entries = malloc(sizeof(VfsEntry) * header->num_files); |
|
|
261 |
if (fread(*entries, sizeof(VfsEntry), header->num_files, f) != header->num_files) { |
|
|
262 |
free(*entries); |
|
|
263 |
fclose(f); |
|
|
264 |
return 0; |
|
|
265 |
} |
|
|
266 |
|
|
|
267 |
fclose(f); |
|
|
268 |
return 1; |
|
|
269 |
} |
|
|
270 |
|
|
|
271 |
void cmd_list(const char* path) { |
|
|
272 |
VfsHeader header; |
|
|
273 |
VfsEntry* entries = NULL; |
|
|
274 |
if (!read_pak(path, &header, &entries)) { |
|
|
275 |
fprintf(stderr, "Error: Could not read pak file %s\n", path); |
|
|
276 |
return; |
|
|
277 |
} |
|
|
278 |
|
|
|
279 |
printf("Listing %u files in %s:\n", header.num_files, path); |
|
|
280 |
printf("%-48s %16s %12s\n", "Path", "Size", "Offset"); |
|
|
281 |
printf("--------------------------------------------------------------------------------\n"); |
|
|
282 |
for (uint32_t i = 0; i < header.num_files; i++) { |
|
|
283 |
char size_str[32]; |
|
|
284 |
format_size(size_str, sizeof(size_str), entries[i].size); |
|
|
285 |
printf("%-48s %16s %12lu\n", entries[i].path, size_str, (unsigned long)entries[i].offset); |
|
|
286 |
} |
|
|
287 |
|
|
|
288 |
free(entries); |
|
|
289 |
} |
|
|
290 |
|
|
|
291 |
void cmd_info(const char* path) { |
|
|
292 |
VfsHeader header; |
|
|
293 |
VfsEntry* entries = NULL; |
|
|
294 |
if (!read_pak(path, &header, &entries)) { |
|
|
295 |
fprintf(stderr, "Error: Could not read pak file %s\n", path); |
|
|
296 |
return; |
|
|
297 |
} |
|
|
298 |
|
|
|
299 |
uint64_t total_data_size = 0; |
|
|
300 |
for (uint32_t i = 0; i < header.num_files; i++) { |
|
|
301 |
total_data_size += entries[i].size; |
|
|
302 |
} |
|
|
303 |
|
|
|
304 |
struct stat st; |
|
|
305 |
uint64_t file_size = 0; |
|
|
306 |
if (stat(path, &st) == 0) file_size = st.st_size; |
|
|
307 |
|
|
|
308 |
char data_size_str[32], archive_size_str[32]; |
|
|
309 |
format_size(data_size_str, sizeof(data_size_str), total_data_size); |
|
|
310 |
format_size(archive_size_str, sizeof(archive_size_str), file_size); |
|
|
311 |
|
|
|
312 |
printf("PAK Information: %s\n", path); |
|
|
313 |
printf(" Magic: %.4s\n", header.magic); |
|
|
314 |
printf(" File Count: %u\n", header.num_files); |
|
|
315 |
printf(" Data Size: %lu bytes (%s)\n", (unsigned long)total_data_size, data_size_str); |
|
|
316 |
printf(" Archive Size: %lu bytes (%s)\n", (unsigned long)file_size, archive_size_str); |
|
|
317 |
printf(" Header Size: %lu bytes\n", (unsigned long)sizeof(VfsHeader)); |
|
|
318 |
printf(" Index Size: %lu bytes\n", (unsigned long)(sizeof(VfsEntry) * header.num_files)); |
|
|
319 |
|
|
|
320 |
free(entries); |
|
|
321 |
} |
|
|
322 |
|
|
|
323 |
typedef struct TreeNode { |
|
|
324 |
char* name; |
|
|
325 |
struct TreeNode* children; |
|
|
326 |
struct TreeNode* next; |
|
|
327 |
uint64_t size; |
|
|
328 |
int is_file; |
|
|
329 |
} TreeNode; |
|
|
330 |
|
|
|
331 |
TreeNode* create_node(const char* name, int is_file, uint64_t size) { |
|
|
332 |
TreeNode* n = calloc(1, sizeof(TreeNode)); |
|
|
333 |
n->name = strdup(name); |
|
|
334 |
n->is_file = is_file; |
|
|
335 |
n->size = size; |
|
|
336 |
return n; |
|
|
337 |
} |
|
|
338 |
|
|
|
339 |
void add_to_tree(TreeNode* root, const char* path, uint64_t size) { |
|
|
340 |
char temp[1024]; |
|
|
341 |
strncpy(temp, path, 1023); |
|
|
342 |
temp[1023] = '\0'; |
|
|
343 |
char* saveptr; |
|
|
344 |
char* token = strtok_r(temp, "/", &saveptr); |
|
|
345 |
TreeNode* current = root; |
|
|
346 |
|
|
|
347 |
while (token) { |
|
|
348 |
char* next_token = strtok_r(NULL, "/", &saveptr); |
|
|
349 |
int is_file = (next_token == NULL); |
|
|
350 |
|
|
|
351 |
TreeNode* child = current->children; |
|
|
352 |
TreeNode* prev = NULL; |
|
|
353 |
while (child) { |
|
|
354 |
if (strcmp(child->name, token) == 0) break; |
|
|
355 |
prev = child; |
|
|
356 |
child = child->next; |
|
|
357 |
} |
|
|
358 |
|
|
|
359 |
if (!child) { |
|
|
360 |
child = create_node(token, is_file, is_file ? size : 0); |
|
|
361 |
if (prev) prev->next = child; |
|
|
362 |
else current->children = child; |
|
|
363 |
} else if (!is_file) { |
|
|
364 |
// Directory node already exists, continue traversal |
|
|
365 |
} else { |
|
|
366 |
child->size = size; |
|
|
367 |
child->is_file = 1; |
|
|
368 |
} |
|
|
369 |
|
|
|
370 |
current = child; |
|
|
371 |
token = next_token; |
|
|
372 |
} |
|
|
373 |
} |
|
|
374 |
|
|
|
375 |
void print_tree(TreeNode* node, const char* prefix, int is_last) { |
|
|
376 |
if (node->name[0] != '\0') { |
|
|
377 |
printf("%s%s %s", prefix, is_last ? "└──" : "├──", node->name); |
|
|
378 |
if (node->is_file) { |
|
|
379 |
char size_str[32]; |
|
|
380 |
format_size(size_str, sizeof(size_str), node->size); |
|
|
381 |
printf(" (%s)", size_str); |
|
|
382 |
} |
|
|
383 |
printf("\n"); |
|
|
384 |
} |
|
|
385 |
|
|
|
386 |
char new_prefix[1024]; |
|
|
387 |
if (node->name[0] == '\0') { |
|
|
388 |
new_prefix[0] = '\0'; |
|
|
389 |
} else { |
|
|
390 |
snprintf(new_prefix, sizeof(new_prefix), "%s%s ", prefix, is_last ? " " : "│"); |
|
|
391 |
} |
|
|
392 |
|
|
|
393 |
TreeNode* child = node->children; |
|
|
394 |
while (child) { |
|
|
395 |
print_tree(child, new_prefix, child->next == NULL); |
|
|
396 |
child = child->next; |
|
|
397 |
} |
|
|
398 |
} |
|
|
399 |
|
|
|
400 |
void free_tree(TreeNode* node) { |
|
|
401 |
TreeNode* child = node->children; |
|
|
402 |
while (child) { |
|
|
403 |
TreeNode* next = child->next; |
|
|
404 |
free_tree(child); |
|
|
405 |
child = next; |
|
|
406 |
} |
|
|
407 |
free(node->name); |
|
|
408 |
free(node); |
|
|
409 |
} |
|
|
410 |
|
|
|
411 |
void cmd_tree(const char* path) { |
|
|
412 |
VfsHeader header; |
|
|
413 |
VfsEntry* entries = NULL; |
|
|
414 |
if (!read_pak(path, &header, &entries)) { |
|
|
415 |
fprintf(stderr, "Error: Could not read pak file %s\n", path); |
|
|
416 |
return; |
|
|
417 |
} |
|
|
418 |
|
|
|
419 |
TreeNode* root = create_node("", 0, 0); |
|
|
420 |
for (uint32_t i = 0; i < header.num_files; i++) { |
|
|
421 |
add_to_tree(root, entries[i].path, entries[i].size); |
|
|
422 |
} |
|
|
423 |
|
|
|
424 |
printf("Tree view of %s:\n", path); |
|
|
425 |
print_tree(root, "", 1); |
|
|
426 |
|
|
|
427 |
free_tree(root); |
|
|
428 |
free(entries); |
|
|
429 |
} |
|
|
430 |
|
|
|
431 |
void print_usage(const char* prog) { |
|
|
432 |
printf("Usage:\n"); |
|
|
433 |
printf(" %s -p, --pack <output.pak> <folder1> [folder2] ...\n", prog); |
|
|
434 |
printf(" %s -l, --list <input.pak>\n", prog); |
|
|
435 |
printf(" %s -t, --tree <input.pak>\n", prog); |
|
|
436 |
printf(" %s -i, --info <input.pak>\n", prog); |
|
|
437 |
printf(" %s -h, --help\n", prog); |
|
|
438 |
} |
|
|
439 |
|
|
|
440 |
int main(int argc, char** argv) { |
|
|
441 |
if (argc < 2) { |
|
|
442 |
print_usage(argv[0]); |
|
|
443 |
return 1; |
|
|
444 |
} |
|
|
445 |
|
|
|
446 |
static struct option long_options[] = { |
|
|
447 |
{"pack", no_argument, 0, 'p'}, |
|
|
448 |
{"list", no_argument, 0, 'l'}, |
|
|
449 |
{"tree", no_argument, 0, 't'}, |
|
|
450 |
{"info", no_argument, 0, 'i'}, |
|
|
451 |
{"help", no_argument, 0, 'h'}, |
|
|
452 |
{0, 0, 0, 0} |
|
|
453 |
}; |
|
|
454 |
|
|
|
455 |
int mode = 0; |
|
|
456 |
int opt; |
|
|
457 |
while ((opt = getopt_long(argc, argv, "pltih", long_options, NULL)) != -1) { |
|
|
458 |
switch (opt) { |
|
|
459 |
case 'p': mode = 'p'; break; |
|
|
460 |
case 'l': mode = 'l'; break; |
|
|
461 |
case 't': mode = 't'; break; |
|
|
462 |
case 'i': mode = 'i'; break; |
|
|
463 |
case 'h': print_usage(argv[0]); return 0; |
|
|
464 |
default: print_usage(argv[0]); return 1; |
|
|
465 |
} |
|
|
466 |
} |
|
|
467 |
|
|
|
468 |
if (mode == 0) { |
|
|
469 |
print_usage(argv[0]); |
|
|
470 |
return 1; |
|
|
471 |
} |
|
|
472 |
|
|
|
473 |
if (optind >= argc) { |
|
|
474 |
fprintf(stderr, "Error: Missing target file argument.\n"); |
|
|
475 |
print_usage(argv[0]); |
|
|
476 |
return 1; |
|
|
477 |
} |
|
|
478 |
|
|
|
479 |
const char* target = argv[optind]; |
|
|
480 |
|
|
|
481 |
switch (mode) { |
|
|
482 |
case 'p': |
|
|
483 |
cmd_pack(target, argc - optind - 1, argv + optind + 1); |
|
|
484 |
break; |
|
|
485 |
case 'l': |
|
|
486 |
cmd_list(target); |
|
|
487 |
break; |
|
|
488 |
case 't': |
|
|
489 |
cmd_tree(target); |
|
|
490 |
break; |
|
|
491 |
case 'i': |
|
|
492 |
cmd_info(target); |
|
|
493 |
break; |
|
|
494 |
} |
|
|
495 |
|
|
|
496 |
return 0; |
|
|
497 |
} |