Добро пожаловать, гость ( Вход | Регистрация )
| Дата поста: | В теме: | За сообщение: | Спасибо сказали: | ||
|---|---|---|---|---|---|
| Mar 2 2026, 23:32 | Конвертор видео ГЭГ 2 DV -> OGM | Добро пожаловать! Да, теперь всё отлично компилируется под ANSI C, без чемодана зависимостей, всего 18 Кб. Ещё раз огромное спасибо за программу! Глянул быстро - поменял только типы на стандартные из <stdint.h>. Конвертированные файлы проигрываются, но FFMPEG пишет следующие ошибки: CODE [NULL @ 025e19e0] [IMGUTILS @ 0022f404] Picture size 0x0 is invalid [NULL @ 025e19e0] Ignoring invalid width/height values MPlayer (линуксовый видеоплеер) пишет такое после окончания проигрывания: CODE [mpeg4 @ 01148f80]ac-tex damaged at 19 18 [mpeg4 @ 01148f80]Error at MB: 613 [mpeg4 @ 01148f80]concealing 222 DC, 222 AC, 222 MV errors Я завтра посмотрю подробнее что там, сейчас уже спать валюсь. А! Ключ "-hide_banner" убрал у ffmpeg при проверке - он не во всех версиях есть (как я понимаю, только у новых). Всякие ссылки по формату .OGM, которые я читал в прошлом году, когда думал такую программу сделать, если, вдруг, нужно. Ссылки на веб-архив, потому что половины сайтов уже нет. Описание формата там тоже не полное, но хотя бы кое-что понятнее становится. По второй ссылке ещё можно скачать программу для парсинга файлов .OGM и выводе информации о них. Пока не забыл - там основной подводный камень при описании структур был в том, что не учитывались выравнивающие байты. Например, поле указано как uint16_t (2 байта), смотришь в файл, а там ещё два нулевых байта (выравнивание на границу в 32 бита). Так что тут лучше брать существующие файлы, смотреть как они устроены и пытаться что-то похожее воспроизвести. CODE https://web.archive.org/web/20041210052652fw_/http://www.tobias.everwicked.com/packfmt.htm https://web.archive.org/web/20041014033857/http://www.shounen.ru/docs/ogm/ogm.shtml [codebox]/* dvbang2ogv.c * ANSI C / C89, Visual Studio 2008 friendly * License: MIT, Copyright 2026 Lumis * * Converts "DV!!" .dv files (non-standard container) into Ogg/OGM (.ogv) without * using ffmpeg for muxing. Video is MPEG-4 Part 2 elementary stream kept byte-exact. * Audio is Ogg/Vorbis pages copied byte-exact (no re-encode). * * Matches the latest Python + C++ variants: * - DV!! header: 19 LE u32 (76 bytes), hdr[0]='DV!!', hdr[4]=w, hdr[5]=h, * hdr[7]=packet_count N, hdr[9]=audio_preroll_bytes M. * - Table: N entries * 20 bytes (5 LE u32 each): * size = entry0 & 0x7fffffff * audio_bytes = entry[3] * - Data: preroll (M bytes) + N packets: * video += packet[0 : size-audio_bytes] * audio += packet[size-audio_bytes : size] * * OGV/OGM mux: * - Copy audio BOS page unchanged * - Write video BOS (0x01 + ogm stream_header, subtype "XVID") * - Write video comment (0x03 + vorbis-style vendor comment, no user comments) * - Copy remaining Vorbis header pages unchanged (until 3 Vorbis packets end) * - Interleave: * at = audio_page.granule / sample_rate * emit video frames while ((v_i+1)/fps) <= at + (0.5/fps) * then copy audio page * Finally flush remaining video frames. * * Determinism: * - video serial hardcoded to 0xC0DEC0DE; if equals audio serial => xor with 0xFFFFFFFF. * * Optional: * --validate : runs "ffmpeg ... -f null -" via system() (disabled by default) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /* VS2008: snprintf is _snprintf */ #ifndef snprintf #define snprintf _snprintf #endif /* ---------- Fixed constants ---------- */ #define MAGIC_DV_LE 0x21215644UL /* "DV!!" little-endian */ #define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL typedef struct { uint8_t* p; uint32_t len; uint32_t cap; } Buf; typedef struct { uint8_t header_type; uint64_t granule; uint32_t serial; uint32_t seq; uint8_t* segtable; uint32_t segtable_len; uint8_t* body; uint32_t body_len; uint8_t* bytes; uint32_t bytes_len; } OggPage; typedef struct { uint8_t* p; uint32_t len; } Span; /* ---------- Errors ---------- */ static void die(const char* msg) { printf("ERROR: %s\n", msg); exit(2); } static void die2(const char* msg, const char* more) { printf("ERROR: %s%s\n", msg, more ? more : ""); exit(2); } /* ---------- LE helpers ---------- */ static uint32_t rd32le(const uint8_t* p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); } static uint64_t rd64le(const uint8_t* p) { uint64_t v; v = (uint64_t)p[0]; v |= (uint64_t)p[1] << 8; v |= (uint64_t)p[2] << 16; v |= (uint64_t)p[3] << 24; v |= (uint64_t)p[4] << 32; v |= (uint64_t)p[5] << 40; v |= (uint64_t)p[6] << 48; v |= (uint64_t)p[7] << 56; return v; } static void wr32le(uint8_t* p, uint32_t v) { p[0] = (uint8_t)(v & 0xFF); p[1] = (uint8_t)((v >> 8) & 0xFF); p[2] = (uint8_t)((v >> 16) & 0xFF); p[3] = (uint8_t)((v >> 24) & 0xFF); } static void wr64le(uint8_t* p, uint64_t v) { p[0] = (uint8_t)(v & 0xFF); p[1] = (uint8_t)((v >> 8) & 0xFF); p[2] = (uint8_t)((v >> 16) & 0xFF); p[3] = (uint8_t)((v >> 24) & 0xFF); p[4] = (uint8_t)((v >> 32) & 0xFF); p[5] = (uint8_t)((v >> 40) & 0xFF); p[6] = (uint8_t)((v >> 48) & 0xFF); p[7] = (uint8_t)((v >> 56) & 0xFF); } /* ---------- Buf ---------- */ static void buf_init(Buf* b ) { b->p = 0; b->len = 0; b->cap = 0; } static void buf_reserve(Buf* b, uint32_t need) { uint32_t ncap; if (need <= b->cap) return; ncap = b->cap ? (b->cap + (b->cap >> 1) + 64) : 256; if (ncap < need) ncap = need; b->p = (uint8_t*)realloc(b->p, ncap); if (!b->p) die("Out of memory"); b->cap = ncap; } static void buf_append(Buf* b, const uint8_t* data, uint32_t n) { if (n == 0) return; buf_reserve(b, b->len + n); memcpy(b->p + b->len, data, n); b->len += n; } static void buf_append_u8(Buf* b, uint8_t v) { buf_reserve(b, b->len + 1); b->p[b->len++] = v; } static void buf_free(Buf* b ) { if (b->p) free(b->p); b->p = 0; b->len = 0; b->cap = 0; } /* ---------- File I/O ---------- */ static uint8_t* read_file_all(const char* path, uint32_t* out_len) { FILE* f; long sz; uint8_t* data; f = fopen(path, "rb"); if (!f) return 0; fseek(f, 0, SEEK_END); sz = ftell(f); if (sz < 0) { fclose(f); return 0; } fseek(f, 0, SEEK_SET); data = (uint8_t*)malloc((uint32_t)sz); if (!data) { fclose(f); return 0; } if (sz > 0 && fread(data, 1, (uint32_t)sz, f) != (uint32_t)sz) { free(data); fclose(f); return 0; } fclose(f); *out_len = (uint32_t)sz; return data; } /* ---------- Ogg CRC ---------- */ static uint32_t CRC_TABLE[256]; static void crc_table_init(void) { uint32_t poly, i, j, r; poly = 0x04C11DB7UL; for (i = 0; i < 256; i++) { r = i << 24; for (j = 0; j < 8; j++) { if (r & 0x80000000UL) r = (r << 1) ^ poly; else r <<= 1; } CRC_TABLE[i] = r; } } static uint32_t ogg_crc(const uint8_t* page, uint32_t len) { uint32_t crc, i; crc = 0; for (i = 0; i < len; i++) { uint8_t b = page[i]; crc = (crc << 8) ^ CRC_TABLE[((crc >> 24) & 0xFF) ^ b]; } return crc; } /* ---------- Ogg page builder ---------- * Builds one page containing pkt_count packets (commonly 1 packet per page here). */ static void ogg_build_page(Buf* out, const uint8_t* const* pkts, const uint32_t* pkt_lens, uint32_t pkt_count, uint32_t serial, uint32_t seq, uint64_t granule, uint8_t header_type) { uint8_t segs[255]; uint32_t seg_count, i; Buf body; Buf page; uint8_t hdr[27]; uint32_t crc; seg_count = 0; buf_init(&body); buf_init(&page); /* Build body and lacing values */ for (i = 0; i < pkt_count; i++) { uint32_t n; buf_append(&body, pkts[i], pkt_lens[i]); n = pkt_lens[i]; while (n >= 255) { if (seg_count >= 255) die("Too many segments in one Ogg page"); segs[seg_count++] = 255; n -= 255; } if (seg_count >= 255) die("Too many segments in one Ogg page"); segs[seg_count++] = (uint8_t)n; } /* Header */ memset(hdr, 0, sizeof(hdr)); hdr[0] = 'O'; hdr[1] = 'g'; hdr[2] = 'g'; hdr[3] = 'S'; hdr[4] = 0; /* version */ hdr[5] = header_type; wr64le(hdr + 6, granule); wr32le(hdr + 14, serial); wr32le(hdr + 18, seq); wr32le(hdr + 22, 0); /* CRC placeholder */ hdr[26] = (uint8_t)seg_count; buf_append(&page, hdr, 27); buf_append(&page, segs, seg_count); buf_append(&page, body.p, body.len); /* Patch CRC */ crc = ogg_crc(page.p, page.len); wr32le(page.p + 22, crc); buf_append(out, page.p, page.len); buf_free(&page); buf_free(&body); } /* ---------- Ogg page parser (minimal) ---------- */ static OggPage* ogg_parse_pages(const uint8_t* data, uint32_t len, uint32_t* out_count) { OggPage* pages; uint32_t cap, count, i; cap = 64; count = 0; pages = (OggPage*)calloc(cap, sizeof(OggPage)); if (!pages) die("Out of memory"); i = 0; while (1) { uint32_t j, k, body_len, body_off, seg_off; int found; uint8_t nseg; uint8_t header_type; uint64_t granule; uint32_t serial, seq; found = 0; for (j = i; j + 4 <= len; j++) { if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S') { found = 1; break; } } if (!found) break; if (j + 27 > len) break; header_type = data[j + 5]; granule = rd64le(data + j + 6); serial = rd32le(data + j + 14); seq = rd32le(data + j + 18); nseg = data[j + 26]; seg_off = j + 27; body_off = seg_off + (uint32_t)nseg; if (body_off > len) break; body_len = 0; for (k = 0; k < (uint32_t)nseg; k++) body_len += data[seg_off + k]; if (body_off + body_len > len) break; if (count >= cap) { cap = cap + cap/2 + 32; pages = (OggPage*)realloc(pages, cap * sizeof(OggPage)); if (!pages) die("Out of memory"); } pages[count].header_type = header_type; pages[count].granule = granule; pages[count].serial = serial; pages[count].seq = seq; pages[count].segtable_len = (uint32_t)nseg; pages[count].segtable = (uint8_t*)malloc((uint32_t)nseg); if (!pages[count].segtable) die("Out of memory"); memcpy(pages[count].segtable, data + seg_off, (uint32_t)nseg); pages[count].body_len = body_len; pages[count].body = (uint8_t*)malloc(body_len); if (!pages[count].body) die("Out of memory"); memcpy(pages[count].body, data + body_off, body_len); pages[count].bytes_len = (body_off + body_len) - j; pages[count].bytes = (uint8_t*)malloc(pages[count].bytes_len); if (!pages[count].bytes) die("Out of memory"); memcpy(pages[count].bytes, data + j, pages[count].bytes_len); count++; i = body_off + body_len; } *out_count = count; return pages; } static void ogg_free_pages(OggPage* pages, uint32_t count) { uint32_t i; for (i = 0; i < count; i++) { if (pages[i].segtable) free(pages[i].segtable); if (pages[i].body) free(pages[i].body); if (pages[i].bytes) free(pages[i].bytes); } free(pages); } /* ---------- Vorbis helpers ---------- */ static uint32_t vorbis_detect_sample_rate(const OggPage* pages, uint32_t count) { Buf cur; uint32_t i, sidx; buf_init(&cur); for (i = 0; i < count; i++) { uint32_t off = 0; for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { uint8_t s = pages[i].segtable[sidx]; buf_append(&cur, pages[i].body + off, (uint32_t)s); off += (uint32_t)s; if (s < 255) { if (cur.len >= 16 && cur.p[0] == 0x01 && cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' && cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s') { uint32_t sr = rd32le(cur.p + 12); buf_free(&cur); return sr; } buf_free(&cur); return 0; } } } buf_free(&cur); return 0; } static uint32_t vorbis_header_end_page_index(const OggPage* pages, uint32_t count) { uint32_t pkt_count, i, sidx; pkt_count = 0; for (i = 0; i < count; i++) { for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { if (pages[i].segtable[sidx] < 255) { pkt_count++; if (pkt_count >= 3) return i; } } } return (count ? (count - 1) : 0); } /* ---------- DV!! extraction ---------- */ static void dvbang_extract(const uint8_t* dv, uint32_t dv_len, uint32_t* out_w, uint32_t* out_h, uint8_t** out_video, uint32_t* out_video_len, uint8_t** out_audio, uint32_t* out_audio_len, uint32_t* out_packet_count) { uint32_t hdr[19]; uint32_t i; uint32_t N, M; uint32_t table_off, table_len, data_off; uint32_t pos; Buf video, audio; if (dv_len < 76) die("Input too small for DV!! header"); for (i = 0; i < 19; i++) hdr[i] = rd32le(dv + i*4); if (hdr[0] != MAGIC_DV_LE) die("Bad DV!! signature"); *out_w = hdr[4]; *out_h = hdr[5]; N = hdr[7]; M = hdr[9]; *out_packet_count = N; table_off = 76; table_len = N * 20UL; if (dv_len < table_off + table_len) die("Truncated: packet table"); data_off = table_off + table_len; if (dv_len < data_off + M) die("Truncated: preroll region"); buf_init(&video); buf_init(&audio); /* audio begins with preroll */ buf_append(&audio, dv + data_off, M); pos = data_off + M; for (i = 0; i < N; i++) { const uint8_t* ent; uint32_t size_flags, size, audio_bytes; ent = dv + (table_off + i*20UL); size_flags = rd32le(ent); size = size_flags & 0x7FFFFFFFUL; audio_bytes = rd32le(ent + 12); if (pos + size > dv_len) die("Truncated: packet region"); if (audio_bytes > size) die("Corrupt: audio_bytes > size"); if (audio_bytes) { uint32_t vlen = size - audio_bytes; buf_append(&video, dv + pos, vlen); buf_append(&audio, dv + pos + vlen, audio_bytes); } else { buf_append(&video, dv + pos, size); } pos += size; } *out_video = video.p; *out_video_len = video.len; *out_audio = audio.p; *out_audio_len = audio.len; } /* ---------- MPEG-4 Part 2 frame splitting ---------- */ static Span* mpeg4_split_vop_frames(const uint8_t* es, uint32_t es_len, uint32_t* out_count) { const uint8_t vop0 = 0x00, vop1 = 0x00, vop2 = 0x01, vop3 = 0xB6; uint32_t* idxs; uint32_t cap, count, i; Span* frames; uint32_t header_len; uint8_t* header; cap = 1024; count = 0; idxs = (uint32_t*)malloc(cap * sizeof(uint32_t)); if (!idxs) die("Out of memory"); i = 0; while (i + 4 <= es_len) { uint32_t j; int found; found = 0; for (j = i; j + 4 <= es_len; j++) { if (es[j]==vop0 && es[j+1]==vop1 && es[j+2]==vop2 && es[j+3]==vop3) { found = 1; break; } } if (!found) break; if (count >= cap) { cap = cap + cap/2 + 256; idxs = (uint32_t*)realloc(idxs, cap * sizeof(uint32_t)); if (!idxs) die("Out of memory"); } idxs[count++] = j; i = j + 4; } if (count == 0) { free(idxs); die("No MPEG-4 VOP start codes (00 00 01 B6) found"); } header_len = idxs[0]; header = 0; if (header_len) { header = (uint8_t*)malloc(header_len); if (!header) die("Out of memory"); memcpy(header, es, header_len); } frames = (Span*)calloc(count, sizeof(Span)); if (!frames) die("Out of memory"); for (i = 0; i < count; i++) { uint32_t start, end, flen; start = idxs[i]; end = (i + 1 < count) ? idxs[i+1] : es_len; flen = end - start; if (i == 0) { uint32_t total = header_len + flen; frames[i].p = (uint8_t*)malloc(total); if (!frames[i].p) die("Out of memory"); if (header_len) memcpy(frames[i].p, header, header_len); memcpy(frames[i].p + header_len, es + start, flen); frames[i].len = total; } else { frames[i].p = (uint8_t*)malloc(flen); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, es + start, flen); frames[i].len = flen; } } if (header) free(header); free(idxs); *out_count = count; return frames; } static void free_frames(Span* frames, uint32_t count) { uint32_t i; for (i = 0; i < count; i++) if (frames[i].p) free(frames[i].p); free(frames); } /* ---------- OGM packet builders ---------- */ static void ogm_build_stream_header_video(uint8_t out52[52], int w, int h, int fps) { /* See ogmtools ogmstreams.h layout; kept identical to prior variants. */ double tu_d; int64_t time_unit; int64_t samples_per_unit; uint32_t default_len; uint32_t buffersize; uint16_t bits_per_sample; uint16_t padding; memset(out52, 0, 52); memcpy(out52 + 0, "video", 5); memcpy(out52 + 8, "XVID", 4); wr32le(out52 + 12, 52); tu_d = 10000000.0 / (double)fps; time_unit = (int64_t)(tu_d + 0.5); samples_per_unit = 1; default_len = 1; buffersize = 0; bits_per_sample = 0; padding = 0; wr64le(out52 + 16, (uint64_t)time_unit); wr64le(out52 + 24, (uint64_t)samples_per_unit); wr32le(out52 + 32, default_len); wr32le(out52 + 36, buffersize); out52[40] = (uint8_t)(bits_per_sample & 0xFF); out52[41] = (uint8_t)((bits_per_sample >> 8) & 0xFF); wr32le(out52 + 42, (uint32_t)w); wr32le(out52 + 46, (uint32_t)h); out52[50] = (uint8_t)(padding & 0xFF); out52[51] = (uint8_t)((padding >> 8) & 0xFF); } static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps) { uint8_t sh[52]; buf_init(pkt); ogm_build_stream_header_video(sh, w, h, fps); buf_append_u8(pkt, 0x01); buf_append(pkt, sh, 52); } static void ogm_make_video_comment_pkt(Buf* pkt) { const char* vendor = "dvbang"; uint32_t vlen; uint8_t tmp[4]; vlen = (uint32_t)strlen(vendor); buf_init(pkt); buf_append_u8(pkt, 0x03); wr32le(tmp, vlen); buf_append(pkt, tmp, 4); buf_append(pkt, (const uint8_t*)vendor, vlen); wr32le(tmp, 0); buf_append(pkt, tmp, 4); } static void ogm_make_video_data_pkt(Buf* pkt, const uint8_t* frame, uint32_t frame_len) { uint8_t flags; buf_init(pkt); /* lenbytes=2 (bit1 set => 0x80), keyframe=1 (0x08) */ flags = (uint8_t)(0x80 | 0x08); buf_append_u8(pkt, flags); buf_append_u8(pkt, 1); /* duration samples=1 (LE16) */ buf_append_u8(pkt, 0); buf_append(pkt, frame, frame_len); } /* ---------- Mux (writes .ogv) ---------- */ static void mux_ogv(const char* out_path, const OggPage* audio_pages, uint32_t audio_page_count, uint32_t sample_rate, int w, int h, int fps, const Span* frames, uint32_t frame_count) { FILE* fo; Buf out; uint32_t audio_serial, video_serial, hdr_end; uint32_t vseq, v_i; double fps_d, half_frame; Buf vh, vc, vp; const uint8_t* pkts1[1]; uint32_t lens1[1]; uint32_t ai; if (audio_page_count == 0) die("No audio pages"); audio_serial = audio_pages[0].serial; video_serial = VIDEO_SERIAL_FIXED; if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL; hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count); buf_init(&out); /* audio BOS copied */ buf_append(&out, audio_pages[0].bytes, audio_pages[0].bytes_len); /* video BOS */ ogm_make_video_header_pkt(&vh, w, h, fps); pkts1[0] = vh.p; lens1[0] = vh.len; ogg_build_page(&out, pkts1, lens1, 1, video_serial, 0, 0, 0x02); buf_free(&vh); vseq = 1; /* video comment */ ogm_make_video_comment_pkt(&vc); pkts1[0] = vc.p; lens1[0] = vc.len; ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, 0, 0x00); buf_free(&vc); /* remaining audio header pages unchanged */ for (ai = 1; ai <= hdr_end && ai < audio_page_count; ai++) { buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } fps_d = (double)fps; half_frame = 0.5 / fps_d; v_i = 0; /* interleave */ for (ai = hdr_end + 1; ai < audio_page_count; ai++) { double at; at = 0.0; if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate; while (v_i < frame_count) { double vt_next; vt_next = (double)(v_i + 1) / fps_d; if (vt_next <= at + half_frame) { uint64_t gran; uint8_t header_type; ogm_make_video_data_pkt(&vp, frames[v_i].p, frames[v_i].len); pkts1[0] = vp.p; lens1[0] = vp.len; gran = (uint64_t)(v_i + 1); header_type = (uint8_t)((v_i + 1 == frame_count) ? 0x04 : 0x00); ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, gran, header_type); buf_free(&vp); v_i++; } else { break; } } /* copy audio page */ buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } /* flush remaining video */ while (v_i < frame_count) { uint64_t gran; uint8_t header_type; ogm_make_video_data_pkt(&vp, frames[v_i].p, frames[v_i].len); pkts1[0] = vp.p; lens1[0] = vp.len; gran = (uint64_t)(v_i + 1); header_type = (uint8_t)((v_i + 1 == frame_count) ? 0x04 : 0x00); ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, gran, header_type); buf_free(&vp); v_i++; } /* write output */ fo = fopen(out_path, "wb"); if (!fo) die("Cannot open output file"); if (out.len && fwrite(out.p, 1, out.len, fo) != out.len) { fclose(fo); die("Write failed"); } fclose(fo); buf_free(&out); } /* ---------- CLI helpers ---------- */ static int streq(const char* a, const char* b ) { return strcmp(a,b ) == 0; } static void usage(const char* exe) { printf( "Usage:\n %s <input.dv> [output.ogv] [--validate]\n\n" "Options:\n" " --validate run ffmpeg decode test (optional; ffmpeg must be in PATH)\n" " -h,--help show help\n", exe ); } static void make_default_out(const char* in, char* out, uint32_t outsz) { const char* dot; uint32_t n; dot = strrchr(in, '.'); if (!dot) { snprintf(out, outsz, "%s.ogv", in); return; } n = (uint32_t)(dot - in); if (n + 4 + 1 >= outsz) die("Output path too long"); memcpy(out, in, n); memcpy(out + n, ".ogv", 5); } int main(int argc, char** argv) { const char* in_path; const char* out_arg; char out_path[1024]; int validate; int fps; uint8_t* dv; uint32_t dv_len; uint32_t w, h, packet_count; uint8_t* video_es; uint32_t video_es_len; uint8_t* audio_ogg; uint32_t audio_ogg_len; OggPage* audio_pages; uint32_t audio_page_count; uint32_t sample_rate; Span* frames; uint32_t frame_count; int i; int positional; /* defaults */ in_path = 0; out_arg = 0; validate = 0; fps = 25; /* fixed as in your tested files */ if (argc < 2) { usage(argv[0]); return 2; } positional = 0; for (i = 1; i < argc; i++) { const char* a = argv[i]; if (streq(a, "-h") || streq(a, "--help")) { usage(argv[0]); return 0; } if (streq(a, "--validate")) { validate = 1; continue; } if (a[0] == '-') { fprintf(stderr, "Unknown option: %s\n\n", a); usage(argv[0]); return 2; } if (positional == 0) { in_path = a; positional++; continue; } if (positional == 1) { out_arg = a; positional++; continue; } fprintf(stderr, "Too many arguments.\n\n"); usage(argv[0]); return 2; } if (!in_path) { usage(argv[0]); return 2; } if (out_arg) { strncpy(out_path, out_arg, sizeof(out_path) - 1); out_path[sizeof(out_path) - 1] = 0; } else { make_default_out(in_path, out_path, (uint32_t)sizeof(out_path)); } crc_table_init(); dv = read_file_all(in_path, &dv_len); if (!dv) die2("Cannot read input file: ", in_path); /* Extract */ w = h = packet_count = 0; video_es = 0; video_es_len = 0; audio_ogg = 0; audio_ogg_len = 0; dvbang_extract(dv, dv_len, &w, &h, &video_es, &video_es_len, &audio_ogg, &audio_ogg_len, &packet_count); /* Parse audio pages */ audio_pages = 0; audio_page_count = 0; audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count); if (!audio_pages || audio_page_count == 0) die("Audio parse failed: no Ogg pages"); /* Sample rate */ sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count); if (sample_rate == 0) { fprintf(stderr, "WARNING: could not detect Vorbis sample rate; assuming 44100.\n"); sample_rate = 44100; } /* Split frames */ frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count); /* Mux */ mux_ogv(out_path, audio_pages, audio_page_count, sample_rate, (int)w, (int)h, fps, frames, frame_count); printf("ogv: %s\n", out_path); printf("video: MPEG-4 Part 2 (%ux%u, frames=%u, fps=%d)\n", w, h, frame_count, fps); printf("audio: Vorbis (sr=%u, pages=%u)\n", sample_rate, audio_page_count); /* Optional validation */ if (validate) { char cmd[1400]; int rc; snprintf(cmd, sizeof(cmd), "ffmpeg -loglevel error -i \"%s\" -f null -", out_path); rc = system(cmd); if (rc != 0) { fprintf(stderr, "WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n"); } } /* cleanup */ if (dv) free(dv); if (video_es) free(video_es); if (audio_ogg) free(audio_ogg); if (audio_pages) ogg_free_pages(audio_pages, audio_page_count); if (frames) free_frames(frames, frame_count); return 0; }[/codebox] |
Lumis, | ||
| Feb 7 2026, 03:48 | Новости сайта | QUOTE(-=CHE@TER=- @ Feb 7 2016, 13:45) [snapback]4022[/snapback] Друзья! Нам сегодня исполнилось 10 лет! Вы таки будете смеяться, друзья, но нашему проекту CTPAX-X исполнилось XX лет! Да, мы целых 20 лет в сети.Поздравляю всех с этой круглой датой! Краткую историю я изложил ещё на 10-тилетие проекта, так что не знаю, давайте что ли, о текущих делах. Из последнего: 1. Я потихоньку поправил кучу всяких вещей в HTML/CSS вёрстке сайта, так что он теперь должен быть W3C совместимым на любой странице. Кое-какие кривоватости тянулись аж чуть ли не с того времени, когда я переписал движок сайта в 2007 году. 2. Помаленьку исправляю и переписываю свои старые программы. Кое-где исправляю ошибки, кое-где переделываю распаковщики, чтобы они правильно, согласно дизассемблированному коду игры, работали, а не "примерно как-то так" как ранее. 3. Новые программы добавляются на сайт гораздо реже, потому что в последнее время другие дела много времени отнимают, к тому же чтобы написать распаковщик, нужно играть или изучать новые игры (или те, что ещё не видел из старых), а я мало во что новое играю. Так что, получается, что чаще что-то пишу для сайта по заказу. 4. Ещё время на форум уходит - тут помогаю, когда у кого-то вопросы появляются, а также своими мыслями делюсь, когда есть время и желание. 5. Также помогаю, где могу, вести ModdingWiki. На Extractor.ru активности ноль, Xentax.com закрылся, Zenhax.com просто перевели в режим только-чтения (там теперь статические HTML страницы вместо форума), так что мы теперь - последние из могикан. Ну и, конечно, во главе любых вещей и явлений в человеческом обществе всегда стоят люди. Это наша главная ценность. За 10 лет многое изменилось - кто-то из "старичков" совсем забросил наш проект, а кто-то, как, например, kampaster (который стал нашим казначеем и теперь отвечает за копилку и разруливает финансовые вопросы), наоборот, подключился. На форуме несколько новичков добавилось. Хотелось бы только чтобы они были активнее и сами тоже какими-нибудь интересными находками и мыслями делились. Но это так, пожелания. У нас тут дружеская атмосфера, так что каждый делает то, что хочет. И конкретно по людям из раздела "О проекте", кому интересно: Blade WEB-мастер, дизайн сайта, идея проекта. Отдельная благодарность, за то, что не побоялся начать всё это и сподвигнул всех остальных. С 2009 года ничего о нём не слышал. На связь он тоже больше не выходил. Надеюсь, у него всё в жизни хорошо, просто интересы и приоритеты поменялись. RAYN3 Материалы, распаковщики и моральная поддержка. Жив-здоров. Иногда мы с ним переписываемся. Alex Администрирование и поддержка форума. Он же DGS. После того как я случайно грохнул всех пользователей на форуме он не пытался свой аккаунт восстановить и на связь не выходил. Надеюсь, у него всё хорошо. -=CHE@TER=- Обеспечение хостинга и материалы со CTPAX-CHEATER; версия сайта 2.x. Ну, про себя я уже писал. kampaster Казначей, управление платёжными системами для пожертвований и оплаты хостинга. Забавная история в том, что он в 2010 или 2011, не помню точно, написал в "Обратную связь" с просьбой сделать распаковщик для Sublustrum (Outcry) (см. там даже первый комментарий его). А спустя много лет, когда я написал на главной, что не могу больше поддерживать проект, т.к. проблемы с финансами, он вызвался помочь. Я даже представить себе не мог, что все эти годы он заходил на сайт и следил за нашей активностью. Разработчики-программисты (в алфавитном порядке): Axsis Судя по профилю на форуме последний раз был активен 3 года назад - в феврале 2023. Где и как он сейчас я не знаю. В последние годы он меньше всех остальных активности проявлял, подозреваю, что много дел, забот и хлопот в реальной жизни. Надеюсь, у него всё хорошо. Bourn После того как я случайно грохнул пользователей на форуме тоже свой логин не восстанавливал. Жалко. Будем надеяться, что у него тоже всё хорошо. -=CHE@TER=- Аналогично, про себя я уже писал. Grom PE Один из немногих из нашей команды, с кем я однажды лично встречался в 2009 году (он был в моём городе проездом). Сейчас стал ленивой задницей - уже год где-то, если не больше, не может починить свой сайт grompe.org.ru из-за чего мне на CTPAX-X в блоке "Наши друзья" пришлось ссылку на архивную копию его сайта менять. Я немного в курсе его личной жизни, так что стал беспокоиться, когда он пропал и перестал на почту отвечать. Недавно он появился и мы с ним немного пообщались, пока он снова на почту не забил, но я рад что у него всё хорошо. jTommy Не активен с марта 2015. В принципе, он говорил, что с программирования в аппаратно-железячную область ушёл, так как его это больше интересовало. А ещё он работал не буду говорить где на тот момент, просто надеюсь, что он умный парень и понял что к чему за прошедшие годы и у него всё хорошо сейчас. Siberian GRemlin Жив-здоров, продолжает заниматься переводами. Как я понял из свежих новостей с его сайта - ударился в японский и японскую культуру. Мне бы хотелось, чтобы он завершил перевод трилогии "Кирандии", там только третья часть осталась, но, вероятно, это отложено в сторону надолго, если не насовсем. Xplorer С конца 2010 года неактивен. Мне как-то писали по поводу добавления новых игр в ToWav одно время очень много и я решил таки написать ему на почту, на которую он тут был зарегистрирован, спросить насчёт развития программы, а также можно ли давать кому-то его контакты. Мне пришёл автоматический ответ о том, что его почтового ящика больше нет. Подозреваю, что он был как-то связан с игровой индустрией, возможно, занимался разработкой игр в какой-то компании. Я ничего не могу утверждать, а только строить предположения, но в интернетах пишут, что код для декодирования XWB, или как там тот формат XBox-овской музыки назывался, был взят с какого-то SDK, который, вроде бы, в открытом доступе нигде до этого не светился. Из чего я делаю вывод, что он ради помощи игрокам, чтобы они могли достать музыку, возможно (ещё раз - это моё предположение), мог нарушить NDA из-за чего был вынужден "лечь на дно" и разорвать со всеми контакты, чтобы не вычислили тех, от кого он получил SDK (опять же, это всё моя теория, что на самом деле произошло, я не знаю). Почти хрестоматийная легенда о Данко. Надеюсь у него всё отлично и обошлось без последствий. Хочу сказать всем и каждому - спасибо вам всем большое! Вот. Так или иначе все эти 20 лет помаленьку, понемножку, по чуть-чуть, но работа у нас на сайте шла. Главное, что мы не стоим на месте, потому что жизнь - это движение. Все эти 20 лет мы накапливали знания и делились опытом (зря что ли тут столько ИИ ботов с утра до ночи теперь тусуется, хех). Я не знаю, что будет в будущем и не хочу загадывать. Пусть будет много хорошего, приятного и интересного! Поздравляю нас всех с 20-тилетием нашего проекта, друзья! |
useretail, | ||
| Dec 4 2025, 01:19 | Activision Uninstaller расшифровка [*.LOG] | Я всю программу не буду приводить, только код расшифровки. Для первого файла динамическая таблица-ключ строится, а для второго - статическая в 400 байт (да, они так в файле и записаны). Как пользоваться: CODE uint8_t *p; // указатель на данные файла прочитанные в память uint32_t sz; // размер данных (размер файла) // читаем в память *.LOG файл и скармливаем этой функции // 13 - это потом что первые 13 байт не шифруются - это сигнатура "atviuninstall" activ_decrypt_log(&p[13], sz - 13, 13); // читаем в память STRINGS.DAT файл и скармливаем этой функции activ_decrypt_dat(p, sz); Сам код функций: [codebox]static uint8_t activ_xor[700]; uint8_t activ_buildkey(uint32_t i) { uint32_t v1, v2, v3; v1 = 1; v2 = i % 700; if ((i % 700) > 100) { v3 = (v2 - 1) / 100; do { v2 -= 100; --v3; v1 = (19 * v1) % 701; } while (v3); } if (v2 > 10) { v3 = (v2 - 1) / 10; do { v2 -= 10; --v3; v1 = (323 * v1) % 701; } while (v3); } for ( ; v2; v1 = (2 * v1) % 701) { --v2; } return(v1); } void activ_buildtable(void) { uint32_t i; for (i = 0; i < 700; i++) { activ_xor[i] = activ_buildkey(i); } } /* *.LOG */ void activ_decrypt_log(uint8_t *p, uint32_t l, uint32_t o) { uint32_t i; if (p && l) { activ_buildtable(); o %= 700; for (i = 0; i < l; i++) { p[i] ^= activ_xor[o]; o++; if (o >= 700) { o = 0; } } } } static uint8_t activ_log[400] = { 0xB9, 0x61, 0x88, 0x96, 0x41, 0x91, 0x99, 0x37, 0x8D, 0x8A, 0xB9, 0x9C, 0xB6, 0x5F, 0x11, 0x50, 0xE7, 0x1D, 0x22, 0x1A, 0xBF, 0xFE, 0x0A, 0x8B, 0xF6, 0xDF, 0x02, 0xD3, 0x44, 0xF4, 0x2C, 0xD9, 0x7E, 0xBB, 0x8C, 0x8A, 0x03, 0x2E, 0x53, 0x70, 0x56, 0x3D, 0x64, 0x87, 0x0C, 0x7B, 0x11, 0x4D, 0xE9, 0x18, 0x25, 0x19, 0x46, 0x0F, 0x28, 0xAD, 0xB5, 0xA0, 0xDE, 0x0E, 0xE8, 0x01, 0x11, 0x94, 0xCF, 0x51, 0x90, 0x3D, 0x01, 0xCD, 0x78, 0x46, 0x5D, 0x46, 0xB2, 0xFD, 0xF1, 0xD3, 0xBD, 0xD1, 0x1B, 0xC3, 0xAE, 0xAB, 0xED, 0xD5, 0x75, 0x81, 0xD6, 0xAC, 0x61, 0x28, 0x7F, 0x7D, 0xE7, 0x69, 0xF4, 0x0B, 0x9F, 0x58, 0x04, 0xD5, 0x8F, 0xE1, 0xEA, 0x8D, 0xAC, 0xA4, 0x2D, 0xCD, 0x9E, 0x03, 0xC4, 0x06, 0xC4, 0x78, 0x7E, 0xB9, 0x78, 0x2C, 0xA1, 0xE9, 0x94, 0xC6, 0xD1, 0xD0, 0x34, 0x81, 0x34, 0xD1, 0xBE, 0x82, 0xD4, 0xAE, 0x20, 0x68, 0x45, 0xFB, 0x59, 0x24, 0x87, 0xD2, 0x3B, 0x0B, 0x2D, 0xC9, 0x6F, 0x29, 0xBF, 0x21, 0xB9, 0xD9, 0x5E, 0x40, 0x7D, 0x93, 0xA6, 0x61, 0x83, 0x04, 0xD9, 0x8B, 0xF7, 0x64, 0x39, 0xBF, 0xB4, 0x04, 0xB5, 0x76, 0xC2, 0x26, 0xC8, 0x4B, 0x1C, 0x12, 0x9F, 0xF4, 0xB7, 0x66, 0x7A, 0x76, 0xC1, 0xAE, 0x54, 0x99, 0x26, 0x34, 0xC6, 0x9A, 0x59, 0x1A, 0x29, 0x21, 0x51, 0xA5, 0xFB, 0x72, 0xD2, 0xDC, 0x83, 0xE7, 0xED, 0x52, 0xB8, 0x9E, 0xCA, 0x40, 0x60, 0x6F, 0xA5, 0xD6, 0x75, 0x21, 0x17, 0xD3, 0xCC, 0xDD, 0x97, 0x54, 0xF9, 0xE2, 0x40, 0xE9, 0x6D, 0x7B, 0xD5, 0xEE, 0xE1, 0x2E, 0x02, 0x19, 0xF8, 0x37, 0xE4, 0x4F, 0x1F, 0x35, 0xCC, 0xBC, 0xB8, 0x22, 0x40, 0x22, 0x79, 0x88, 0x43, 0x71, 0x0E, 0xF2, 0xD7, 0x99, 0x06, 0xA1, 0xBF, 0x9C, 0xEC, 0x81, 0x89, 0xE7, 0xB4, 0x5C, 0xCC, 0xE2, 0x5A, 0x4C, 0xAF, 0xC6, 0xC5, 0x76, 0xAA, 0xAF, 0xF1, 0xF5, 0x91, 0xF2, 0x4D, 0x15, 0xCE, 0xB0, 0x62, 0xC2, 0xED, 0xAB, 0xB6, 0x40, 0x5F, 0x59, 0xF0, 0x1C, 0x77, 0x37, 0x3C, 0x62, 0xB9, 0x60, 0xF1, 0x10, 0x54, 0x5E, 0x72, 0xCB, 0xED, 0x40, 0x52, 0xD1, 0x9E, 0xEC, 0xBB, 0x2F, 0x3F, 0xB7, 0x10, 0x34, 0xE4, 0x33, 0xD1, 0x25, 0xA6, 0x48, 0xBF, 0x32, 0xA7, 0x87, 0x41, 0xA9, 0x50, 0xBA, 0x07, 0x6A, 0xDD, 0xC0, 0xED, 0x9B, 0x1C, 0x98, 0x22, 0x9C, 0x71, 0xBB, 0x89, 0x3D, 0x1E, 0xAF, 0x5F, 0x2F, 0xC1, 0xD9, 0x1F, 0xB9, 0x1E, 0x93, 0xA2, 0xAC, 0x1F, 0x7E, 0x8B, 0x99, 0x19, 0x19, 0xE2, 0x41, 0x51, 0x93, 0xFF, 0x4D, 0xBF, 0xDE, 0xAA, 0x40, 0x12, 0x05, 0x80, 0xA9, 0xF4, 0xBF, 0x99, 0x9D, 0x8E, 0x44, 0x67, 0x64, 0x4F, 0x5F, 0xE1, 0x73, 0xE9, 0xC5, 0xE1, 0x9A, 0x9D, 0xA5, 0xCB, 0x7F, 0xB9, 0x80, 0x70, 0x4B, 0x5F, 0x3B }; /* STRINGS.DAT */ void activ_decrypt_dat(uint8_t *p, uint32_t l) { uint32_t i; if (p && l) { for (i = 0; i < l; i++) { p[i] ^= activ_log[i % 400]; } } }[/codebox] |
PavelDAS, | ||
| Nov 21 2025, 12:28 | InstallShield расшифровка Uninst.isu | Хм. Походу там фиксированное смещение (фиксированный размер первой части файла). Заменил в своём коде выше: CODE fread(q, 712, 1, fl); fwrite(q, 712, 1, f); На такое: CODE /* fixed offset */ i = 0x457 - ftell(fl); fread(q, i, 1, fl); fwrite(q, i, 1, f); |
PavelDAS, | ||
| Nov 6 2025, 16:12 | Wise Installer (script.bin) | QUOTE(PavelDAS @ Nov 6 2025, 08:27) [snapback]4838[/snapback] Понял, что они формируются степенью двойки, но при расшифровке не всегда есть число с таким параметром... Так это же битовая маска: ссылка.QUOTE(PavelDAS @ Nov 6 2025, 08:27) [snapback]4838[/snapback] 805313095 раскладывается на числа, которых нет... 805313095 - это 0x30001A47 (hex) или 110000000000000001101001000111 в двоичном представлении.Как видишь, в числе 9 не нулевых битов - т.е. использовано 9 флагов: 805313095 = 0x30001A47 = 0x1 or 0x2 or 0x4 or 0x40 or 0x200 or 0x800 or 0x1000 or 0x10000000 or 0x20000000 or - это операция битового "или" (в сях и си-подобных языках это "|" - вертикальная черта). if (7 == (4 | 2 | 1)) { echo 'true'; } |
PavelDAS, | ||
| Jul 9 2025, 10:04 | CheMax | Во! Спасибо за развёрнутый ответ. Теперь по пунктам. QUOTE(Shadeov @ Jul 9 2025, 08:56) [snapback]4799[/snapback] Цель — альтернативный клиент для CheMax с обратной совместимостью. Планирую сайт + кроссплатформенные клиенты (Mac, Windows, iOS, Android и т.д.). Идея классная, только вот с точки зрения конечного пользователя сложновата в использовании. Проще будет, наверное, БД последней версии CheMax конвертировать в одностраничный HTML файл и выложить в Интернет. Кому надо скачают к себе на диск для просмотра в offline, кому не надо - смогут через Интернет обозреватель с любого устройства смотреть. Если из БД выкинуть все игры, где написано только "для этой игры есть трейнер на сайте CheMax" (сайт закрылся и трейнеры вместе с ним пропали) и больше ничего (как это ни странно, таких игр много, причём этот текст тупо дублируется), то итоговая HTML страничка ещё и занимать мало будет. Ещё можно сделать список с играми как плавающий блок слева или справа, чтобы всегда на экране был и якорями-ссылками для быстрого перехода к нужной игре.Идея в том, чтобы просто загружать CheMax.exe и cheats.dat, после чего база открывается в современном интерфейсе — прямо в браузере, без установки. Можно, как уже писал ранее, сделать сайт-вики с кодами для CheMax, где люди смогут их обновлять, исправлять, добавлять. Я не вижу смысла именно заморачиваться поддержкой всех версий (разве что, кроме как хобби) в силу следующих причин: 1. Неудобно использовать. Пользователю чтобы посмотреть какие-то коды, которые он когда-то в этой программе видел, нужно где-то найти дистрибутив программы, установить или как-то его распаковать, затем достать оттуда нужные файлы, затем залить их на сайт - я ставлю на то, что такое количество телодвижений очень многих людей отпугнёт, а кому-то просто всё желание заморачиваться отобьёт. Да, можно добавить поддержку InnoSetup установщика и .ZIP архива, чтобы можно было дистрибутив программы заливать на сайт без установки, но дистрибутив всё равно нужно будет где-то найти. 2. Сайт CheMax закрылся, похоже, автор не планирует продолжать проект. Сами по себе коды к играм не могут быть защищены авторским правом (если, вдруг, беспокоит этот вопрос). К тому же, скорее всего, коды для БД просто собирались из всяких сборников и сайтов в Интернете. Программа была полезна для экономии трафика в 2000-ых годах, когда Интернет дорого стоил и не был безлимитным, то иметь под рукой энциклопедию было удобно. А сейчас с общедоступным Интернетом такая программа не особо актуальна в виде отдельного приложения. QUOTE(Shadeov @ Jul 9 2025, 08:56) [snapback]4799[/snapback] Если безболезненной распаковки не получится, то, скорее всего, просто вытащу смещения вручную и буду сопоставлять по хешу .dat‑файлов. Да, кстати, именно такое решение и хотел предложить - заготовить смещения + имена игр от всех существующих версий и по размеру/хешу от .dat файла использовать нужные. Процесс, правда, будет долгий и трудоёмкий - непонятно сколько там версий было, что-то можно из WebArchive достать, что-то с того же Old-Dos, но все ли там версии - неизвестно.В крайнем случае — использовать только уже готовые ваши смещения от последних версий. QUOTE(Shadeov @ Jul 9 2025, 08:56) [snapback]4799[/snapback] Пока набросал небольшой сайт на основе вашей DeCheMax — https://dechemax.vercel.app/ Глянул - круто!Открывает последние версии CheMax и CheMax FC. Old пока не проверял. Всё на клиенте: база сохраняется в кэше браузера и там же распаковывается. Как доделаю — обязательно выложу исходники. Я в любом случае отговаривать не буду - если есть желание, силы и время - то почему бы и нет? Хобби проекты тем интересны и приятны, что люди делают просто то что им хочется и нравится. А ещё иногда такие штуки делаются просто чтобы освоить какие-то новые технологии и на чём-то потренироваться - просто так писать что-то абстрактное неинтересно, а вот работать над конкретной задачей уже гораздо увлекательнее. В общем, я за любой кипишь, кроме голодовки. (*улыбается*) |
Shadeov, | ||
| Jul 8 2025, 17:32 | CheMax | QUOTE(Shadeov @ Jul 8 2025, 17:14) [snapback]4794[/snapback] Привет всем! Добро пожаловать на форум!QUOTE(Shadeov @ Jul 8 2025, 17:14) [snapback]4794[/snapback] Подскажите, пожалуйста, как достать ключи смещений в CheMax? Нужна пошаговая инструкция для "новичка". Готовые смещения есть в архиве к программе database decrypter (ссылку ранее в этой теме давал) к последним версиям CheMax: 20.8 для английской версии (файл "eng_20_8.txt") и 21.4 для русской (файл "rus_21_4.txt"). Вроде бы, это должны быть последнии версии программы.Хочу сохранить это наследие, адаптировать под современные системы и при этом обеспечить обратную совместимость с базой данных. Всё никак не дадим спокойно помереть ЧеМаксу Если нужны для каких-то старых версий, то тут придётся попотеть. Я так делал: 1. Смещения для описаний находятся внутри CheMax.exe, но файл защищён ASPack. 2. Я снимал ASPack программой Dr.WEB.FLY-CODE.Unpacker.0.1. 3. Далее нужно вытащить строки (названия игр) и смещения - они отдельно находятся. 4. Чтобы вытащить имена игр, засовываем распакованный файл "STEP1_ASPACK.exe" в программу по работе с ресурсами (я для этого Resource Hacker использовал) и сохраняем куда-нибудь .DFM файл с главной формой программы. Затем в Delphi (у меня он стоит) создаём пустой проект, затем сохраняем и закрываем его. Заменяем файл формы сохранённой формой от CheMax. После этого открываем проект снова в Delphi, на форме выбираем список с именами игр, выбираем редактировать, затем сохраняем в буфер обмена Ctrl+C всё содержимое и вставляем Ctrl+V в какой-нибудь пустой текстовый файл. 5. Теперь нужно достать смещения. Здесь сложнее, потому что они в бинарном виде и как их найти я не могу написать - я тупо вручную отсматривал файл в HEX-редаторе. У меня осталась вот такая черновая рабочая программа, которой я смещения доставал - надеюсь пригодится как отправная точка для работы. Обращаю внимание, что смещений, вроде бы, на 1 больше, чем названий игр (последнее смещение равно размеру файла с описаниями) - это для того, чтобы размер описания к игре можно было получить как разницу между следующим и текущим смещением. 6. Ну и потом нужно просто объединить смещения и названия игр - можно написать для этого программу или скрипт, например, на том же PHP или на чём удобнее. Программу ниже нужно скомпилировать и перенаправить вывод в файл при запуске: offsdump.exe > offs.txt Исходный код "offsdump.c": [codebox]#include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <string.h> #include <stdint.h> /* eng 19.2 */ /* static uint32_t offslist[] = { 0xD7C14, 0xDBA94, 0xDE974 }; */ /* eng 20.8 */ static uint32_t offslist[] = { 0xD8614, 0xDC494, 0xDF374 }; /* rus 21.4 */ /*static uint32_t offslist[] = { 0xDBA14, 0xDF894, 0xE2774, 0xE5654, 0xE8534, 0xEB414, 0xEE2F4 };*/ static uint32_t offs[4000]; int main(int argc, char *argv[]) { uint32_t i, j; FILE *fl; fl = fopen("STEP1_ASPACK.exe", "rb"); if (!fl) { return(1); } for (i = 0; i < (sizeof(offslist) / sizeof(offslist[0])); i++) { fseek(fl, offslist[i], SEEK_SET); fread(offs, sizeof(offs), 1, fl); for (j = 0; j < 4000; j++) { if (!offs[j]) { break; } printf("%08X\n", offs[j]); } } fclose(fl); return(0); }[/codebox] |
Shadeov, | ||
| May 7 2025, 11:58 | Баги сайта и форума | Отключил создание гостевых сессий на форуме - давно хотел. Штатными средствами это невозможно сделать, так что пришлось в исходный код лезть. При первом входе на форум больше не будет уродских s=0123456789abcdef0123456789abcdef в ссылках. А куки с session_id теперь создаются только при попытке входа (когда логин и пароль ввели и пытаетесь войти). Какие плюсы: 1. Как я уже сказал, не будет уродских ссылок. 2. Форум будет лучше индексироваться тем же Archive.org и другими поисковыми системами - ссылки не будут различаться из-за "&s=..." в них. 3. Опять же, лог-файлы форума будет проще анализировать - там не будет километровых "&s=..." в ссылках. Какие минусы: 1. Параноики куки не использующие (кто даже больший параноик, чем я) зайти на форум не смогут. 2. Возможно, могут гости отвалиться. Ну, т.е. вся эта статистика "X чел. читают эту тему". 3. Возможно, я мог что-нибудь случайно поломать, так что пишите, если кто-то что-то обнаружил. Если, вдруг, кто-то не сможет зайти на форум, то пишите мне на почту или в обратную связь на сайте - будем разбираться. Почему я этим решил всё же озадачиться? Дело в том, что полез вчера в панель управления хостинга спам фильтры для почты обновить (какие-то дебилы с рекламой Oracle уже капитально достали) и обнаружил, что у нас 500 Мб занимают недельные логи веб-сервера нашего сайта. И это при том, что все логи, кроме логов за сегодня, упакованы в .GZ архивы. И это при том, что весь наш сайт сейчас, включая форум и обе БД занимает менее 150 Мб. Служба поддержки ответила, чтобы я не переживал, всё нормально, но меня это напрягло. Скачал логи за 2025-05-05 у форума, распаковал. Получилось такое: forum.ctpax-x.org.access.log-20250505 - 347 Мб - логи доступа к страницам и файлам forum.ctpax-x.org.error.log-20250505 - 153 Мб - логи с ошибками Я, честно сказать, просто офигел когда это увидел. Заглянул в логи доступа, там куча ботов. Сделал фильтрацию файла access в другой файл убрав все строки логов с ботами. Получилось 348 Кб - в 1000 (прописью: в тысячу!) раз меньший файл. Решил собрать статистику, получилась следующая картина (текст можно сохранить в текстовый файл "bots.csv" чтобы потом в Excel открыть и круговую диаграмму построить - позволяет наглядно оценить масштаб катастрофы): CODE AhrefsBot;641 Amazonbot;223546 Applebot;40 bingbot;688 BitSightBot;23 BLEXBot;55202 Bytespider;298 ChatGPT-User;6 DotBot;280 facebookexternalhit;266 Googlebot;413 keys-so-bot;74 meta-externalagent;1083405 MJ12bot;8442 OAI-SearchBot;13 PerplexityBot;6 PetalBot;16932 SemrushBot;4888 VelenPublicWebCrawler;20 YandexBot;12 Yatcybot;294 Кстати говоря, обновил список известных ботов для форума добавив туда всех вышеперечисленных. Не трудно заметить что meta-externalagent (это Facebook) и Amazonbot (это Amazon) совсем охренели. Особенно лицокнига - более миллиона (!!!) обращений за сутки! Да вы там совсем опухли что ли?! Причём, если погуглить, то на meta-externalagent много жалуются, что бот создаёт какой-то просто невыносимый трафик на сайт. Люди изобретают всякие решения (например) чтобы бота не блокировать, но его охреневшие аппетиты как-то поубавить. Но самое прикольное, что почти весь error.log - это примерно вот такие записи: QUOTE 2025/05/04 03:51:26 [warn] 1234567#1234567: *12345678 an upstream response is buffered to a temporary file /tmp/proxy/1/23/0001234567 while reading upstream, client: 57.141.0.28, server: forum.ctpax-x.org, request: "GET /index.php... 57.141.0.xxx - это как раз адреса ботов meta-externalagent. И такого дерьма на 150+ Мб. Цукерберг, или кто там у вас главный, немедленно прекрати! Это к слову о том, что современные мощности позволяют писать какой угодно шлакокод. А то что эта фигня на палке тупо сайты DDoS'ит - об этом никто не думает. |
Siberian GRemlin, | ||
| May 5 2025, 16:58 | Delphi, Asm, C, WinAPI, PHP, ... | Недавно я обновил свою функцию basename() которую использовал в программах для получения имени файла из полного пути. Обновил, добавив туда помимо проверки на символы "\" и "/", также на символ ":". По идее, не совсем правильно искать этот символ по всей строке, ну да ладно. Кто-нибудь спросит "а что, собственно, случилось?" на что я поведаю такую историю. Давным-давно, когда DOS и прочие операционные системы только появились, не было каталогов (они же папки, они же директории) и все файлы были навалены в корень диска. Вернее, даже, дисковода, потому что жёстких дисков тогда тоже не было - было два дисковода A: и B:, где в первом, как правило, держали дискету с программой, а во втором дискету с файлами этой программой созданными. Поэтому, чтобы обратиться к файлу на другом дисководе, писали так (команда type выводит файл на экран): type B:TEXTFILE.TXT Т.е. указывали диск, затем двоеточие, и только потом имя файла. Шло время и в DOS появились те самые каталоги-папки-директории, где разделитель был символ "\". Так что путь теперь записывался так: type B:\TEXTFILE.TXT Однако, в целях обратной совместимости, запись с указанием файла в корне диска без последующего символа "\" так и осталась. Даже после того как вышел Windows и много ещё чего стряслось. Поэтому даже сегодня можно написать: type B:TEMP\TEXTFILE.TXT И оно будет работать на Windows XP. А вот на Windows 7 использование такого способа уже не работает. Подозреваю, что начиная с Vista убрали обратную совместимость. Добавлю, что в файловых системах NTFS двоеточие после имени файла используется для указания имени потока файла: C:\Temp\TEXTFILE.TXT:stream_name |
Siberian GRemlin, | ||
| Apr 26 2025, 00:50 | CheMax | Так они там не зашифрованы - просто в конце находятся. Сначала список игр, потом, через запятую, смещения (байтовые) в десятичном виде. Я как раз в программе это обрабатывал, чтобы расшифровывалось всё, кроме этого списка в хвосте. |
useretail, | ||
| Apr 20 2025, 10:24 | Прогресс и программы | QUOTE(useretail @ Apr 20 2025, 02:32) [snapback]4788[/snapback] спасибо, что не забанил Не, я не такой - не баню людей просто так. (*улыбается*)Кстати, спасибо за обновлённый код для CheMax! Я его в codebox обернул (это почти как code, только с прокруткой для длинных текстов), чтобы он много места не занимал на форуме. |
useretail, | ||
| Apr 19 2025, 09:48 | Прогресс и программы | QUOTE(useretail @ Apr 19 2025, 01:17) [snapback]4785[/snapback] так это домашнее задание - Вот и молодец. А теперь отдай спичку Уэфу, он гравицапу купит.- А этот сказал, что все нужны. - Пошутил я. - Весёлые вы ребята! © к/ф Кин-дза-дза! (*улыбается*) P.S. Давно тебя видно не было. Спасибо, что живой! |
useretail, | ||
| Aug 19 2024, 17:13 | Описание форматов файлов | Размышлял над тем где этот текст будет лучше разместить - решил, что в этой теме будет наиболее подходящее место. Речь пойдёт о недавно обновлённом конвертере Liberation Studio .WMV decrypter. Я хочу сразу написать о том что и как там было, по свежим следам, пока помню и не забыл какие-нибудь небольшие, но важные детали. Так вот, когда я написал этот конвертер в 2009 году, то он поддерживал только первую и вторую часть игры "Алиса" - там было несложное шифрование заголовка. Но вот начиная с третьей части шифровать стали не заголовок, а данные со звуковыми и видео блоками, из-за чего файлы проигрывались с щелчками и треском в аудио дорожке и разноцветным мусором вместо видео. QUOTE Что мешало: Я почему-то, почти до последнего момента, считал, что шифровали не сами блоки, а заголовки к ним, как это было в некоторых подобных играх, где, например, в .AVI формате обнуляли в заголовках ширину, высоту и идентификатор кодека у сжатых данных. С 2009 года в комментариях, на почту, в обратную связь и так далее, время от времени приходили просьбы добавить поддержку третьей "Алисы" и "Жучка" (как выяснили умельцы, подменяя файлы, там такое же шифрование было). Я с 2009 года один-два раза в год пытался с этим шифрованием разобраться, но всё никак не мог. QUOTE Что помогло: В 2008 году я писал по работе видео проигрыватель, который работал как раз с DirectShow (компонент DirectX отвечающий за проигрывание видео файлов системными кодеками), так что знал что такое FilterGraph, SourceFilter, SinkFilter, что такое In Pin и Out Pin, как их соединять и так далее, а также знал о программе GraphEdit (DirectShow Graph Tool by Microsoft) и прочих таких вещах. Если бы это были для меня неизвестные технологии, то, скорее всего, осваивать их с нуля только чтобы написать конвертер я бы не стал, так что конвертер, вероятно, до сих пор не был бы написан. В общем, засунув исполняемый файл третьей "Алисы" в дизассемблер я увидел, что там строится FilterGraph и, примерно, понял что происходит. Смотрите, если запустить упомянутый выше GraphEdit и открыть (File -> Render Media File...) там любой .WMV файл, то увидим примерно следующую схему (содержимое FilterGraph): CODE +-------------+ |"Raw Audio 0"| => [in0 "WMAudio Decoder DMO" out0] => [Audio input pin (rendered) "Default DirectSound Device" (clock)] |start_1.wmv | |"Raw Video 0"| => [in0 "WMVideo Decoder DMO" out0] => [VMR input0 "Video Renderer"] +-------------+ Итак, что мы видим на этой схеме? Мы видим, что от входного файла "start_1.wmv" идут два потока: звуковой и видео. Сначала оба потока идут через соответствующие декодеры, а потом в фильтры, которые выводят уже декодированные (разжатые) потоки на экран и звуковую карту (воспроизводят). Несложно догадаться, что между первым блоком и декодеровщиками авторы игры всунули свои собственные фильтры, которые на ходу расшифровывают данные, а потом уже передают их декодерам. Это можно сделать заполнив FilterGraph после его создания вручную (но кому такое не надо, то используют специальный метод, куда можно подать только путь до входного файла или ссылку, а все фильтры и связи между ними за тебя DirectShow построит). То есть задача какая: понять где находятся эти фильтры и посмотреть как и что там внутри шифруется. И вот тут, вы не поверите, я 15 лет натурально страдал. Дело в том, что программа написана на ООП, да и сам интерфейс DirectX хотя там и есть процедурный способ вызова, но на уровне машинных команд он всё равно объектный. Указатели на указатели. Куда что указывает, откуда берётся - хрен поймёшь. Т.е. я вижу код, который создаёт FilterGraph и засовывает туда свои фильтры, но адресов тех фильтров не вижу, а те подпрограммы, куда я захожу, ни на что внятное не похожи. Гуглив так и сяк, я нашёл программу "DirectShowSpy.dll", которая после регистрации в системе (через "regsvr32.exe /i DirectShowSpy.dll" и не забудьте (!) потом удалить через ключ "/u" вместо "/i") пишет в файл "C:\DirectShowSpy.log" всякое разное и интересное, а также делает все FilterGraph в системе возможными для совместного использования с возможностью подключения (в GraphEdit это File-> Connect to Remote Graph...). И я даже при запуске игры и подключения к одному из таких FilterGraph вижу те самые SinkFilter, которые дешифруют, но при попытке посмотреть их свойства GraphEdit тупо падает. При попытке добавить фильтр Dump (см. ниже) всё тоже падает даже в GraphStudioNext (см. ниже). В общем я и так, и сяк, и об косяк, но никак не мог понять где же этот чёртов код, которые занимается дешифровкой. После очередного комментария на позапрошлой неделе на страничке с программой, я решил, что попробую ещё раз побиться об косяк. Все свои тесты я проводил на файле "start_1.wmv" с логотипом издателя GFI (Game Factory Interactive), при этом у меня был ещё один такой файл, но от второй "Алисы" с расшифрованным заголовком. Для удобства обозначим их так: start_1ok.wmv - расшифрованный от второй "Алисы" (ok) start_2no.wmv - зашифрованный от третьей "Алисы" (no) Увы, файлы разного размера и очень сильно отличаются. Да, если кто-то подумал что можно поставить бряк на чтение файлов в DirectShow, затем на обращение к памяти и посмотреть где идёт изменение памяти (расшифровка), то... я это тоже пробовал, но с тем как прочитанный буфер кидается из одного места памяти в другое (а там ещё FileMapping) - вы реально умрёте, пока доберётесь в отладчике до места расшифровки. Но на прошлой неделе мне в голову пришла гениальная мысль, которая всё и решила. Я снова загрузил нормальный файл в GraphEdit, затем добавил в FilterGraph фильтр под названием "Dump" (Graph -> Insert Filters... -> DirectShow Filters -> Dump). QUOTE Что мешало: При раскрытии дерева "DirectShow Filters" у меня падал GraphEdit. Падал он потому, что когда я купил новый телефон в 2013 году (Samsung S5610), то программа для этого телефона (Samsung Kies) начала в систему всякую херню ставить и я прервал установку Windows Media чего-то там. Поэтому у меня при построении списка всё накрывалось тазом. Пришлось искать и скачивать программу GraphStudioNext - которая почти как GraphEdit, только там больше функционала и она не падает. Главное в заголовке PE EXE опустить версию системы с 6.0 (Windows Vista) до 5.0 (Windows 2000), чтобы эта программа запускалась на Windows XP. И да, я пытался поставить Kies в песочнице, но так как там нужен драйвер для работы с сотовым телефоном, то программа, естественно, из песочницы, нифига телефон не видела. Ещё можно было таки нагадить в систему до конца Windows Media чего-то там, затем попробовать его удалить (если получится), но я решил оставить как есть. Дальше я удалил все фильтры у видео и сразу после Source фильтра соединил пин с Dump фильтром, указал файл и записал туда все кадры подряд (в файл они уходят без служебных заголовков, только сами данные). После чего я проделал тоже самое с аудио. А потом всё описанное, но уже с зашифрованным файлом. На всякий случай скажу очевидное: чтобы что-то куда-то писалось, нужно после построения фильтров и соединения пинов, запустить FilterGraph на проигрывание. Получились у меня условно такие файлы: vid_ok.dat - нормальное видео aud_ok.dat - нормальный звук vid_no.dat - шифрованное видео aud_no.dat - шифрованный звук И вот тут я понял, что, походу, конвертеру быть, ибо дампы видео и звука у нормального и шифрованного файла совпадали по размеру! Я тут же сделал сравнение видеофайлов: CODE fc /b vid_ok.dat vid_no.dat > vid_list.txt И получил всего 119 байт разницы на 216 Кб. При сравнении аудио файлов получилось 16 разных байт на 69 Кб. Когда я делал сравнение, то не ожидал что всё настолько просто. Я искренне думал, что файл заставки в третьей части "Алисы" был пережат (отсюда и отличающийся размер у .WMV), поэтому хотел посмотреть как сжатый поток начинается - по идее, что-то похожее должно быть (анимация-то в видео одинаковая), может быть, пойму как шифруются первые байты и уже от этого смогу найти полностью код шифрования. Я не мог поверить в свою удачу и начал анализировать изменённые байты (первые 5 байт видео): CODE 000001F4: 17 16 000007D8: BC BD 00000DE3: B3 B2 00001299: 28 29 0000181D: DC DD 00001E64: B8 B9 Сразу видно, что байт либо стал больше на единицу, либо на единицу же уменьшился. Так как непонятно когда прибавляется, а когда вычитается, то я предположил, что это был "byte xor 1" - проверил своё предположение по всем байтам - так и оказалось. А ещё, судя по всему, так как изменений было немного, то, вероятно, в каждом кадре менялся ровно 1 байт. Далее я перевёл первое смещение 0x1F4 в десятичное число и получил 500 - ровное число, скорее всего искусственное, введённое человеком (как 100, 200, 1337 и так далее), потому что в аудио данных первое смещение тоже было 0x1F4. Иными словами, мы теперь знаем, как выйти на код декодирования - нужно искать какой-то такой код: CODE data[500] ^= 1; /* Pascal: data[500] := data[500] xor 1; */ Проще всего найти по смещению, так что я начал искать последовательность байт "F4 01" в "Alice3.exe" и я их нашёл: CODE .0042BC9F: xor byte [ecx + 01F4h], 1 Весь код выглядел так (восстановленный из ассемблера): CODE if (size >= 500) { data[500] ^= 1; } Кто скажет что не так с проверкой - тот молодец. (*улыбается*) Подсказка: когда size == 500, то в массиве data[] есть индексы только с 0 до 499 включительно (итого: 500 элементов), иными словами data[500] - это выход за пределы памяти и потенциальное падение программы с ошибкой (авторам игры просто повезло, что у них таких блоков не оказалось). Но на этом мои мучения не закончились. Да, я теперь знаю метод шифрования, но как подобраться к тем кадрам? Логично для этого открыть документацию Advanced Systems Format (ASF) Specification. Формат .WMV, кто не знал, этот улучшенный и углубленный .ASF. И вот тут я уже бился головой, потому что непонятно как эти поля парсить: то ли они вложенные, то ли нужно те же самые данные иначе интерпретировать. Короче, я натурально страдал. Скачал даже исходные коды ffmpeg и пытался оттуда парсер .ASF/.WMV выдернуть, но там настолько всё запутано, что бросил это гиблое дело. Мне очень повезло, что я нашёл в итоге простой и понятный (хоть и с ООП, но от этого я быстро избавился) код для парсинга .ASF/.WMV на GitHub: ASF file parser by Maoxu Li. После чего я допилил парсер под свои нужды, добавил расшифровку и видео с логотипом было расшифровано и проигралось без ошибок! Проверял через Windows сборку MPlayer (юниксовая программа) - этот проигрыватель в консоль ошибки кидает, если с видео или аудио что-то не так при декодировании. Я тут же взял самый большой .WMV файл из игры, натравил на него программу и... старые ошибки исчезли, зато появились новые. После этого я нашёл файл поменьше, где тоже были ошибки (всё же 102 Мб не быстро расшифровывается, плюс потом откатывать из резервной копии, чтобы снова проверить - лишнее время) и начал выводить в консоль данные по блокам, которые расшифровывал. Выяснилось, что блоки, иногда, меньше, чем 500 байт. Причём в .ASF/.WMV количество пакетов делить на размер всей части файла с данными - получаем фиксированный размер одного пакета. Но так получается, что блоки, относящиеся целиком к одному кадру или кусочку звука, не умещаются в один такой пакет, поэтому дробятся на несколько. А вот в SinkFilter они приходят уже собранные в одно целое. В спецификации увидел, что есть такая штука как Media Object Number и он идёт по порядку: 0, 1, 2, 3 и так далее. Поставил проверку в функцию расшифровки, что если текущий номер объекта не равен предыдущему и размер больше 500 и не расшифрован, только тогда расшифровывать. Ошибок стало сильно меньше, но они всё равно остались. Начал логировать номер объекта и, ах, ты ж, долбанный, ты нафиг: 110, 111, 112, 112, 113, 113, 114, 114, 114, 25, 115, 116, 116, ... То что номера дублируются - это нормально, как я уже говорил, блоки могут разбиваться, если целиком в пакет не влезают. А вот 25 между 114 и 115 будет расшифровано (вернее зашифровано) хотя не должно. Получается блоки могут идти в любом порядке, плюс само поле Media Object Number занимает всего 1 байт, так что после 255 опять получаем 0 (в рамках достаточно длинного видео - легко). Однако я разобрался, что есть ещё поле Offset Into Media Object - это смещение текущего блока данных относительно начала объекта с номером Media Object Number. И смещение всегда ноль, если это новый объект. Иными словами - текущее смещение - это сумма всех уже пройденных и обработанных блоков в этом объекте. В итоге функция расшифровки получилась следующей: CODE static uint8_t obj_stat[256]; /*static uint32_t nTotal;*/ void wmv_decrypt(uint8_t *p, uint32_t len, uint8_t num, uint32_t ofs) { /* printf("%08X %04X %02X\n", ofs, len, num);*/ if (p) { /* patched Media Object */ if (!obj_stat[num]) { /* new Media Object (always started with zero offset) */ if (!ofs) { obj_stat[num] = 1; } } /* not patched Media Object */ if (obj_stat[num]) { if ((ofs + len) > 500) { /*nTotal++; printf("%02X %02X (%u)\n", p[500 - nOffset] ^ 1, p[500 - nOffset], nOffset + l);*/ p[500 - ofs] ^= 1; obj_stat[num] = 0; } } } } Я специально оставил в коде закрытый в комментарии отладочный вывод информации. Что такое nTotal? Помните, я выше писал, что у меня была разница в 119 байт у видео и 16 байт у звука? В сумме будет 135 (это для упомянутого файла start_1.wmv на котором тестировал) - я выводил это число после завершения программы, чтобы убедиться что у меня все байты были расшифрованы и не были зашифрованные лишние. А в SinkFilter у меня была бы просто проверка на 500 вместо всего вот этого! Другое дело, что непонятно как бы там потом данные снова в .WMV файл собрать без пережатия - я не уверен, что такие фильтры существуют, а писать свой - проще забить и забыть. В общем, да, так я и справился с этой игрой и форматом. 15 лет спустя. QUOTE Что мешало: Почему я сразу не нашёл код декодирования? Знаете, я, может, его и видел, но ожидал найти цикл по блоку данных с некоторыми вычислениями, а не проверку и один "xor 1", что, вообще, больше походило на переключение какого-то флага, чем на расшифровку. Вторая вещь - как я уже писал в самом начале, я думал что шифровали заголовок. Авторы игры, видимо, тоже что-то такое подозревали, так что решили усложнить жизнь - сделали отступ в 500 байт от начала данных, к тому же меняли всего 1 байт (если точнее - всего 1 бит в байте). Вроде бы и пустяк, а сколько головняка. Фух! Надеюсь описанное кому-нибудь пригодится. Спасибо всем, кто дочитал до этого момента и уж тем более разобрался в моей писанине - вы невероятны! (*улыбается*) |
Siberian GRemlin, | ||
| Feb 22 2024, 14:21 | CheMax | Siberian GRemlin, useretail, | |||
| Feb 12 2024, 18:07 | CheMax | В 2022 году домен CheMax.ru был разделегирован, так что история данной программы на этом закончилась, поэтому можно подвести уже окончательный итог. Для начала отвечу на самое первое сообщение этой темы (понимаю, что уже не актуально, ну да ладно): QUOTE(Кантемир @ Mar 18 2007, 18:00) [snapback]536[/snapback] Просто хочу оболочку и возможности сохранить, а база кодов с нула хочу написать Нет, это невозможно. Дело в том, что описания кодов хранятся зашифрованные в текстовом файле "Cheats.dat", в то время как соответствующие этим текстам имена игр и смещения до них в этом файле, намертво прошиты в "Chemax.exe", который ещё и ASPack'ом упакован (защищён от изменений). Поэтому, максимум, что можно сделать, это изменить коды к существующим играм так, чтобы они не выходили за отведённое им место (меньше текста можно - лишнее пробелами заменить, больше - нельзя).и добавить секреты. Блин как редактировать база данных? А может ктонибудь создать прогу на подобия это? Просто там в chemaxе нельзя ни редактировать ни с нула. Сама программа и некоторые её старые и новые версии, помимо веб-архива, доступны ещё здесь: CheMax. И тут, поковыряв старые и новые версии этой программы, я выяснил интересную штуку про ключи шифрования. Оказывается, ключ "a@g5eDu(*5" использовался только для русской версии. В английской версии ещё с 2004 года использовался ключ "qZ8&5N3eS0" на который перевели и русскую между 2013 и 2018 годом - видимо, автору надоело пересобирать исполняемый файл с разными ключами при выпуске новых версий. Так что, признаю, моё предположение о том что ключ поменяли, потому что я его в этой теме публично засветил, было неверное. Попробую при следующем обновлении сайта добавить программу для расшифровки любой базы данных (с автоматическим определением ключа расшифровки), а также два текстовых файла со смещениями и именами игр в последних версиях "Cheats.dat" (RUS 21.4 и ENG 20.8) для тех, кто захочет распотрошить базу данных (распаковал и распарсил исполняемые файлы "CheMax.exe"). Увы, это, конечно, не вернёт потерянных трейнеров и других полезных утилит, которые накрылись вместе с сайтом программы. Функция для расшифровки любой базы данных выглядит так (возвращает не нулевое значение, если буфер удалось расшифровать): CODE uint32_t CheMaxDecrypt(uint8_t *p, uint32_t size) { uint8_t key[10]; uint32_t i, k; i = 0; /* sanity check */ if (p && size && ((*p == 0x81) || (*p == 0x91))) { /* detect key */ if (*p == 0x81) { memcpy(key, "a@g5eDu(*5", sizeof(key)); } else { memcpy(key, "qZ8&5N3eS0", sizeof(key)); } /* decrypt database with key */ for (i = 0; i < size; i++) { k = i % sizeof(key); p[i] += ((p[i] > key[k]) ? 0 : 255) - key[k]; } i = 1; } return(i); } На этом, вероятно, уже точно всё - тему можно считать закрытой. |
Siberian GRemlin, | ||
| Feb 10 2024, 15:29 | Новости сайта | Нашему проекту 7-го февраля 18 лет исполнилось - достигли совершеннолетия, можно сказать. Недавно гуглил со ссылками на наш сайт и вот чего нашёл (2021 год, кстати, на том канале это видео с наибольшим количеством просмотров): Need for Speed Most Wanted | How to Extract Soundtrack from Game File | 2021 Tutorial Аж смахнул скупую мужскую слезу - столько лет прошло, а нашей программой до сих пор пользуются. Какие же мы офигенные все - даже столько лет спустя. (*улыбается*) Не зря мы столько сил и времени вложили в эту программу. Я у себя на домашней страничке уже добавил в статью про конвертер для NFS, но хочу повториться (нисколько не умаляя труда каждого из нас), что большая часть кода для NFS Multimedia Converter была написана товарищем jTommy - по сути, если бы он тогда не взялся за это, то ничего бы не было. Было очень классно - огромное вам всем спасибо, друзья, за то что есть что вспомнить! |
Siberian GRemlin, | ||
| Sep 20 2022, 10:55 | Описание форматов файлов | Закрывая тему по игре Total Overdose - на сайте опять просили написать утилиту для упаковки. Меня откровенно ломало это делать, но подумалось, что есть способ проще, т.к. формат, по сути, почти что .ZIP - возможно, игра его и поддерживает. Заглянул внутрь исполняемого файла игры TOD.EXE и сделал поиск ".zip" - нашёл такие строки (я здесь по строкам разбил, но это ASCIIZ строки, где каждая заканчивается нулём): CODE /uk_sounds.naz Как видите, к каждому .NAZ идёт в паре .ZIP (видимо, если .NAZ не найден). Остальные архивы я нашёл внутри исполняемого файла одной строкой (это одна строка, с запятыми и пробелами):/uk_sounds.zip /it_sounds.naz /it_sounds.zip /de_sounds.naz /de_sounds.zip /fr_sounds.naz /fr_sounds.zip /es_sounds.naz /es_sounds.zip CODE blocks.naz, sounds.naz, videos00.naz, videos01.naz Что как бы говорит о том, что новые .NAZ файлы создавать бесполезно, т.к. игра не ищет по маске *.NAZ, а работает строго только с именами упомянутыми выше.И тут меня осенило, что, возможно, функция открывающая архивы одна, где внутри, уже по типу файла, определяет как его открывать: как .NAZ или как .ZIP. Тогда я сделал следующее: 1. Дешифровал все архивы .NAZ в .ZIP при помощи утилиты nastozip. 2. Убрал во временный каталог все оригинальные .NAZ файлы. 3. Переименовал все полученные .ZIP архивы в .NAZ (просто раширение сменил). 4. Запустил игру и... всё заработало будто так и было. Даже новую игру начал - никаких ошибок. Всё, конечно, не тестировал, но, подозреваю, что проблем быть не должно. Мораль сей басни такова: вот так лень победила желание что-то делать (конвертер из .ZIP обратно в .NAZ). |
Siberian GRemlin, | ||
| Aug 30 2022, 09:27 | My Big Sister (PS Vita) [Adventure Game Studio (AGS)] | А! Вот оно что. Проверил отдельно "agstract.exe" - он нормально работает. Значит, действительно, проблема в MSYS2 и том, что он файл найти не может. Нужно посмотреть какой каталог текущий, возможно, что, наоборот, не /bin/, а какой-то другой. Набери в MSYS2 команду "ls" (без ковычек, это что-то типа "dir" в Windows) и посмотри какие файлы будут выведены на экран - в каком каталоге ты сейчас находишься туда и перемести свой "game.exe". Или через команду "cd" (работает также, как и в Windows) перейди в нужный и оттуда уже вызывай. Конкретно MSYS2 у меня не стоит, но описанная схема в общем и целом для подобных иксовых программ должна работать. |
necros2k7, | ||
| May 14 2021, 18:08 | C# есть такие??? | Я последний раз что-то делал на шарпах году... эээ... в 2005 наверное. С тех пор к ним не притрагиваюсь. По идее можно на MSDN посмотреть, вот, например, описание FileStream и работы с файлами: FileStream Class (System.IO). |
Rash_forever, | ||
| Dec 25 2020, 15:27 | Жёсткий диск | QUOTE(Siberian GRemlin @ Dec 25 2020, 13:30) [snapback]4624[/snapback] Windows 7, NTFS. Я тоже сперва подумал, что проблема в «Total Commander», выделил те 65536 файлов в папке и удалил, но остальные файлы не появились – папка пустая. Саму папку пробовал удалять?QUOTE «R.Saver» видит 65536 удалённых файлов, но не последних, а первых. Методом сложения пустого места и размера всех файлов на диске получилось, что недостаёт 4 гигов. Тогда у меня вопрос, а как ты изначально узнал, что создалось более 65536 файлов? Потому что если chkdsk ошибок не находит, то MFT должна быть в порядке. Я сейчас у себя на диске NTFS посмотрел:chkdsk /f ошибок на этом диске не находит. 102840639488 - всего байт (T) 805502976 - свободно байт (F) 101681394989 - сумма размера всех файлов на диске (S) При этом если посчитать T - F: 102840639488 - 805502976 = 102035136512 - типа, сколько должно быть занято (E) Однако же: 102035136512 (E) - 101681394989 (S) = 353 741 523 ~ 337 Mb разница Но у меня и диск небольшой - около 100 Гб. Так что вполне возможно у тебя, если диск огромный, то 4 Гб на разницу и уходит. Там же размер не побайтно считается, а по рамеру кластера (average - среднее, а не точное значение): QUOTE On the typical hard disk partition, the average amount of space that is lost in this manner can be calculated by using the equation (cluster size)/2 * (number of files). Попробуй на другом диске также посчитать, как ты здесь посчитал (хорошо, если он такого же объёма и заполненности, чтобы порядок примерно одинаковый был) и сравнить результаты.QUOTE(Siberian GRemlin @ Dec 25 2020, 13:30) [snapback]4624[/snapback] chkdsk /f /r на четвёртом этапе сжирает всю оперативку, что ОС кричит караул. Если не вырубать его, а оставить в таком состоянии, то он обрабатывает примерно по 1 файлу в секунду, иногда со стопорами и скачками на несколько тысяч. За пару часов он проверил около 10%, после чего я его вырубил. Если ты хочешь поверхность проверить, то это лучше делать загрузившись с диска, а не в системе.На всякий случай тут пару ссылок нашёл, но там везде chkdsk проблемы находил, и решения без форматирования, к сожалению, там нет: _ttps://answers.microsoft.com/en-us/windows/forum/windows_7-performance/is-there-a-way-to-delete-orphaned-files/9bfc5c27-5243-4488-be07-ad8a62760326 _ttps://answers.microsoft.com/en-us/windows/forum/all/chkdsk-f-gives-result-of-orphaned-files/4f7d04bd-1eb8-4fe7-829a-335ec18dc94d _ttps://answers.microsoft.com/en-us/windows/forum/all/ntfs-corruption/71208334-5734-4e96-9e3a-9582f547feee _ttps://drobocommunity.m-ize.com/t/unindexed-file-chkdsk-wont-complete-run/3211 |
Siberian GRemlin, | ||
| Jul 2 2020, 19:03 | Delphi, Asm, C, WinAPI, PHP, ... | Это слегка не так работает. %d - вывести digit %ld - вывести long digit (для архитектур, где размер int больше long, например int 16, а long 32) %lld - вывести long long digit (как правило int 64) %0lld - тоже самое что и %lld (символ один и не указан размер) %03lld - тоже самое, что %03d (выравнять тремя нулями слева, если число меньше), но для int64 CODE #include <stdio.h> #include <stdlib.h> #include <windows.h> int main(void) { ULARGE_INTEGER x; x.QuadPart = 1; printf("%0lld\n", x.QuadPart); // будет просто 1 printf("%03lld\n", x.QuadPart); // будет 001 return(0); } В сях нет стандартных функций для разделения тысяч, чтобы из 1234567 сделать 1,234,567 или 1 234 567. Боюсь тебе придётся вручную искать код который это делает. Попробуй поискать по работе со строками и символом 0x2C (запятая). Ставлю на то, что как раз твои строки и работают с числами - смотри где они используются. "%03lld" нужно чтобы у тебя числа менее тысячи добивались нулями: 3007 => 3,007, а не 3,7. "%0lld" - это остаток в левой части, в примере 3007 - это будет тройка, т.к. её добивать нулями не нужно. |
Siberian GRemlin, | ||
| Apr 10 2020, 18:20 | Command & Conquer: Red Alert [Hidden Easter Egg] | Не думал, что вернусь к этой теме, но... Сделал сегодня атаку по словарю с комбинацией из двух слов - и таки что вы думаете? (*улыбается*) QUOTE Hidden commandline argument for hash 0xD95C68A2: FROMINSTALL Насчёт первого хеша я уверен на 100%, а насчёт второго практически (я ещё не закончил брутфорс - там 6 часов на том компьютере с 4 ядрами, к которому у меня сейчас доступ есть, при этом я распараллелил процесс и там все ядра заняты - задача на 4 потока разбита). Подробности как закончу напишу - я, вообще-то, демо-версия C&C:RA брутфорсю (там есть новые ключи и тоже от них хеши). Кстати, я обновил статью про The Neverhood у себя на домашней страничке - там теперь человеческие хеши (плюс разобрался что коды делают, кроме одного) - вчера тоже из двух слов перебирал (35 минут ушло в силу того что там алгоритм легче и можно вместо слов уже посчитанные хеши объединять, что в разы быстрее), не все, но большую часть кодов удалось чем-то адекватным заменить.Hidden word for network chat to see developers easter egg for hash 0x72A47EF6: FELTPLAYWORK |
Siberian GRemlin, | ||
| Feb 20 2020, 14:11 | Clock Tower (PS1) | Да всё нормально. Ещё раз подчёркиваю: я не против помочь и хочу помочь, но я никак не могу понять в чём проблема зайти в нужную тему (ссылку я уже давал не один раз), и написать там вопрос? Нажать на ссылку и перейти в тему, нажать там на кнопку "Ответить", написать вопрос, нажать "Отправить"? Это же не сложно? Или я чего-то не знаю? Я правда не могу понять что происходит. В чём проблема-то? Просто уже целая неделя прошла, за это время можно было 1 раз написать сообщение в нужной теме и проблемы бы не было. Объясняю свою позицию: я хочу чтобы на форуме всё было более или менее упорядоченно. Чтобы если понадобится что-то найти по программированию, то это всё будет в одной теме. Если по ресурсам - то для этого есть подфорум. По программам - свой подфорум. И так далее. Ну вот в жизни, если хочешь сварить суп и не можешь найти крышку от кастрюли, то ты же не пойдёшь её в спальню или ванную искать, а будешь её искать на кухне, верно? Ещё раз: я хочу помочь и я готов помочь. Но задай, пожалуйста, вопрос в нужно месте. Или объясни почему это так сложно задать его именно там? Я вот свою мотивацию объяснил и аргументировал. В этом тоже нет ничего сложного (на мой взгляд). Спасибо. |
Rash_forever, | ||
| Feb 19 2020, 11:31 | Clock Tower (PS1) | QUOTE(-=CHE@TER=- @ Jan 27 2020, 14:28) [snapback]4482[/snapback] А если есть вопросы по программированию, то их лучше >>>в другой теме<<< задавать. |
Rash_forever, | ||
| Feb 18 2020, 13:51 | Clock Tower (PS1) | Хороший вопрос. Если человек спрашивает, значит пытается разобраться - это здорово! Но, главное в программировании, да и, наверное, во многих других областях - это внимание к деталям. Я парой сообщений выше давал ссылку на форум Extractor.ru с примером похожей программы и комментариями, где этот момент был объяснён. Но я могу объяснить гораздо подробнее и понятнее, с примером, если подобный вопрос, как я уже писал парой сообщений выше, будет задан в другой теме, ссылку на которую я, опять таки, в том же сообщении уже давал. Вот. Внимание к деталям - это очень важно. Спасибо! |
Rash_forever, | ||
1 2 3 > »
| Упрощённая версия | Сейчас: 16th March 2026 - 21:17 |