25 July 2003 --------------------------------------------------------------------- From: Anonymous Subject: VCRplus+ Code that wants to be free I'm not an expert on anonymous posting/distribution, so I'm requesting your help with this. Background: In 1992, Ken Shirriff, Curt Welch and Andrew Kinsman published a partial analysis of the VCRplus+ "PlusCode" coding scheme in Cryptologia 16(3) July 1992, pp 227-234. They figured out the codes of length 1 through 6, but didn't break the 7 and 8 digit codes. Later, programs implementing their logic appeared on the net, adding encoding as well as decoding. New stuff: This program expands on those programs, and does the 7 and 8 digit codes, which includes TV/cable channels over 64 and times not an even multiple of 30 minutes, and/or not lasting an even multiple of a half hour. It is therefore useful for personal recording of local sources at non-round-number times or on non-broadcast channels, and for generating codes that aren't in the newspaper. The (encoding) algorithm is not fast enough to be used for commercial purposes, having a brute-force section in it. But someone might be able to improve on that. I am not breaking any confidentiality agreements. I have no access to inside information on the system. This was done by reverse engineering. Please consider posting this on cryptome, under the crypto category. /*******************************************************************\ * VCRplus+ Encoding, Decoding and regression test code. * Works with standard USA codes, 1 through 8 digits long, * without leading zeroes. * * This program is released to the public domain. \*******************************************************************/ /*******************************************************************\ * System include files \*******************************************************************/ #include #include #include #include /*******************************************************************\ * Preprocessor constants \*******************************************************************/ #define KEY001 (68150631) #define KEY002 (9371) #define NDIGITS 10 /*******************************************************************\ * Globals \*******************************************************************/ int g_iflag; /* Omit initial scramble on decode/encode */ int g_debug; /* Bits enable debug output */ int g_verbose; /* More typeout, but not debug stuff */ int g_encode; /* 0 = decode, 1 = encode */ int g_newspaper; char *g_progname; char *g_channel_name; int g_year_today; int g_month_today; int g_day_today; int g_channel; int g_starttime; int g_starttimem; int g_duration; int g_durationm; void main_debug (void); void clear_ndigits (unsigned char *); void split_digits (int n, unsigned char *a); int hhmm2mmmm (int); int mmmm2hhmm (int); int count_digits (int); int set_pwr (int); /*******************************************************************\ * Tables \*******************************************************************/ /* regression test codes, to make sure things didn't get broken */ /* Inconsistent: starts are hhmm, durations are mmmm */ /* Table trimmed for anonymity */ typedef struct test { int code; int year; int month; int day; int chan; int start; int duration; } _test; static _test tests[] = { {3316, 1991, 5, 10, 4, 2100, 120}, {21362, 1992, 3, 11, 24, 1930, 30}, /* ... */ {0, 0, 0, 0, 0, 0, 0} }; /* General utilities and data structures */ /* List of month names, so we can speak English */ static char monthname[][12] = { {"January"}, {"February"}, {"March"}, {"April"}, {"May"}, {"June"}, {"July"}, {"August"}, {"September"}, {"October"}, {"November"}, {"December"} }; /* TV guide channel assignments */ typedef struct { int num; char *name; } numname; static numname chan_to_name[] = { /* Trimmed for anonymity */ {02, "WAAB2"}, {03, "WAAC3"}, {04, "WAAD4"}, {05, "WAAE5"}, {06, "WAAF6"}, {07, "WAAG7"}, {8, "WAAH8"}, {9, "WAAI9"}, {10, "WAAJ10"}, {11, "WAAK11"}, {12, "WAAL12"}, {13, "WAAM13"}, {33, "HBO"}, {35, "AMC"}, {37, "DSC"}, {38, "NIK"}, {39, "A&E"}, {41, "SHO"}, {42, "CNN"}, {43, "TBS"}, {44, "USA"}, {45, "MAX"}, {46, "LIF"}, {58, "TMC"}, {62, "VH1"}, {63, "E"}, {75, "COM"}, {80, "STARZ"}, {89, "SCI"}, {90, "MSNBC"}, {99, "QVC"}, {103, "HAL"}, {-1, "???"}, }; unsigned char ttbl[192] = { (18 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (16 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (19 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (16 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (15 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (17 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (18 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (14 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (19 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (17 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (14 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (20 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (17 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (16 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (20 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (15 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (20 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (21 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (20 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (18 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (19 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (22 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (21 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (14 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (15 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (22 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (11 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (11 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (23 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (16 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (21 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (21 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (12 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (13 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 9 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (13 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (21 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (12 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (10 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (18 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (22 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (12 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 8 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 8 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (17 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 9 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (22 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (10 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (19 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 7 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (23 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (10 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 7 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (13 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 7 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (11 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (14 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (10 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 8 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (23 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (13 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (12 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 9 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 6 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (18 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 6 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 5 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 0 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (23 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (22 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (13 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 9 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (16 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (16 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (14 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (20 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (18 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 6 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (12 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 0 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 1 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 0 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (17 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 0 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 8 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 7 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (21 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 5 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (15 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (11 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (11 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 8 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (22 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 9 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (21 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (16 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (23 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 1 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (14 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 1 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 3 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (15 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (15 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (23 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (19 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 8 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 4 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 3 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (13 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (10 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 7 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 1 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (23 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 3 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 2 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , (22 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 4 * 2) + (00 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 6 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 4 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 2 * 2) + (30 ? 1 : 0) + ((( 30 - 30)/30) * 48) , ( 6 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 0 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (22 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 1 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 0 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (23 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (16 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 8 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 0 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (19 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 9 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (20 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 5 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (17 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 2 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (19 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 9 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (17 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 6 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (18 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (14 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (11 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 0 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 8 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (10 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (14 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 1 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 7 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (20 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 3 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 3 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , (13 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (12 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 2 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (21 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (11 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (18 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 6 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 5 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 2 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (15 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 7 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 6 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (17 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 4 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 7 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 4 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 4 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 1 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (12 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 1 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 2 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (19 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 3 * 2) + (00 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (10 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 2 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 3 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 5 * 2) + (00 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 9 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 2 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (20 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 4 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (15 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 4 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , (13 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , (12 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 3 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (10 * 2) + (30 ? 1 : 0) + ((( 60 - 30)/30) * 48) , ( 5 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) , ( 5 * 2) + (30 ? 1 : 0) + (((120 - 30)/30) * 48) , ( 5 * 2) + (30 ? 1 : 0) + ((( 90 - 30)/30) * 48) , (11 * 2) + (00 ? 1 : 0) + ((( 90 - 30)/30) * 48) }; /* A table of additional scrambling constants, * used only for codes of length 7 and 8. */ unsigned char OverSixTable[31] = { 0x25, 0x13, 0x34, 0x14, 0x52, 0x25, 0x25, 0x43, 0x45, 0x14, 0x43, 0x51, 0x21, 0x12, 0x23, 0x43, 0x51, 0x32, 0x35, 0x52, 0x34, 0x25, 0x34, 0x52, 0x13, 0x15, 0x41, 0x35, 0x52, 0x51, 0x12 }; /*******************************************************************\ * Utility routines \*******************************************************************/ void dumpnames (void) { numname *nnp; for (nnp = chan_to_name;; nnp++) { if (nnp->num < 0) break; printf ("%d\t%s\n", nnp->num, nnp->name); } } char * channame (int chan) { numname *nnp; for (nnp = chan_to_name;; nnp++) { if (nnp->num < 0) break; if (nnp->num == chan) break; } return (nnp->name); } /* Usage message and exit */ static void usage (void) { printf ( "Usage: %s [-eivd] [vcrplus+_code]\n" "\tflags:\n" "\t[-y year] [-m month] [-d day]\n" "\t[-c channel] [-t hhmm] [-l hhmm]\n", g_progname); printf ("\tDefaults: year, month, day = today. time = 1930, length = 30\n"); printf ("\tExample decode: %s -y 1991 -m 5 3316\n", g_progname); printf ("\tExample encode: %s -e -y 1991 -m 5 -d 10 -c 4 -t 2100 -l 200\n", g_progname); } static void usagex (void) { usage (); exit (-1); } static void errdie (char *s) { printf ("ERROR: %s\n", s); usagex (); } /* Convert minutes of the day into friendly time with AM/PM */ /* The string that is returned is overwritten each time we're called */ /* Example return strings: "9 AM", "5:30 PM", "12:15 PM", "12 noon", "12 midnight" */ /* */ static char * timestr (int t) { int hour, min; static char str[256]; char ampm[10], hm[10]; hour = t / 100; min = t % 100; if (hour < 0 || hour > 24) { fprintf (stderr, "timestr: I'm confused, time %d implies hour = %d which is bad!\n", t, hour); exit (-1); } if (min < 0 || min > 59) { fprintf (stderr, "timestr: I'm confused, time %d implies minute = %d which is bad!\n", t, min); exit (-1); } if (hour == 24 && min > 0) { fprintf (stderr, "timestr: I'm confused, time %d is beyond 24 hours range!\n", t, min); exit (-1); } if (hour < 12) { strcpy (ampm, "AM"); } else { strcpy (ampm, "PM"); } if (min == 0) { if (hour == 0 || hour == 24) { strcpy (ampm, "midnight"); } else if (hour == 12) { strcpy (ampm, "noon"); } } if (hour == 0) hour = 12; if (hour > 12) hour -= 12; if (min == 0) sprintf (hm, "%d", hour); else sprintf (hm, "%d:%02d", hour, min); sprintf (str, "%s %s", hm, ampm); return (str); } int count_digits (int val) { int ndigits; if (val < 0) { printf ("Error: code 0 or negative\n"); ndigits = 0; } else if (val < 1) ndigits = 0; else if (val < 10) ndigits = 1; else if (val < 100) ndigits = 2; else if (val < 1000) ndigits = 3; else if (val < 10000) ndigits = 4; else if (val < 100000) ndigits = 5; else if (val < 1000000) ndigits = 6; else if (val < 10000000) ndigits = 7; else if (val < 100000000) ndigits = 8; else ndigits = 9; if (ndigits > 8) { printf ("ERROR: %d has more than 8 digits (it has %d digits)\n", val, ndigits); usagex (); } return ndigits; } int set_pwr (int ndigits) { int pwr = 0x7fffffff; if (0 == ndigits) pwr = 1; if (1 == ndigits) pwr = 10; if (2 == ndigits) pwr = 100; if (3 == ndigits) pwr = 1000; if (4 == ndigits) pwr = 10000; if (5 == ndigits) pwr = 100000; if (6 == ndigits) pwr = 1000000; if (7 == ndigits) pwr = 10000000; if (8 == ndigits) pwr = 100000000; if (9 == ndigits) pwr = 1000000000; return pwr; } void clear_ndigits (unsigned char *a) { int i; for (i = 0; i < NDIGITS; i++) { a[i] = 0; } } void split_digits (int n, unsigned char *a) { int i; unsigned char digit; clear_ndigits (a); for (i = 0; i < NDIGITS; i++) { digit = n % 10; a[i] = digit; n = (n - digit) / 10; } } int hhmm2mmmm (int hhmm) { int hh, mm; hh = hhmm / 100; mm = hhmm % 100; if (mm > 59) { printf ("ERROR: Minutes too large in %d:%2d - truncated to %d:00\n", hh, mm, hh); mm = 0; } return (hh * 60) + mm; } int mmmm2hhmm (int mmmm) { int hh, mm; hh = mmmm / 60; mm = mmmm % 60; return (hh * 100) + mm; } /*******************************************************************\ * Routines for VCRplus+ encoding/decoding \*******************************************************************/ /* func1, for decoding only */ int func1 (int code) { int x = code; int nd; unsigned char a[NDIGITS]; int sum; int i, j; int ndigits; split_digits (x, a); ndigits = count_digits (x); nd = ndigits - 1; do { i = 0; do { j = 1; if (nd >= 1) { do { a[j] = (a[j - 1] + a[j]) % 10; } while (++j <= nd); } } while (++i <= 2); } while (a[nd] == 0); sum = 0; j = 1; for (i = 0; i < NDIGITS; i++) { sum += j * a[i]; j *= 10; } return sum; } static int encode_final_transform (x, y) int x, y; { int i, j, digit, sum; int a[9], b[9], out[17]; for (i = 0; i < 9; i++) { digit = x % 10; a[i] = digit; x = (x - digit) / 10; } for (i = 0; i < 9; i++) { digit = y % 10; b[i] = digit; y = (y - digit) / 10; } for (i = 0; i < 17; i++) { out[i] = 0; } for (i = 0; i <= 8; i++) { for (j = 0; j <= 8; j++) { out[i + j] += b[j] * a[i]; } } j = 1; sum = 0; for (i = 0; i <= 8; i++) { sum += j * (out[i] % 10); j *= 10; } return (sum); } static int encfunc1 (val) int val; { int ndigits, pwr; ndigits = 0; pwr = 1; while (val >= pwr) { ndigits++; pwr *= 10; } if (ndigits > 8) { printf ("ERROR: %d has more than 8 digits (it has %d digits)\n", val, ndigits); usagex (); } pwr /= 10; if (0 == g_iflag) { do { val = encode_final_transform (val, KEY002) % (pwr * 10); } while (val < pwr); } return (val); } /* Extra modifications to the time/duration for 7 and 8 digit codes */ void twiddle_tt (int tidx, int *tp, int *dp, int *x1p, int *x2p) { int tt, x1, x2, t, d; int t1, t2, b; x1 = x2 = t1 = t2 = d = b = t = 0; tt = tidx; if ((tt >= 768) && (tt <= 3647)) { t1 = ((tt - 768) % 10) + 1; t2 = t1 * 5; if (t1 >= 6) { x2 = t2 - 25; } else { x1 = t2; } tt -= 768; tt = tt / 10; } else if ((tt >= 3648) && (tt <= 6527)) { t1 = ((tt - 3648) % 6) + 1; if (t1 == 1) { x1 = 15; } else { x2 = (5 * t1) - 5; } tt = ((tt - 3648) / 6) + 288; } else if ((tt >= 6528) && (tt <= 13727)) { x1 = 5 + ((((tt - 6528) % 25) % 5) * 5); x2 = 5 + ((((tt - 6528) % 25) / 5) * 5); tt -= 6528; tt = tt / 25; } else if (tt >= 13728) { x2 = 5 + (((tt - 13728) % 5) * 5); x1 = 15; tt -= 13728; tt = (tt / 5) + 288; } if (tt < 192) { /* Lookup in table */ b = ttbl[tt]; t = b % 48; /* Half hours after midnight */ d = b / 48; /* Half hours of duration - 1 */ } else { t = 47 - ((tt - 192) % 48); /* Half hours after midnight */ d = tt / 48; /* Half hours of duration - 1 */ } *tp = t; *dp = d; *x1p = x1; /* Shorter duration */ *x2p = x2; /* Later start time */ } static void bit_shuffle (code, tval, dval, cval) int code; unsigned int *tval; unsigned int *dval; unsigned int *cval; { unsigned int tt, cc; int x1, x2; int nn; int top5; int bot3; int rem; int t, d, outtime, outdur; tt = 0; cc = 0; x1 = x2 = 0; nn = code - 1; top5 = nn / 1000; bot3 = nn % 1000; rem = bot3 & 0x1f; /* Time/duration */ if (rem & (1 << 0)) tt |= (1 << 0); if (rem & (1 << 2)) tt |= (1 << 1); if (rem & (1 << 4)) tt |= (1 << 2); if (top5 & (1 << 0)) tt |= (1 << 3); if (top5 & (1 << 3)) tt |= (1 << 4); if (top5 & (1 << 4)) tt |= (1 << 5); if (top5 & (1 << 5)) tt |= (1 << 6); if (top5 & (1 << 7)) tt |= (1 << 7); if (top5 & (1 << 9)) tt |= (1 << 8); if (top5 & (1 << 10)) tt |= (1 << 9); if (top5 & (1 << 11)) tt |= (1 << 10); if (top5 & (1 << 13)) tt |= (1 << 11); if (top5 & (1 << 14)) tt |= (1 << 12); if (top5 & (1 << 15)) tt |= (1 << 13); /* Channel */ if (rem & (1 << 1)) cc |= (1 << 0); if (rem & (1 << 3)) cc |= (1 << 1); if (top5 & (1 << 1)) cc |= (1 << 2); if (top5 & (1 << 2)) cc |= (1 << 3); if (top5 & (1 << 6)) cc |= (1 << 4); if (top5 & (1 << 8)) cc |= (1 << 5); if (top5 & (1 << 12)) cc |= (1 << 6); if (top5 & (1 << 16)) cc |= (1 << 7); /* Following not verified - haven't seen a code with the high bit on here */ if (top5 & (1 << 16)) { if (top5 & (1 << 12)) tt |= (1 << 11); if (top5 & (1 << 13)) tt |= (1 << 12); if (top5 & (1 << 14)) tt |= (1 << 13); if (top5 & (1 << 15)) cc |= (1 << 6); } twiddle_tt (tt, &t, &d, &x1, &x2); /* Return as minutes after midnight, minutes of duration. */ outtime = (30 * t) + x2; outdur = ((d + 1) * 30) - x1; *cval = cc + 1; *dval = outdur; /* As minutes */ *tval = outtime; /* As minutes after midnight */ } static void interleave (tval, cval, top5out, bot3out) int tval, cval; int *top5out; int *bot3out; { int top5 = 0; int bot3 = 0; if (tval & (1 << 0)) (bot3 |= (1 << 0)); if (cval & (1 << 0)) (bot3 |= (1 << 1)); if (tval & (1 << 1)) (bot3 |= (1 << 2)); if (cval & (1 << 1)) (bot3 |= (1 << 3)); if (tval & (1 << 2)) (bot3 |= (1 << 4)); if (tval & (1 << 3)) (top5 |= (1 << 0)); if (cval & (1 << 2)) (top5 |= (1 << 1)); if (cval & (1 << 3)) (top5 |= (1 << 2)); if (tval & (1 << 4)) (top5 |= (1 << 3)); if (tval & (1 << 5)) (top5 |= (1 << 4)); if (tval & (1 << 6)) (top5 |= (1 << 5)); if (cval & (1 << 4)) (top5 |= (1 << 6)); if (tval & (1 << 7)) (top5 |= (1 << 7)); if (cval & (1 << 5)) (top5 |= (1 << 8)); if (tval & (1 << 8)) (top5 |= (1 << 9)); if (tval & (1 << 9)) (top5 |= (1 << 10)); if (tval & (1 << 10)) (top5 |= (1 << 11)); if (cval & (1 << 6)) (top5 |= (1 << 12)); if (tval & (1 << 11)) (top5 |= (1 << 13)); if (tval & (1 << 12)) (top5 |= (1 << 14)); if (tval & (1 << 13)) (top5 |= (1 << 15)); if (cval & (1 << 7)) (top5 |= (1 << 16)); /* Untested below. Channels over 127. */ if (top5 & (1 << 16)) { if (tval & (1 << 11)) (top5 |= (1 << 12)); if (tval & (1 << 12)) (top5 |= (1 << 13)); if (tval & (1 << 13)) (top5 |= (1 << 14)); if (cval & (1 << 6)) (top5 |= (1 << 15)); } /* Untested above. Channels over 127. */ *top5out = top5; *bot3out = bot3; } int revluk (int starttimem, int durationm) { /* Both in minutes. */ int t, d, x1, x2; int tidx, startm, dur; int startd = starttimem; for (tidx = 0; tidx < 16384; tidx++) { twiddle_tt (tidx, &t, &d, &x1, &x2); dur = 30 + (d * 30) - x1; startm = (t * 30) + x2; if ((dur == durationm) && (startm == startd)) { return tidx; } } return -1; } void map_top (year, month_out, day_out, top5, rem, mtoutp, remoutp) int year; int month_out; int day_out; int top5; int rem; int *mtoutp; int *remoutp; { int year_mod16, year_mod100; int ndigits, nd, flag7, j, k, t1, t2, t3, ym, datum, mtout; int month_today = month_out; int year_today = year; unsigned char n[NDIGITS]; year_mod100 = year % 100; year_mod16 = year_mod100 % 16; split_digits (top5, n); ndigits = count_digits (top5) + 3; nd = ndigits - 3 - 1; rem = (rem + n[0] + n[1] + n[2] + n[3] + n[4]) % 32; if (ndigits <= 6) { do { k = 0; do { n[nd] = (n[nd] + day_out) % 10; if (nd > 0) { for (j = nd - 1; j >= 0; j--) { n[j] = (n[j] + n[j + 1]) % 10; } } rem += n[0]; } while (++k <= year_mod16); } while (n[nd] == 0); rem = (rem + (day_out * (month_today + 1))) % 32; } else { /* ndigits > 6 */ flag7 = (ndigits == 7) ? 1 : 0; ym = (year_today * 12) + month_today; do { k = 1; do { t1 = (ym + 310 - k) % 31; t1 = OverSixTable[t1]; t2 = (t1 & 0x0f) - flag7; t3 = ((t1 >> 4) & 0x0f) - flag7; if ((t2 == 0) && (t3 < 3)) { t2 = 4; } else if ((t2 == 0) && (t3 >= 3)) { t2 = 2; } else if (t3 == 0) { t3 = (t2 >= 3) ? 2 : 4; } t1 = n[--t2] + (10 * n[--t3]); do { datum = ttbl[t1] - ym; while ((datum < 0) || (datum >= 192)) { datum += 192; } if (datum > 99) t1 = datum; } while (datum > 99); if ((t2 >= 0) && (t2 < 5) && (t3 >= 0) && (t3 < 5)) { n[t2] = datum % 10; n[t3] = datum / 10; } else { errdie ("ERROR: internal table index wild!\n"); } } while (++k <= 31); } while (n[nd] == 0); } /* End code for lengths 7 and 8 */ mtout = 10000 * n[4] + 1000 * n[3] + 100 * n[2] + 10 * n[1] + n[0]; *mtoutp = mtout; *remoutp = rem; } /*******************************************************************\ * The decode routine \*******************************************************************/ void decode_main (int month_today, int day_today, int year_today, int newspaper, int *day_ret, int *channel_ret, int *starttime_ret, int *duration_ret) { int s1_out, bot3, top5, quo, rem; int mtout, tval, dval, cval; int day_out, channel_out; int starttime_out, duration_out; int modnews; year_today = year_today % 100; if (month_today < 1 || month_today > 12) { printf ("Invalid month\n"); usagex (); } if (day_today < 1 || day_today > 31) { printf ("Invalid day of the month\n"); usagex (); } if (newspaper < 1) { printf ("DON'T TRY NUMBERS LESS THAN 1!\n"); usagex (); } if (g_iflag) { s1_out = newspaper; } else { s1_out = func1 (newspaper); } top5 = s1_out / 1000; bot3 = (s1_out % 1000); quo = (bot3 - 1) / 32; rem = (bot3 - 1) % 32; day_out = quo + 1; map_top (year_today, month_today, day_out, top5, rem, &mtout, &rem); modnews = mtout * 1000; modnews += (day_out << 5) + rem - 31; bit_shuffle (modnews, &tval, &dval, &cval); starttime_out = tval; duration_out = dval; channel_out = cval; *day_ret = day_out; *channel_ret = channel_out; *starttime_ret = starttime_out; *duration_ret = duration_out; return; } /*******************************************************************\ * The encode routine \*******************************************************************/ /* * Returns VCRplus code (range 0..99999999), or -1 if error. */ int encode_main (int month, int day, int year, int channel, int starttimem, int durationm) { int j = 0; int s1_out = 0; int bot3 = 0; int top5 = 0; int quo = 0; int rem = 0; int newspaper = 0; int cval = 0; int tval = 0; int big_top5 = 0; int big_rem = 0; int mtout = 0; year = year % 100; cval = channel - 1; tval = revluk (starttimem, durationm); if (tval < 0) { return (-1); } interleave (tval, cval, &big_top5, &big_rem); top5 = 0; j = 0; /* * Brute-force search to invert top5. Need closed-form solution. This was * OK for 6-digit codes, but is slow for 8-digits. */ for (top5 = 0; top5 < 100000; top5++) { rem = 0; map_top (year, month, day, top5, rem, &mtout, &rem); if (mtout == big_top5) { break; } } quo = day - 1; rem = (big_rem + 320 - rem) % 32; bot3 = rem + 1 + (32 * quo); s1_out = bot3 + (1000 * top5); newspaper = encfunc1 (s1_out); return (newspaper); } /*******************************************************************\ * Debug and regression test routine - mostly removed for anonymity \*******************************************************************/ void main_debug () { int i, j, regresserrs; int day_out; int channel_out, starttime_out, duration_out; struct test *tp; if (g_debug & 16) { printf (" 192-entry lookup table:\n"); for (i = 0; i < 192; i++) { j = ttbl[i]; printf (" %2.2X", j); if ((i & 0x0f) == 0x0f) { printf ("\n"); } } printf ("\n"); for (i = 0; i < 192; i++) { j = ttbl[i]; printf (" %3.3o", j); if ((i & 0x0f) == 0x0f) { printf ("\n"); } } printf ("\n"); /* dumpnames(); */ } if (g_debug & 32) { /* Regression tests */ tp = tests; regresserrs = 0; while (tp->code > 0) { g_newspaper = tp->code; g_day_today = 1; g_month_today = tp->month; g_year_today = tp->year; /* First, test that set of data for decoding */ decode_main (g_month_today, g_day_today, g_year_today, g_newspaper, &day_out, &channel_out, &starttime_out, &duration_out); starttime_out = mmmm2hhmm (starttime_out); if ((day_out == tp->day) && (channel_out == tp->chan) && (starttime_out == tp->start) && (duration_out == tp->duration)) { if (g_verbose) { printf ("Code: %d %d-%d should be %d, %d, %d, %d\n", g_newspaper, tp->year, tp->month, tp->day, tp->chan, tp->start, tp->duration); printf ("Result: Day %d, Chan %d, Start %d, Length %d\n", day_out, channel_out, starttime_out, duration_out); printf ("Ok.\n\n"); } } else { printf ("Code: %d %d-%d should be %d, %d, %d, %d\n", g_newspaper, tp->year, tp->month, tp->day, tp->chan, tp->start, tp->duration); printf ("Result: Day %d, Chan %d, Start %d, Length %d\n", day_out, channel_out, starttime_out, duration_out); printf ("***** ERROR *****\n\n"); regresserrs++; } /* Now, reverse it and see if the encode gives the newspaper */ g_newspaper = encode_main (tp->month, tp->day, tp->year, tp->chan, hhmm2mmmm (tp->start), tp->duration ); if (g_verbose) { printf ("Encode %d-%d-%d Chan %d Start %d, Durn %d" " should be %d\n", tp->year, tp->month, tp->day, tp->chan, tp->start, tp->duration, tp->code); printf ("Result: Day %d, Chan %d, Start %d, Length %d\n", day_out, channel_out, starttime_out, duration_out); } if (tp->code == g_newspaper) { if (g_verbose) { printf ("Ok.\n\n"); } } else { printf ("Encode %d-%d-%d Chan %d Start %d, Durn %d" " should be %d\n", tp->year, tp->month, tp->day, tp->chan, tp->start, tp->duration, tp->code); printf ("Result: Newspaper: %d\n", g_newspaper); printf ("***** ERROR *****\n\n"); regresserrs++; } tp++; } if (regresserrs) { printf ("*** ERRORS in regression tests ***\n"); } else { printf ("Regression tests passed.\n"); } } } /*******************************************************************\ * The MAIN main() routine \*******************************************************************/ int main (argc, argv) int argc; char *argv[]; { char ch; int rem; int day_out, channel_out; int starttime_out, duration_out; int s_year_today; int s_month_today; int s_day_today; int s_channel; int s_starttime; int s_duration; struct tm *tm; time_t t; /* defaults */ g_progname = argv[0]; g_iflag = 0; g_debug = 0; g_encode = 0; g_starttime = s_starttime = 1930; g_duration = s_duration = 30; g_starttimem = hhmm2mmmm (g_starttime); g_durationm = hhmm2mmmm (g_duration); g_channel = s_channel = 2; t = time (NULL); tm = localtime (&t); s_month_today = g_month_today = tm->tm_mon + 1; s_day_today = g_day_today = tm->tm_mday; s_year_today = g_year_today = tm->tm_year + 1900; while ((ch = getopt (argc, argv, "veg:y:m:d:c:t:l:i")) != -1) { switch (ch) { case 'g': /* as in -g to compiler for debuGging */ g_debug = strtol (optarg, (char **)NULL, 0); break; case 'i': g_iflag++; break; case 'e': g_encode++; break; case 'v': g_verbose++; break; case 'y': s_year_today = strtol (optarg, (char **)NULL, 0); if ((s_year_today < 0) || (s_year_today > 2999)) errdie ("Year out of range"); break; case 'm': s_month_today = strtol (optarg, (char **)NULL, 0); if ((s_month_today < 1) || (s_month_today > 12)) errdie ("Month out of range 1-12"); break; case 'd': s_day_today = strtol (optarg, (char **)NULL, 0); if ((s_day_today < 1) || (s_day_today > 31)) errdie ("Day out of range 1-31"); break; case 'c': s_channel = strtol (optarg, (char **)NULL, 0); if ((s_channel < 1) || (s_channel > 128)) errdie ("Channel out of range 1-128"); break; case 't': s_starttime = atoi (optarg); /* Decimal only for times */ rem = s_starttime % 100; if ((rem % 5) || (rem < 0) || (rem > 55) || (s_starttime < 0) || (s_starttime > 2355) ) errdie ("Start time not an even five minutes from 0000 to 2355"); break; case 'l': s_duration = atoi (optarg); /* Decimal only for times */ rem = s_duration % 100; if ((rem % 5) || (s_duration < 5) || (s_duration > 300) ) errdie ("Duration not a multiple of 5 from 5 to 300"); break; case 'h': usage (); exit (0); break; } } argc -= optind; argv += optind; if (g_debug) { main_debug (); /* do debugging stuff first */ } g_year_today = s_year_today; g_month_today = s_month_today; g_day_today = s_day_today; g_channel = s_channel; g_starttime = s_starttime; g_duration = s_duration; g_starttimem = hhmm2mmmm (g_starttime); g_durationm = hhmm2mmmm (g_duration); if (0 == g_encode) { if (argc != 1) { if (g_debug) { exit (0); } printf ("ERROR: decoding requires a code as single argument.\n"); usagex (); } g_newspaper = strtol (argv[0], (char **)NULL, 0); if (g_verbose) { printf ("Today's date: %s %d %d\n", monthname[g_month_today - 1], g_day_today, g_year_today); } decode_main (g_month_today, g_day_today, g_year_today, g_newspaper, &day_out, &channel_out, &starttime_out, &duration_out); if (g_year_today < 50) /* window: <50=year 2000's, >50=year 1900's */ g_year_today += 100; if (g_year_today < 1900)/* convert to 4-digit year */ g_year_today += 1900; g_channel_name = channame (channel_out); printf ("Program: %s %d %d, channel %d (%s), at %s, duration %d:%2.2d.\n", monthname[g_month_today - 1], day_out, g_year_today, channel_out, g_channel_name, timestr (mmmm2hhmm (starttime_out)), duration_out / 60, duration_out % 60); } else { /* Encoding */ g_newspaper = encode_main (g_month_today, g_day_today, g_year_today, g_channel, g_starttimem, g_durationm); if (g_verbose) { /* Announce what we're decoding */ printf ("%2d %2d %2d %3d %4d %3d VCR++ code=%8d\n", g_month_today, g_day_today, g_year_today, g_channel, g_starttime, g_duration, g_newspaper); } else { printf ("%d\n", g_newspaper); } exit (0); } exit (0); return 0; }