Native iPhone port of Factorio 2.0.77
Run the original macOS ARM64 game code on iOS: - tools/retarget.py: header-only Mach-O retargeting to an iOS dylib - platform/native: UIKit host and macOS API shims (AppKit for SDL's Cocoa backend, CoreGraphics displays, CoreAudio device, Metal device properties, Wuffs PNG decoding, Steam and peripheral stubs) - touch, overlay, iOS keyboard and hardware keyboard input - tools/build_native.py: build, sign, install, data copy, launch - docs: build guide, controls, architecture Also includes the earlier static-translation research (ARM64 -> WASM -> native) under docs/research, runtime/, tests/ and tools/. No game files are included; the build reads them from a local install.
This commit is contained in:
commit
51d085e865
89 files changed
+7999
No files matched your search
@@ -0,0 +1,176 @@
|
||||
#include "save_header.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <zlib.h>
|
||||
|
||||
enum { PREFIX_LIMIT = 1024 * 1024, CHUNK = 4096 };
|
||||
static uint16_t u16(const unsigned char *p) { return (uint16_t)(p[0] | ((uint16_t)p[1] << 8)); }
|
||||
static uint32_t u32(const unsigned char *p) { return (uint32_t)u16(p) | ((uint32_t)u16(p + 2) << 16); }
|
||||
static int read_at(FILE *f, uint64_t position, void *out, size_t length) {
|
||||
if (position > INT64_MAX || fseeko(f, (off_t)position, SEEK_SET)) return 0;
|
||||
return fread(out, 1, length, f) == length;
|
||||
}
|
||||
static int suffix(const char *name, size_t length, const char *tail) {
|
||||
size_t count = strlen(tail);
|
||||
return length >= count && !memcmp(name + length - count, tail, count) &&
|
||||
(length == count || name[length - count - 1] == '/');
|
||||
}
|
||||
|
||||
typedef struct { uint32_t local, compressed; uint16_t method, flags, name_length; char name[4097]; } Entry;
|
||||
static int find_level(FILE *file, uint64_t file_size, Entry *selected) {
|
||||
if (file_size < 22) return FACTORIO_SAVE_INVALID_ZIP;
|
||||
size_t count = file_size < 65557 ? (size_t)file_size : 65557;
|
||||
unsigned char *tail = malloc(count);
|
||||
if (!tail) return FACTORIO_SAVE_IO;
|
||||
if (!read_at(file, file_size - count, tail, count)) { free(tail); return FACTORIO_SAVE_IO; }
|
||||
size_t end = count;
|
||||
for (size_t i = count - 22 + 1; i-- > 0;) {
|
||||
if (u32(tail + i) == 0x06054b50 && (size_t)u16(tail + i + 20) == count - i - 22) {
|
||||
end = i; break;
|
||||
}
|
||||
}
|
||||
if (end == count) { free(tail); return FACTORIO_SAVE_INVALID_ZIP; }
|
||||
unsigned char *e = tail + end;
|
||||
uint32_t central_size = u32(e + 12), central_offset = u32(e + 16);
|
||||
uint16_t entries = u16(e + 10);
|
||||
if (u16(e + 4) || u16(e + 6) || entries != u16(e + 8) || entries == 0xffff ||
|
||||
central_size == 0xffffffff || central_offset == 0xffffffff) {
|
||||
free(tail); return FACTORIO_SAVE_UNSUPPORTED;
|
||||
}
|
||||
uint64_t central_end = (uint64_t)central_offset + central_size;
|
||||
if (central_end > file_size - count + end) { free(tail); return FACTORIO_SAVE_INVALID_ZIP; }
|
||||
free(tail);
|
||||
uint64_t cursor = central_offset;
|
||||
int priority = 0;
|
||||
for (unsigned index = 0; index < entries; ++index) {
|
||||
unsigned char header[46];
|
||||
if (cursor > central_end || central_end - cursor < sizeof(header) ||
|
||||
!read_at(file, cursor, header, sizeof(header)) || u32(header) != 0x02014b50) return FACTORIO_SAVE_INVALID_ZIP;
|
||||
uint16_t name_length = u16(header + 28);
|
||||
uint64_t next = cursor + 46 + name_length + u16(header + 30) + u16(header + 32);
|
||||
if (next > central_end || name_length > 4096) return FACTORIO_SAVE_UNSUPPORTED;
|
||||
char name[4097];
|
||||
if (!read_at(file, cursor + 46, name, name_length)) return FACTORIO_SAVE_IO;
|
||||
if (memchr(name, 0, name_length)) return FACTORIO_SAVE_INVALID_ZIP;
|
||||
name[name_length] = 0;
|
||||
int match = suffix(name, name_length, "level.dat0") ? 2 : suffix(name, name_length, "level.dat");
|
||||
if (match && match >= priority) {
|
||||
if (match == priority) return FACTORIO_SAVE_INVALID_ZIP; /* ambiguous roots */
|
||||
if (u16(header + 34) || u32(header + 20) == 0xffffffff || u32(header + 42) == 0xffffffff)
|
||||
return FACTORIO_SAVE_UNSUPPORTED;
|
||||
*selected = (Entry){.local = u32(header + 42), .compressed = u32(header + 20),
|
||||
.method = u16(header + 10), .flags = u16(header + 8), .name_length = name_length};
|
||||
memcpy(selected->name, name, name_length + 1);
|
||||
priority = match;
|
||||
}
|
||||
cursor = next;
|
||||
}
|
||||
if (!priority) return FACTORIO_SAVE_NO_LEVEL;
|
||||
if ((selected->flags & 0x41) || (selected->method != 0 && selected->method != 8)) return FACTORIO_SAVE_UNSUPPORTED;
|
||||
return FACTORIO_SAVE_OK;
|
||||
}
|
||||
|
||||
static int feed_inner(z_stream *inner, unsigned char *bytes, size_t length, unsigned char *header, size_t requested) {
|
||||
inner->next_in = bytes; inner->avail_in = (uInt)length;
|
||||
inner->next_out = header + inner->total_out; inner->avail_out = (uInt)(requested - inner->total_out);
|
||||
int status = inflate(inner, Z_NO_FLUSH);
|
||||
if (status != Z_OK && status != Z_STREAM_END && status != Z_BUF_ERROR) return FACTORIO_SAVE_COMPRESSION;
|
||||
if (inner->total_out == requested) return FACTORIO_SAVE_OK;
|
||||
return status == Z_STREAM_END ? FACTORIO_SAVE_SHORT_HEADER : -1;
|
||||
}
|
||||
|
||||
static int prefix(FILE *file, uint64_t file_size, const Entry *entry, unsigned char *header, size_t requested) {
|
||||
unsigned char local[30];
|
||||
if (!read_at(file, entry->local, local, sizeof(local)) || u32(local) != 0x04034b50) return FACTORIO_SAVE_INVALID_ZIP;
|
||||
if (u16(local + 8) != entry->method || u16(local + 6) != entry->flags ||
|
||||
u16(local + 26) != entry->name_length) return FACTORIO_SAVE_INVALID_ZIP;
|
||||
char name[4097];
|
||||
if (!read_at(file, (uint64_t)entry->local + 30, name, entry->name_length) ||
|
||||
memcmp(name, entry->name, entry->name_length)) return FACTORIO_SAVE_INVALID_ZIP;
|
||||
uint64_t data_offset = (uint64_t)entry->local + 30 + u16(local + 26) + u16(local + 28);
|
||||
if (data_offset > file_size || entry->compressed > file_size - data_offset) return FACTORIO_SAVE_INVALID_ZIP;
|
||||
z_stream outer = {0}, inner = {0};
|
||||
if (inflateInit(&inner) != Z_OK) return FACTORIO_SAVE_COMPRESSION;
|
||||
if (entry->method == 8 && inflateInit2(&outer, -MAX_WBITS) != Z_OK) {
|
||||
inflateEnd(&inner); return FACTORIO_SAVE_COMPRESSION;
|
||||
}
|
||||
int result = FACTORIO_SAVE_SHORT_HEADER;
|
||||
unsigned char input[CHUNK], decoded[CHUNK];
|
||||
uint64_t consumed = 0, produced = 0;
|
||||
while (consumed < entry->compressed && consumed < PREFIX_LIMIT) {
|
||||
size_t amount = entry->compressed - consumed < CHUNK ? (size_t)(entry->compressed - consumed) : CHUNK;
|
||||
if (!read_at(file, data_offset + consumed, input, amount)) { result = FACTORIO_SAVE_IO; break; }
|
||||
consumed += amount;
|
||||
if (entry->method == 0) {
|
||||
result = feed_inner(&inner, input, amount, header, requested);
|
||||
if (result >= 0) goto finish;
|
||||
} else {
|
||||
outer.next_in = input; outer.avail_in = (uInt)amount;
|
||||
do {
|
||||
outer.next_out = decoded; outer.avail_out = sizeof(decoded);
|
||||
int status = inflate(&outer, Z_NO_FLUSH);
|
||||
if (status != Z_OK && status != Z_STREAM_END && status != Z_BUF_ERROR) {
|
||||
result = FACTORIO_SAVE_COMPRESSION; goto finish;
|
||||
}
|
||||
size_t bytes = sizeof(decoded) - outer.avail_out;
|
||||
produced += bytes;
|
||||
if (produced > PREFIX_LIMIT) { result = FACTORIO_SAVE_PREFIX_LIMIT; goto finish; }
|
||||
result = feed_inner(&inner, decoded, bytes, header, requested);
|
||||
if (result >= 0) goto finish;
|
||||
if (status == Z_STREAM_END) { result = FACTORIO_SAVE_SHORT_HEADER; goto finish; }
|
||||
if (!bytes && !outer.avail_in) break;
|
||||
if (!bytes && status == Z_BUF_ERROR) { result = FACTORIO_SAVE_COMPRESSION; goto finish; }
|
||||
} while (outer.avail_in || !outer.avail_out);
|
||||
}
|
||||
}
|
||||
result = consumed >= PREFIX_LIMIT ? FACTORIO_SAVE_PREFIX_LIMIT : FACTORIO_SAVE_SHORT_HEADER;
|
||||
finish:
|
||||
if (entry->method == 8) inflateEnd(&outer);
|
||||
inflateEnd(&inner);
|
||||
return result;
|
||||
}
|
||||
|
||||
int factorio_save_prefix(const char *path, uint8_t *out, size_t length) {
|
||||
if (!path || !out) return FACTORIO_SAVE_IO;
|
||||
if (!length || length > PREFIX_LIMIT) return FACTORIO_SAVE_PREFIX_LIMIT;
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (!file) return FACTORIO_SAVE_IO;
|
||||
if (fseeko(file, 0, SEEK_END)) { fclose(file); return FACTORIO_SAVE_IO; }
|
||||
off_t end = ftello(file);
|
||||
if (end < 0) { fclose(file); return FACTORIO_SAVE_IO; }
|
||||
Entry entry;
|
||||
int status = find_level(file, (uint64_t)end, &entry);
|
||||
unsigned char *temporary = NULL;
|
||||
if (!status) {
|
||||
temporary = malloc(length);
|
||||
if (!temporary) status = FACTORIO_SAVE_IO;
|
||||
else status = prefix(file, (uint64_t)end, &entry, temporary, length);
|
||||
}
|
||||
fclose(file);
|
||||
if (!status) memcpy(out, temporary, length);
|
||||
free(temporary);
|
||||
return status;
|
||||
}
|
||||
|
||||
int factorio_save_header(const char *path, FactorioMapVersion *out) {
|
||||
if (!out) return FACTORIO_SAVE_IO;
|
||||
unsigned char header[9];
|
||||
int status = factorio_save_prefix(path, header, sizeof(header));
|
||||
if (!status && factorio_version_parse(header, 9, out)) status = FACTORIO_SAVE_SHORT_HEADER;
|
||||
return status;
|
||||
}
|
||||
|
||||
const char *factorio_save_status_string(int status) {
|
||||
switch (status) {
|
||||
case FACTORIO_SAVE_OK: return "Version header read; full map is not loaded";
|
||||
case FACTORIO_SAVE_IO: return "Cannot read the selected file";
|
||||
case FACTORIO_SAVE_INVALID_ZIP: return "Invalid or ambiguous ZIP archive";
|
||||
case FACTORIO_SAVE_NO_LEVEL: return "No level.dat0 or level.dat entry";
|
||||
case FACTORIO_SAVE_UNSUPPORTED: return "Unsupported archive layout, compression or encryption";
|
||||
case FACTORIO_SAVE_COMPRESSION: return "Invalid compressed level header";
|
||||
case FACTORIO_SAVE_SHORT_HEADER: return "Incomplete map version header";
|
||||
case FACTORIO_SAVE_PREFIX_LIMIT: return "Header decompression limit reached";
|
||||
default: return "Unknown save-header error";
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user